Documentation
contrapair is a launchpad on Solana where every token is paired to a tokenized stock. Trading fees buy that stock, and the shares are paid out to whoever holds the token. Nobody holds the keys to the money — not the creator, and not us.
Overview
A token launched here behaves like any other Solana memecoin. It runs on a Meteora Dynamic Bonding Curve, it is quoted in SOL, and it shows up on Axiom, Photon and every other aggregator without us asking them to. That part is deliberately ordinary.
What is different is where the trading fees go. A share of every trade is claimed by a program-owned vault, swapped into the tokenized stock the creator chose at launch, and distributed to holders in proportion to how much they held and for how long.
A memecoin usually pays its holders in nothing but hope. This one pays in shares of a real company — and the shares are bought before anyone claims them, not promised.
How a launch works
Creating a token is a single transaction. It builds the bonding curve config, creates the pool, hands the pool's creator role to a program address, and binds that address to your chosen stock.
| Property | Value |
|---|---|
| Total supply | — |
| Decimals | — |
| Mint authority | revoked at launch |
| Freeze authority | none |
| Quote asset | SOL |
| Graduates at | — |
| Launch cost | rent only |
The supply is fixed because Meteora's program mints it once and calls
set_authority(MintTokens, None) in the same instruction.
That is not a setting we chose and could change — the program refuses
configs that ask to keep mint authority at all. No one can print more,
including us.
We charge no launch fee. Launching costs about 0.0275 SOL, paid once, and none of it reaches us. Most of it is Solana rent for the accounts a launch creates — the mint, the pool state, two token accounts, the metadata and the vault — which stays in those accounts. The remaining 0.01 SOL is what Metaplex charges to register a token's name and image; that one is spent, not held.
The vault has no key
The pool's creator role — the thing that can claim trading fees — is transferred to a Program Derived Address in the same transaction that creates the pool. A PDA is an address with no private key. Nobody can sign for it; only the program can, and only in the ways the program allows.
This is the part worth checking yourself. Read pool.creator
on any launch and derive ["vault", pool] against the
program id. They match, or the launch did not come from here.
Pairing to a stock
At launch the creator picks a tokenized stock. The choice is written into the vault and cannot be changed afterwards — there is no admin instruction to change it, and we could not do it for you if you asked.
The pairing is not the pool's quote asset. Your token still trades against SOL, which is what keeps it visible everywhere people actually trade. The stock is what the vault buys with the holders' share of the fees.
Tokenized stocks are Token-2022 mints issued by third parties — Backed for listed companies, PreStocks for pre-IPO names. Each one is backed 1:1 by real shares held off-chain. See Risks for what that means for you.
Buyback instead of a stock
A creator can pair to nothing and have the holders' share buy the token back and burn it instead. Same fee stream, different destination — scarcity rather than shares.
Fees and the split
A flat — on every trade, buy and sell, for the life of the token. No schedule, no launch-day spike.
— trading fee
└── Meteora takes 20 % hardcoded in the curve program
└── 1.2 % survives
├── 0.18 % protocol buys and burns $CONTRA
└── 1.02 % vault the creator splits this
├── up to 0.61 % creator cap of 60 %
└── at least 0.41 % holders floor of 40 %
The 60 % ceiling on the creator's share is enforced by the program, not by the interface. A launch that asks for more is rejected on chain. Holders always get at least 40 % of the vault stream.
A creator who takes less than the maximum is visible on the token's page. It is the cheapest signal a creator can send.
For comparison: pump.fun charges 1 % and pays creators nothing recurring. PONS charges 1 % split 70/30 between creator and protocol. Neither pays anything to holders.
Graduation
When the curve has taken in —, the pool migrates to a Meteora AMM and trading continues there. Liquidity is permanently locked at migration.
20 % of the supply meets the collected SOL in the new pool, which sets the price. Fully diluted value at graduation is therefore five times the threshold — —.
Fees keep flowing to the vault after graduation. Nothing about the payout mechanism changes; only the venue does.
How payouts are decided
Your share is not a snapshot of who held at some moment. It is balance multiplied by time, integrated over the token's whole life:
weight_i = ∫ balance_i(t) dt
share_i = weight_i / Σ weights
A snapshot is trivial to game — buy a minute before, sell a minute after. An integral is not, because the only way to raise your weight is to actually hold.
It makes sniping expensive rather than impossible, and it is worth being honest about the difference. A hundred times the position for a hundredth of the time gives the same weight. What protects long-term holders is that weight accrues over the token's entire lifetime — after a year, someone who held throughout has roughly five hundred times the weight of someone who buys big a minute before a distribution.
Who is excluded
The curve's own vault, the AMM pool after graduation, and the contrapair vault itself hold tokens but are not people. They are excluded by owner, not by account address — during the bonding phase the curve often holds three quarters of the supply, and getting this wrong would send most of a distribution to nobody.
Claiming
Allocations are published as a Merkle root on chain. The tree is cumulative — a leaf holds the total ever allocated to that wallet, and claiming pays the difference between that and what has already been taken.
This means a stale proof is harmless. Replaying an old one pays zero rather than paying twice, and a missed distribution is picked up automatically by the next one.
leaf = sha256(0x00 ‖ claimant ‖ amount_le_u64)
node = sha256(0x01 ‖ min(a,b) ‖ max(a,b))
The domain prefixes matter: without them a node hash could be presented as a leaf, which is a standard second-preimage attack on Merkle trees.
Payouts never expire. There is no deadline and no forfeiture.
The crank
Two jobs have to run for a vault to work: claiming the fees from the curve, and swapping them into the stock. Anyone can do either. Both instructions are permissionless and pay the caller.
| Instruction | Who | Reward |
|---|---|---|
crank_claim | anyone | 1 % of the claim, floor 0.001 SOL |
buy_stock | anyone | — |
A claim only runs once at least 0.05 SOL has accrued, which stops someone spamming the instruction to farm the floor reward.
The destinations are bound to the vault's own token accounts by the program. This matters more than it sounds: Meteora's curve program puts no owner constraints on the accounts a claim pays into, so a permissionless crank without that binding would be a free withdrawal for whoever called it first. We found that by successfully attacking an early version.
What is claimable right now is readable from
GET /crankable — see Indexer API. It
returns every vault with work waiting, how much, and which of the two
jobs it needs.
Which stocks
Not every tokenized stock can be paired against. The list is measured continuously rather than hardcoded, and the measurement is simple: ask Jupiter what it costs to buy that stock at the size the crank actually trades, which is up to about $900. Under 5 % price impact and the stock is listed, with the cost shown next to it.
Liquidity and volume were both tried as gates first and both rank wrong. AAPLx has $65k of liquidity and costs 0.84 %; HOODx has $168k and costs 2.55 %. MCDx sat in a "too thin to list" pile at $15k and costs 0.22 %. Aggregator liquidity figures do not see the RFQ and market-maker flow Jupiter reaches.
Roughly 27 stocks currently pass, including Apple, Microsoft, Alphabet, Amazon, Meta, Nvidia, Tesla, Berkshire, SpaceX, gold, the S&P 500 and the Nasdaq 100.
If a stock's liquidity dries up after launch, the crank cannot convert and SOL accumulates in the vault instead. That is shown on the token page as a waiting balance rather than left silent — a token that pays nothing should not look identical to one that works.
Risks
Read this part properly.
- Tokenized stocks are issued by a third party. The mints carry a permanent delegate, an active freeze authority and a pausable flag. The issuer can move, freeze or halt them. Your payout is a claim on those instruments and inherits every one of those powers.
- They are not equity. No voting rights, no dividends, no shareholder protections. OpenAI has publicly stated that tokenized representations of its shares are not official equity.
- A transfer hook exists on xStock mints. It is currently inactive, but the issuer can enable it. If they do, claiming stops working until the instruction is updated to pass the hook's extra accounts. It fails visibly rather than silently.
- The program has not been audited by anyone outside its own test suite.
- Memecoins usually go to zero. Pairing one to a stock does not change that. The stock accrues from trading volume, so a token nobody trades pays out nothing.
- Nothing here is investment advice, and we are not licensed to give any.
Program and addresses
| What | Address |
|---|---|
| contrapair | B3XB5WZBFqvDHFyV2TXoafbVRP5rsi7CAQZ1H9YEeudX |
| Meteora DBC | dbcij3LWUppWqq96dh6gJWwBifmcGfLSB5D4DuSMaqN |
| Jupiter v6 | JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4 |
| DBC pool authority | FhVo3mqL8PW5pH5U2CN4XE33DokiyZnUwuGpH2hmHLuM |
The program is not yet deployed to mainnet. The address above is the keypair it will be deployed to; treat everything in this section as the shape of the thing rather than something you can call today.
Vault account
One vault per launch, at ["vault", pool]. It is both the
state account and the authority — it owns its own token accounts and
signs every CPI.
offset size field
0 8 discriminator
8 32 pool DBC virtual pool
40 32 config DBC config
72 32 base_mint the launched token
104 32 quote_mint wSOL
136 32 stock_mint the paired stock
168 32 creator
200 1 bump
201 2 creator_share_bps capped at 6000
203 2 crank_reward_bps
205 8 total_quote_claimed
213 8 total_to_creator
221 8 total_to_holders
229 8 total_crank_rewards
237 8 total_stock_bought
245 64 reserved
309 bytes total
Decode it by hand rather than through an IDL if you want the failure to be loud: a length mismatch means the layout changed, which is better than silently reading the wrong field.
Instructions
| Instruction | Who | What |
|---|---|---|
initialize_vault | launcher | Binds pool to vault and stock, moves the fee stream to the PDA |
crank_claim | anyone | Claims fees, pays the cranker and the creator, leaves the rest |
buy_stock | anyone | Swaps the vault's wSOL to the stock through a whitelisted program |
publish_root | root authority | Publishes a cumulative Merkle root |
claim_stock | holder | Pays cumulative minus already claimed, against a proof |
Calling buy_stock
buy_stock verifies outcomes, not routes. It checks that
the stock balance rose by at least min_out, that quote
fell by at most max_in, and that the memecoin account was
not touched. How the swap happened is not its business, which is why a
Jupiter route works without the program knowing what Jupiter is.
Four things the caller must get right, all measured against real routes:
- Build a VersionedTransaction. Every Jupiter route uses address lookup tables and legacy transactions cannot reference them. A multi-hop route will not even serialize as legacy.
- Set the compute limit to about 400k. A route through two AMMs consumed 125k before failing on the default 200k ceiling.
- Ask for
onlyDirectRoutes. 32 accounts instead of 43–64, which is the difference between comfortable and not. - Measure the built transaction before sending. Size varies
between calls because Jupiter picks routes by market conditions, and
maxAccountsis a hint it does not honour.
crank_claim and buy_stock must be separate
transactions. The swap alone takes 880–1189 bytes of the 1232 available.
Indexer API
Chain data, already computed. All amounts are strings — weights are balance times seconds and exceed what a JSON number can hold without losing precision.
| Endpoint | Returns |
|---|---|
GET /tokens | every launch, for the grid |
GET /token?pool= | one launch, with holders and vault state |
GET /portfolio?wallet= | holdings and what is claimable |
GET /trades?pool= | recent trades, at the price actually paid |
GET /candles?pool=&tf= | OHLCV; tf is 5s, 15s, 1m, 5m, 15m, 1h, 4h or 1d |
GET /crankable | vaults with work waiting |
GET /stocks | the pairable list with measured cost |
GET /health | sync status |
The scanner reads preTokenBalances against
postTokenBalances rather than parsing instructions. That
catches every way tokens can move — a plain transfer, a swap, a CPI
from a program nobody has heard of — without needing to know the
format. A new DEX cannot slip past it.
Price comes from the same place. For each transaction the pool is the fixed point: quote in and base out is a buy, the reverse is a sell, and the two amounts give the price. The trader's own SOL side is invisible — the wrapped-SOL account is opened and closed inside the same transaction, so its net change is zero.
The chart nets out the trading fee. The fee is always taken in quote, so a buy settles above the curve and a sell below it; on raw numbers a rising curve can read as a fall. Trade history keeps the price paid, fee included, because that is what the trader actually paid.
The indexer subscribes to each pool account, so a trade is indexed about a second after it confirms rather than at the next sync — which is what makes a five-second candle mean anything.
contrapair is experimental software handling real money. Everything above describes what the program does, not a promise about what any token will be worth. Nothing here is investment advice.