Skip to main content
Clearing lets you charge per request at prices far below a transaction’s cost. The payer deposits *USDZ once, under a clearing key of its own, and signs a running total with each request. One deposit pays any seller that offers clearing. Metal’s clearing service answers each payment with a signed receipt and certifies the totals on chain each epoch, a batch of a few seconds of payments. An epoch is final once its challenge window, five minutes, has passed. Either side then queues a withdrawal of what is its own and claims it once a later epoch carrying it is final, 20 minutes or more after the payment. Clearing is its own x402 scheme, bajillion, settled through the same facilitator as exact. A seller can offer both. A payer set up for clearing pays by clearing wherever the offer is for its wallet’s deployment and within its maxAmount, and refuses any other request on its network unless set up to pay it by exact.

Clearing wallets

Clearing pays from and to a clearing account: a key of its own, held by payer and seller alike, separate from any Metal account. openClearingWallet(directory) from @metalntwx/metal-pay/node creates one the first time and opens it after that. Its public key, wallet.signer.publicKey, is the seller’s payTo and the account a payer deposits under. The directory holds the key and the payer’s record of its payments: keep it, and open it from one process at a time. A wallet’s record is for one Clearing deployment: if the deployment is replaced, open a new wallet, and take the old account’s funds out with the old wallet, by withdrawFromClearing, or recoverFromClearing once the deployment has faulted. It needs Node 22.13 or later (23.4 or later on Node 23), for node:sqlite.

1. Offer clearing

On the server from the Seller Quickstart, register the clearing scheme beside exact and list both:
Prices are dollars in *USDZ. The offer names the Clearing contract and the clearing service your facilitator settles against, which its /supported lists in the bajillion entry’s extra. If it lists none in *USDZ on the network, building the offer fails. The payer’s client refuses an offer for any contract but the one it is set up with. Each payment is bound to the URL asked for, so a payment made for one URL buys nothing at another.

2. Deposit

The payer deposits from a funded key into the Clearing contract, under its clearing key. The facilitator’s /supported names the contract, extra.settlement, the clearing service, extra.serviceUrl, and its validators, extra.validatorUrls: keep all three with the wallet, since recovery needs them if the facilitator is gone.
from sends an approval unless its allowance already covers the amount, then the deposit, and pays the gas of both. The contract takes deposits between epochs, so the call waits for that window first. Amounts are the token’s base units, and a deposit is a whole number of millionths of a token, 0.000001 *USDZ: Clearing counts in those units. A Clearing contract holds at most 2^64 - 1 of them in all, about 1.8e13 *USDZ, one pool shared by every account on the network. The call refuses at once a deposit that would take it past that, and a contract that holds another token. Room comes back as withdrawals’ payouts are claimed.

3. Pay

In the same file as step 2, with its imports, account and wallet:
The wallet’s maxAmount is the most one clearing payment may cost. A request on the wallet’s network with no clearing offer it can pay, for another deployment or above that maxAmount, fails, pays nothing and names which: raise maxAmount or pay another seller. Pass { exactFallback: true } as registerClearingClient’s fourth argument to pay such a request by exact instead, an on-chain transfer from account outside the deposit. createMetalPaymentClient’s own maxAmount, 1 *USDZ by default, caps those and every other payment but a clearing one. A payment is made when the service issues its receipt, so a request that fails after that still cost its price. One clearing account pays one request at a time, about one every three seconds, so jobs that pay in parallel each take a wallet and a deposit. Each deposit waits for the gap between epochs, so deposit every job’s share before starting any job. A fresh deposit pays from the next epoch. If the service’s answer to a payment is lost, the account holds that payment and pays nothing else until it goes through or is refused. Send the same request again, to the same URL at the same price: the held payment is sent again, so it never pays twice. If that URL no longer offers clearing at that price, send the held payment with clearing.pay((await clearing.pendingInvoice())!). A held payment is settled from the service’s receipt when the service kept one, and released when it has none and the epoch it was signed for has closed, the service is past it, or the chain registered it under another anchor. A payment the service does not take fails with a refusal that says what to do. (await wallet.store.load())?.payments is the wallet’s record of the payments the service took, by invoice id, each with invoice.amount in the token’s base units and invoice.recipient, the seller’s clearing key. clearingSpendable({ rpcUrl, settlement, serviceUrl, account: wallet.signer.publicKey }) is the service’s estimate of what the account can still spend, in the token’s base units. It counts a deposit from the epoch after it lands, and around an epoch’s close it can read low, even below zero. Nothing shows a deposit not yet counted, so budget from your own ledger of deposits and payments, and use the estimate only to tell whether a payment can go now.

4. Withdraw

A payer continues the same file. A seller uses the wallet from step 1, with a funded viem account of its own as account:
A payer’s unused deposit and a seller’s earnings leave the same way. Only a finalized balance can leave: payments from the open epoch wait for it to finalize. The first call queues a withdrawal of the finalized balance, paid to from or to a destination given, and answers sent with its transaction under queued and an empty claimed. With no payout to claim and no finalized balance, it answers empty. That includes a balance whose last payments are not yet final: call again after the challenge window that follows the account’s last payment or deposit. Until the epoch carrying the withdrawal finalizes, a call answers waiting with that epoch. Once it has finalized, the next call claims the payout to the destination the withdrawal named, answers sent with the amounts and transactions under claimed, and queues any balance finalized since. A payout the destination refuses is deferred: it waits in the contract until that address calls withdrawUnclaimed. from pays the gas of each transaction. clearingBalance(serviceUrl, wallet.signer.publicKey) returns the account’s balance as of its last final epoch, { amount, state, opening }: amount in Clearing’s millionths of a *USDZ, and opening the proof a withdrawal closes it with, or null when the service retains none. A loop that withdraws everything stops at an empty once amount is "0" and no payment or deposit of the account is newer than a challenge window. A withdrawal or claim refused or reverted because an epoch finalized as it was sent moves nothing but the gas: call again after the next epoch. If the clearing service cannot be reached, the error says so; once it has stopped, pass one of the extra.validatorUrls kept from step 2 as serviceUrl. A validator proves a payout or a balance only for an epoch it retained, so try each until one has. Once the service has missed a deadline, the call fails with an error that names recoverFromClearing.

When a payment is final

When an epoch closes, the clearing service and its validators certify its totals on chain. For the epoch’s challenge window, anyone holding a receipt the totals contradict can prove it on chain, which halts the deployment. After the window the epoch is final: its receipts are final, and the withdrawals it carries can be claimed. The Clearing contract’s policy() gives a deployment’s windows in seconds: the admission delay, the shortest and longest challenge window, the deposit timeout, and the shortest and longest withdrawal notice. To show the epoch clock, read the contract with the exported clearingAbi. Epochs are numbered from 0. admittedCount is how many the contract has admitted, so the open epoch, or the next to open, is number admittedCount, and epoch N is final once finalizedCount is above N. The epoch in withdrawFromClearing’s waiting is claimable once it is final. If the service stops, the contract has a way out. Once the service misses a deadline, to admit an epoch, a deposit or a queued withdrawal, anyone can fault the deployment and start recovery. Each account then takes its last finalized balance, and a deposit no final epoch counted is refunded. recoverFromClearing takes the same options as withdrawFromClearing, with the service or any of its validators as serviceUrl, or a list of them, and does each step that is due: pass the service and the extra.validatorUrls from step 2. Each proof comes from the first in the list that has retained its epoch. It answers operating while the service keeps the deadlines that hold the account’s funds, waiting while an epoch admitted before the fault is in its challenge window, and recovered with what it took. Once the deployment has faulted, a withdrawal whose epoch is already final is claimed at once, and waiting lists it under claimed. When no service gives a proof, the error names it and what was sent, and a later call takes the rest. A service usually stops with an epoch registered: recovery can then start about five minutes after the stop, and finishes about five minutes after that. If it stopped between epochs, a finalized balance with no withdrawal queued, or a final withdrawal not yet claimed, holds no deadline, so recoverFromClearing answers operating for them. Call withdrawFromClearing with a validator as serviceUrl, as in step 4, to claim the final withdrawal and queue one of the balance; recovery can start once that one’s deadline passes, the deployment’s maximum withdrawal notice later, 7 days.

Refusals

A clearing payment the service does not take fails the request, and fetchWithPayment throws an error whose cause is a ClearingRefusal. Its reason says why, retry what to do, and its message both: Once the deployment has faulted, a payment fails with an error that names recoverFromClearing, and so does sending a held payment again. A payment the service refuses for any other reason fails with an error that says to send the request again. The facilitator refuses a clearing payment presented to a seller with one of these reasons: A settled clearing payment’s transaction is empty, since nothing is sent on chain for it; extra.clearing carries the receipt, the invoice and status: "preconfirmed": the receipt is the service’s acknowledgement, final once its epoch is. GET /settlements?payTo=<clearing key> lists them with scheme bajillion and an empty transaction: match a payment by its id, the extra.settlement.id of its settle answer.

Seller Quickstart

The seller this page adds clearing to.

Facilitator

Endpoints, limits, refusal reasons and retries.