Trading Pairs
Every fr* asset trades against frBTC on SUBFROSTAMM, SUBFROST's constant product AMM on alkanes. There is one pool per asset, all created through the same factory. BTC enters and leaves through frBTC (32:0), so a single Bitcoin transaction can wrap BTC and trade or add liquidity in one step.
All four pools are live on mainnet. SUBFROST seeded frSOL/frBTC (2:103202), frXRP/frBTC (2:103203) and frADA/frBTC (2:103204) with about $480 a side each, at market, in blocks 971467 to 971469. The frFB/frBTC pool 2:102725 is also live; see the Fractal docs. These are the only SUBFROSTAMM pools for these pairs. FindPool on the factory (below) returns the same ids; any other id is not ours.
Contracts
| contract | id | notes |
|---|---|---|
| SUBFROSTAMM factory (router) | 4:333052 | creates and finds pools, routes swaps and liquidity |
| frBTC | 32:0 | 8 decimals. Opcode 77 wraps BTC, opcode 78 unwraps |
| frSOL | 4:50050 | lamport, 9 decimals |
| frXRP | 4:50051 | drop, 6 decimals |
| frADA | 4:50052 | lovelace, 6 decimals |
| frFB | 4:88888 | 8 decimals |
| pool | pool id | reserve order |
|---|---|---|
| frSOL/frBTC | 2:103202 | (frSOL, frBTC) |
| frXRP/frBTC | 2:103203 | (frXRP, frBTC) |
| frADA/frBTC | 2:103204 | (frADA, frBTC) |
| frFB/frBTC | 2:102725 | (frFB, frBTC) |
A pool's coins are stored in sorted id order, so the fr* asset (block 4) is always coin 0 and frBTC (block 32) is coin 1. The pool id is also the id of its LP token.
Finding a pool
FindPool is opcode 2 on the factory, with both coin ids. It returns the pool id as two u128 LE words, or reverts if the pair has no pool.
alkanes-cli -p mainnet --jsonrpc-url https://mainnet.subfrost.io/v4/jsonrpc \
alkanes simulate 4:333052:2:4:50051:32:0 # FindPool(frXRP, frBTC)
Opcode 3 (GetAllPools) returns a count followed by every pool id. subfrost-cli pools and subfrost-cli pool <BLOCK:TX> show the same pools with reserves already decoded.
Reading a pool
| pool opcode | name | returns |
|---|---|---|
| 97 | GetReserves | reserve0, reserve1, lp_ppm, protocol_ppm, season_ppm, height_last, each a u128 LE |
| 20 | GetFees | lp_ppm, protocol_ppm, season_ppm |
| 24 | GetAmountOut(i, amount_in) | the output for amount_in of coin i, at the current reserves |
| 101 | GetTotalSupply | LP supply |
| 98 | GetPriceCumulativeLast | TWAP accumulators (Q112, price times blocks) |
| 999 | PoolDetails | OYL compatible pool info, then SUBFROSTAMM fields |
alkanes-cli -p mainnet --jsonrpc-url https://mainnet.subfrost.io/v4/jsonrpc \
alkanes simulate 2:<pool>:97 # reserves and fees
Quoting
The swap fee is the sum of the three ppm values. For an exact input in of one coin:
fee = lp_ppm + protocol_ppm + season_ppm
out = in · (1e6 − fee) · R_out / (R_in · 1e6 + in · (1e6 − fee))
Opcode 24 gives the same number from the pool itself. Quote from the reserves at the height you build at, set amount_out_min from your slippage tolerance, and simulate before signing.
These pools start small. A trade moves the price by roughly amount / reserve, so on a $1,000 pool a $50 trade moves it about 10%. Always pass a real amount_out_min.
Swapping
Swaps go through the factory, never the pool directly.
| factory opcode | call |
|---|---|
| 29 | SwapExactTokensForTokensImplicit(amount_out_min, deadline, path_len, path…): swaps everything of path[0] that arrived with the call |
| 13 | SwapExactTokensForTokens(amount_in, amount_out_min, deadline, path_len, path…) |
| 14 | SwapTokensForExactTokens(amount_out, amount_in_max, deadline, path_len, path…) |
path_len is the number of alkane ids in the path (hops plus one) and is required, followed by 2 · path_len words. deadline is a block height; 340282366920938463463374607431768211455 (u128::MAX) means none. Prefer opcode 29 when you send the exact input in the parcel. Do not use opcode 13 with amount_in = 0: it does not mean "everything" and runs out of fuel.
frXRP to frBTC:
[4, 333052, 29, amount_out_min, deadline, 2, 4, 50051, 32, 0]
frSOL to frXRP is two hops through frBTC (path_len 3):
[4, 333052, 29, amount_out_min, deadline, 3, 4, 50050, 32, 0, 4, 50051]
To reach BTC, finish with a frBTC unwrap ([32, 0, 78, signer_vout, deadline], see Wrapping frBTC). For DIESEL, FIRE or BTCUSD, hop from frBTC into those pools.
BTC in, fr* asset out, one transaction
Wrapping is opcode 77 on frBTC: the transaction pays BTC to the frBTC signer address, and the minted frBTC goes to the protostone's pointer. Point the wrap at the swap protostone:
subfrost-cli tx build \
--output signer:frbtc:600000 \
--output self \
--protostones '[32,0,77]:p1:v1,[4,333052,29,<min_out>,<deadline>,2,32,0,4,50051]:v1:v1' \
--fee-rate 2 --out plan.json
subfrost-cli tx simulate plan.json
subfrost-cli tx sign plan.json && subfrost-cli tx broadcast plan.json
Here v0 is the frBTC signer output carrying the BTC to wrap and v1 is your own output, which receives the frXRP and any refund. subfrost-cli tx inspect plan.json names every output and protostone target before you sign, and tx run does build, inspect, simulate, sign and broadcast in one go.
Liquidity
| factory opcode | call |
|---|---|
| 11 | AddLiquidity(a.block, a.tx, b.block, b.tx, a_min, b_min, min_lp, deadline): both coins in the parcel; the excess of the over supplied coin is returned |
| 12 | RemoveLiquidity(a.block, a.tx, b.block, b.tx, a_min, b_min, deadline): burns every LP unit in the parcel |
| 1 | CreatePool(a.block, a.tx, b.block, b.tx, min_lp): permissionless, once per pair; the seed coins ride in the parcel |
Size an add at the pool's current ratio: b = a · reserve1 / reserve0. The LP minted is min(a · supply / reserve0, b · supply / reserve1), read supply from opcode 101. For the first deposit (CreatePool) it is isqrt(a · b) − 1000, with 1000 locked forever.
Wrap BTC and add liquidity, one transaction
Hold the fr* asset, bring BTC, and get LP back in one transaction. The first protostone moves the asset to the add, the second wraps BTC into it, the third adds:
subfrost-cli tx build \
--output signer:frbtc:<sats> \
--output self \
--spend 4:50051:<drops> \
--protostones '[4:50051:<drops>:p2]:v1:v1,[32,0,77]:p2:v1,[4,333052,11,4,50051,32,0,<a_min>,<b_min>,<min_lp>,<deadline>]:v1:v1' \
--fee-rate 2 --out plan.json
The LP and the unused part of either side return to v1. Removing is the reverse: spend the LP (--spend 2:<pool>:all), call opcode 12, and unwrap the frBTC to BTC in a following protostone if you want BTC.
Routed deposits into the pools
The frFB intents already turn a deposit on another chain into a finished position on Bitcoin: swap to frBTC, BTC, DIESEL, FIRE, a stablecoin through BTCUSD (4:1778), or an LP position, with a share kept in BTC or DIESEL for later fees. The same route intents are coming to frXRP first, then frADA and frSOL, landing in the pools on this page:
- LP. Part of the deposit is swapped to frBTC and both sides are added, so an XRP deposit arrives as frXRP/frBTC LP. If the pool is too thin for the deposit, the app falls back to a plain mint and asks you to bring BTC to pair with it, the flow above.
- Swaps. frBTC, BTC, DIESEL and FIRE, all through the asset's frBTC pool.
- STABLE. Always routed through BTCUSD (
4:1778), then out to USDC or USDT on EVM chains.
Until routes are switched on for an asset, its deposits mint plainly to the recipient and every trade on this page is done by the holder.
Checklist for integrators
- Take pool ids from this page (or
FindPool), never from a token list, a chat message or a link, and check thatFindPoolon4:333052returns the same id. A pool thatFindPooldoesn't return is not the SUBFROSTAMM pool for that pair. - Quote from opcode 97 at the height you build, and always set
amount_out_min,a_min,b_minandmin_lp. - Simulate every transaction (
subfrost-cli tx simulate, oralkanes_simulate) and refuse to sign on a revert. - Units differ per asset: 9 decimals for frSOL, 6 for frXRP and frADA, 8 for frBTC and frFB.
- See the developer reference for deposits and burns, and Agents and automation for bots.