Look up an agent
Developer docs · v0

Docs

How agents, networks and lenders use Fivebar, banking for AI agents: the tally-tab x402 scheme, the payment contracts, the facilitator and the Score API.

PrototypeNothing here is live on mainnet. The contracts have had internal reviews but no external audit. Use testnets only.
01

Overview

x402 makes an agent pay before every call. Fivebar adds credit: a pool of lender USDC pays the seller at once, and the agent's owner settles a weekly statement. Larger jobs can be held in escrow until delivery is checked.

Robinhood Chain is Fivebar's home chain: agents onboard there and pay in USDG. Base works too, in USDC, and the credit contracts (tabs, the pool, escrow) run on Base Sepolia testnet today. Warden NFTs, the token and governance live on Ethereum. Solana is next. Six parts take part in each payment:

  1. AOwner

    A person or company. Opens a line, posts USDC collateral, sets a mandate, repays the statement.

  2. BAgent

    Holds a signing key registered on the line. Signs a payment intent when a seller answers 402.

  3. CSeller

    Any x402 API. Lists tally-tab in its accepts and forwards the payment to the facilitator.

  4. DFacilitator

    Checks the intent, dry-runs it, then submits TabManager.pay. Its key only pays gas and never holds funds.

  5. ETabManager

    Enforces the line, the mandate and the credit limit, bills the owner and pays the seller from the pool.

  6. FCreditPool

    An ERC-4626 USDC vault. Lenders deposit and earn half of every seller fee.

02

Quickstart

Run the whole flow on a local chain. You need Node 22 and Foundry.

Terminal
git clone https://github.com/fivebar-xyz/tally-main
cd tally-main/contracts
mkdir -p lib
git clone --depth 1 --branch v5.1.0 https://github.com/OpenZeppelin/openzeppelin-contracts lib/openzeppelin-contracts
git clone --depth 1 --branch v1.9.4 https://github.com/foundry-rs/forge-std lib/forge-std
forge test                   # 87 contract tests

cd ../services
npm install
npm run build:contracts
npm test                     # 60 tests on a local chain
npm run demo                 # an agent buys 4 quotes on credit

The demo starts anvil, deploys the contracts, funds the pool with 10,000 test USDC, opens a line with 100 USDC collateral and a $1 per call, $10 budget mandate, then buys four quotes from a demo seller.

03

Lines and mandates

A line belongs to an owner and names one agent key. Its credit limit is its collateral times maxLtvBps, which is 100% in v0. Every tab is fully secured, so lenders cannot lose principal to a default.

A mandate limits what the agent may spend on the line:

FieldTypeMeaning
maxPerPaymentuint128Largest single payment, in USDC base units (6 decimals).
budgetuint128Total the agent may spend under this mandate.
validUntiluint64Unix time after which payments are refused.
sellersRootbytes32Merkle root of allowed sellers. Zero allows any seller.

Seller leaves follow the OpenZeppelin StandardMerkleTree format: keccak256(bytes.concat(keccak256(abi.encode(seller)))). Pass the proof as the third argument to pay.

Sponsored lines (Fivebar for Networks)

A network can back many agents with one deposit. It opens a sponsor account, deposits USDC once, then opens a line for each agent. Each line keeps its own mandate and statements; together they can never owe more than the sponsor’s deposit. A per-agent cap is optional: NO_CAP for none, 0 to freeze an agent.

Network setup · cast
cast send $TABS "openSponsor()" --account network                       # emits SponsorOpened(sponsorId, …)
cast send $TABS "depositSponsorCollateral(uint256,uint256)" $SPONSOR 5000000000 --account network
cast send $TABS "openSponsoredLine(uint256,address,uint128)" $SPONSOR $AGENT 100000000 --account network
# then setMandate(lineId, …) for each agent, as for any line

One agent’s default is paid from the shared deposit and lowers the limit for the others. Sponsor ownership can’t be transferred, so own the sponsor from a Safe.

Vouching: agents underwrite agents

A new agent has no history, so it gets no credit. An established agent can vouch for it: it stakes USDC in a sponsor account, opens a line for the newcomer and hands day-to-day control to the newcomer's owner. The newcomer runs the line (mandate, agent key, workers, sweeps) but can't touch the stake or the cap. The voucher earns half of the treasury's share of every fee the line pays, and takes the loss if it defaults.

Vouch · cast
cast send $TABS "openSponsor()" --account voucher
cast send $TABS "depositSponsorCollateral(uint256,uint256)" $SPONSOR 200000000 --account voucher   # the stake
cast send $TABS "openSponsoredLine(uint256,address,uint128)" $SPONSOR $NEW_AGENT 100000000 --account voucher
cast send $TABS "setManager(uint256,address)" $LINE $NEWCOMER_OWNER --account voucher
cast send $TABS "acceptManager(uint256)" $LINE --account newcomer                                  # newcomer opts in
cast send $TABS "claimVoucherFees(address)" $TO --account voucher                                  # later: collect earnings

The voucher can freeze the line (setLineCap(line, 0)) or take control back (setManager(line, 0x0)) at any time.

Epochs

Each line has an epoch. Calling setMandate, revokeMandate or setAgent sets a fresh, unpredictable epoch, which invalidates every intent signed earlier, including any pre-signed for a guessed future epoch. Clients read the current epoch from getLine. To kill a single signed intent, the owner calls cancelNonce.

Owner setup · cast
cast send $USDC "approve(address,uint256)" $TABS 100000000 --account owner
cast send $TABS "openLine(address)" $AGENT --account owner          # emits LineOpened(lineId, …)
cast send $TABS "depositCollateral(uint256,uint256)" $LINE 100000000 --account owner
cast send $TABS "setMandate(uint256,uint128,uint128,uint64,bytes32)" \
  $LINE 1000000 10000000 $(($(date +%s)+2592000)) $(cast hash-zero) --account owner
# $1 per payment, $10 budget, 30 days, any seller
04

Payment intents

The agent authorizes each payment by signing an EIP-712 PaymentIntent. Anyone can submit it; normally the facilitator does. Agents can be plain keys or ERC-1271 smart wallets.

EIP-712
domain = { name: "Fivebar TabManager", version: "1", chainId, verifyingContract: TabManager }

PaymentIntent(
  uint256 lineId,
  uint32  epoch,      // must equal the line's current epoch
  address seller,
  uint256 amount,     // USDC base units
  bytes32 resource,   // keccak256 of the exact URL being paid for
  bool    escrowed,   // true: hold in escrow until delivery is checked
  uint256 nonce,      // any unused number; the client uses 32 random bytes
  uint256 deadline    // unix time
)

pay checks, in order: the line exists, the amount is above zero, the seller is not a protocol address, the deadline, the epoch, the nonce, the signature, the mandate, the past-due state and the credit limit. Each failure reverts with a named error (see the reference).

05

Billing and default

Billing works like a card statement. The first billable spend opens a 7-day cycle. When the cycle closes, its spending becomes the statement, due 3 days later. After 2 more days of grace, anyone can liquidate the line.

  • New spending stops once a statement is past its due date, until it is repaid.
  • Repay with repay(lineId, amount). It pays the statement first, then the open cycle. Anyone may repay a line.
  • Liquidation seizes the whole billable balance (statement plus open cycle) from collateral and returns it to the pool. Escrowed amounts are left alone and settle through escrow.
  • v0 charges no interest. The admin can change the periods with setParams; the billing period must be at least the payment window plus grace. Changes apply to cycles opened afterwards: a statement keeps the payment window and grace in force when its cycle opened.
06

Escrow

An intent with escrowed: true sends the money from the pool to the Escrow contract instead of the seller. The amount is held: it counts against the credit limit but is not billed and cannot be liquidated.

ActionWhoWhenResult
passVerifierWithin the 1-day review windowSeller paid minus the fee; amount billed to the owner
flagVerifierWithin the review windowHold is disputed; a 7-day ruling deadline starts
claimAnyoneAfter the review window, if not flaggedReleased to the seller
resolveArbiterAny time, held or flaggedRefund (back to the pool, budget restored) or release
expireAnyoneAfter the ruling deadline, if still flaggedRefund
In v0 the verifier and arbiter are trusted roles run by Fivebar. Warden juror panels replace the arbiter in phase 3.
07

For agents

The Fivebar Branch: banking tools for any agent (MCP)

The quickest way in. The Fivebar Branch is an MCP server that runs on your agent's own computer. It onboards the agent from nothing to paying on x402: onboard (the checklist), create_wallet, backup_wallet, confirm_backup, request_funding, register_identity (ERC-8004) and first_payment. Then it banks: whoami, balance, pay, check_seller and my_score, plus pair_with_owner, refill, statement and repay on testnet. Any framework that speaks MCP can use it: Claude, Cursor, LangGraph, ElizaOS and others.

Claude Code
claude mcp add fivebar -- npx -y fivebar
MCP client config
{
  "mcpServers": {
    "fivebar": {
      "command": "npx",
      "args": ["-y", "fivebar"],
      "env": { "TALLY_MAX_PER_CALL": "1" }
    }
  }
}

The wallet is created on the agent's computer and stored encrypted in ~/.fivebar/agent (standard v3 key file). Nothing is sent to Fivebar. Funding, the ERC-8004 ID and payments stay locked until the agent has written a backup to a folder its human chose, proved the backup restores, and the human has read back the check code from it: an agent key with no backup is one deleted folder away from a lost wallet. Agents that can read the web can start from onboard.md.

Settings: TALLY_NETWORK is base (default: real USDC, x402 and ERC-8004) or base-sepolia (testnet, which adds prepaid accounts and tabs: set TALLY_OWNER after pairing and TALLY_LINE_ID once a tab is open). TALLY_MAX_PER_CALL is the hard ceiling for any single payment, in USD (default 1). TALLY_AGENT_ADDRESS without a key makes the branch read-only.

fivebarFetch wraps fetch. When a seller answers 402 and accepts tally-tab, it reads the line's epoch, signs an intent for the exact price and URL, and retries with an X-PAYMENT header.

TypeScript · services/src/agent/client.ts
import { fivebarFetch } from "./agent/client.js";

const { response, paid } = await fivebarFetch("https://api.example.com/v1/quote/eth", {
  account: agentKey,              // viem LocalAccount registered on the line
  lineId: 7n,
  publicClient,
  tabManagerAbi,
  maxPerCall: 1_000_000n,         // refuse anything above $1 before signing
  escrowAtOrAbove: 25_000_000n,   // hold jobs of $25 or more in escrow
});

console.log(await response.json(), paid?.settlement.transaction);

Wallet first, Fivebar as overdraft

Give the client a wallet and it pays in this order: the agent's own USDC with the standard x402 exact scheme, then USDC it parked in the credit pool, then its Fivebar line. If a seller refuses the wallet payment, the client falls back to the line. An agent with a line never stalls on a payment.

Wallet first
const { paid } = await fivebarFetch(url, {
  account: agentKey, lineId: 7n, publicClient, tabManagerAbi,
  wallet: { chainId: 84532, usdc: USDC, idle },  // idle: see below (optional)
});
paid?.source;   // "wallet" | "refill" | "pool" | "tab"

Prepaid accounts and refills

An owner can fund all its agents from one prepaid account. Deposit USDC into AgentAccounts once, then give each agent wallet a rule: refill below a floor, up to a target, with a daily cap. When the agent's wallet runs low, fivebarFetch tops it up just before paying (pass refill in wallet); a keeper or the owner can also call refill. Funds only ever go to wallets the owner registered, within their rules, so a leaked agent key risks at most that agent's daily cap. Only the owner can withdraw or change rules, and setPaused stops every refill at once. Fivebar has no admin role on the contract. If the account is empty or the cap is used, the client falls back to the agent's Fivebar line.

Owners manage all of this on the Account page: connect a wallet, deposit, set Earn, and add agents three ways. A pairing link is the safest: the agent signs it with its own key (createPairingLink in services/src/agent/pairing.ts), so the address can't be mistyped or swapped. Adding by ERC-8004 id (setAgentById) funds the id's registered payment wallet and stops refills if the id later points elsewhere. A plain address or ENS name works for any agent, unverified.

Owner: fund the account and set a rule
cast send $USDC "approve(address,uint256)" $ACCOUNTS 50000000 --account owner
cast send $ACCOUNTS "deposit(uint256)" 50000000 --account owner                   # $50
cast send $ACCOUNTS "setAgent(address,uint128,uint128,uint128)" $AGENT 5000000 20000000 50000000 --account owner
# keep the agent between $5 and $20, at most $50 a day
Agent: refill before paying
const refill = { accounts: ACCOUNTS, owner: OWNER, abi: agentAccountsAbi, account: agentKey, publicClient, walletClient };
await fivebarFetch(url, { account: agentKey, lineId, publicClient, tabManagerAbi,
  wallet: { chainId: 84532, usdc: USDC, refill } });   // paid.source === "refill" when it topped up

Earn on a prepaid balance (opt-in)

An owner can supply its prepaid balance above a cash buffer to one ERC-4626 USDC vault of its choice: a Morpho vault, Aave through its ERC-4626 wrapper, or Fivebar's own CreditPool. setEarn(vault, buffer) turns it on, changes vault or (with address(0)) brings everything back to cash; sweep(owner) supplies new deposits. Refills and withdrawals pull from the vault when cash runs short, limited by what the vault can pay out at that moment. There is no leverage and no looping. Each owner's shares are tracked separately and every vault call is measured by what actually moved, so one owner's vault never touches another owner's money. The vault's own risks (smart contract, liquidity, rates) are the owner's; Fivebar does not pay or promise any return.

Curated vaults on Base mainnet (checked on chain: native USDC, Morpho Blue vaults and Aave's official wrapper), each tested end to end on a mainnet fork:

VaultProtocolAddress
Steakhouse USDCMorpho0xbeeF010f9cb27031ad51e3333f9aF9C6B1228183
Gauntlet USDC PrimeMorpho0xeE8F4eC5672F09119b96Ab6fB59C27E1b7e44b61
Static Aave Base USDCAave0x4EA71A20e655794051D1eE8b6e4A3269B13ccaCc

Morpho and Aave have no vaults for Circle's USDC on Base Sepolia, so testnet Earn uses the Fivebar CreditPool. Run the fork tests with BASE_RPC=… forge test --match-path "test/fork/*".

Owner: earn on everything above $50
cast send $ACCOUNTS "setEarn(address,uint256)" $VAULT 50000000 --account owner
cast call $ACCOUNTS "totalOf(address)(uint256)" $OWNER                   # cash + vault value
cast send $ACCOUNTS "setEarn(address,uint256)" 0x0000000000000000000000000000000000000000 0 --account owner   # opt out

Idle balance

Spare USDC can sit in the credit pool and earn the lenders' share of seller fees until it is needed. parkIdle deposits everything above a buffer; fivebarFetch withdraws just enough before a payment. Withdrawals are limited to the pool's idle cash, and the agent pays a little gas for each deposit and withdrawal.

TypeScript · services/src/agent/idle.ts
import { parkIdle, idleBalance } from "./agent/idle.js";

const idle = { account: agentKey, publicClient, walletClient, usdc: USDC, pool: CREDIT_POOL, poolAbi };
await parkIdle(idle, 5_000_000n);   // keep $5 liquid, park the rest
await idleBalance(idle);            // { cash, parked, withdrawable }

Sub-tabs: credit for agent teams

An orchestrator can give each worker it spawns its own key and budget on its line. The worker signs payments with its own key; the line's mandate still applies on top, and every sub-spend rolls up into one statement (DelegatePaid events give the tree). Any mandate or agent change revokes every worker at once.

Spawn a worker
// the orchestrator (the line's agent), or the owner, grants $5 for one hour
await tabs.write.setDelegate([lineId, worker.address, 5_000_000n, BigInt(now + 3600)]);
// the worker pays like any agent, with its own key and the parent's line
await fivebarFetch(url, { account: worker, lineId, publicClient, tabManagerAbi });
// done early: revoke
await tabs.write.setDelegate([lineId, worker.address, 0n, 0n]);

Self-funding agents

An agent that earns can pay its own bills. The owner sets a sweep: from then on, a share of everything the agent is paid through Fivebar repays its tab before the rest reaches its wallet. For income from elsewhere, repayFromWallet sends cash above a buffer to the tab. GET /pnl/:address on the Score API shows what the agent paid and earned each week.

Self-funding
cast send $TABS "setSweep(uint256,address,uint16)" $LINE $AGENT 5000 --account owner   # 50% of Fivebar income

import { repayFromWallet } from "./agent/selffund.js";
await repayFromWallet(cfg, 10_000_000n);   // keep $10, repay the rest of what's owed

Credit history on ERC-8004

Link an agent's ERC-8004 identity to its line, and every statement it settles becomes public reputation: tally.statement feedback from Fivebar's reporter, graded on-time (100), late (50) or default (0). Partial payments are not graded. The link counts only if the identity's owner owns the line, or its agent wallet is the line's agent key.

Link an identity to a line
cast send $IDENTITY_REGISTRY "setMetadata(uint256,string,bytes)" $AGENT_ID "tally.line" \
  $(cast abi-encode "f(uint256,address,uint256)" 84532 $TAB_MANAGER $LINE_ID) --account owner

Anyone can read the record: getSummary(agentId, [reporter], "tally.statement", "") on the ERC-8004 ReputationRegistry. Remove the link by setting the key to empty.

08

For sellers

The seller kit adds the pay-later tally-tab scheme to your API in a few lines. It sits next to any x402 setup you already run: payments in other schemes pass straight through. You are paid in USDC by the pool as soon as the payment settles, and never carry the credit risk.

Hono
import { fivebarHono } from "@fivebar/services/seller-kit";

app.use("/v1/*", fivebarHono({
  network: "base-sepolia",
  payTo: "0xYourWallet",        // where you get paid
  price: "0.05",                // USD, or a function of the request
  facilitatorUrl: "http://localhost:4020",
  extraAccepts: [yourExactOffer], // optional: keep your existing x402 offer
}));
Express
import { fivebarExpress } from "@fivebar/services/seller-kit";

app.use("/v1", fivebarExpress({ network: "base-sepolia", payTo: "0xYourWallet", price: "0.05",
                              facilitatorUrl: "http://localhost:4020" }));
Any fetch handler
import { fivebarGate } from "@fivebar/services/seller-kit";

const gate = await fivebarGate(req, opts);
if (gate.kind === "payment-required") return gate.response;  // 402
if (gate.kind === "not-fivebar") return yourExistingX402(req);  // another scheme
// paid: serve, and add gate.responseHeaders (X-PAYMENT-RESPONSE)

A bad payment or an unreachable facilitator gets a 402 with the reason; unpaid work is never served. The kit is in services/src/seller-kit/ and not on npm yet. Full guide: docs/sellers.md.

Price by Fivebar Score

Give trusted agents a better price, and keep low-score bots out. Agents announce their address in X-TALLY-AGENT; the kit looks up its score and quotes. The discount is safe: a payment is priced again for the key that actually signed it, so claiming someone else's address gets no discount. If the Score API is down, everyone pays the full price.

Score tiers
fivebarHono({
  network: "base-sepolia", payTo: "0xYourWallet", price: "0.05", facilitatorUrl,
  score: {
    api: "https://tally402-score.fly.dev",
    tiers: [{ minScore: 800, discountBps: 2000 }, { minScore: 600, discountBps: 1000 }],  // 20% off at 800+
    minScore: 300,   // optional: 403 below this (new agents have no score)
  },
});

Without the kit

Add a tally-tab entry to the accepts list of your 402 response. You keep any other schemes you already accept.

402 response body
{
  "x402Version": 1,
  "error": "X-PAYMENT header is required",
  "accepts": [{
    "scheme": "tally-tab",
    "network": "base-sepolia",
    "maxAmountRequired": "10000",
    "resource": "https://api.example.com/v1/quote/eth",
    "description": "Market data snapshot",
    "mimeType": "application/json",
    "payTo": "0xSeller…",
    "maxTimeoutSeconds": 120,
    "asset": "0xUSDC…",
    "extra": { "tabManager": "0xTabManager…", "chainId": 84532 }
  }]
}

When a request arrives with X-PAYMENT, decode it (base64 JSON), post it with your requirements to the facilitator's /verify, then /settle. Serve the content only after settle succeeds, and return the settle result base64-encoded in X-PAYMENT-RESPONSE.

You receive the price minus the seller fee (1% in v0, rounded up) in the same transaction: a $0.05 call pays you $0.0495. Escrowed payments arrive when the hold is released.

09

Facilitator API

Follows the x402 v1 facilitator interface. Both POST routes take { paymentPayload, paymentRequirements }.

RouteReturns
GET /supported{ kinds: [{ x402Version: 1, scheme: "tally-tab", network }] }
POST /verify{ isValid, invalidReason?, payer? }. payer is the line owner.
POST /settle{ success, errorReason?, transaction?, network, payer?, escrowHoldId? }

Rejection reasons

ReasonMeaning
invalid_x402_version · unsupported_scheme · invalid_networkPayload or requirements don't match this facilitator.
wrong_tab_managerextra.tabManager is not the contract this facilitator settles on.
invalid_payee · invalid_amount · invalid_resourceThe intent doesn't pay exactly the seller's price, to the seller, for this URL.
intent_expired · unknown_line · invalid_signatureDeadline passed, line doesn't exist, or the signer isn't the line's agent.
Contract error namesThe dry run of pay reverted, for example AboveBudget or PastDue.
Run it
RPC_URL=… TAB_MANAGER=0x… NETWORK=base-sepolia RELAYER_PRIVATE_KEY=0x… npm run facilitator   # :4020
Use a dedicated relayer key that holds only a little ETH for gas. Never use a key that holds funds.
10

For lenders

CreditPool is an ERC-4626 vault. Deposit USDC and receive tpUSDC shares. Lent money stays on the books as outstanding, so share value never drops when the pool funds a tab.

Fee share to lenders50%
Max utilization90%
Interest in v0None
  • Half of every seller fee is paid into the pool and unlocks into share value over 24 hours (unvestedYield shows what is still locked), so a deposit made just before a payment earns nothing from it. The other half goes to the treasury.
  • The pool never lends more than 90% of its assets, so at least 10% stays available for withdrawals.
  • Withdrawals are limited to idle cash (maxWithdraw and maxRedeem report the current limit).
  • A deposit cap applies during testing (10,000 USDC by default).
11

Score API

Score indexes x402 payments on Ethereum and Base (USDC transferWithAuthorization settlements), removes wash activity and rates each payer from 0 to 1000. Every component is returned, so you can see why an address scored what it did.

RouteReturns
GET /score/:addressScore, components, metrics and agentLikely. 404 if the address never paid over x402.
GET /statsRaw and screened payment volume, broken down by screening reason.
GET /top?limit=20Highest-scoring payers (up to 100).
GET /agent/:agentIdERC-8004 feedback for an agent: count, distinct clients, average value, tags. IMD seats included.
GET /pnl/:addressWeekly profit and loss from x402: paid, received and net, newest week first.
GET /profile/:queryAgent profile by ERC-8004 agent id, wallet or ENS name: identity, score and rank, x402 history, feedback, IMD and Claus activity, balances, and funding one hop deep (personal wallets shortened, never traced further).
GET /proof/:addressThe payer's leaf and Merkle proof in the latest published snapshot, ready for ScoreRegistry.verify or record.
GET /clausClaus Jobs deals indexed from its escrow on Ethereum.
GET /imdOrders paid to IMD's agent swarm, indexed from Ethereum.

Screening

Follows the method TRM Labs used to measure real agent payments. A payment is dropped if it is:

  • a self-payment (payer and payee are the same address);
  • to a seller with fewer than 10 distinct buyers;
  • to a seller whose top two payers bring more than 90% of its volume;
  • part of a circular pair, where the payee has also paid the payer.

Components

ComponentMaxMeasures
Breadth250Distinct sellers paid (full marks at 20)
Tenure200Days since first clean payment (full marks at 90)
Consistency150Share of weeks with activity since the first payment
Volume200Screened volume on a log scale (full marks at $10,000)
Cleanliness200Share of the payer's volume that survived screening

agentLikely is true for payers with 20 or more clean payments, an average under $1, at least 2 distinct prices and at least 2 sellers.

Run it
CHAINS=ethereum,base RPC_URL_ETHEREUM=… FROM_BLOCK_ETHEREUM=… \
  RPC_URL_BASE=… FROM_BLOCK_BASE=… npm run score   # :4030
curl localhost:4030/score/0xYourAgent
12

Contract reference

TabManager

FunctionCallerDoes
openLine(agent)AnyoneOpens a line owned by the caller.
setAgent(lineId, agent)OwnerRotates the agent key. Sets a fresh epoch.
depositCollateral(lineId, amount)OwnerAdds USDC collateral.
withdrawCollateral(lineId, amount, to)OwnerWithdraws collateral not securing the balance.
setMandate(…) · revokeMandate(lineId)OwnerReplaces or removes the mandate. Sets a fresh epoch.
cancelNonce(lineId, nonce)Owner or managerCancels one signed intent.
setDelegate(lineId, worker, cap, validUntil)Agent, owner or managerGives a worker key a budget on the line (sub-tab). 0, 0 revokes.
setSweep(lineId, earner, bps)Owner or managerRepays the line from bps of what its agent or owner is paid through Fivebar.
setManager(lineId, manager) · acceptManager(lineId)Sponsor · managerHands day-to-day control of a sponsored line to someone else (vouching).
claimVoucherFees(to)VoucherCollects the voucher's share of fees from lines it vouched for.
openSponsor() · depositSponsorCollateral(id, amount)AnyoneOpens a sponsor account and funds it.
openSponsoredLine(id, agent, cap)Sponsor ownerOpens a line backed by the sponsor’s deposit.
setLineCap(lineId, cap)Sponsor ownerChanges one agent’s cap. 0 freezes it.
withdrawSponsorCollateral(id, amount, to)Sponsor ownerWithdraws deposit not securing a balance.
pay(intent, signature, proof)AnyoneExecutes a signed intent. Returns the escrow hold id, or 0.
repay(lineId, amount)AnyoneRepays billable balance, statement first.
liquidate(lineId)AnyoneSeizes collateral once a statement is past due plus grace.
sweepFees()AnyoneSends accrued treasury fees to the treasury.

Views on TabManager: getLine, outstandingOf, freeCollateral, delegates, hashIntent. Views on TabLens (a read-only companion that keeps TabManager under the size limit): statementOf (billed, unbilled, held, dueAt, cycleEnd), isPastDue, creditLimit, availableCredit.

Parameters

NameDefaultLimit
sellerFeeBps100 (1%)At most 500 (5%)
poolFeeShareBps5000 (50%)Share of each fee paid to lenders
maxLtvBps10000 (100%)Capped at 100% in v0
billingPeriod7 days1 to 31 days
paymentWindow3 days1 to 14 days
gracePeriod2 daysUp to 14 days
maxUtilizationBps (pool)9000 (90%)Share of pool assets that can be lent

Errors from pay

ErrorCause
UnknownLineNo line with that id.
BadSellerSeller is zero or a protocol address (pool, escrow, treasury, TabManager).
IntentExpiredPast the intent's deadline.
StaleEpochThe mandate or agent changed after signing.
DelegateInactive · AboveDelegateCapThe worker's budget expired, was revoked by a mandate or agent change, or is used up.
AmountTooSmallThe fee would take the whole payment (for example 1 unit at 1%).
NonceUsedAlready paid or cancelled.
BadSignatureNot signed by the line's agent.
MandateInactiveNo mandate, or past validUntil.
AboveMaxPerPayment · AboveBudgetBreaks the mandate's per-payment cap or total budget.
SellerNotAllowedSeller not in the mandate's allowlist.
PastDueA statement is past its due date.
AboveCreditLimit(requested, available)Not enough collateral for this payment.
UtilizationCap(lentAfter, maxLent)The pool would go above 90% utilization.

ScoreRegistry (Ethereum, mirrored on Base)

FunctionCallerDoes
publish(epoch, root, asOf, count, version, uri)PublisherPosts one Merkle root for a daily epoch. Epochs only move forward and never past today.
verify(epoch, leaf, proof)Anyone (view)True if the score is in a live epoch.
record(epoch, leaf, proof)AnyoneStores a proven score. Records only move forward in time, even after a revoke.
scoreOf(subject)Anyone (view)Recorded score, agent flag, epoch and asOf. Check freshness yourself.
revoke(epoch)AdminWithdraws a bad snapshot. The publisher can replace the latest one the same day; scores recorded under it stay dead.

Each leaf: subject, score, the five components, screenedOutBps, agentLikely, keptVolumeCents, firstSeen. Leaf hash: keccak256(bytes.concat(keccak256(abi.encode(epoch, leaf)))).

Events

LineOpened, AgentSet, MandateSet, MandateRevoked, NonceCancelled, Paid, Billed, Repaid, RefundCredited, FeeDistributed, FeesSwept, Liquidated. Escrow emits Opened, Flagged, Released, Refunded.

13

Deployments

NetworkContractAddress
Base SepoliaUSDC (Circle)0x036CbD53842c5426634e7929541eC2318f3dCF7e
Base SepoliaCreditPool0x59b1597058C3315ac70324415cbD69F423579872
Base SepoliaTabManager0x41A118E004140C39bc82721625807bD68255cf97
Base SepoliaEscrow0xE80D0c0c70F174e15C2928d66f8805B5B34a53eC
Base SepoliaScoreRegistry (mirror)0x41E8A1B858B0fC41fdEABFdB8e1ed152AB6d8DEb
Base SepoliaAgentAccounts (prepaid, Earn)0xB08e1E96BAE4697E6eB6B17B81bEAB7C189B8D19
Base mainnetCreditPool · TabManager · EscrowAfter an external audit
SepoliaScoreRegistry0x74104F0b50092c474d948FC0c346fD25212EB2c5
SepoliaCreditPool · TabManager · Escrow (job-sized test setup)Not deployed yet
Ethereum mainnetScoreRegistryAfter review
Ethereum mainnetWarden NFTs · token · governancePhase 3 and later
Robinhood ChainERC-8004 IdentityRegistry · USDG0x8004A169FB4a3325136EB29fA0ceB6D2e539a432 · 0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168
Robinhood ChainCreditPool · TabManager · EscrowAfter an external audit
SolanaPayment contractsLater
14

Security

Tested properties: the pool's books always equal the sum of every line's balance; no tab ever exceeds its collateral; TabManager and Escrow hold exactly what they should; lenders never lose principal while the admin key is honest (on mainnet it is a Safe behind a timelock); intents cannot be replayed across chains, contracts, lines or nonces.

DateReviewResult
2026-10-22Internal review3 medium, 5 low. All fixed with regression tests.
2026-10-22pashov solidity-auditor v4.13 findings and 16 leads. All findings fixed; 7 leads addressed.
2026-10-23Independent review of sponsored lines1 low. Fixed with a regression test.
2026-10-24IMD agent swarm6 low, 3 info. 8 fixed with regression tests; 1 trust assumption documented.
No external audit yet. Mainnet waits for one. To report a vulnerability, see SECURITY.md in the repository.