Cookbook

The shortest correct form of each operation an integrator ships. For swaps, route first: Quotes & Routing. Addresses from GET /v1/venues, ABIs from GET /v1/abis/{name}, both keyed by chain id; wire shapes and SDK signatures are canonical in the API & SDK Reference.


1. Setup

LangStack
TypeScriptviem / ethers + @btr-protocol/sdk
Pythonweb3.py
Rustalloy / ethers-rs
SolidityFoundry; declare the interface from Basic Operations §1
API=https://api.btr.markets curl -s $API/v1/venues | jq 'keys' curl -s $API/v1/abis/Pool | jq 'length'

Solidity callers: IPool.sol does not declare the trading functions. Use the IBtrPool block in Basic Operations §1; every Solidity snippet below assumes it.


2. Swap

2.1. TypeScript, routed

import { planToLegs, buildSwapCalls, wirePlanToSwap } from '@btr-protocol/sdk/router'; import { routeAsync } from '@btr-protocol/sdk/amm'; // `wires`: NamedPoolWire[] for `pools` (NamedPool[]); the wire answer is snake_case. const res = await routeAsync({ pools: wires, token_in, token_out, amount_in: hexAmount }); const { plan } = wirePlanToSwap(pools, { tokenIn: token_in, tokenOut: token_out, amountIn: Number(amountIn) / 10 ** decimalsOf(token_in) }, res, decimalsOf); const legs = planToLegs(plan, { slippageFrac: 0.005, // direct legs only; a two-hop part needs serverFloors from /v1/chain/route tokenOf, isOfficialPool, // required; fails closed on any pool the factory does not assert nativeIn: true, amountInUnits: amountIn, // exact bigint; omitting it can overshoot the balance by 1 wei }); if (!legs) throw new Error('unroutable'); const calls = buildSwapCalls(legs, { recipient: user, sender: user, wrappedNative }); // send from the user account: EIP-5792 wallet_sendCalls, or sequentially

2.2. TypeScript, single pool

import { getSwapQuote, swap, defaultDeadline } from '@btr-protocol/sdk/pool'; // First argument is an EIP-1193 provider (window.ethereum), not a viem client. const q = await getSwapQuote(provider, pool, tokenIn, tokenOut, amountIn); await swap(provider, pool, { tokenIn, tokenOut, amountIn, minAmountOut: (q.amountOut * 9950n) / 10000n, // 50 bps recipient: user, deadline: defaultDeadline(), });

2.3. Python (web3.py, single pool)

from web3 import Web3 w3 = Web3(Web3.HTTPProvider(rpc)) pool = w3.eth.contract(address=pool_addr, abi=pool_abi) # GET /v1/abis/Pool token = w3.eth.contract(address=token_in, abi=erc20_abi) quote = pool.functions.getSwapQuote(token_in, token_out, amount_in).call() min_out = quote[0] * 9950 // 10000 # quote[0] = amountOut token.functions.approve(pool_addr, amount_in).transact({"from": user}) pool.functions.swap_qe( token_in, token_out, amount_in, min_out, user, 2**256 - 1, # deadline opt-out: type(uint256).max ).transact({"from": user})

For a routed fill, POST /v1/route and encode each leg’s approve + swap_qe the same way. Legs must be sent from the user account, no Multicall3.

2.4. Rust (alloy)

// Route off-chain, then send one approve + swap per leg from the user's wallet. let plan: RouteResponse = reqwest::Client::new() .post("https://api.btr.markets/v1/route") .json(&serde_json::json!({ "pools": pools, "token_in": token_in, "token_out": token_out, "amount_in": format!("0x{amount_in:x}"), })) .send().await?.json().await?; // Bind the ABI from GET /v1/abis/Pool, then per leg. // U256 has no Mul<u64>: lift the scalars, or use mul_div from your bigint crate. let min_out = amount_out * U256::from(9950u64) / U256::from(10000u64); let tx = pool.swap_qe(token_in, token_out, amount_in, min_out, recipient, U256::MAX);

2.5. Solidity (vault / strategy)

using SafeERC20 for IERC20; // OpenZeppelin; forceApprove lives here, not on IERC20 IERC20(tokenIn).forceApprove(pool, amountIn); uint256 out = IBtrPool(pool).swap_qe( tokenIn, tokenOut, amountIn, minOut, address(this), block.timestamp );

On-chain strategies normally fix a pool: no off-chain routing inside the vault. Aggregators still discover venues per chain via /v1/venues, off chain.


3. Liquidity

3.1. Deposit / withdraw (TypeScript)

import { deposit, withdraw, defaultDeadline, NATIVE_TOKEN } from '@btr-protocol/sdk/pool'; await deposit(provider, pool, { token, amount }); // token = NATIVE_TOKEN → sent as msg.value await withdraw(provider, pool, { token, lpAmount, minAmountOut, deadline: defaultDeadline(), });

3.2. Dual-route mint (TypeScript)

import { rankDeposit, buildDepositCalls } from '@btr-protocol/sdk/router'; // Args 2 and 3 are symbols, not addresses; amountIn is human units, not bigint. // opts needs slippageFrac and backend: { meta, baseDecimals } (the backend prices every leg). // Returns { best, routes }; best is null when every route is gated off. const { best } = await rankDeposit(pools, 'USDC', 'WETH', 1000, opts); if (!best) throw new Error('no feasible mint route'); const calls = buildDepositCalls( poolAddress, best.id === 'market-first' ? { mode: 'market', legs, depositToken, depositAmount } // Σ per-part minOut : { mode: 'transfer', token, amount, targetToken, lpAmountIn, minLpAmountOut }, { recipient: user, wrappedNative }, );

buildDepositCalls consumes the MarketMintArgs / TransferMintArgs union built from best. Market mode is [approve?, swap(X→target)…, deposit(target)]; transfer mode is [approve?, deposit(X), swapLiability] and takes one approval total, since the LP burn needs no allowance. Fresh deposits in transfer mode are not batchable: the anti-JIT cooldown gates them (Flow Guards).

3.3. Cross-asset exit: withdrawTo

// Burn LP on tokenFrom, receive tokenTo, priced along the anchor path. IBtrPool(pool).withdrawTo(tokenFrom, tokenTo, lpAmount, minAmountOut, deadline);

3.4. Transfer liability (swapLiability)

Moves the LP claim from one leg to another. Reserves do not move; the claim converts at the pool rate C, and both legs need LIABILITY_SWAP_ENABLED_BIT (Basic Operations §2). Mechanics: Liability Swaps.

uint256 lpOut = IBtrPool(pool).swapLiability( tokenIn, // leg to leave tokenOut, // leg to enter lpAmountIn, minLpAmountOut, deadline );
import { encodeFunctionData } from 'viem'; import { POOL_ABI } from '@btr-protocol/sdk/abis'; // No first-class sender: encode it. const data = encodeFunctionData({ abi: POOL_ABI, functionName: 'swapLiability', args: [tokenIn, tokenOut, lpIn, minLpOut, deadline], });

The front end labels this LP action swap (action matrix).

3.5. Donate

IBtrPool(pool).donate(token, amount);

Raises the leg’s LP index; reserves and liabilities are credited equally. Deposit into a leg before donating to it: a donation into an unopened leg is permanently unclaimable. See Incentivization §3.


4. Flash loan

BTR’s flash loan callback is the canonical ERC-3156 onFlashLoan, and the first argument to flashLoan is the pool. Repay by transferring to the pool: Flash checks the pool’s balance and never calls transferFrom on the receiver.

IFlash(flash).flashLoan(pool, IERC3156FlashBorrower(address(this)), token, amount, data); function onFlashLoan(address initiator, address token, uint256 amount, uint256 fee, bytes calldata data) external returns (bytes32) { require(msg.sender == flash, "untrusted caller"); // … use funds … IERC20(token).transfer(pool, amount + fee); // NOT approve(flash) return keccak256("ERC3156FlashBorrower.onFlashLoan"); }

Views: maxFlashLoan(pool, token), flashFee(pool, token, amount). Full interface, fee arithmetic and the safety pattern: Composability §1.


5. Deploy a pool

createPool is AC-owner only: ask the protocol owner for a pool, and get your administering seat named in initdata.

// Pool, PoolFactory: interfaces from the ABIs in @btr-protocol/sdk/abis (Pool.ts, PoolFactory.ts). address predicted = PoolFactory(factory).predictPool(salt); // address before deploy address pool = PoolFactory(factory).createPool( salt, // bytes32, the reserved CREATE3 salt baseToken, tokens, // non-empty, else InvalidInput abi.encodeCall(Pool.initialize, (baseToken, wnative, feeParams, poolAdmin, lpTag)) ); // pool == predicted

tokens seeds the factory’s discovery index. It does not list assets for trading: that is a separate, gated ADD_ASSET operation on Admin (Admin). The address derives from the salt and the factory alone, so it is the same on every chain of a fleet and neither the base token nor the roster moves it. Full walkthrough: Pool Deployment & Curation.


6. Read state

import { getPoolData, getCoverageRatio, getLPBalance } from '@btr-protocol/sdk/pool'; const data = await getPoolData(provider, pool, tokens, poolName); // 4 args const coverage = await getCoverageRatio(provider, pool, token); const shares = await getLPBalance(provider, pool, user, token); const activity = await fetch('https://api.btr.markets/v1/activity?limit=50').then((r) => r.json());

Integration examples: runnable TypeScript and Foundry files for swap, router swap, deposit, withdraw, debt swap and oracle reads, with fork tests.

API & SDK Reference · Basic Operations · Pool · Admin · Glossary