FEATURES
Smart Pay: one number, any asset
Tab's Smart Pay collapses every balance you hold — USDC on Base, ETH on Ink, BNB on BSC, CELO on Celo, USDC and SOL on Solana — into a single dollar number. When you pay, you just type the amount in USD. Tab picks the cheapest source asset and route, executes it gaslessly where it can, and lands USDC in the recipient's wallet on the chain they prefer.
Smart Pay vs raw bridge aggregator
| LI.FI / Squid / Socket | Tab Smart Pay | |
|---|---|---|
| What it is | API for cross-chain swap+bridge | End-user payment app + agent SDK on top of a bridge aggregator |
| Caller integration | You build the wallet, signing, UI, retries | Tab handles all of it; you call one POST |
| Gas | Caller pays native on every chain | Gasless via 7702 (EVM) + Subscriptions program (Solana) |
| Multi-source aggregation | Per-call, one source per route | Single call fans out across N source chains in parallel |
| Recipient by @handle | Not supported; addresses only | Native — resolves @handle to the recipient's preferred chain |
How balances are valued
The dashboard hero $number is a server-side roll-up of every active chain's USDC balance + native balance, multiplied by spot price. USDC is pinned to $1. Native coins (ETH, BNB, CELO, SOL) are priced via a multi-source feed (CoinGecko, Binance, Coinbase) with a 60s cache, so a single API outage doesn't zero out the display.
The same feed powers the routing engine: when you pay $400, Smart Pay knows your 0.15 ETH is worth $400, even though it's denominated in ETH.
Routing priority
The router scans every balance you hold and picks in this order:
- USDC on the recipient's chain (direct transfer, no swap, no bridge)
- USDC on any other chain (relay.link bridge only)
- Native asset on recipient's chain (swap only)
- Native on any other chain (swap + bridge in one relay.link route)
Solana sources are fully wired. USDC + native SOL on Solana can route to any EVM destination via relay.link, and EVM balances can target Solana destinations the same way. On the Solana side, native SOL is held as wSOL inside the user's wallet so the Subscriptions Delegation Program can spend it gaslessly — wallets like Phantom auto-display wSOL + native SOL as a single "SOL" balance, so this is invisible to the user.
Direct path (gasless)
When the router finds USDC already on the recipient's chain, Smart Pay reuses your existing TabBot 7702 delegationto send it. The flow is identical to a /tip: Tab's executor signs and submits the redeemDelegationscall, pays the gas, and returns the tx hash within ~3 seconds. You pay nothing in gas, nothing in fees beyond Tab's standard 0% on P2P.
Relay path (cross-asset / cross-chain)
When the cheapest source isn't USDC on the destination chain, Smart Pay pulls the source asset from the user via their 7702 delegation (one tx), then submits a relay.link swap+bridge from Tab's executor (one tx) which delivers USDC to the recipient on the destination chain. Fully gasless from the user's perspective — they typed "Pay $X" and Tab handled the rest.
Fee model
Direct path (same chain) has zero fee — sender pays exact, recipient receives exact.
Cross-chain path incurs the bridge / swap fee from relay.link. Tab uses tip semantics by default: the sender pays exactly amountUsd, the recipient receives amountUsd − fee. The receipt shows the breakdown: "$10 sent → $9.94 delivered, fee $0.06." Same model Cashapp / Venmo use for international transfers.
For merchant bills / POS where the recipient must receive an exact amount, pass mode: "pos" in the body. (Pre-confirm preview UX lands in a follow-up; the flag is wired today so the protocol shape is stable.)
Slippage
Every relay route quotes its expected USD output. Smart Pay rejects routes where output is below amount × (1 - slippageCap); default cap is 2%. Pass slippageCap in the body to tighten or loosen. The 2% default covers the typical relay.link bridge fee + DEX slippage on small ($1-$10) cross-chain amounts; prior 1% was too tight and rejected legit routes.
Multi-source aggregation
When no single source chain holds enough but the caller's total balance does, the planner returns kind: "multi"and fans out N legs in parallel. Each leg is a self-contained pull-then-bridge; the recipient sees one combined delivery. Same-chain legs run as zero-fee direct transfers; cross-chain legs eat a per-leg bridge fee. Tip-semantics only — POS multi-source is non-trivial and parked.
Response (kind: "multi"):
{
ok: true, kind: "multi",
legs: [
{ ok: true, chain: "base", asset: "USDC", sourceTxHash, recipientAmount, requestId? },
{ ok: true, chain: "bsc", asset: "USDC", sourceTxHash, recipientAmount, requestId? },
...
],
allOk: true, // false when at least one leg failed
senderPaysSummary: "$5 USDC on Base + $5 USDC on BSC",
recipientAmount, recipientAsset, recipientChain,
senderPaysUsd, recipientAmountUsd, feeUsd
}Candidate routes (pick a source)
POST /api/pay/smart/candidatesreturns the top-N candidate source routes pre-priced, cheapest first. Dashboards use this to render a "Pay via Base USDC (cheapest), BSC USDC ($0.08 fee), Solana SOL ($0.12 fee)" picker so users choose consciously instead of trusting the auto-pick. Each candidate is a full planRoute call with an explicit source pinned; failed routes are dropped silently.
POST /api/pay/smart/candidates
Body:
{ amount, recipient, recipientAsset?, recipientChain?, slippageCap?, limit? }
Response:
{
ok: true,
candidates: [
{
sourceChain, sourceAsset, kind: "direct" | "relay",
senderPaysAmount, senderPaysUsd,
recipientAmount, recipientAmountUsd,
feeAmount, feeUsd, feePct, // feePct as 0-1 ratio
etaSeconds?, bridge?
},
...
]
}Real-time bridge progress
For the relay + multi paths the source tx is submitted before the destination fills (relay.link relayer races to fill on the dest chain). Clients poll GET /api/pay/smart/status?requestId=<id> for state transitions (pending → filled | failed | refunded). The BridgeModal and SmartPayModal on the dashboard render this as a 4-step counter (⚙ Preparing → ⏳ Waiting for confirmation → 🔄 Bridge processing → ✅ Bridge complete) with a progress bar; the TG/Discord bots show the same step labels with a unicode progress bar. Source funds are safe even if polling times out — the relayer continues in the background.
API
POST /api/pay/smart
Headers: cookie (session-authed) OR Bearer <TAB_API_KEY> (agent SDK)
Body:
{
"amount": "4.20", // recipient asset's natural units
"recipient": "0x..." | "@alice", // address OR handle
"recipientChain": "base", // optional, defaults to base
"recipientAsset": "USDC", // also ETH | BNB | CELO | SOL
"slippageCap": 0.02, // optional, defaults to 2%
"mode": "tip" | "pos", // optional, defaults to "tip"
"orderId": "ord_..." // optional; when set, Tab marks the
// order completed server-side after
// Smart Pay lands + fires
// order.completed. Required for
// native-asset orders to avoid
// stuck awaiting_payment state.
}
(Backward-compat: `amountUsd` is accepted as an alias for `amount`
when `recipientAsset` is USDC.)
Response (direct path, same chain, gasless):
{
ok: true, kind: "direct", txHash: "0x...",
senderPaysUsd: 4.20, recipientAmountUsd: 4.20, feeUsd: 0
}
Response (relay path, cross-chain, gasless):
{
ok: true, kind: "relay",
pullTxHash: "0x...", // 7702 pull from sender
sourceTxHash: "0x...", // relay swap submitted by Tab
requestId: "...", // poll /status with this
sourceChain, destinationChain,
senderPaysUsd: 4.20, // exact, what was pulled
recipientAmountUsd: 4.17, // after bridge fee (tip mode)
feeUsd: 0.03
}
Response (multi-source path — see section above):
{ ok: true, kind: "multi", legs: [...], allOk, senderPaysSummary, ... }
Response (insufficient or failure):
{ ok: false, reason: "Need $4.20, wallet has $1.23 total..." }Explicit source (bridge / exact-input mode)
By default Smart Pay picks the cheapest source asset automatically. When you need to pin the source, for instance the user has explicitly chosen "send my BNB on BSC, not my USDC on Base," pass sourceChain, sourceAsset, and sourceAmounttogether. The router skips auto-pick and uses exactly that source.
This is also the bridge flow: Tab's in-dashboard bridge modal is implemented as Smart Pay to self (recipient: "self") with an explicit source. One execution path covers both cross-asset payments and pure bridges, so the API surface stays small.
POST /api/pay/smart
Body:
{
"sourceChain": "bsc",
"sourceAsset": "BNB",
"sourceAmount": "0.05", // exact input in BNB units
"recipient": "self", // OR "0x..." OR "@alice"
"recipientChain": "base",
"recipientAsset": "USDC", // default
"slippageCap": 0.02
}When sourceAmount is set, the top-level amount / amountUsd field is ignored, the sender is specifying what they spend, not what the recipient receives. sourceAsset accepts USDC, ETH, BNB, CELO, or SOL. sourceChain + recipientChain may be identical (cross-asset, same-chain) or different (bridge).
Bot endpoint
Tab's bots can initiate Smart Pay on behalf of a resolved sender via POST /api/bot/pay/smart with the same body shape plus senderHandle and atipId for dedup. Auth uses the BOT_API_KEY bearer.
Prices API
Bots that need spot prices for their own UI (receipt cards, balance widgets) should hit Tab's public /api/prices endpoint instead of CoinGecko directly:
GET /api/prices // returns ETH, BNB, CELO, SOL, USDC
GET /api/prices?symbols=ETH,BNB // subset
Response:
{ prices: { ETH: 3500, BNB: 600, ... }, fetchedAt: 1779950000 }The endpoint is CDN-cached for 30s with stale-while-revalidate for 60s, so any number of bot instances can call it freely without exhausting upstream rate limits.
FAQ
What happens if a leg fails on a multi-source pack?
Each leg is independent. The response's legs[] array reports per-leg ok; allOk: false means at least one leg didn't complete. Successful legs still deliver to the recipient — partial payment, not all-or-nothing. Refund the failed source on your side via POST /api/orders/[id]/refund if needed.
Why is the default slippage 2%?
Small ($1-$10) cross-chain amounts typically eat 1.2-1.7% in bridge + DEX fees from relay.link. 1% rejected legit routes constantly; 2% balances safety with executability. Tighten via slippageCap for larger amounts where the percentage loss matters more than route availability.
What chain does the recipient receive on?
Whichever they prefer. Pass recipientChainexplicitly or omit it to use the recipient's default chain (resolved from their @handle). Smart Pay always lands USDC (or the requested native asset) on that chain, regardless of where the sender's balance came from.
Does Tab take a fee?
Tab charges 0% on P2P(sender → recipient). The only deduction on a cross-chain route is relay.link's bridge/swap fee, which is opaque to Tab. Same-chain direct transfers are fully fee-free.
How fast does it settle?
Direct path (same chain, same asset): ~3 seconds. Cross-chain relay path: source-side tx is ~3 seconds, destination fill is usually 10-30 seconds depending on the destination chain's block time. Clients poll /api/pay/smart/status with the requestId for live state.
Can agents call Smart Pay?
Yes, via Bearer-authenticated API key (write scope). The agent SDK @tabdotbar/agent-sdk exposes tab.pay.smart() with the same shape; MCP, OpenAI, and Anthropic tool wrappers all dispatch through it.