The bankroll

Venues

Four venues, and the only thing that really separates them is whether a Solana program can see the money. On Solana it can, and nobody can lie about equity. Off Solana it cannot, and the difference is paid for in trust. Perps go to Hyperliquid and the money leaves Solana. Options do not: the vault holds its own quote asset and Derive is read for the price.

The table that matters#

VenueWhat forChainCustodyEquity
Raydium LaunchLabthe launch itself and fee accrualSolanaPolicy PDA is the pool creator, and StonkFun forwards the creator share to itn/a
Meteora DAMM v2LP compounding and the buybackSolanaPolicy PDA owns the positionLocal, read directly
Hyperliquidperpetualsits own L1master account holds funds, an agent wallet tradesRemote, attested
Deriveoptions pricingan OP Stack L2none: the vault holds its quote asset on Solana and reads a public bookLocal, on chain

Value has reached Hyperliquid, and an agent key has traded it

On 2026-09-23 the first real value crossed: 20 USDC from Arbitrum One to Hyperliquid's bridge, credited to the account, then an agent key approved on that account opened a real position. Before that day no value had ever reached a venue in this project. The detail, including the transaction, is below.

The crossing is operator-run, and that is a real caveat

A Solana program cannot verify another chain, so nothing about the bridge is trustless. agent_fund_venue pays a Solana token account the policy authority registered in advance, and an operator-run relayer carries the value from there: it consolidates, swaps to USDC on Solana through Jupiter, and pays out of a USDC float it already holds on the EVM side, so the bridge is a periodic rebalance rather than something in a single launch's critical path.

What bounds it is not the relayer's honesty. It can only ever be paid by agent_fund_venue, it holds no instruction on our program, and if it took funds and did not deliver then remote_equity never rises, the gap against bridged_out is visible on chain, and the deviation rail pauses the policy on the next attestation. See remote equity and attestation.

The Solana legs#

Two program ids are pinned in Config, each for different instructions, and the passthrough guard checks the CPI target against the right one before signing.

Pinned asProgramUsed by
launchlab_programLanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3ujclaim_fees
amm_programcpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGGcompound_lp and buyback_and_burn

Meteora DAMM v2 was measured live at roughly 6,667 transactions per minute on 2026-09-22.

A Local venue account is an ordinary SPL token account. Its equity is its balance, read directly from the chain, trusting nobody. That is the current implementation and it stays that way for the Solana leg.

Liquidity ends up in two places, deliberately

The launch's own liquidity sits on the LaunchLab curve, which graduates to Raydium CPMM, while the compounded LP position is a Meteora DAMM v2 position the policy PDA owns outright. That fragments depth and leaves an arbitrage between the two pools, and the compounded LP earns fees only on the Meteora side.

The trade was made on purpose: the LaunchLab creator position is locked and cannot be added to, while the Meteora position is ours, addable to at any time, and optionally lockable. Fragmentation is the price of being able to compound at all.

Both halves of that have since been checked rather than assumed. Raydium's lock program publishes exactly four instructions, none of which increases an existing lock, and lockCpLiquidity mints a fresh fee NFT per call. It is moot for StonkFun anyway: with creator_scale = 0 the creator is handed no locked position at all at migration. On the Meteora side, create_position and add_liquidity are separate repeatable instructions, and a simulated add_liquidity against a pre-existing funded position was accepted and carried to the token transfer.

Meteora's on-chain IDL is stale. Use the SDK IDL.

The IDL account published at the program id is v0.2.0 and does not match the deployed bytecode: claim_protocol_fee and zap_protocol_fee are in it and are not dispatched, while claim_protocol_fee2 and update_delegate_permission are absent from it and are. For the three instructions this program needs, add_liquidity, claim_position_fee and swap, both IDLs carry identical discriminators, arguments and account order.

One thing remains inferred rather than observed: that a PDA can own a Meteora position. create_position takes owner as an unchecked account rather than a signer and creates the position NFT account with that owner, and Meteora's own CPI docs tell integrators to forward PDA signer seeds. A devnet create_position would settle it and has not been run.

Hyperliquid, for perpetuals#

Measured live on 2026-09-22 from api.hyperliquid.xyz/info:

MeasureValue
Markets234
Open interest$14.08B
24h volume$7.75B
BTC open interest$4.03B
BTC 24h volume$3.32B

Why not a Solana perp venue#

Because measurably nobody is using them. On the same day, newest program activity on Solana mainnet was:

VenueMost recent activity
Drift v22.7 days ago
Zeta1.7 days ago
Mango v46.9 days ago
Adrena8 hours ago, at 0.1 transactions per minute
Raydium CLMM (control)1,000 signatures inside 1 second

The control exists to show the measurement method works: a busy Solana program looks nothing like the four above.

The Solana perp venues are deployed and effectively unused. Lighter was the alternative and loses on two counts: its only trade-but-not-withdraw key class is maker-only (post-only, ALO, cancel), which forbids the taker orders a directional strategy needs, and its Robinhood-domain instance excludes the US, the UK and Canada.

Sub-accounts, not vaults#

Sub-accounts are createable and fundable through documented actions (createSubAccount, subAccountTransfer). Vaults have no createVault action in the SDK at all: creation is a web-app-only human step, which breaks the permissionless keeper model, and vaults add a leader profit share and a deposit lockup that are irrelevant to a single-owner bankroll.

One consequence worth knowing: approveAgent carries no sub-account field, so per-strategy isolation comes from which sub-account gets funded, not from the venue scoping the key.

The first real crossing, and the order that followed it#

Both happened on 2026-09-23, and they are the two things this leg had never done before.

StepWhat actually happened
The depositA plain ERC-20 transfer of 20 real USDC on Arbitrum One into Hyperliquid's bridge, Arbitrum transaction 0x39fde3ab57935b381afbce748e16036253b0d36ade7631542c7f1724844fede9. The bridge credits whoever sent, so the sender and the Hyperliquid account are the same address.
Where it landedRead back immediately afterwards: clearinghouseState showed accountValue 20.0, withdrawable 20.0 and no positions, while spotClearinghouseState showed no balances at all. The deposit went straight to the perp account.
The agent keyapproveAgent succeeded against that account under the name bankroll. That is itself evidence the credit had landed, because Hyperliquid refuses every action from an account that has never been funded.
The orderSigned by the agent key, not the master: leverage set to 5x cross on SOL, then FILLED at oid 554650877349, size 0.1 at 114.71. Read back as one position with $11.47 of position value and $2.294 of margin used against $19.99 of account value.

So the signing implementation is correct against the real mainnet exchange rather than only against the vendor's 26 published test vectors, and an approved agent can open a leveraged position on the master's account without the master signing anything.

A live bug the crossing found, which is the point of doing it

The relayer's waitForCredit polled the spot balance only. On a deposit that had already fully succeeded it would have timed out and reported failure, leaving an operator looking at a "failed" relay holding 20 USDC, with an obvious and expensive next move: send it again. It now checks both accounts and reports which one credited, and the spot-to-perp transfer is skipped when the money is already in perp.

Can a Hyperliquid agent key withdraw? Unverified#

Unverified, which is neither safe nor unsafe

An approved agent key now exists and has traded, so the thing that used to block the probe, an unfunded account, is gone. The probe itself still has not been run. Until it is, the honest status of this leg is unverified, and no page on this site describes the Hyperliquid leg as safe.

What the evidence does say

Two independent statements in Hyperliquid's official SDK support it. examples/basic_agent.py says outright that the agent does not have permission to transfer or withdraw funds, and examples/basic_transfer.py enforces it as a runtime guard rather than prose. approve_agent() signs a distinct approveAgent action, separate from usdSend, spotSend, withdraw3, vaultTransfer and subAccountTransfer, each with its own EIP-712 struct.

hyperliquid-python-sdk, examples/basic_transfer.py
if exchange.account_address != exchange.wallet.address:
    raise Exception("Agents do not have permission to perform internal transfers")

What is not confirmed is the server-side authorization matrix. Hyperliquid's validator source is closed, so this rests on the vendor saying so twice, not on inspectable enforcement. Worse, whether subAccountTransfer and vaultTransfer specifically are agent-blocked is stated nowhere; only usdSend carries an explicit guard.

The probe that would settle it

Attempt usd_transfer, withdraw_from_bridge, sub_account_transfer and vault_transfer with the agent key and confirm each is rejected. It is written and it has never been run. Until it is, this leg is trusted, not guaranteed, and it is not described as safe anywhere on this site.

What has been proven on Hyperliquid#

ClaimStatus
Signing is correct26 of 26 vendor test vectors reproduced offline, live calls to the real testnet exchange echoing back exactly our master address, and now a filled order on the real mainnet exchange.
Value can cross to the venueProven on mainnet 2026-09-23: 20 USDC from Arbitrum One landed in the perp account. The crossing is operator-run, not trustless.
An approved agent key can trade the master's accountProven. A 5x cross SOL position was opened by the agent key, and the master signed nothing.
Equity attestation works end to endReal readings posted to chain, including $3,097,182.04 read from a public HLP address, with a later cycle posting a different figure, so it is live rather than cached.
The staleness and deviation rails holdProvoked rather than assumed: 8 of 8, including that a 30% move is recorded and also pauses the policy, so a deviating report never hides the loss.
An approved agent key cannot withdrawUnverified. The probe is written and has never been run. Not evidence that it can; not evidence that it cannot.

An agent key is not cryptographically scoped to a sub-account

approveAgent has no vault or sub-account field, so a key approved at the master account is approved at the master account. The real isolation boundary is not the venue's: it is agent_fund_venue's pre-registered destination on our side.

Derive, for options#

Options are priced against Derive, and the thing worth understanding first is that the vault does not send money there. An options policy holds its quote asset in the bankroll on Solana. Derive supplies the market: which contracts exist, what they are bid and offered at, their mark, their delta and their implied volatility.

That is a smaller dependency than it sounds, and a deliberate one. Reading a book needs no account, no subaccount, no deposit, no bridge and no key. It is a public endpoint. So an options launch has nothing to provision and nothing to trust a venue with, and the failure mode of the venue being unreachable is a price that cannot be read rather than money that cannot be recovered.

deployer picks underlying + call or put   ->   risk number picks moneyness + tenor
Derive's live instrument list   ->   the contract that exists nearest that mandate
Derive's live quote             ->   what the vault's holdings come to at the offer

What is read, and from where#

Two public calls, neither of which is authenticated. public/get_instruments lists what is live, which is how a contract is resolved rather than assumed: there is no point naming a strike that Derive does not list. public/get_ticker gives the quote for one instrument, carrying Derive's own timestamp so a stale read is visible as a stale read.

Sizing is at the offer, never the mark

The mark is Derive's own mid-ish reference and nobody sells at a mark. What the vault's holdings come to is computed against the best offer, which is the price at which the contracts could actually be had. Where a side of the book is empty, Derive reports "0", and that is stored as an absence with its reason rather than as a price of zero. A one-sided book says "cannot be sized".

A read can fail, and it says so

If Derive cannot be reached, the page shows that the quote could not be read and keeps rendering everything that comes from chain: the vault's balance, what the token has generated, the split. A failed read is a failed read, not a statement about the policy, and the two are never collapsed into one another.

The options risk rules#

Leverage on an option is not the premium ratio, it is the delta. A far out-of-the-money call bought with 1% of capital can carry more directional exposure than a 5x perpetual, so capping premium spent would not catch it. Two rules, both required, chosen so an options position can never do something a 5x perp could not:

RuleWhat it means
Defined risk onlyMax loss must be bounded and known at entry: long options, debit spreads, and credit spreads whose width is fully collateralised. No naked short options, ever, at any risk setting. A naked short has unbounded loss, which no leverage cap expresses.
Delta-adjusted notional at most 5x|net_delta| * underlying_price <= 5 * allocated_capital

Vega and gamma are not capped, and that is stated, not hidden

Version one caps delta and requires defined risk, and does not cap vega or gamma. The reasoning is that a defined-risk book cannot lose more than it put up, so the uncapped greeks change how a position behaves but not how much it can cost. It is a stated limitation rather than an oversight.

Those two rules are the headline. The full set is five bounds, A through E, and there is a separate question they do not answer: what the deployer's 0 to 100 risk slider actually governs once the venue is options, given that for a long option the premium paid is the maximum loss. That has its own page: risk in options.

Registering a venue account#

A venue account is an allowlist entry created by register_venue_account, which only the policy authority can call. It records the token account, the kind (Local or Remote), exactly one asset class bit and the asset mint. Four checks have to pass:

  1. The token account must be held in the policy's own quote_mint, else WrongMint.
  2. asset_class must be exactly one known bit, else BadAssetClass.
  3. That bit must be inside the origin's mask, else AssetClassNotInOrigin.
  4. The policy's risk must be at least Config.min_risk_for_class[bit], else RiskBelowAssetClass.

Perps and options both need risk 80 before a venue can be registered

This is the fourth check, and it is the one that catches people out, because the gate is in register_venue_account and not in init_policy. A policy can be created below the threshold and fails only later, when it tries to open the venue it was created for. Full detail on risk, origins and asset classes.

Venue liveness, live#

/status reports each venue's reachability. The two Solana programs are checked with getAccountInfo; the two remote venues are reported by their pollers, and say so explicitly when they have not been polled.

GET /status, venues
"venues": {
  "launchlab": {
    "reachable": true,
    "programId": "LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj",
    "source": "chain:getAccountInfo"
  },
  "meteoraDammV2": {
    "reachable": true,
    "programId": "cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG",
    "source": "chain:getAccountInfo"
  },
  "hyperliquid": { "value": null, "source": "venue poller", "absent": true, "reason": "not polled yet" },
  "derive":      { "value": null, "source": "venue poller", "absent": true, "reason": "not polled yet" }
}

A venue that has not been polled returns absent: true with a reason rather than a zero or a false. That distinction runs through the whole API and is described on the API overview.