Skip to content
Testnet demo on Whitechain Sepolia: test tokens (ITC) only, no real money. Requests are served by a real open-weight model on the founder's machine when it is online, otherwise by a demo seller that returns canned responses.
Inferit

Whitechain escrow

Inferit settles on Whitechain. Buyer funds sit in the MarketEscrow contract; the operator settles usage in batches, but only inside limits the buyer set and the contract is designed to enforce. The contracts are unaudited testnet software.

Network

Whitechain Sepolia is an OP Stack L2 testnet: chain id 1874, RPC https://rpc.testnet.whitechain.io, explorer explorer.testnet.whitechain.io. Gas is paid in test WBT from the Whitechain faucet.

The settlement token is ITC (Inferit Test Credit): 6 decimals, so one base unit is exactly one µUSD and 1 ITC counts as one test dollar in prices. Its faucet() mints test ITC at most once per 24h per address, and faucetTo(address) (v2) lets anyone pay the gas of that mint for another address. It has no monetary value.

Live deployment

Read from the contract itself, so this always matches what is deployed. The deployment record lives in the repository at chains/whitechain/deployments/1874.json (contracts) and 1874.admin.json (the Safe).

What the API reports (GET /v1/rails)

Roles and limits

  • Owner: a 2-of-3 Safe. Sets the fee recipient, the guardian and the settlement limit, proposes operator changes, and unpauses. It cannot move buyer funds or change the fee cap or the withdrawal delay. Ownership changes are two-step.
  • Operator (the API's hot key): can call settle within the rules below and, in v0.3, credit a front-run x402 authorization to its signer out of the escrow's unattributed balance (below); nothing else. A new operator takes effect only after a timelock (24h on the hosted deployment), so a compromised admin key cannot swap it instantly.
  • Guardian: can pause deposits and settlement in an emergency, nothing else. Only the owner can unpause.
  • Circuit breaker: the total the operator can settle per window, across all buyers, is capped by settleLimit. A leaked operator key can therefore settle at most each buyer's remaining cap, and no more than the window limit in total, before the owner pauses it.
  • Sellers: register and change their payout from their own wallet only. No API key can redirect earnings.
  • Facilitator key (v0.3, the API's second hot key, separate from the operator): submits x402 payments with depositWithAuthorization and, on testnet, mints sponsored ITC with faucetTo. Anyone may make those two calls, so the key has no role in the escrow: it pays gas and nothing else. It cannot settle, and it cannot send a payment anywhere but the signer's own escrow balance.

Calls

MarketEscrow / TestCredit
// buyer
TestCredit.approve(escrow, amount)                   // ITC, 6 decimals
MarketEscrow.depositAndSetCap(amount, cap, expiry)   // or deposit() + setSpendingCap()
MarketEscrow.depositWithPermit(buyer, amount, cap, expiry, deadline, v, r, s, termsSignature)  // v0.3: gasless, relayed (EIP-2612)
MarketEscrow.depositWithAuthorization(from, value, validAfter, validBefore, nonce, v, r, s)   // v0.3: x402 (EIP-3009)
MarketEscrow.requestWithdraw(amount)                 // starts the delay
MarketEscrow.executeWithdraw()                       // after withdrawDelay; cancelWithdraw() also available

// seller
MarketEscrow.registerSeller(payout)                  // from the seller wallet; payout = msg.sender if zero
MarketEscrow.withdrawEarnings()                      // pays the registered payout

// views
buyerState(buyer)   -> (deposit, cap, spent, expiry, pendingWithdraw, withdrawReadyAt)
sellerState(seller) -> (registered, payout, earnings)

The spending cap

The cap is the total the operator may ever settle from you, counted cumulatively from spent. Setting it again replaces the cap but keeps spent, so raising a 100 ITC cap to 150 after spending 40 leaves 110 of headroom. After expiry, nothing more can be settled from you until you set a new cap, so the API stops accepting new usage a settlement margin (at least five minutes) before the expiry. The buy flow suggests a cap equal to your deposit, at most 25 ITC, expiring in 7 days. A relayed, gasless variant (setSpendingCapBySig, EIP-712) exists for wallets that cannot send transactions.

x402 and gasless deposits (v0.3)

MarketEscrow v0.3 adds two ways to deposit without the buyer sending a transaction. Both are relayed: someone else submits the buyer's signature and pays the gas. Both are blocked while the escrow is paused, like every deposit. Agents should start at Pay per request with x402; this section is the contract view.

depositWithAuthorization (x402)

The buyer signs an EIP-3009 TransferWithAuthorization for the token, naming the escrow as the recipient. This is exactly what a standard x402 client signs for the exact scheme. The API's facilitator submits it:

MarketEscrow.depositWithAuthorization (v0.3)
depositWithAuthorization(from, value, validAfter, validBefore, nonce, v, r, s)   // blocked while paused

require  (from, nonce) not credited before             // authorizationCredited(from, nonce)

if token.authorizationState(from, nonce) is unused:    // the normal path: anyone may call (the facilitator does)
    token.transferWithAuthorization(from, escrow, value, validAfter, validBefore, nonce, v, r, s)
    // the token checks the signature, the validity window and the nonce;
    // a signature that names any other recipient fails
else:                                                  // recovery: the token already executed it
    require  caller is the operator or the owner
    require  the signature recovers to "from" over TransferWithAuthorization(from, to = escrow, ...)
             under token.DOMAIN_SEPARATOR()
    require  unattributedBalance() >= value            // balance - (totalDeposits + totalEarnings + feeAccrued)
    // recovered = true

credit "from":   deposit += value
                 cap += value;  expiry = max(expiry, now + AUTH_CREDIT_TTL)
                 // if the old cap had expired: cap = spent + value, expiry = now + AUTH_CREDIT_TTL
emit Deposited(from, value), SpendingCapSet(from, cap, expiry),
     DepositedWithAuthorization(from, value, nonce, recovered)
  • Credited to the signer, for spending. The payment raises both the deposit and the cap by value, so the operator can settle the request it paid for. It also pushes the cap's expiry to at least now + AUTH_CREDIT_TTL, an immutable constructor parameter (30 days by default). Money paid through x402 is meant to be spent, so it is never stranded behind an expired cap. Whatever the request does not use stays as the buyer's balance, withdrawable like any deposit.
  • If the old cap had expired, the payment starts fresh terms: the cap becomes spent + value, so headroom the buyer let lapse is not revived. Like any cap change it uses up the buyer's escrow nonce, so an outstanding setSpendingCapBySig signature stops being valid.
  • Front-running cannot steal a payment. An authorization is public once it is sent. If someone submits it to the token's transferWithAuthorization directly, the tokens still land in the escrow, because the signature fixes the recipient. They are just not attributed to anyone yet, and nobody can withdraw them. The operator or the owner then credits them to the signer with the same call (recovered = true). The escrow checks the signature itself, that the same (from, nonce) was never credited, and that the unattributed balance covers the amount. Recovery is limited to those two roles because the token cannot tell a used authorization from a cancelled one; they first check that the token's AuthorizationUsed came with a transfer to the escrow in the same transaction. For a payment the API is serving, it runs that check and sends the recovery from the operator key itself, then serves the request.
  • Solvency. v0.3 holds token.balanceOf(escrow) ≥ totalDeposits + totalEarnings + feeAccrued, and all three are readable on chain. The difference is unattributedBalance(). It grows only when tokens are sent to the escrow directly, such as a front-run authorization, and only a recovered credit can attribute it.
  • Token. ITC v2 (TestCredit) implements EIP-3009 like Circle's FiatToken v2: transferWithAuthorization, receiveWithAuthorization, cancelAuthorization, authorizationState, with one EIP-712 domain (name "Inferit Test Credit", version "1") shared with EIP-2612 permit. USDC.e implements the same functions, so the same escrow works with it on mainnet.

depositWithPermit (gasless deposit)

The buyer signs two messages and sends no transaction: an EIP-2612 permit that lets the escrow pull the amount, and the deposit terms (amount, cap, expiry) under the escrow's own EIP-712 domain. A relayer submits both and pays the gas:

MarketEscrow.depositWithPermit (v0.3)
depositWithPermit(buyer, amount, cap, expiry, deadline, v, r, s, termsSignature)   // blocked while paused

if caller != buyer:                                    // relayed: the buyer also signs the terms
    require  termsSignature is buyer's EIP-712 signature (the escrow's domain) over
             DepositWithPermit(buyer, amount, cap, expiry, nonce = nonces(buyer), deadline)
token.permit(buyer, escrow, amount, deadline, v, r, s) // EIP-2612; a permit someone already submitted is tolerated
require  token.allowance(buyer, escrow) >= amount
pull amount from buyer, credit it as buyer's deposit
set buyer's cap and expiry, as depositAndSetCap does   // consumes buyer's escrow nonce

This is the gasless version of depositAndSetCap. The cap and expiry are the ones the buyer signed, so a relayer or a front-runner cannot choose them, and the usual rules for raising and lowering a cap apply. It needs a token with permit, which ITC v2 and USDC.e both have. The website does not use it yet.

The facilitator key

The hosted API submits x402 payments, and testnet faucet claims for agents with no gas (faucetTo), from WHITECHAIN_FACILITATOR_KEY. It is a hot key separate from the settlement operator, funded with WBT for gas only. It has no role in the escrow, so a leak costs at most its WBT. Every payment it submits is still credited to the wallet that signed it. It cannot recover a front-run authorization; that takes the operator or the owner. The operator's bounds (caps, registered sellers, the fee cap, the circuit breaker) are unchanged.

Settlement

The API meters each request in micro-units (µITC) and groups unsettled charges into batches. A buyer is settled once their unsettled usage reaches 0.10 ITC or their oldest charge reaches the maximum age, whichever comes first (the hosted testnet demo uses 2 minutes so reviewers see settlement quickly; the default and the mainnet plan is 1 hour), and at once when a withdrawal, a lower cap or the cap's expiry is near. Each batch is one settle transaction:

checks the contract performs
settle(bytes32 batchId, Line[] lines)   // operator only
Line { buyer, seller, sellerAmount, fee }

require  seller is registered
require  fee * 10_000 <= sellerAmount * maxFeeBps     // maxFeeBps immutable, <= 1000 (10%)
require  block.timestamp <= buyer.expiry
require  buyer.spent + sellerAmount + fee <= buyer.cap
require  buyer.deposit >= total for the buyer
require  settled in this window + batch total <= settleLimit   // circuit breaker
require  batchId not used before                       // idempotent
// any failing line reverts the whole batch

Effects: your deposit is debited, the seller's earnings credited and the fee accrued, with LineSettled and BatchSettled events. Your own escrow events, with explorer links, are on your dashboard; market-wide totals are on On-chain metrics.

Finality

Whitechain is an OP Stack L2. A settlement is first included by the sequencer (unsafe, a soft confirmation), becomes safe once its batch is posted to Ethereum (typically within about 30 minutes), and finalized once that Ethereum block is final (about 13 minutes later). Only finalized is irreversible; "pending finality" means included but not yet finalized.

The site shows each settlement as pending finality until its block is at or below the chain's finalized head, then as finalized.

Who pays gas

  • Buyers: approve and depositAndSetCap, and withdrawals.
  • Sellers: registerSeller and withdrawEarnings.
  • The operator: every settle batch. Buyers do not pay gas per request.
  • The facilitator key (v0.3): every x402 payment (depositWithAuthorization) and every sponsored testnet faucet claim. An agent paying with x402 needs no WBT at all until it wants to withdraw. A relayed depositWithPermit is paid for by whoever relays it.

Gas on an OP Stack chain includes a small L1 data fee. On testnet it is all test WBT.

What is public

Everything in the escrow is public chain data. LineSettled names the buyer and seller addresses of every line with its amounts, buyerState and sellerState show any account's deposit, cap, spent and earnings, and SellerRegistered links a seller to its payout address. Anyone can list sellers and their volume, and a buyer can match its own requests to the seller that served them. See On-chain visibility.

x402 payments are public too: each one is a DepositedWithAuthorization event naming the paying wallet, and its settlement lines are ordinary LineSettled events.

Withdrawals

requestWithdraw starts a fixed delay (immutable per deployment; see the live value above). Usage you already consumed can still settle during the delay, which is what lets the API serve requests before they settle. After the delay, executeWithdraw returns the funds. The contract is designed so withdrawals keep working while deposits and settlement are paused.

See Security & trust for what this does and does not protect against.