SDK
SDK overview
@tabdotbar/agent-sdk is a TypeScript SDK that wraps the Tab REST API, ships tool schemas for OpenAI and Anthropic, and exposes an MCP server you can drop into Claude Desktop or Cursor. One package, three integration paths.
Install
npm install @tabdotbar/agent-sdk viem
viem is a peer dep. The SDK uses it for direct on-chain reads against the canonical ERC-8004 registry.
Server usage
import { Tab } from "@tabdotbar/agent-sdk";
const tab = new Tab({
apiKey: process.env.TAB_API_KEY!, // sk_live_... or sk_test_...
baseUrl: process.env.TAB_BASE_URL!, // e.g. https://thetab.bar
});
const { order, checkout_url } = await tab.orders.create({
amount: "12.50",
chain: "base",
recipient: "@alice",
idempotencyKey: crypto.randomUUID(),
});
console.log(checkout_url);Wait for settlement
const settled = await tab.orders.waitForSettlement(order.id, {
signal: AbortSignal.timeout(5 * 60_000),
});Polls with exponential backoff, tolerates transient network errors, and honours an external AbortSignal so the caller can cancel cleanly.
Resolve a handle
const { record } = await tab.handles.resolve("@alice");
console.log(record.address, record.solanaAddress);Smart Pay
tab.pay.smartpicks the cheapest source asset across the caller's wallet (USDC on any chain, plus ETH/BNB/CELO/SOL), bridges or swaps if needed via relay.link, and lands USDC in the recipient's wallet on the chain they pick. Fully gasless: Tab's executor pays.
const res = await tab.pay.smart({
amount: "4.20", // recipient asset's natural units
recipient: "@alice", // OR a 0x address / Solana base58
recipientChain: "base", // settle here; can be "solana" too
recipientAsset: "USDC", // also: "ETH", "BNB", "CELO", "SOL"
slippageCap: 0.02, // optional, default 2%
});
if (res.ok && res.kind === "direct") {
console.log("Settled in", res.txHash);
} else if (res.ok && res.kind === "relay") {
console.log("Bridged via", res.sourceChain, "->", res.recipientChain);
if (res.requestId) console.log("requestId:", res.requestId);
} else if (res.ok && res.kind === "multi") {
console.log("Sourced from", res.legs.length, "chains:", res.senderPaysSummary);
}Three execution paths: direct (target asset already on the target chain, one 7702 redeem tx, ~3s), relay (cross-asset / cross-chain, pull from sender then relay.link swap+bridge, ~20-30s), or multi(no single source covers the amount but the wallet's total does — Tab pulls from N chains in parallel; per-leg outcomes in res.legs[] with an allOk flag for partial-success cases).tab.pay.smart returns once the source tx is in; for relay routes, poll tab.bridge.status(requestId) to wait for the destination fill.
Bridge
Bridging is exposed as a thin wrapper on Smart Pay: tab.bridge.executemoves any asset on any chain to any asset on any chain in the caller's own wallet. Internally it calls the same /api/pay/smart endpoint with recipient: "self" and an explicit source.
const res = await tab.bridge.execute({
fromChain: "bsc",
fromAsset: "BNB",
amount: "0.05", // exact input in source-asset units
toChain: "base",
toAsset: "USDC",
});Preview a bridge first with tab.bridge.quote(params) and poll fill state with tab.bridge.status(requestId). Same response shape as Smart Pay. For native SOL delivery to Solana, use tab.solana.topUp({ amountSol }) — that endpoint handles the wSOL → native unwrap so the recipient lands with usable lamports.
OpenAI tool calling
import OpenAI from "openai";
import { Tab } from "@tabdotbar/agent-sdk";
import { tabTools, runTool } from "@tabdotbar/agent-sdk/openai";
const openai = new OpenAI();
const tab = new Tab({ apiKey, baseUrl });
const completion = await openai.chat.completions.create({
model: "gpt-4o",
tools: tabTools,
messages: [
{ role: "user", content: "Pay @alice 5 USDC on base." },
],
});
for (const call of completion.choices[0].message.tool_calls ?? []) {
const result = await runTool(tab, call.function.name, JSON.parse(call.function.arguments));
// result is { ok: true, value } or { ok: false, error: { code, message } }.
// Feed JSON.stringify(result) back into the next turn so the model can react.
}runTool never throws. Every failure (network, 4xx, malformed args) comes back as { ok: false, error } so the model can decide whether to retry or ask the user.
Anthropic tool use
import Anthropic from "@anthropic-ai/sdk";
import { tabTools, runTool } from "@tabdotbar/agent-sdk/anthropic";
const msg = await anthropic.messages.create({
model: "claude-opus-4-7",
max_tokens: 1024,
tools: tabTools,
messages: [{ role: "user", content: "Send 3 USDC to @bob on base." }],
});
for (const block of msg.content) {
if (block.type === "tool_use") {
const result = await runTool(tab, block.name, block.input as Record<string, unknown>);
}
}MCP server
Drop Tab into Claude Desktop, Cursor, or any MCP-compatible client. Add this to your client's MCP config:
{
"mcpServers": {
"tab": {
"command": "npx",
"args": ["-y", "@tabdotbar/agent-sdk"],
"env": {
"TAB_API_KEY": "sk_live_...",
"TAB_BASE_URL": "https://thetab.bar"
}
}
}
}The MCP server exposes a full suite of payment and bridge tools, orders, escrow, subscriptions, Smart Pay, bridging, x402 pay-per-call, and ERC-8004 agent discovery. Your client picks them up automatically.
ERC-8004. Direct on-chain reads
The agent discovery tools default to Tab's REST cache (fast, handle-aware). When you need to verify what an agent really published, the SDK can read the ERC-8004 registry directly via viem. No Tab backend involved.
const { agentId } = await tab.agents.resolveAgentId("@alice");
const manifest = await tab.agents.manifestOnChain(agentId, { chain: "base" });
const owner = await tab.agents.ownerOnChain(agentId, { chain: "base" });
const wallet = await tab.agents.walletOnChain(agentId, { chain: "base" });
const rep = await tab.agents.reputationOnChain(agentId, { chain: "base" });Same registry address (0x8004A169FB4a3325136EB29fA0ceB6D2e539a432) on Base, BSC, and Celo. Pass an rpcUrl override to use a paid endpoint for production traffic.
Verifying signed payment receipts
import { verifyReceipt, type SignedReceipt } from "@tabdotbar/agent-sdk/receipts";
const receipt: SignedReceipt = JSON.parse(/* receipt JSON */);
const { record } = await tab.handles.resolve(receipt.message.recipientHandle);
const valid = await verifyReceipt(receipt, record.address);
if (!valid) throw new Error("Receipt signature invalid");The EIP-712 domain pins the chain id, so a receipt for base cannot be replayed as a receipt for optimism. The verifier rejects receipts with zero-address payers.
What you can still do yourself
Everything. The SDK is a convenience layer; if you want to talk to the router directly with viem or ethers, every type is documented in TabRouter and TabBotRouter.
Error handling
Direct SDK calls throw TabApiError on non-2xx responses. The runTool helpers never throw. They return { ok: false, error } instead.
import { Tab, TabApiError } from "@tabdotbar/agent-sdk";
try {
await tab.orders.create({ /* ... */ });
} catch (e) {
if (e instanceof TabApiError && e.code === "rate_limited") {
// honour Retry-After and try again
}
throw e;
}