Technical note

The program that holds the money.

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

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.

What a person does

  1. Stake at least 1 platform token.
  2. An author pays a non-refundable creation fee, default $25, and publishes a content hash. They set an interest threshold, default floor $1,000.
  3. Backers send USDC into the project escrow. The pool can close early once the threshold is met. The window is 14 days.
  4. The author either builds it themselves, naming a net fee, a duration, and milestones, or opens 14 days of bids and a 3-day vote.
  5. If the escrow is still short of the gross goal, funding stays open for up to 21 days. Deposits can continue during the build and can go past the goal.
  6. The builder posts a proof hash for the next milestone. Backers vote. Only that milestone's share is paid.
  7. If the deadline passes, an extension is refused, or a milestone vote fails, each backer claims their share of what is still in escrow.

States

StateWhat moves it
InterestThreshold met, or the deadline passes below it.
BiddingOpen build only. Bids close on the deadline.
Builder voteOne winner with quorum, or no unique winner.
FundingEscrow reaches the gross goal, or the deadline passes short.
ExecutionMilestones are proved and voted, one at a time.
RefundingBackers pull the unreleased remainder.
CompletedEvery 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.

Accounts

AccountSeeds
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.

Instructions

CallWhoEffect
initializeAdminStores mints, fees, and durations. Creates the treasury.
update_configAdminChanges numbers for projects created after the update.
set_pausedAdminBlocks new projects, deposits, and bids. Votes, claims, and payouts still run.
transfer_adminAdminMoves the admin key.
withdraw_treasuryAdminMoves protocol USDC out of the treasury. Not out of an escrow.
stake / unstakeHolderLocks or releases the platform token. While a lock is active, one token must remain.
create_projectAuthorPays the creation fee into the treasury and opens interest.
contributeBackerMoves USDC into escrow during interest, bidding, the builder vote, funding, or execution.
settle_interestAnyoneOpens bidding or funding, or a refund.
submit_bidBuilderPosts or replaces a bid before the bid deadline. One bid per wallet.
settle_biddingAnyoneOpens the builder vote, or a refund if there are no bids.
vote_builderEligible backerOne vote for one bid.
settle_builderAnyoneCopies the unique winner onto the project and opens funding or execution.
abandon_builder_voteAnyoneRefunds when the vote is closed and there is no unique winner.
settle_fundingAnyoneStarts the build, or refunds if the goal was missed.
post_proofBuilderOpens the vote on the next milestone.
vote_progressEligible backerYes or no on the open milestone.
settle_progressAnyonePays that milestone, or opens a refund.
request_extensionBuilderOpens a vote to add time.
vote_extension / settle_extensionBackers / anyoneAdds the time, or opens a refund.
expireAnyoneAfter the build deadline, with no vote still open, opens a refund.
claimBackerPulls that wallet's share of the remaining escrow.

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.

Money

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.

Votes

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.

Time

These are the defaults written at initialization. An admin can change them for later projects.

LimitDefault
Creation fee$25, kept by the treasury
Interest threshold$1,000 to $1,000,000, chosen per project
Interest14 days, or earlier if the threshold is met
Bids14 days
Vote3 days
Top-up funding21 days, skipped if the goal is already met
BuildNamed by the builder, at most 180 days
Extensions2, 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.

Claim

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.