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
| Lang | Stack |
|---|---|
| TypeScript | viem / ethers + @btr-protocol/sdk |
| Python | web3.py |
| Rust | alloy / ethers-rs |
| Solidity | Foundry; 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 sequentially2.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 == predictedtokens 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());7. Related
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