Anvora is a prelaunch-only bonding-curve fundraising protocol on Solana with built-in revenue sharing. A creator launches a campaign; backers buy the campaign’s token on a bonding curve during a fixed trading window; when the window closes, the raised SOL is paid out to the creator. Backers who stake their tokens earn a pro-rata claim on the campaign’s revenue — the staker share of every sell fee, plus anything deposited into the campaign — credited to them as it arrives.
Each campaign mints its own token — named and tickered by its creator, 9 decimals — as a standard, hookless Token-2022 (metadata extensions only), so it stays fully composable. Revenue isn’t tied to simply holding the token — backers opt in by staking, and rewards accrue to the staked positions the program custodies on-chain.
The creator picks a slug, metadata and a duration (5 minutes to 30 days on this platform) and calls create_campaign. The program mints the curve token and opens the window.
While the window is open, anyone can buy from — or sell back to — the bonding curve. Price moves deterministically with supply: every buy raises it, every sell lowers it. Both directions are slippage-guarded. Backers can also stake their tokens to start earning the campaign's revenue share — a one-way commitment: staked tokens are locked, and unsellable, until the campaign closes.
Once the window ends, settle is permissionless — anyone can close the campaign. Unsold curve tokens are burned and the raised SOL is split (see Fees). It does not move the revenue vault: every share was assigned the moment the money arrived, so claims were already open. Staked tokens unlock here. If the creator goes inactive, a third-party caller earns a small bounty for settling, so campaigns can never stay locked.
Anyone can deposit SOL into the RevenueVault at any time, during the campaign as well as after it. Each deposit is split immediately between whoever is staked at that instant, pro-rata to their staked tokens — so it needs at least one staker, and late arrivals never claim what came in before them.
All fees live on-chain in the program — the platform cannot change them per campaign. Basis points below are the exact program constants.
| Action | Fee | Where it goes |
|---|---|---|
| Create campaign | 0.03 SOL, flat | Paid once by the creator, plus the usual Solana network fee. About a third of it is rent for the accounts the instruction opens — the campaign, the mint and its metadata, and the three vaults — which stays locked in those accounts; measured on a live campaign it comes to 0.0095 SOL. The rest, roughly 0.0205 SOL, goes to the protocol treasury as the creation fee. The price is fixed either way: a longer name or URI costs more rent and leaves the protocol less, never the creator more. |
| Buy | 0% | Buys are free of protocol fee by design — entries are favoured. |
| Sell | 2.5% of gross proceeds | 1% goes to the campaign’s stakers, paid into the RevenueVault as the sale happens · 1.5% to the protocol treasury. |
| Settlement | 6% of the SOL left in the curve — 5% when the creator signs the settle themselves | Creator 94%, protocol 5%, and a 1% bounty to whoever signs the settle. The program has no branch on who that is, so a creator settling their own campaign collects the bounty too — which is how a self-settle reaches 95%. |
The bounty keeps the system live without punishing creators who act: settle your own campaign and you collect it yourself. Two smaller flows land at settlement as well — the staker share of sells collected while nobody was staked goes to the creator, and a creator cut too small to leave their wallet rent-exempt is folded to the protocol rather than failing the settle. Affiliation referral cuts (see below) come out of the protocol’s own share, so they never reduce the creator, staker or caller amounts above.
Pricing follows a constant-product curve over virtual reserves (x · y = k): each campaign starts from the same fixed parameters, so every curve behaves identically and the price path depends only on net buying. The math runs entirely on-chain; the charts and previews on this site re-implement the same formulas for display.
| Token standard | Token-2022 — standard & hookless (metadata extensions only) |
| Name / ticker | Chosen by the creator — ticker 2–3 characters |
| Decimals | 9 |
| Total supply per campaign | 1,073,000,000 tokens, minted in full at create_campaign |
| Sellable on the curve | 1,073,000,000 tokens — the whole supply sits in the curve vault, nothing is held back |
| Initial virtual reserves | 1,073,000,000 tokens / 30 SOL |
| Unsold tokens at settle | Burned — supply only ever shrinks after the window closes |
Revenue is shared with stakers, not passive holders. A backer opts in by calling stake, which moves their tokens into the per-campaign stake vault (a program-owned account, seeds ["stake_vault", campaign]) and opens a StakePosition on first stake. Staking is one-way until the campaign closes: unstake is refused before settlement, so a staker cannot ride the rewards and then exit ahead of everyone else. close_position reclaims the position’s rent once it is empty and fully claimed.
Two streams fund the pool: the staker share of every sell fee (1% of each sell, paid into the vault as the sale happens) and whatever anyone deposits via deposit_revenue (during the window and at any time after, always split on arrival).
Distribution uses a per-token reward accumulator (fixed-point): every deposit raises the accumulator by deposit ÷ total staked, and a staker’s claim is their staked amount times the accumulator delta since their last checkpoint, floored to lamports once at credit time. Rounding dust below one lamport per token is carried forward, never lost.
Revenue is assigned the moment it arrives, to whoever is staked at that instant, and claim_revenue is open in both phases — what a late staker misses is everything that came in before them, not the claim itself. Because rewards track an explicit staked balance custodied by the program — rather than a transfer hook watching every wallet — the campaign token is a plain, fully composable Token-2022.
The protocol supports referral attribution. Referral cuts are always carved from the protocol’s own share — creators, stakers and the curve are never diluted. There is a single payout point:
create_campaign) earns 2% of the raised SOL at settle, taken from the protocol’s settlement share.On-chain, the program only enforces that a referrer is not the creator themselves — who the referrer is, and how attribution is tracked, is the app’s responsibility. The cut is not sent to the referrer’s wallet at settle: it is credited to a referral vault, a PDA derived from the referrer’s key alone, which they drain whenever they like with claim_referral_fees. Settle is permissionless, so no address a creator did not author may sit in its critical path — a poisoned referral link would otherwise be able to freeze a campaign at closing time.
FxmpWaFwkyqWug7hmPTaHWWqZTqevSunr3q9WJp5MYvD| Instruction | Args | What it does |
|---|---|---|
accept_protocol_authority | — | Step 2 of the rotation — the staged key co-signs to take effect (Ownable2Step handshake). |
buy | token_amount, max_sol_cost | Buy tokens from the bonding curve with SOL. Slippage-guarded by max_sol_cost. |
claim_referral_fees | — | A referrer drains their referral vault to any wallet they choose. The vault is a PDA of their own key, credited at every settle of a campaign they referred. |
claim_revenue | — | Staker claims their pro-rata share of everything assigned to them since their last claim. Open during the campaign as well as after it. |
close_position | — | Close an emptied StakePosition and reclaim its rent — only once it holds no stake and no unclaimed rewards. |
create_campaign | args | Launch a campaign: mints the curve token (a standard, hookless Token-2022), opens the trading window, and records the creator's referrer for affiliation. |
deposit_revenue | amount | Deposit SOL into the RevenueVault for stakers. Split immediately between whoever is staked at that instant, during the window or after it — so it is refused when nobody is staked. |
initialize_protocol_treasury | — | One-time protocol setup — creates the singleton treasury PDA (program upgrade authority only). |
rotate_protocol_authority | new_authority | Step 1 of the two-step authority rotation — stages a new protocol authority that must be accepted before it takes effect. |
sell | token_amount, min_sol_output | Sell tokens back to the curve. Slippage-guarded by min_sol_output; the sell fee applies. The staker share is paid into the revenue vault as the sale happens, so it is claimable straight away. |
settle | — | Close the campaign once the window ends. Permissionless: burns unsold tokens and splits the raised SOL between creator, protocol, caller and the creator's referral vault. It does not touch the revenue vault — every entitlement was fixed when the money arrived. |
stake | amount | Stake campaign tokens into the per-campaign stake vault to earn the revenue share. Opens a StakePosition on first stake; works during and after the window. One-way: staked tokens cannot come back out before settlement. |
unstake | amount | Withdraw staked tokens back to your wallet, settling any accrued rewards into the position first. Refused until the campaign is settled — staking is a commitment for the length of the campaign, not a position you can time. |
withdraw_protocol_fees | — | Protocol authority withdraws accrued protocol fees from the treasury. |
Campaign | One per campaign — curve reserves, trading-window timestamps, status, total staked, the reward accumulator, and the creator's referrer. |
ProtocolTreasury | Singleton — protocol authority key (plus a staged pending authority for two-step rotation) and accrued protocol fees. |
ReferralVault | One per referrer, shared across every campaign they referred — holds their settle cuts until they claim them. |
RevenueVault | One per campaign — the SOL revenue pool stakers claim from, created up front so revenue can be deposited during the window. |
StakePosition | One per (campaign, wallet) — the owner's staked amount, reward checkpoint and pending (claimable) rewards. Replaces the old hook-tracked HolderAccount. |
| ProtocolTreasury | ["protocol_treasury"] |
| Campaign | ["campaign", creator, slug] |
| Mint | ["mint", campaign] |
| RevenueVault | ["revenue_vault", campaign] |
| StakeVault | ["stake_vault", campaign] |
| StakePosition | ["stake_position", campaign, owner] |
| ReferralVault | ["referral_vault", referrer] |
12 of the 14 instructions emit an Anchor event — one event each, and this is what powers the live charts and feeds on this site. The other 2 emit nothing at all: close_position and initialize_protocol_treasury, by design — follow those two by reading the accounts, because no event is ever coming. The 12:
CampaignBuyCampaignCreatedCampaignSellCampaignSettledProtocolAuthorityRotatedProtocolAuthorityRotationProposedProtocolFeesWithdrawnReferralFeesClaimedRevenueClaimedRevenueDepositedStakedUnstakedThe 34 custom errors the program can return (codes start at 6000, Anchor convention):
| Code | Name | Message |
|---|---|---|
6000 | InvalidSlug | Slug must be 1-32 characters of lowercase letters, digits and hyphens, and cannot start or end with a hyphen. |
6001 | InvalidMetadataUri | Metadata URI must start with https:// or ipfs://, be at most 128 bytes, and contain only printable ASCII. |
6002 | InvalidCampaignEnd | The campaign end timestamp must be in the future. |
6003 | CampaignDurationTooShort | The campaign duration is shorter than the minimum allowed. |
6004 | CampaignDurationTooLong | The campaign duration is longer than the maximum allowed. |
6005 | InvalidQuantity | The quantity must be greater than zero. |
6006 | MathOverflow | Arithmetic overflow. |
6007 | CampaignClosed | The campaign trading window has not started or is already closed. |
6008 | CampaignExpired | The campaign trading window has expired; only settlement is allowed now. |
6009 | CampaignAlreadySettled | The campaign is already settled. |
6010 | CampaignSettledRequired | The campaign is not settled yet. |
6011 | CampaignNotEnded | The campaign cannot be settled before the end timestamp. |
6012 | InsufficientCurveLiquidity | Not enough liquidity remains in the curve. |
6013 | SlippageExceeded | The slippage limit was exceeded. |
6014 | InvalidRevenueVault | The provided revenue vault does not match the campaign. |
6015 | NoFundsAvailable | No funds are currently withdrawable. |
6016 | NoClaimAvailable | No revenue is currently claimable. |
6017 | NoStakersToReward | No tokens are staked, so a deposit has no one to distribute to. |
6018 | VaultBalanceMismatch | The vault balance is inconsistent with the recorded reserves. |
6019 | InvalidStakePositionOwner | The provided stake position owner does not match the signer. |
6020 | InvalidStakePositionCampaign | The provided stake position campaign does not match the campaign. |
6021 | Unauthorized | Signer is not authorized to perform this action. |
6022 | ProtocolUpgradeAuthorityRequired | Only the program upgrade authority can initialize the protocol treasury. |
6023 | RewardAccountingError | Reward accounting invariant violated: accumulator < checkpoint. |
6024 | InsufficientStake | Cannot unstake more than the staked amount. |
6025 | StakeStillActive | Cannot close a stake position that still has staked tokens. |
6026 | UnclaimedRewards | Cannot close a stake position with unclaimed rewards. |
6027 | SelfReferral | A referrer cannot be the same wallet as the referred user. |
6028 | InvalidReferrer | The provided referral vault does not match the campaign's creator referrer. |
6029 | CreationCostBelowRent | The fixed creation cost is lower than the rent of the accounts being created. |
6030 | InvalidWithdrawDestination | The withdrawal destination cannot be the protocol treasury itself. |
6031 | InvalidTokenName | Token name must be 1-32 bytes, without surrounding whitespace or characters that alter how it renders. |
6032 | InvalidTokenSymbol | Token symbol must be 2-3 characters of uppercase letters and digits, starting with a letter. |
6033 | MetadataAuthorityInvalid | The campaign PDA could not be set as the mint metadata update authority. |
The full Anchor IDL (interface definition — everything the tables above are generated from) is available as JSON. Use it with Anchor’s TypeScript client to integrate against the program directly:
import { Program } from "@coral-xyz/anchor";
import idl from "./anvora.json";
const program = new Program(idl, provider);
// program.methods.buy(tokenAmount, maxSolCost)…