On Monad
Tab is one thing: a Settlement is a transfer and a ledger entry in one transaction, and the Credit Limit is a pure function of the Settlements already applied. Everything on this page is built around that fact rather than into it. An x402 payment, an identity, a label, a passkey, a swap: each is stated on the page it appears on as what it is, and none of them raises a limit.
The map
| Piece | What Tab does with it |
|---|---|
| x402, V2 | The prepaid fallback on a credit refusal, and the currency Tab fronts on credit |
| Monad API Hub | Buy now, pay later for pay-per-request data services |
| Permit2 | Gasless Settlement: the Agent signs, anyone submits |
| ERC-8004 | Identities for the demo Service and Agent, indexed and shown, and reputation feedback a Service writes after each Settlement it receives |
| Envio HyperSync | The indexer's cold start, past the public RPC's log cap |
| Nansen | A weekly profile of each Agent, bought per call over x402, and data fronted on credit |
| Mera passkeys | A Dashboard account with no extension and no seed phrase, and a sealed agent book under a second PRF namespace |
| MetaMask Agent Wallet | mm tab, the rail as wallet plugin commands, run end to end on Mainnet |
| Privy server wallets | An Agent key outside the Agent's process, bounded by a signing policy, run live on Testnet |
| Chainlink CRE | The delinquency keeper's schedule, as a CRE workflow that has run under the simulator against the hosted keeper and is not deployed to a DON |
| Kuru | Settling from a token the tab is not denominated in |
| NEAR Intents | Filling the Agent's Monad balance from USDC on another chain before it settles |
| Agora AUSD | A first-class Asset on Mainnet |
x402: the fallback, and the thing fronted
x402 is the prepaid protocol: a server answers 402 with a PAYMENT-REQUIRED header naming what it accepts, the client signs a payment and repeats the request with PAYMENT-SIGNATURE, and the server delivers once a facilitator has settled the payment on chain.
Tab is the opposite model.
The two meet in two places, and the direction matters in each.
The offer on a refusal.
A Service that refuses a call on LimitExceeded puts an x402 exact requirement for the same charge beside its Tab-Charge-* headers.
An Agent that would rather pay for that one call than settle its tab first signs an EIP-3009 transferWithAuthorization for the amount and repeats the request; Monad's facilitator at x402-facilitator.molandak.org verifies it, the work is delivered, and the facilitator settles the transfer to the Service's Collection address.
Nothing lands on the Open Tab, because nothing is owed.
tab_call does this when tab.config names an x402 signer and reports it in an x402 block; an Agent without one gets the LIMIT_EXCEEDED it would have got anyway.
The upstream fronted on credit.
The reverse direction is the one that changes what an Agent can do.
The Monad API Hub puts dozens of data providers behind one x402 endpoint, each priced per call in USDC, and every one of them is prepaid.
A Tab Service fronts them: the Agent calls /hub/apihub/run on the Service on credit, the Service forwards to the Hub, reads the price off the Hub's 402, pays it with its own key, and meters the Agent's Open Tab for that price plus its published margin.
The Agent never signs an x402 payment and never holds the Hub's currency; it settles its tab later, in one transaction, like any other charge.
The Hub takes USDC on Monad Mainnet only.
The Mainnet Service pays it on its own chain; the Testnet Service pays it on Mainnet with a key funded there and meters the Agent in mUSDC on Testnet, base unit for base unit, so the two networks meet in the Service's own books and nowhere in Tab's contracts.
The fronted tool is priced on chain like any other, at one base unit per unit, and a call consumes as many units as it cost, because the chain refuses a unit price the price list does not hold and a fronted price is not known until the upstream answers.
/hub/nansen/* fronts Nansen's own x402 API the same way.
tab_discover reads the Hub's manifest and lists a fronted provider's endpoints under the Service as the tools they are, and Browse lists them as fronted cards after the tools priced on chain.
An x402 payment is not a Settlement
The Dashboard, the SDK and the read API say so wherever one appears. An Agent that only ever prepays never earns a limit, which is correct: it has demonstrated nothing about repayment, because it has never owed anything.
It moves the Asset to the Service and enters no history. The Credit Limit is computed from Settlements applied through TabSettlement, and only from those.
Permit2: settle without gas
TabSettlement.settleWithPermit2 takes a PermitWitnessTransferFrom the Agent signed against Uniswap's Permit2 at its canonical address, 0x000000000022D473030F116dDEE9F6B43aC78BA3.
The witness is TabSettlement(bytes32 serviceId, address asset, uint128 amount, address surface, uint256 chainId), so the signature can pay this Service this amount in this Asset through this contract on this chain and nothing else.
Permit2 verifies it and moves the Asset; the same applySettlement runs in the same transaction; SettledGasless names the relayer beside Settled.
The Agent's whole requirement is the Asset and one approve(Permit2) on it.
The gateway's POST /relay/settle simulates the call from the operator, refuses a bad permit by name, submits it, and returns the same receipt the direct path returns.
The SDK's monad-relayed strategy signs and posts; tab_settle takes it as strategyId, and the CLI as --strategy monad-relayed.
ERC-8004: who the parties are
Monad's Identity and Reputation registries are at 0x8004A169FB4a3325136EB29fA0ceB6D2e539a432 and 0x8004BAa17C55a88189AE136b182e5fdA19dE9b63 on Mainnet, and at 0x8004A818BFB912233c491871b3d84c89A494BD9e and 0x8004B663056A597Dffe9eCcC1965A193B7388713 on Testnet.
The demo Service is agent 10254 on Mainnet and 1913 on Testnet, and the demo Agent is 10255 and 1914, each registered by the 03_RegisterIdentity script in the contracts package with a registration file that says what the party does and how it settles.
The registry indexes the Identity Registry's Transfer, Registered, URIUpdated and MetadataSet events into one row per agent, reads each agent's registration file and its reputation summary, and serves them: an Agent's page opens with every identity whose owner or wallet is that address, and a Service's row carries its operator's.
Each section states the block it was read at and the registry it was read from.
An identity is a name, not a credential: the Credit Limit reads none of it.
ERC-8004 reputation: the repayment record, where anyone can read it
With GATEWAY_REPUTATION_ENABLED=true, a Service's gateway writes one feedback entry to the Reputation registry after each Settlement it receives, about the Agent that paid it.
Both hosted gateways run with it on, so every Settlement to tab.demo on either network is restated there.
The entry is giveFeedback(agentId, 100, 0, "tab", "settled", endpoint, feedbackURI, feedbackHash), sent from the operator key once the Settlement is indexed.
The value is the same for every Settlement, so the count is the signal: how many Settlements an Agent has made to Tab Services, readable with getSummary by anyone who reads ERC-8004 and never reads Tab's contracts.
feedbackURI is <GATEWAY_PUBLIC_URL>/reputation/<settlementId>, a document the gateway serves naming the amount, the Asset, the time and the transaction, and feedbackHash is the keccak-256 of exactly those bytes.
Anyone can check an entry against the Settled event in that transaction.
The registry accepts feedback from anyone except the agent's own owner or operator, and needs no signature from the Agent.
So an Agent is rated only when it holds an ERC-8004 identity itself, as its owner or its agentWallet, and a Service whose operator key also owns the Agent's identity is refused by the registry; the gateway reads that refusal from a simulation and writes nothing.
The writer counts what it has already written about each agent under its two tags rather than keeping state of its own, so a restart writes nothing twice, and scripts/reputation-backfill.mjs writes the entries for Settlements made before it was switched on, as a dry run unless given --broadcast.
That is why the demo Agent's identities, 1914 on Testnet and 10255 on Mainnet, are owned by the Agent address 0x3a3B6079e418C81a9dE08414Bb07ea817939e7Ce and not by the operator of tab.demo.
Both carry entries written by the tab.demo operator, one per Settlement, with a mean of 100; the Agent page on the Dashboard reads the current count live.
An Agent's page shows the summary in its own section, "Reputation, from the ERC-8004 registry", from Tab Services (the operators of registered Services, under tab and settled) apart from every client, and the registry's GET /agents/:agent/reputation serves the same per agentId.
Reputation restates Settlements; it does not replace them
It makes an Agent's repayment record portable, not stronger: a reader who wants the record itself reads the Settled events each entry names.
The feedback is written by the Service after the Settlement, from facts already on chain. The Credit Limit never reads it.
HyperSync, and the log cap
Monad's public RPC caps eth_getLogs at 100 blocks per request, and the witness a Service needs is the Agent's whole Settlement history.
The registry chunks its reads to fit, and with HYPERSYNC_URL and HYPERSYNC_API_TOKEN set it reads whole ranges from Envio HyperSync in one request instead, so a cold start is bound by the archive's speed rather than by the cap.
The head and the live window always come from the RPC, and everything read from either is folded to the same historyCommitment and checked against the chain before it is served.
Nansen: a weekly profile, bought per call
Each Agent's page on the Mainnet Dashboard carries a profile from Nansen: what the address holds, who funded it, and its last 30 days, with the counterparties it dealt with most under Nansen's own names for them.
The registry buys it over x402 from three of Nansen's profiler endpoints, current-balance, related-wallets and transactions, at one cent each in Mainnet USDC, with no account, no API key and no credits.
The facilitator submits each payment and pays its gas, so the paying key needs only USDC.
It is bought once a week per Agent, whoever is looking.
The first read stores the answer in the registry's Postgres, and every read for the next seven days is served from that row to every visitor, so a restart or a redeploy never pays for the same answer twice.
The section says when it was fetched, when it refreshes and what it cost, and links each payment's transaction.
Three guards keep strangers from spending it: only an address the registry already knows as an Agent on this network is looked up, each call refuses to pay more than a cent, and a daily budget caps the total (NANSEN_DAILY_BUDGET_BASE_UNITS, $0.25 by default).
The calls go one after another, a 429 is asked again after a pause because nothing was paid for it, and a section that still fails is bought again on its own ten minutes later.
The registry serves it at GET /agents/:agent/nansen, apart from the credit read, so tab_status never waits for Nansen or pays for it; NANSEN_X402_PRIVATE_KEY names the paying key.
Nansen's monad chain is Monad Mainnet, so the Testnet Dashboard says so instead.
Its address labels need a key with credits (NANSEN_API_KEY); the page shows them only when a key with credits is configured.
Everything here is an offchain signal, and the section says in one sentence on every render that it changes nothing in the Credit Limit.
Mera passkeys
/keys on the Dashboard is a passkey account.
The seed is the WebAuthn PRF output of one passkey, the owner key is derived from it at m/44'/60'/0'/0/0 and never shown, and each session key after it is the Agent for one runtime, revealed once for that runtime's environment.
The same passkey always yields the same keys, so a runtime that loses its key has not lost the account, and a reader with no wallet extension can authorise a Service from the Dashboard with one touch.
The same passkey also does a job that is not an account's: it seals the agent book, the owner's private notes on each Agent, under a fresh PRF salt per seal, with an AES-256-GCM key that never signs and never leaves the touch that made it.
The Dashboard page walks through both.
MetaMask Agent Wallet
@tabai/agent-wallet-plugin registers mm tab with the MetaMask Agent Wallet CLI.
It installs from npm with mm plugins install @tabai/agent-wallet-plugin and defaults to the recorded deployment on both networks.
On CLI 7.0.0 a first install from npm is removed again by the host's post-install check, for every plugin; the plugin's README has the two-line install that avoids it.
Its wallet-backed commands need mm login, and on Testnet only its reads and dry runs work, because the wallet's gas service does not know chain 10143.
On Mainnet it has run end to end from a MetaMask server wallet: mm tab delegate --broadcast, mm tab authorise, an mm tab call of 0.01 USDC signed by the delegate with no wallet approval, and mm tab settle --broadcast, whose Settlement landed in block 109352198.
Each wallet transaction was approved through MetaMask's email MFA, and the transaction hashes are in the plugin's README.
| Command | What it does |
|---|---|
mm tab discover | Services, Assets, prices and Bonds, from the read API |
mm tab status | An Agent's Open Tab, headroom and Settlements per Asset |
mm tab call | One metered call, on credit, signed by the wallet's registered metering delegate |
mm tab authorise | Builds TabBook.authorise and hands it to the wallet |
mm tab delegate | Makes a local session key and builds MeteringDelegates.setDelegate for it, or revokeDelegate, and hands it to the wallet |
mm tab settle | Builds the approval where the allowance falls short and TabSettlement.settle, and hands each to the wallet in turn |
Every write is handed over with a one-sentence intent, so the wallet's own policy decides what is signed, and a wallet refusal on the approval stops before the Settlement is submitted.
mm tab call is the one command the host bounds: a metered request must be signed, and the CLI exposes no message signing to a plugin.
So mm tab delegate has the wallet submit one transaction naming a local key in MeteringDelegates, and mm tab call signs each metering claim with that key while the chain says it is registered.
The key signs claims and nothing else: it cannot move funds, and every charge it causes stays within the ceilings the wallet set with mm tab authorise.
It lapses at its expiry, at most a year ahead, and mm tab delegate --revoke --broadcast withdraws it in one transaction.
MeteringDelegates is deployed on both networks and both hosted gateways read it on every delegate-signed call, so a revocation takes effect on the next call.
A self-hosted gateway that does not read it refuses a delegate-only call by name.
Privy server wallets
createPrivyAgentSigner is an ethers signer whose key lives in a Privy server wallet, so the Agent's process never holds it.
Metering claims are signed with personal_sign, Permit2 Settlements with eth_signTypedData_v4, and authorise, approve and settle with eth_signTransaction and broadcast through the Agent's own Monad provider.
The wallet is created under a Privy policy that allows exactly those calls on this chain's TabSettlement, TabBook, Permit2 and accepted Assets, with a value of zero, and refuses everything else.
The policy and the wallet are owned by a key that stays off the Agent's machine, and the Agent signs as an additional signer that cannot change either.
Privy enforces the policy off chain, when it is asked to sign, and it needs a Privy app; the integration page has the configuration and the rules.
It has run live on Testnet against a real Privy app.
scripts/privy-agent.mjs --create --broadcast created the policy, the signer and the wallet, and --denied got Privy's own policy_violation refusing a transfer to an address the policy does not name.
--loop then ran the Agent both ways: directly, with authorise, approve and settle signed by eth_signTransaction, and gasless, with the Permit2 Settlement signed by eth_signTypedData_v4 and submitted through the relay, each metering claim signed by personal_sign, until the tabs settled to zero.
Chainlink CRE, and the keeper
A tab whose Settlement Window has closed with the tab still open may be marked delinquent by anyone.
apps/keeper is the process that does it: it takes candidates from the registry's delivery feed, confirms each on chain, and submits markDelinquent when asked to broadcast.
apps/cre-keeper is the schedule, a Chainlink CRE workflow with a cron trigger every ten minutes and an HTTP call to the keeper behind a shared secret.
The keeper is hosted for both networks, at https://keeper-testnet-production-e820.up.railway.app and https://keeper-mainnet-production-0f50.up.railway.app, each sending from a key of its own that holds only MON for gas, with /tick behind the shared secret.
The workflow compiles to WASM, its testnet-settings and production-settings targets point at those two keepers, and it has run under the CRE simulator against the hosted Testnet keeper.
It is not deployed to a DON: that waits on Chainlink deploy access.
The mark is permissionless and the keeper holds no privilege: anyone running the same two processes marks the same tabs.
Kuru: settle in any asset
TabSettlement.settle moves the Asset the tab is denominated in.
createKuruFundedStrategy wraps a strategy, reads the Agent's balance of that Asset before every Settlement, and when it is short, swaps the shortfall in from a configured source token through Kuru's on-chain router, then settles.
The shortfall is the swap's minimum out, so a Settlement is either fully funded or the swap reverts, and the Settlement itself is still the inner strategy's transaction.
NEAR Intents: fund from another chain, then settle on Monad
createIntentsFundedStrategy, strategy id intents-funded, wraps a strategy the same way and reads the Agent's Monad balance of USDC before every Settlement.
When it is short, it asks the NEAR Intents 1Click API for the shortfall delivered to the Agent's own Monad address, pays the quoted USDC in on the funding chain, waits for the delivery, checks the Monad balance again, and only then settles.
The funding step happens before the Settlement and only fills the Agent's balance; the Settlement is still one Monad transaction, same-chain and atomic.
It has run live on Mainnet, with the Agent's USDC on Arbitrum.
The Agent owed 0.20 USDC to tab.demo and held 0.08868 on Monad, so it was 0.11132 short:
| Step | Chain | Transaction |
|---|---|---|
| Deposit 0.113481 USDC to the 1Click deposit address | Arbitrum, block 510411104 | 0xd4cd3e62b519affab2828b0b72e8152ae63dea9ed6891e57001afa3017a8168d |
| NEAR Intents delivers 0.11132 USDC to the Agent's own Monad address | Monad, block 109379795 | 0x5e110d8601b7602274f76c06e80e8a658b07d37eec210d27c0f33b29e9b57f74 |
| The Settlement of 0.20 USDC, applied to the tab in the same transaction | Monad, block 109379821 | 0x863e9ae50961b38e7bf12e7ae14e7384bc543725ea51c8acdd9dc8bf9f817497 |
The slippage tolerance is spent, not refunded: that run paid 0.113481 for 0.11132 delivered.
Of the 0.002161 difference, 0.001125 was the 1% tolerance and the rest the route's own fees, about 1.9% in all on a small amount; a larger shortfall spreads the fixed fees thinner, and a lower slippageBps shrinks the tolerance.
The integration page has the configuration.
Agora AUSD
AUSD at 0x00000000eFE302BEAA2b3e6e1b18d08D69a9012a is in the shared Asset table beside USDC, so a Mainnet Service prices in it, a Bond is posted in it, and every page and tool that prints an Asset names it.
Tab holds no rate between the two: a tab in AUSD is settled in AUSD.
What none of this changes
Take every piece on this page away and the rail is the same rail. A Service meters after delivery, an Agent settles with its own key in one transaction, the chain applies the payment, and the Credit Limit is computed from the record the chain kept. The pieces make it cheaper to reach (passkeys, the plugin), safer to hold (a Privy wallet's policy), cheaper to run (HyperSync, the keeper on a schedule), gasless to settle (Permit2), easier to fund (Kuru, NEAR Intents), and wider in what an Agent can buy on credit (the Hub), and the boundaries page states the edge of each.