Refundable, non-custodial onchain fundraising on Solana — built for small teams and independent builders.
Raising money as a small team or a solo dev usually comes down to two bad options: chase VCs who want a board seat, or run a token sale where your backers eat the entire downside if it flops. I wanted a third one — so I built Keep, a way for independent builders to raise from their own users, where a raise that fails refunds its backers automatically, from the protocol itself. No company holds the money, and no human decides whether you get paid.
This post is the engineering teardown: how that refund actually works, why the incentives hold, and the parts that were genuinely hard to build. Every on-chain read below is something you can verify yourself with @keep-coffee/sdk.
The setup: a fixed-price raise, then a real market
A builder opens a raise against a fixed USDC target. Backers back the raise at a fixed price during the fundraising window. The token supply is split up front:
- 60% to backers, distributed at the fixed price
- 30% seeded into a Raydium pool when the raise fills
- 10% to the founder, success-path vesting only (180 days)
When the target is hit, the protocol bootstraps a Raydium CPMM pool with half the raised USDC + the 30% token allocation, and keeps the other half of the USDC as a reserve. From that moment the token trades on the open market like any other. Two judgments follow — at day 7 and day 30 — each comparing a 1-hour TWAP against a per-project success gate (default 0.85× the launch price).
Everything after this branches on one question: did the market hold the gate?
Success: it's just an open market
If the day-30 TWAP clears the gate, the raise succeeds: the reserve pays the platform fee and the project's funding, and 100% of the LP is locked forever. There is no refund on a successful raise — it graduated into a normal market, and a normal market is not principal-protected. That distinction matters and we keep it sharp everywhere: the refund is a failure backstop, not a money-back guarantee.
Failure: the refund funds itself
If the gate fails (at day 7 or day 30), the protocol does something most launchpads can't, because they never kept the money on-chain:
- Unwind 100% of the LP. The pool is dissolved back to the protocol — USDC on one side, project tokens on the other. The returned project tokens are burned.
- Seed a ClaimPool. The recovered USDC (plus the untouched reserve, net of a time-decay haircut) becomes a refund pool for the holders.
- Burn-to-claim. A backer burns their tokens and receives USDC pro-rata from the pool. The protocol's only cut is a time-decay haircut — 5% on a day-7 failure, 15% on day-30 — paid to the platform and project, never to a custodian. A raise that fails near the gate returns close to the full raise minus the haircut; a deeper failure recovers less USDC overall — but the holders who stayed split all of it (see below).
No custody. No "we'll process your refund." The USDC that refunds you is the USDC the pool gave back, moved by the program under its own authority.
The part that makes the incentives hold
Here's the design decision we're proudest of. When a raise fails, the refund pool is split only among the tokens that are still held — not the full supply. Tokens that were dumped during the hold period don't count toward it.
So the backers who held (and the arbitrageurs who bought the dumped tokens cheap) split the entire refund pool among themselves. A backer who panic-sold into the decline already took their USDC out at the market price, and forfeits their share of the refund to the people still holding.
This flips the usual failure dynamic. In a normal token, dumping early is the rational move and holders are left bag-holding. Here, dumping is self-defeating: you trade a near-full refund for a fire-sale price, and you hand your refund share to whoever stayed. The mechanism pays the patient — and it does it without anyone choosing who deserves it.
Why the gate is a TWAP, and why 0.85
Two non-obvious choices:
Time-weighted, not spot. The judgment reads a 1-hour TWAP, not the instantaneous price. A single-transaction spike — a flash-loan pump or a last-second wick — moves a cumulative-price average by ~0, so you can't fake the verdict atomically. Faking a sustained price means burning real capital against arbitrage for an hour. The window has to deter, not fortress, because the manipulation is already economically weak (forcing a failure means selling the very tokens you'd need to burn for a refund).
The gate equals one minus the haircut. The default gate is 0.85× — exactly the day-30 refund rate. At that price a backer is indifferent between holding the token and taking the failure refund. Below it, the refund is strictly better, so the protocol must not declare success there. Pinning the gate to the haircut makes that an on-chain invariant rather than a tuned guess.
The parts that were genuinely hard
-
The 4 KB stack frame. Solana's SBF runtime caps the per-instruction stack at 4 KB. Creating the launchpad, the mint, two vaults, and minting the supply in one instruction blew the frame (undefined behavior that corrupted a just-deserialized argument). Project creation is split across
create_project→init_vaults, and the deposit instruction stopped initializing the backer's token account inline — the caller creates it idempotently in the same transaction instead. -
Never trap the exit. The emergency-freeze and the day-7/day-30 finalize are path-aware: a freeze pauses success-side payouts but must never block the failure refund, because
finalizeis the only instruction that creates the ClaimPool a refund needs. Freezing it across a hold window would lock backer refunds — so the failure branch andclaim_refundstay reachable even when the protocol is frozen. - A failure that returns ~95%, exactly. The LP unwind enforces a 95% slippage floor on each leg so a front-run can't shrink the refund pool, and the ClaimPool quote drains to integer-division dust rather than stranding USDC.
Honesty (the part most posts skip)
- Non-custodial. Backers pay the on-chain program directly. The protocol never holds or moves your money on a balance sheet; the refund is the reserve and the unwound LP, moved by code.
- Refundable only on the failure paths. If a raise doesn't fill, every backer is refunded in full. If it fills and then fails its price check, the contract refunds automatically from the mechanism, with a 5% (day-7) / 15% (day-30) haircut — the realized amount depends on recovered liquidity and how many holders stayed. A successful raise is an open market — not protected, not a guarantee. Buying on that open market afterward carries no refund at all.
-
Verify it yourself. Don't take this post's word for it — read the launchpad and ClaimPool state straight from the chain (the SDK's
getRaise/getClaimPooldecode them right out of the account bytes) and check the rules against what's claimed here.
Try it
npm i @keep-coffee/sdk @solana/web3.js
import { Connection } from '@solana/web3.js';
import { KeepClient } from '@keep-coffee/sdk';
const keep = new KeepClient({ network: 'mainnet', connection: new Connection(RPC) });
const raise = await keep.getRaise(0); // decode a live raise from chain
const pool = await keep.getClaimPool(0); // null unless it failed a gate
The SDK is MIT and non-custodial — every write returns an unsigned transaction you sign yourself. Source: github.com/keep-coffee/keep-sdk. It's built for AI builders and agents; a LangChain tool example ships in the repo.
Top comments (0)