Technical note
Soltechnics is one Solana program for crowdfunding by collective vote. It keeps USDC in escrow and pays a builder only when that vote accepts a milestone. Each backer can pull back whatever was not released. This page is the behavior of that program. The site does not take project funds yet.
Program id 9FFdLt7jvqy4oFqGCYxR7y8gdpYnDZJjSuti9yKsyyS1. Settlement is USDC. The pass to use the program is a separate SPL token. A wallet must stake at least one whole token before it can create a project, deposit, bid, or vote. That one token stays staked until the longest possible life of the projects it has joined.
An admin key can pause new deposits, change the numbers for future projects, and withdraw the protocol treasury. It cannot move a project's escrow. That key should be a multisig. Existing projects keep the numbers they were created with.
| State | What moves it |
|---|---|
| Interest | Threshold met, or the deadline passes below it. |
| Bidding | Open build only. Bids close on the deadline. |
| Builder vote | One winner with quorum, or no unique winner. |
| Funding | Escrow reaches the gross goal, or the deadline passes short. |
| Execution | Milestones are proved and voted, one at a time. |
| Refunding | Backers pull the unreleased remainder. |
| Completed | Every milestone share has been released. Nothing is left to claim. |
A self-build skips bidding. If the interest pool already covers the gross goal, funding is skipped too. Anyone can advance a phase once its condition is true. No operator has to push the button.
| Account | Seeds |
|---|---|
| Config | ["config"] |
| Treasury | ["treasury"], held by the config |
| Project | ["project", id] |
| Escrow | ["escrow", id], a USDC account held by the project |
| Contribution | ["contribution", project, backer] |
| Bid | ["bid", project, builder], up to 8 |
| Vote | ["vote", project, kind, voter] |
| Stake | ["stake", wallet] and ["stake-vault", wallet] |
Text, video, and repositories stay off the chain. The program stores a 32-byte hash. A vote account existing is what stops a second vote on the same proposal.
| Call | Who | Effect |
|---|---|---|
initialize | Admin | Stores mints, fees, and durations. Creates the treasury. |
update_config | Admin | Changes numbers for projects created after the update. |
set_paused | Admin | Blocks new projects, deposits, and bids. Votes, claims, and payouts still run. |
transfer_admin | Admin | Moves the admin key. |
withdraw_treasury | Admin | Moves protocol USDC out of the treasury. Not out of an escrow. |
stake / unstake | Holder | Locks or releases the platform token. While a lock is active, one token must remain. |
create_project | Author | Pays the creation fee into the treasury and opens interest. |
contribute | Backer | Moves USDC into escrow during interest, bidding, the builder vote, funding, or execution. |
settle_interest | Anyone | Opens bidding or funding, or a refund. |
submit_bid | Builder | Posts or replaces a bid before the bid deadline. One bid per wallet. |
settle_bidding | Anyone | Opens the builder vote, or a refund if there are no bids. |
vote_builder | Eligible backer | One vote for one bid. |
settle_builder | Anyone | Copies the unique winner onto the project and opens funding or execution. |
abandon_builder_vote | Anyone | Refunds when the vote is closed and there is no unique winner. |
settle_funding | Anyone | Starts the build, or refunds if the goal was missed. |
post_proof | Builder | Opens the vote on the next milestone. |
vote_progress | Eligible backer | Yes or no on the open milestone. |
settle_progress | Anyone | Pays that milestone, or opens a refund. |
request_extension | Builder | Opens a vote to add time. |
vote_extension / settle_extension | Backers / anyone | Adds the time, or opens a refund. |
expire | Anyone | After the build deadline, with no vote still open, opens a refund. |
claim | Backer | Pulls that wallet's share of the remaining escrow. |
A deposit is a token transfer into the project escrow. The project's total_deposited increases by the same amount. The author's creation fee never enters this account, and it is never returned.
token::transfer(backer_usdc, escrow, amount)?; project.total_deposited = project.total_deposited + amount;
Deposits are refused once the project is refunding or completed. They are also refused while the platform is paused.
The builder names the amount they want to receive. The goal shown to backers is the gross that makes that net possible after the split. Shares of a released portion are 87% builder, 10% author, and the rest to the protocol. Dust from rounding stays with the protocol.
gross = ceil(net * 10_000 / 8_700) releasable = total_deposited * released_bps / 10_000 builder = releasable * 87 / 100 author = releasable * 10 / 100 protocol = releasable - builder - author
A builder who wants $17,400 produces a goal of $20,000. On full release the builder receives $17,400, the author $2,000, and the protocol $600. If the author is also the builder, both of their shares go to the same USDC account.
Only accepted milestones count. A milestone of 2,000 basis points releases 20% of the pool, in the same three shares. Later deposits raise total_deposited. The next payout pays the difference, so new money is split the same way, including the share of milestones already accepted. If the project then fails, that already-earned difference is paid before backers claim what remains.
The first milestone cannot be more than 20% of the plan. A plan is two to five milestones, each above zero, summing to 10,000 basis points. A single payment at the end is refused, because that first share would be the whole pool.
One wallet has one vote, regardless of how much it deposited, once the deposit is at least the minimum, default $25, and the token was staked before the vote opened. Wallets that cross the minimum after the vote opens do not vote on that proposal. This keeps a last-minute swarm from raising the bar or stuffing the count.
The author who is also the builder does not vote on milestone acceptance or on an extension. They can vote for a builder when they are only a backer.
A result counts when the number of votes cast reaches the required number and the yes votes are strictly greater than the no votes. The required number is the larger of 25% of the eligible wallets, rounded up, and five, but never more wallets than are eligible. Three backers must all vote. Ten backers need five. One hundred need twenty-five.
required = max(ceil(eligible * 25 / 100), min(5, eligible))
The vote can close early when every snapshotted wallet has voted. Otherwise it waits for the deadline, default 3 days. A tie, a missed quorum, or a failed milestone opens a refund of everything not yet released. There is no second attempt on the same milestone.
A builder vote uses the same quorum. The winner must have strictly more votes than every other bid. A tie refunds. If a unique winner exists, the refund instruction refuses and the winner has to be settled.
These are the defaults written at initialization. An admin can change them for later projects.
| Limit | Default |
|---|---|
| Creation fee | $25, kept by the treasury |
| Interest threshold | $1,000 to $1,000,000, chosen per project |
| Interest | 14 days, or earlier if the threshold is met |
| Bids | 14 days |
| Vote | 3 days |
| Top-up funding | 21 days, skipped if the goal is already met |
| Build | Named by the builder, at most 180 days |
| Extensions | 2, each at most half of the original build duration |
An extension cannot be requested while a milestone vote is open, and a proof cannot be posted while an extension vote is open. A refused extension refunds the unreleased funds even if calendar time remains. Expiry waits until an open vote is settled, so a passing vote is not thrown away by the deadline transaction.
The program does not send refunds out by itself. When a project enters refunding, each backer claims their own share and pays their own network fee. The last claim takes the rounding remainder, so the escrow is emptied without a leftover that nobody can reach.
owed = deposit * escrow_balance / weight_left weight_left -= deposit token::transfer(escrow, backer, owed)?;
Weight is the sum of deposits, not the escrow balance. The escrow at that moment is only the unreleased part, because released shares were already paid. Every backer therefore gets the same fraction of what is left, in proportion to what they put in. A released milestone is not clawed back.