SDK (TypeScript)
@pendle/boros-sdk-public is a TypeScript wrapper around the Boros Open API and Send Txs Bot. It removes the boilerplate of building calldata, EIP-712 signing, and dispatching transactions. If you trade from Node, this is the recommended path.
For other languages — or if you want raw HTTP control — go to API instead. The SDK is a thin convenience layer; anything the SDK does, the API page documents end-to-end.
For the agent model the SDK depends on, see Agent Trading.
Install
npm install @pendle/boros-sdk-public viem
viem is a peer dependency. The SDK targets viem@2.x. The math helpers (FixedX18, estimateTickForRate, getRateAtTick) live in a separate package — install it when you need tick/rate conversion:
npm install @pendle/boros-offchain-math
Initialization
The SDK exposes two main classes:
Exchange— your read/write entry point. Wraps placing orders, cancelling, depositing, etc.Agent— manages the agent keypair used to sign trading actions on behalf of your root.
import { createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { arbitrum } from 'viem/chains';
import { Agent, Exchange } from '@pendle/boros-sdk-public';
// 1. Root walletClient. For trading-only flows the account can be omitted —
// the agent signs every trading action. The root account is only needed
// for sensitive flows (deposit, approveAgent, withdraw).
const rootAccount = privateKeyToAccount(process.env.ROOT_PK as `0x${string}`);
const walletClient = createWalletClient({
account: rootAccount,
transport: http(process.env.RPC_URL),
chain: arbitrum,
});
// 2. Agent. Re-use a persisted private key in production; for first-time
// setup, `Agent.create(walletClient)` derives one deterministically from
// a root signature so users don't have to babysit an extra secret.
const agent = Agent.createFromPrivateKey(process.env.AGENT_PK as `0x${string}`);
// 3. Exchange.
const exchange = new Exchange(
walletClient,
rootAccount.address,
/* accountId */ 0,
[process.env.RPC_URL!],
agent,
);
The constructor signature is:
new Exchange(walletClient, root, accountId, rpcUrls, agent?)
agent is optional at construction time — set it later via exchange.setAgent(agent) if you bootstrap one with Agent.create() after the fact.
The SDK ships with the production backend pre-wired (api-boros.pendle.finance/apis). All endpoints — markets, accounts, calldata builders, and send-txs — go through this single host. For staging, override at startup:
import { setOpenApiBackendUrl } from '@pendle/boros-sdk-public';
setOpenApiBackendUrl('https://staging-api-boros.pendle.finance/apis');
Common flows
The SDK groups operations by who signs the calldata — root for sensitive actions, agent for everything else. Skim the table, then jump to whichever section you need.
| Flow | Method | Signed by | Backed by |
|---|---|---|---|
| Generate + approve agent | Agent.create(walletClient) → exchange.approveAgent(agent) | root | EIP-712 → Send Txs Bot |
| Check agent expiry | exchange.getAgentExpiryTime() | — | GET /v1/agents/expiry-time |
| Deposit collateral | exchange.deposit(params) | root | ERC20 approve + on-chain deposit |
| Withdraw collateral | exchange.withdraw(params) | root | request-withdraw (auto-finalized after cooldown) |
| Pay treasury (gas top-up) | exchange.payTreasury(params) | agent | calldata builder + Send Txs Bot |
| Place single order | exchange.placeOrder(params) | agent | calldata builder + Send Txs Bot |
| Place many in one tx | exchange.bulkPlaceOrders({orderRequests}) | agent | calldata builder + Send Txs Bot |
| Cancel orders | exchange.cancelOrders(params) | agent | calldata builder + Send Txs Bot |
| Bulk cancel | exchange.bulkCancelOrders(requests) | agent | calldata builder + Send Txs Bot |
| Enter market (cross) | exchange.enterMarkets(true, [marketId]) | agent | calldata builder + Send Txs Bot |
| Exit market | exchange.exitMarkets(true, [marketId]) | agent | calldata builder + Send Txs Bot |
| Cash transfer (cross↔isolated) | exchange.cashTransfer(params) | agent | calldata builder + Send Txs Bot |
| Read all markets | exchange.getAllMarkets(filters?) | — | GET /v1/markets (paginated, cached 5 min) |
| Read order book | exchange.getOrderBook({marketId, tickSize}) | — | GET /v1/markets/order-book |
| Read your active orders | exchange.getOrdersPage({isActive: true}) | — | GET /v1/accounts/orders (cursor) |
| Read on-chain orders (no indexer lag) | exchange.getActiveOrdersFromContract(params) | — | direct Explorer contract read |
| Read entered markets | exchange.getEnteredMarkets(rootAddress) | — | direct MarketHub contract read |
| Read user positions | exchange.getUserPositions(params) | — | direct contract read |
| Market summary (mid/bid/ask APR) | exchange.getMarketData(marketId) | — | GET /v1/markets/by-ids + contract |
| Gas balance (USD) | exchange.getGasBalance() | — | GET /v1/accounts/gas-balance |
| List assets | exchange.getAssets() | — | GET /v1/assets |
Place a limit order
import { Side, TimeInForce, MarketAccLib, CROSS_MARKET_ID } from '@pendle/boros-sdk-public';
const market = (await exchange.getAllMarkets({ isUiWhitelisted: true }))[0];
const marketAcc = MarketAccLib.pack(rootAccount.address, 0, market.tokenId, CROSS_MARKET_ID);
const { result, executeResponse } = await exchange.placeOrder({
marketAcc,
marketId: market.marketId,
side: Side.LONG,
size: 10n ** 18n,
rate: 0.05, // 5% APR — backend rounds to nearest valid tick
tif: TimeInForce.GOOD_TIL_CANCELLED,
});
Pass rate (human-friendly decimal APR) or limitTick (raw integer tick) — exactly one. rate is the easy path; the backend rounds it to the nearest valid tick for the market. Use limitTick only if you've already computed the tick yourself (e.g. via estimateTickForRate from @pendle/boros-offchain-math).
ammId is optional and defaults to orderbook-only routing. Pass a specific AMM id to route through that AMM instead.
Place many orders in one transaction
bulkPlaceOrders accepts a heterogeneous list — mix single-order requests and per-market bulks in one call. One on-chain submission, one nonce, one gas charge.
await exchange.bulkPlaceOrders({
orderRequests: [
{
cross: true,
bulks: [{
marketId: market.marketId,
orders: {
tif: TimeInForce.GOOD_TIL_CANCELLED,
side: Side.LONG,
sizes: [10n ** 18n, 2n * 10n ** 18n],
limitTicks: [limitTick - 10, limitTick - 20],
},
cancelData: { ids: [], isAll: false, isStrict: false },
}],
},
],
});
Read your active orders (paginated)
import { OrderType } from '@pendle/boros-sdk-public';
let resumeToken: string | undefined;
const all = [];
do {
const page = await exchange.getOrdersPage({
isActive: true,
orderType: [OrderType.LIMIT],
limit: 200,
resumeToken,
});
all.push(...page.results);
resumeToken = page.resumeToken ?? undefined;
} while (resumeToken);
The SDK keeps cursor pagination explicit — there's no getAllOrders() helper. Backends impose per-call CU costs, and the SDK refuses to hide them behind sugar. See Computing Units.
If you need fresh on-chain truth (no indexer lag, useful right after a placeOrder returns), call exchange.getActiveOrdersFromContract({ marketAcc, marketId }) instead — it reads the Explorer contract directly.
Cancel everything in one market
await exchange.cancelOrders({
marketAcc,
marketId: market.marketId,
cancelAll: true,
orderIds: [],
});
Top up gas balance
The Send Txs Bot debits each agent-signed transaction from your off-chain gas budget (USD). Top it up via payTreasury:
await exchange.payTreasury({
isCross: true,
marketId: market.marketId,
usdAmount: 1, // credits ~$1 to gas budget
});
console.log('Gas balance (USD):', await exchange.getGasBalance());
Escape hatch — raw open-api access
The wrapped methods cover common trading flows. For everything else (leaderboard, OHLCV, settlement events, indicators, conditional orders), drop down to the codegen client:
import { getOpenApiSdk } from '@pendle/boros-sdk-public';
const sdk = getOpenApiSdk();
// Anything in the open-api spec is reachable here, fully typed.
const { data } = await sdk.markets.marketsControllerGetOhlcv({
marketId: 1,
resolution: '1h',
from: 1735689600,
to: 1735693200,
});
getOpenApiSdk() returns a typed client generated from the live swagger. New endpoints land here automatically when the SDK is republished — no waiting for hand-written wrappers. Use this whenever the wrapper doesn't exist or you need to pass a parameter the wrapper doesn't expose.
The SDK also re-exports the codegen response types under OpenApi.*:
import { OpenApi } from '@pendle/boros-sdk-public';
function summarize(market: OpenApi.MarketListItemResponse) {
return `${market.marketId} ${market.imData.symbol}`;
}
What the SDK doesn't do
- WebSocket subscriptions — connect to the WebSocket feed using
socket.io-clientdirectly; see WebSocket. - Strategy logic — the SDK is plumbing. Indicators, sizing, risk management are yours to build.
- Stop / take-profit orders —
OrderType.TAKE_PROFIT_MARKETandOrderType.STOP_LOSS_MARKETexist on the type level but the SDK does not yet expose dedicatedplaceStopOrder/placeTakeProfitOrdermethods. UsegetOpenApiSdk()until they land. See Stop Orders.
End-to-end example
Bot Quickstart walks through a real grid bot built on top of the SDK — from approve-agent to a running reconcile loop. ~485 lines of TypeScript total, with full source embedded at the end of that page.
Migrating from @pendle/sdk-boros
@pendle/boros-sdk-public is the externally supported successor of the internal @pendle/sdk-boros package. Same trading surface — Exchange, Agent, calldata builders, codegen API client — minus internal-only helpers that did not belong on the public boundary.
Fresh integrators: skip this section. Lives here for users already wired up to the internal @pendle/sdk-boros.