---
title: "Cookbook"
description: "Working TypeScript, Python, Rust and Solidity snippets for swap, LP, liability transfer, flash and deploy"
audience: tech
type: how-to
status: live
lang: en
updated: "2026-09-02"
publish: true
---
# Cookbook

The shortest correct form of each operation an integrator ships. For swaps, route first: [Quotes & Routing](/docs/5-2-2-quotes-routing). Addresses come from `GET /v1/venues`, ABIs from `GET /v1/abis/{name}`, both keyed by chain id and both listed in the [API & SDK Reference](/docs/5-2-1-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](/docs/5-1-1-basic-operations#1-interfaces) |

```bash
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](/docs/5-1-1-basic-operations#1-interfaces); every Solidity snippet below assumes it.

---

## 2. Swap

### 2.1. TypeScript, routed

```typescript
import { planToLegs, buildSwapCalls } from '@btr-protocol/sdk/router';

const plan = await fetch('https://api.btr.markets/v1/route', {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({ pools, token_in, token_out, amount_in: hexAmount }),
}).then((r) => r.json());

const legs = planToLegs(plan, {
  slippageFrac: 0.005,
  tokenOf,
  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, wrappedNative });
// send from the user account: EIP-5792 wallet_sendCalls, or sequentially
```

### 2.2. TypeScript, single pool

```typescript
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)

```python
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(
    token_in, token_out, amount_in, min_out, user,
    0xFFFFFFFF,                              # deadline opt-out sentinel
).transact({"from": user})
```

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

### 2.4. Rust (alloy)

```rust
// 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(token_in, token_out, amount_in, min_out, recipient, U256::MAX);
```

### 2.5. Solidity (vault / strategy)

```solidity
using SafeERC20 for IERC20;   // OpenZeppelin; forceApprove lives here, not on IERC20

IERC20(tokenIn).forceApprove(pool, amountIn);
uint256 out = IBtrPool(pool).swap(
    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)

```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)

```typescript
import { rankDeposit, buildDepositCalls } from '@btr-protocol/sdk/router';

// Args 2 and 3 are SYMBOLS, not addresses. amountIn is a human-units number, not bigint.
// Returns { best, routes }: `best` is null when every route is gated off.
const { best } = rankDeposit(pools, 'USDC', 'WETH', 1000, opts);
if (!best) throw new Error('no feasible mint route');

// buildDepositCalls takes MarketMintArgs | TransferMintArgs, built from `best`:
const calls = buildDepositCalls(
  poolAddress,
  best.mode === 'market'
    ? { mode: 'market', legs, depositToken, depositAmount }   // Σ per-part minOut
    : { mode: 'transfer', token, amount, targetToken, lpAmountIn, minLpAmountOut },
  { recipient: user, wrappedNative },
);
```

`best` describes the winning route; the `MarketMintArgs` / `TransferMintArgs` union is what `buildDepositCalls` consumes. 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.

### 3.3. Cross-asset exit: `withdrawTo`

```solidity
// 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**; a haircut may apply on an under-covered input leg, and both legs need `LIABILITY_SWAP_ENABLED_BIT` ([Basic Operations §2](/docs/5-1-1-basic-operations#2-check-the-flags-first)).

```solidity
uint256 lpOut = IBtrPool(pool).swapLiability(
    tokenIn,   // leg to leave
    tokenOut,  // leg to enter
    lpAmountIn,
    minLpAmountOut,
    deadline
);
```

```typescript
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](/docs/5-overview#2-action-matrix)).

### 3.5. Donate

```solidity
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**: donating into an unopened leg strands the gift permanently. See [Incentivization §3](/docs/5-3-3-incentivization#3-donations).

---

## 4. Flash loan

BTR uses an ERC-3156-*style* callback named **`postFlashLoan`**, not the canonical `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.

```solidity
IFlash(flash).flashLoan(pool, IERC3156FlashBorrower(address(this)), token, amount, data);

function postFlashLoan(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.postFlashLoan");
}
```

Views: `maxFlashLoan(pool, token)`, `flashFee(pool, token, amount)`. Full interface, fee arithmetic and the safety pattern: [Composability §1](/docs/5-1-4-composability#1-flash-loans).

---

## 5. Deploy a pool

```solidity
address pool = IPoolFactory(factory).createPool(
    baseToken,
    tokens,          // MUST be non-empty - createPool reverts InvalidInput on an empty array
    initdata         // abi.encodeCall(IPool.initialize, (base, wnative, feeParams))
);
```

`tokens` seeds the factory's discovery index and the deployment salt. It does **not** list assets for trading: that is a separate, gated `ADD_ASSET` operation on `Admin`, and `createPool` grants the deployer no administrative authority. Full walkthrough: [Pool Deployment & Curation](/docs/5-1-2-pool-deployment-curation).

---

## 6. Read state

```typescript
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](https://github.com/btr-protocol/examples), the longer form of this page: every snippet below as a runnable file with a test that executes it against a live deployment.

[API & SDK Reference](/docs/5-2-1-api-sdk-reference) · [Basic Operations](/docs/5-1-1-basic-operations) · [Pool](/docs/1-2-1-pool) · [Admin](/docs/1-2-3-admin) · [Glossary](/docs/glossary)
