(08)Technology

Ostra Swap

Ostra Swap trades any token that has a Uniswap v3 or v4 pool against USDG or WETH, stock tokens and Ostra launches included, from a wallet or from an agent's post-quantum vault. Its fee is 0.20%, lower for $OSTRA locked, and half of every fee buys $OSTRA and sends it to the burn address. A private pool lets an agent hold and swap tokens without showing which of its notes it spends.

Chain Robinhood Chain · 4663Code f0806ceOstra Swap 9b622caOstrapad c46fee2

8.1What Ostra Swap is#

Five contracts, live on Robinhood Chain mainnet since 22 October 2026, none of them upgradeable:

ContractWhat it does
OstraRouter 0x5b96…0B01Swaps through Uniswap’s Universal Router against a hub, USDG or WETH, and takes the fee on the hub amount. No owner.
OstraStake 0xaDA4…4BC6$OSTRA locked for at least 7 days lowers the fee of the accounts the lock names. No owner; the tiers never change.
OstraBuyback 0xd860…F3f5Receives half of every fee and the launchpad’s buyback share, buys $OSTRA and sends it to the burn address.
OstraPrivatePool 0xe425…5b76Private balances: notes spent with a zero-knowledge proof made on the agent’s machine. No owner.
SpendVerifier 0x0a0E…0e3BChecks those proofs (STARK) for the private pool.

The source is in ostraagent/ostra-swap; every code link on this page opens it at the commit shown at the top.

8.2How a swap is routed#

A swap is two legs through a hub: tokenIn to USDG or WETH, then the hub to tokenOut, each leg skipped when its token is the hub. Each leg is a Uniswap v3 or v4 route that the router hands to the Universal Router with exactly the leg's input. The router trusts nothing the route says: it counts its own balances before and after, and the swap stands only when tokenOut arrived for at least its minimum after the feeOstraRouter.sol:49-95. Native ETH a leg pays out is wrapped to WETH.

The agent's server finds the route itself, through Uniswap v3 and v4 pools against USDG or WETH, and quotes it before anything is signedswap.ts:1-17. Tested on a mainnet fork: a USDG to NVDA swap with its fee split Swap.fork.t.sol:185-205.

8.3Swaps from a vault#

An OstraVault can transfer but not approve, so a vault swaps through an escrow order. Its one-time key signs a single transfer of the input to an address that CREATE2 derives from every term of the order: the tokens, the amount, the minimum out, where the output and any refund go, the deadline and the route. The relayer sends that spend and the router's executeEscrow in the same transactionSwap.fork.t.sol:585-613.

  • Nobody can redirect the funds. The escrow pays only the router, and the router pays only the order's recipient, its refund address and the fee. One post-quantum signature binds it all.
  • A failed swap is refunded, never stuck. A swap that misses its minimum, runs out of gas or no longer holds sends everything back to the vault in the same transaction, so the vault's spend always lands and its key always moves on Swap.fork.t.sol:695-722.
  • The fee is the recipient's. The account that receives the output is the one whose lock counts, so naming someone else's refund address gains nothing Swap.fork.t.sol:635-653.

From an agent: ostra_swap_quote gives the expected and minimum output, the hub and the fee, signing nothing tools.ts:341; ostra_swap swaps from the agent's vault of tokenIn to its vault of tokenOut tools.ts:347. Slippage is 1% by default and 10% at most swap.ts:67-68. USDG, ETH (WETH), $OSTRA and the listed stock tokens can be named by symbol; any other token, a token launched on Ostra among them, by its address.

Many MCP clients give up on a call after 60 seconds, and a swap can take longer while its order lands. So a swap not done within 40 seconds (OSTRA_SWAP_ANSWER_MS) answers pending and goes on, and a second swap from the same vault is refused while one runs tools.ts:178-183. ostra_swap_orders shows the swaps still running, the orders not finished and the newest finished ones, with what each spent and paid, or why it did not swap tools.ts:359.

The router also takes standing orders from vaults, a limit order or a DCA in tranches, run by an executor that chooses only when, never howOstraRouter.sol:49-95. The MCP server does not place them yet.

8.4The fee, and OSTRA locks#

The fee is taken on the hub amount between the two legs, at the rate OstraStake gives the account the swap is charged to: the sender for a swap from a wallet, the recipient of the output for a vault's escrow order. Half goes to OstraBuyback, the rest to the treasuryOstraRouter.sol:560-575:

$OSTRA locked for the accountFeeOf which buyback
Less than 100,0000.20%0.10%
100,0000.15%0.075%
250,0000.10%0.05%
500,000 or more0%0%

The tiers are constants of OstraStake, which has no owner: they never change OstraStake.sol:30-36. A lock works like thisOstraStake.sol:9-20 OstraStake.sol:75-109:

  • a locker's position counts in full for each account it names, one to eight: a holder's wallet and its vaults, which cannot lock themselves;
  • every lock, and every change of those accounts, locks the whole position again for 7 days; after that the locker may withdraw all of it;
  • while locked, the accounts may only grow, so one position serves at most eight accounts per 7 days and cannot lend its tier one transaction at a time;
  • $OSTRA counts only while it sits in OstraStake, so a discount cannot be borrowed for one transaction.

8.5Buyback and burn#

OstraBuyback receives half of every Ostra Swap fee and the launchpad's buyback share. Its keeper turns what arrives into WETH along a Uniswap v3 path, through USDG at most, then buys $OSTRA with it in the pool $OSTRA graduated to (native ETH / $OSTRA on Uniswap v4) and sends every $OSTRA it bought to the burn address 0x…dEaD OstraBuyback.sol:19-28 OstraBuyback.sol:172-204. The contract builds every Universal Router command itself, each paying only itself: funds leave only as $OSTRA to the burn address, or as the input of a swap into WETH or $OSTRA that pays it back. The keeper chooses the amounts, the path and each trade's minimum; the owner only names the keeperSwap.fork.t.sol:1399-1414.

The keeper is Ostra's deployer account, which also owns the launchpad's dispatcher. Every hour it buyback.ts:1-13:

  1. claims the launchpad's buyback share of every pool currency to OstraBuyback; the share owed in $OSTRA needs no buy and goes straight to the burn address;
  2. turns USDG and stock tokens into WETH along the v3 path that simulates best;
  3. buys $OSTRA with the WETH and sends it to the burn address.

Each trade is simulated first and sent with that result less 1% as its minimum, so a trade that would fail is not sent. Less than 1 USDG or 0.0002 WETH waits for the next run buyback.ts:31-34. Every step is a public transaction of the keeper.

8.6Private balances#

OstraPrivatePool keeps tokens as notes: commitments in trees of 220 leaves. Spending a note takes a STARK proof, made on the agent's machine, that the note is in a tree, that it was never spent, and that its amount covers what leaves, the relayer's fee and the change, which becomes a new note for the same owner. The proof is bound to the exact call: the chain, the pool, the relayer, the deadline and where the funds go OstraPrivatePool.sol:42-77.

PublicHidden
The token, the amount, the pair and the time of each shield, swap and withdrawal; the vault a shield comes from, where a withdrawal goes, the relayer and its feeWhich note is spent, and whose it is
  • private_shield moves a token from the agent's vault into a note tools.ts:381.
  • private_swap swaps a note through OstraRouter into a new note; private_unshield pays a note out to an address. The relayer sends both and is paid from the note, so no wallet of the agent's shows PrivatePool.fork.t.sol:403-439.
  • private_unshield with toPrivateVault pays USDG to a new private vault of the agent, one per unshield, which private_withdraw spends from private-balance.ts:39-47. Its first balance is the amount the unshield paid, so a round amount, or a note split over several unshields, is harder to match.
  • auto_shield moves what the creator vault holds above a reserve, once that is at least 1 USDG, into a note on a sweep every minute tools.ts:417-423.
  • private_recover finds the notes and the private vaults again from the seed and the pool's events alone tools.ts:387.

A private swap pays the base fee of 0.20%, whatever is locked: the pool swaps for every note owner as one account, and a lock naming it would lower everyone's fee at once PrivatePool.fork.t.sol:558-588. Every term of a spend is bound by its proof PrivatePool.fork.t.sol:465-516.

Private balances and the shielded pool#

The older shielded pool moved USDG in standard 10 USDG leaves into new vaults, in batches of at least 8 deposits. No batch ever formed on mainnet, so since MCP 0.1.6 agents keep privacy in the private pool only, and the web app moves to it next: one pool, one larger set of notes to hide among, and proofs that rest on hashes alone.

8.7Limits#

  • The contracts have not been audited by an outside firm.
  • The treasury, the keeper of OstraBuyback and its owner are Ostra's deployer, an ordinary secp256k1 account that is also the launchpad's owner. As keeper it sets each trade's minimum, so a stolen key could trade OstraBuyback's funds at a bad price but never send them anywhere else; as launchpad owner it could claim the launchpad's unclaimed buyback share to any address (see security).
  • A private swap's output tag is public before it lands: anyone may shield exactly the note it would add and make it revert, at the cost of giving its owner at least the minimum OstraPrivatePool.sol:42-77.
  • Tokens whose balances change by themselves (rebasing, fees on transfer) are not supported in the private pool.
  • Locking $OSTRA has no button in the MCP server or the web app yet.