Distributing pump.fun creator fees as liquidity-gated tokenized equity
A staking mechanism in which a memecoin's only durable revenue is converted, every cycle, into an externally-priced asset that its own order book does not set the price of.
Abstract
$STOCKPILE is a pump.fun token whose creator fees are converted into tokenized equities and paid to the people staking it. Every cycle the engine claims the token's accrued creator fees in SOL, selects an xStock that passes a live liquidity gate at that cycle's exact spend size, buys it on Jupiter, and credits it pro-rata to every wallet staking at that instant using accumulated-reward-per-share accounting with one accumulator per mint. Stakers claim, then withdraw the real Token-2022 tokens to a wallet they control. The engineering weight of this system is not in the distribution — that part is a solved problem — it is in knowing, at the moment of spending, which assets can actually be bought without handing a staker a position they cannot exit.
01The problem
Creator fees are the only durable revenue a pump.fun token has. Every trade in the token pays a fee to the creator wallet, and it keeps paying for as long as anyone trades. It needs no emission schedule, no treasury sale, no counterparty and no promise about the future. It is the one cash flow that exists whether or not the project ships anything else — and by default it accrues to exactly one wallet and stops there.
The obvious use for that revenue is to buy back and distribute the token itself. That is circular, and the circularity is not cosmetic. Paying holders in the asset whose price you are supporting means the reward and the thing being rewarded are the same instrument: every distribution dilutes the recipient's own position the moment they sell it, the "yield" is denominated in a unit whose price is set by the same order book you are buying into, and the number only goes up for as long as the buying continues. It is a mechanism that reads as revenue and behaves as reflexivity.
Paying in a different asset breaks the circle. The reward then carries a price this token's order book has no part in setting, a staker selling their reward does not press on the thing they are staking, and the distribution can be denominated in something with an external mark rather than in units of hope.
That is the whole premise. Everything after this section is what it costs to do it without hurting the people it is meant to pay.
02Why tokenized equities
The reward asset has to satisfy four constraints simultaneously:
- Externally priced. Its mark is set by a market that has nothing to do with $STOCKPILE.
- Genuinely transferable. A staker must be able to take custody of it, in their own wallet, and do whatever they like with it afterwards.
- A claim on something. Backed and redeemable through an issuer, rather than a wrapper around sentiment.
- Settleable on Solana inside one transaction, cheaply enough that paying hundreds of recipients is not itself the cost centre.
xStocks satisfy all four. They are Token-2022 SPL tokens issued by Backed Finance, 8 decimals, carrying an Xs vanity mint prefix, each tracking one share of a real equity or ETF and redeemable through the issuer. When a staker withdraws, what lands in their wallet is the Token-2022 token itself — not an IOU in this database, not a synthetic balance that only exists on this site.
They also introduce a problem that does not exist for SOL or USDC, and section 03 is entirely about it: the on-chain liquidity of a tokenized equity has no relationship to the size or fame of the underlying company.
03The liquidity gate
Recognisability and on-chain liquidity are uncorrelated. This is the single most important empirical fact in the system, and almost every naive version of this idea gets destroyed by it.
A token appearing in a token list tells you that it exists. It does not tell you that $500 of it can be bought without moving the price by 90%. To find out, you have to quote it. The tables below are measured price impact from live Jupiter ExactIn quotes (SOL → stock), recorded in src/stocks.js on 2026-08-06.
| Symbol | Company | Impact | Tier |
|---|---|---|---|
| AAPLx | Apple | 0.026% | deep |
| TSLAx | Tesla | 0.034% | deep |
| SPCXx | SpaceX | 0.037% | deep |
| SPYx | S&P 500 | 0.052% | deep |
| NVDAx | NVIDIA | 0.108% | deep |
| QQQx | Nasdaq 100 | 0.183% | core |
| MSTRx | MicroStrategy | 0.367% | core |
| MSFTx | Microsoft | 0.534% | core |
| PLTRx | Palantir | 1.314% | thin |
| Symbol | Company | Impact | Verdict |
|---|---|---|---|
| NFLXx | Netflix | 6.140% | was in the previous rotation |
| Vx | Visa | 6.932% | rejected |
| TQQQx | TQQQ | 15.290% | rejected |
| PEPx | PepsiCo | 15.695% | rejected |
| IBMx | IBM | 25.373% | rejected |
| GSx | Goldman Sachs | 48.841% | rejected |
| CSCOx | Cisco | 91.667% | rejected |
| JNJx | Johnson & Johnson | 96.712% | rejected |
| HONx | Honeywell | 99.968% | pool is a rounding error |
| CMCSAx | Comcast | 99.983% | rejected |
| CRMx | Salesforce | no route | no pool at all |
Depth is not impact
Price impact answers "what would this trade cost". It does not answer "how big is the book", and for something that has to keep buying every cycle those are different questions. Pooled liquidity is read separately, live, from Jupiter's market data:
| Symbol | Pooled liquidity | Holders | Impact @5 SOL |
|---|---|---|---|
| XOMx | $4,924 | 2,537 | 0.4255% |
| MCDx | $68,870 | 3,995 | 0.4174% |
| GLDx | $150,216 | 6,009 | 0.0135% |
| SPYx | $2,535,951 | 26,658 | 0.0250% |
A static list decays
The decisive argument against hardcoding a ticker list is not that a bad list exists — it is that a good list stops being good. GMEx measured 0.302% on one day and 3.961% the next: a 13× move in twenty-four hours, in a name that had already been tiered as comfortably tradeable on the first reading. Worse, the second reading was flat at ~3.95% across 0.05, 0.25 and 1 SOL, which is the signature of a fee-heavy pool rather than a shallow one — so trading smaller does not help.
NFLXx is the same lesson on a longer timescale. It was in the previous generation's rotation. Its pool has since drained to 6.14%, and nothing about the ticker changed to announce it.
Two layers, two different jobs
Depth is a property of the moment, not of the ticker. So the system measures continuously and splits the work in two, with deliberately different tolerances for being wrong:
A periodic sweep of the whole universe at three sizes (0.25, 1 and 5 SOL), ascending so a size that fails to route ends the walk instead of paying for quotes that can only repeat the answer. Runs on boot and every LIQUIDITY_REFRESH_MINUTES, paced at 150 ms between requests with four retries on exponential backoff, because a 429 misread as "no liquidity" would delist a perfectly good stock. It powers the ordering and the Rotation dashboard. It cannot authorise anything.
One quote, for this stock, at this cycle's exact lamport size, taken seconds before the swap. It is the only thing in the system that can approve a buy, and it approves nothing it cannot verify.
The gate fails closed, and the failure modes are enumerated rather than collapsed: no route is a refusal; a quote that comes back without a price-impact figure is a refusal, not a zero sorted to the top of the list; an impact above the cap is a refusal with the number attached. The distinction the probe draws between ok === false (measured over the cap) and ok === null (never got a straight answer) matters for the same reason: those are different claims, and only the first removes a name from the rotation. An unanswered stock stays in, because the gate will still stop a bad buy — and being wrong there costs one skipped cycle instead of a delisting.
Layering is what buys that leniency. The probe is allowed to be approximate precisely because nothing it says can move money. And because pickStock() walks the rotation rather than failing on the first refusal, a temporarily thin name costs itself one turn instead of costing the platform a whole cycle.
GMEx is kept in the table on purpose, and it is the honest edge case. At the 1.5% cap the gate skips it on every cycle and the Rotation page says so with the live number next to it. Silently dropping a name people are looking for teaches nobody anything; showing GME sitting at 3.96% and being refused shows the gate working. If that pool tightens, it gets bought automatically, with no code change. That is the entire point of measuring instead of hardcoding.
04Reward accounting
Rewards use accumulated-reward-per-share — the Synthetix/MasterChef pattern — generalised from one reward asset to N, because here every cycle pays a different xStock and a staker accumulates a basket rather than a balance. There is one accumulator per mint.
on distribution:
acc[mint] += distributed[mint] × 1e18 / totalStaked
pending(user, mint):
staked × acc[mint] / 1e18 − debt[user][mint]
The obvious implementation — loop over every staker each cycle and add their slice — is O(stakers) per cycle and rewrites the entire user table every five minutes. The accumulator makes a distribution write exactly one number per mint, so the cost of settling a cycle is O(1) per stock regardless of staker count. A user's unsettled reward is derived on read, touches no other user's row, and is exact.
Dust is carried, never dropped
Both operations are integer division and both floor. What the accumulator can actually pay out is inc × totalStaked / 1e18, which is less than or equal to the amount available; the shortfall is the rounding remainder and it is written into dust[mint] and added to the next distribution of that stock. No base unit is lost and none is silently gifted to whoever happens to sort first. With no stakers at all, the entire amount is carried rather than credited — otherwise the first person to stake afterwards would inherit every cycle that preceded them.
The two cases that make it correct
An existing staker earns a brand-new stock's first distribution
The first time a stock is ever bought, no staker has a reward-debt entry for it. debtOf returns 0n for a missing key, and the accumulator also started at zero, so their pending resolves to exactly their proportional share of that first distribution. Correct — they were staked for it.
A new staker earns none of the history
The mirror case is the one that bites. A user who stakes after a stock has already paid out must not receive a share of that history, so syncDebt iterates every mint the platform has ever credited — not just the ones this user already has an entry for — and stamps the current accumulator on all of them. Miss that and a fresh staker retroactively claims every past cycle.
Both stake and unstake settle before the weight changes, so a change in position size never rewrites what the previous position already earned. Rewards accrue right up to the instant of unstaking, and not one block past it.
05Payout architecture
The naive version of paying stakers is one line: treasury → holder, repeat. It works, and it also draws a picture. Every explorer and bubblemap tool renders "one wallet funded these 400 accounts" as a single bright hub, which is the exact visual signature of a farmed token. The holders are real, the payouts are real, and it still looks fake.
So every payment is routed through a relay: a keypair generated in memory, funded, used and emptied inside one atomic transaction.
- 1Treasury sends the relay exactly (1 + newAtaCount) × rent lamports.
- 2The relay opens its own Token-2022 associated token account for the stock, paying its own rent.
- 3Treasury sends the batch total into the relay's account in a single transferChecked.
- 4Per recipient: the relay opens their token account (idempotent, relay pays the rent) and transfers their slice.
- 5The relay closes its now-empty token account; that rent refunds to the relay.
- 6The relay returns exactly one rent to the treasury, leaving itself at zero.
The consequence is that the on-chain funder recorded against every holder's token account is a wallet which, by the time the transaction confirms, holds 0 lamports and 0 token accounts — and is therefore pruned by the runtime. The link exists for the length of one transaction, and then the account it pointed at no longer exists. A different relay handles every batch and every retry, so there is no second edge to join them back up.
The funding in step 1 has to be exact. A relay that ends with a balance is a relay that survives as a visible account, so the number of accounts it pays for must match the number it was funded for. Where that count cannot be read reliably, the code deliberately over-estimates: under-funding fails the batch outright, while over-funding strands one rent in a dead wallet, which is the cheap direction to be wrong in.
The wire format sets the batch size
A batch is a legacy transaction and it has to fit inside Solana's packet limit. Measured, at seven recipients:
At eight recipients the message is roughly 1270 bytes (25 account keys × 32, two signatures, ~305 bytes of instructions), serialize() throws on every attempt, and the whole run reports everybody unpaid. The batch size is therefore hard-capped at 7 by the wire format rather than by preference, and the compute budget is raised to 400,000 units because the 200k default truncates the tail of a full batch and fails the transaction.
Retries must not double-pay
A transaction the client stopped waiting for can still land inside its blockhash window. Before signing any replacement, every already-submitted signature is re-checked; then the code waits for the previous attempt's blockhash to become permanently unlandable — polling block height against lastValidBlockHeight — while continuing to watch for a late landing. A blockhash stays valid for roughly 60–80 seconds while confirmation gives up at about 50, so without that wait a replacement can be signed while the original is still perfectly landable and both can execute. A duplicate payout is unrecoverable; a slow one is not.
For the same reason, "never left the building" and "on the wire but unconfirmable" are reported as different outcomes. A batch the RPC accepted but nobody could confirm comes back marked unconfirmed, with its signatures, and is explicitly not declared unpaid — a withdrawal refunds a user's balance on a definite failure and must never refund a transfer that lands a minute later.
06Treasury safety
The treasury wallet holds the operator's own SOL and receives creator fees into the same address. The bot must therefore be structurally incapable of spending the former. Its budget is not the wallet balance — it is a decoupled ledger, claimPoolLamports, which only grows by the measured balance delta across a claim, and only shrinks by SOL observed to have left.
Both figures come from on-chain reads taken immediately before and after the action, never from a quoted or requested number. This is not fastidiousness: pump.fun's fee API returns a signature and nothing else — no amount, no receipt, no breakdown — so the only trustworthy answer to "how much did we just earn" is the treasury's own balance. A hopeful parse of a field that stops meaning what it used to would otherwise inflate the pool and authorise spending SOL that never arrived.
Two consequences follow from measuring rather than trusting:
- The delta is net of the fee just paid to claim. That is correct — the spendable pool should only grow by what actually landed.
- A confirmed claim with a zero delta is a success, not a failure: it means there were no fees to collect. Escalating the priority fee and retrying would burn SOL to re-learn the same thing, so only a claim that never confirms is retried.
What the ledger cannot know
A balance delta cannot distinguish fee income from any other SOL that happens to arrive in the same window — a top-up, a sale, a mis-sent transfer would all read identically, and all of them would be spent on stock and become unrecoverable. That ambiguity is handled explicitly rather than hidden: a single-cycle claim above MAX_CLAIM_SOL is quarantined into a held balance, nothing is spent, and a human decides. If it really was fee income, release it; if it was a mistaken transfer, leave it. Fee wallets should only ever receive fees.
The floor
spendable = min(
pool × (100 − RESERVE_PERCENT) / 100, // the ledger's permission
balance − TREASURY_FLOOR_SOL // the wallet's physical limit
)
The floor is never crossed, so a transaction fee can always be paid even if the accounting drifts. Above that, the rent for opening stakers' stock accounts is subtracted before the swap size is computed, so the buy cannot consume the SOL the payout still needs. A cycle whose spendable pool falls below MIN_CYCLE_SOL is carried over rather than executed thinly.
Signing discipline and custody
- The claim path never blind-signs a transaction handed back by a remote service. It must require exactly one signature and that signer must be this treasury, or it is refused; the blockhash is replaced with a fresh one before signing, since the one baked in was minted before the round-trip.
- The treasury keypair is a private class field, so serialising a claimer into a log line, an API response or an error dump cannot leak it.
- Deposit-wallet private keys are AES-256-GCM encrypted at rest. Relay keypairs are generated at runtime and never stored at all.
- Changing a withdrawal address starts a cooldown before it can be used, so a stolen session cannot instantly redirect funds.
- Solvency is computed publicly: liabilities from the ledger, assets read from the chain. A liability that cannot be priced is counted in full in token units and never netted to zero, and an unreadable chain reports null — an unknown result is never rendered as a pass.
- Boot refuses fatal misconfigurations outright: live mode with demo mode on, live mode without an encryption key or treasury key. Any value still matching a published .env.example placeholder is treated as unset, so an operator who pastes a real treasury key and overlooks the encryption key cannot boot live behind a master key every reader of the repository knows.
07Verification
On 2026-08-06 every one of the 25 xStocks then listed in the universe was bought for real on Solana mainnet, in one run, from one funded wallet.
| Line item | SOL | Detail |
|---|---|---|
| Token-account rent | 0.053940 | 25 new accounts × 0.002158, one-time |
| Network fees | ~0.002625 | across 25 transactions |
| Swap size | 0.020000 | 25 × 0.0008 SOL |
| Total spent | 0.076565 | from a 0.100000 SOL balance |
Three names filled above the 1.5% impact cap: GMEx at 3.956%, UNHx at 1.960% and AVGOx at 1.882%. The routes existed and the swaps landed — that run was a buy-path proof, not a production cycle, and the gate is what a production cycle consults. UNHx and AVGOx were then moved to the rejected set with the numbers that disqualified them, because a name the gate refuses on every cycle is not a candidate, it is noise in the rotation. GMEx was kept, for the reason given in section 03.
Re-running it
None of this asks to be taken on trust. Every claim above is reproducible from the repository against live mainnet, and the read-only checks cost nothing:
npm run probe # re-measure the whole universe from live Jupiter quotes
npm run verify-chain # transaction sizing, routing, ATA rent, relay-batch construction
npm run verify-buys # simulate the real swap for every stock against current chain state
Expect the probe to disagree with the tables in section 03. That is the thesis, not a defect: depth moves, and a paper that reproduced identically a month later would be describing a static list.
08Risks and limitations
This is not investment advice and nothing here is a licensed financial product recommendation. Read this section before staking anything you cannot afford to lose.
- The design is custodial. Staked tokens and unwithdrawn rewards sit in one treasury address. That is a single high-value target, by construction. Encrypted deposit keys, the withdrawal cooldown and published solvency reduce the blast radius; none of them make it non-custodial. Only stake what you accept could be lost to a compromise, a bug or an operator error.
- Rewards are fee-funded and therefore volume-dependent. There is no yield, no APY, no emission and no promise of a return. If trading volume stops, distributions stop. A cycle with nothing to claim distributes nothing and creates no debt owed to anyone.
- $STOCKPILE is a volatile memecoin and can go to zero. Staking it does not protect its price.
- xStocks carry issuer risk. They are third-party tokenized products. Their redeemability, pricing and continued on-chain liquidity are outside this platform's control.
- The gate protects the entry, not the exit. It measures SOL → stock impact at buy time. A staker selling later faces whatever that pool has become — and section 03 is a catalogue of how fast that can change. NFLXx at 6.14% is what it looks like from the wrong side.
- Balance-delta accounting is coarse. It cannot tell fee income from a mis-sent transfer. The claim cap is a backstop, not a classifier.
- Tokenized equities may be restricted where you live and may not be available to US persons. Checking your own jurisdiction is your responsibility, not this site's.
- Infrastructure fails. RPC endpoints, Jupiter routes and the creator-fee API all have bad days. A failed cycle carries over; it does not create a debt.
- There has been no third-party audit, and there is no partnership, endorsement or guarantee of any outcome. Nothing on this page should be read as claiming otherwise. The source is the specification: read it.
09Parameters
Every knob is an environment variable with a default, and the defaults are the safe ones: out of the box the platform boots into a simulation that moves no funds, so the whole system is explorable before a single key exists.
| Parameter | Default | Controls |
|---|---|---|
| DRY_RUN | true | Master switch. Nothing on-chain is signed or sent; reads only. |
| DEMO_MODE | true | Synthetic fees and seeded stakers. Only legal alongside DRY_RUN — the combination with live mode is a fatal boot error. |
| CYCLE_INTERVAL_SECONDS | 300 | Claim → gate → buy → distribute, once per interval. |
| MAX_PRICE_IMPACT_PCT | 1.5 | The gate. A live quote above this refuses the buy. |
| MAX_TIER | core | How deep into the measured tiers the rotation reaches: deep, core or thin. |
| LIQUIDITY_REFRESH_MINUTES | 30 | How often the universe is re-measured, so ranking tracks reality rather than the seeded table. |
| SWAP_SLIPPAGE_BPS | 150 | Slippage tolerance on the Jupiter swap. Floored at 10. |
| RESERVE_PERCENT | 10 | Held back from every claim as the SOL gas and rent buffer. |
| MIN_CYCLE_SOL | 0.02 | Below this the cycle is skipped and carried over. |
| TREASURY_FLOOR_SOL | 0.05 | Hard floor. Never spent below, whatever the ledger says. |
| MAX_CLAIM_SOL | 25 | Single-cycle claims above this are quarantined for a human. 0 disables. |
| PLATFORM_FEE_BPS | 0 | Optional operator cut taken before stakers are credited. Capped at 2000. |
| PAYOUT_BATCH_SIZE | 6 | Recipients per relay transaction. Clamped to 7 by the packet limit. |
| MAX_RECIPIENTS_PER_CYCLE | 600 | Safety bound on a single distribution; largest weights are paid first. |
| PRIORITY_FEE_SOL | 0.0005 | First-attempt priority fee. Doubles on each retry. |
| ATA_RENT_ESTIMATE_SOL | 0.0023 | Fallback rent figure when the on-chain lookup fails, so one RPC hiccup cannot abort a run. |
| MIN_STAKE | 0 | Checked against the resulting position, not the increment, so topping up stays possible. |
| MIN_WITHDRAWAL_SOL | 0.001 | Floor on a SOL withdrawal. |
| WITHDRAWAL_CHANGE_COOLDOWN_MINUTES | 60 | Anti-drain delay after a withdrawal address changes. |
| SWEEP_EVERY_TICKS | 3 | How often deposit wallets are swept into the treasury. |
| MINT_WATCH_POLL_SECONDS | 20 | Poll interval when the mint is set to auto-detect from the treasury. |
The mechanism is small enough to hold in your head and every number in this document came out of the source or a mainnet transaction. If a claim here cannot be reproduced, it does not belong here.