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;
}