l402-el2
New L402, ported from Lightning to Ethereum Layer 2

Let AI agents pay for tools in Bitcoin — without a transaction per call.

L402-EL2 lets an AI agent pay an MCP server for every tool call in wrapped BTC (cbBTC, WBTC, tBTC). Payments travel off-chain as EIP-712 signatures on a prefunded channel: the blockchain is touched only twice — when the channel opens and when the provider collects.

$ npm install && npm run compile:contracts && npm run example:e2e

No chain, no account, no configuration: the end-to-end demo runs on an in-process EVM.

agent ⇄ mcp.example.com
  1. POST/mcp · tools/call search_web
  2. 402Payment Required
  3. WWW-Authenticate: L402 macaroon="…", invoice="…", payment_request="…"
  4. EIP-712 voucher · cumulativeAmount 250 · 0 gas · <1 ms
  5. POSTAuthorization: L402 <macaroon>:<voucher>
  6. 200result · verified in ~5 ms
  7. … thousands of calls, 0 transactions …
  8. settleBatch() · 1 L2 tx collects everything
~$0.05 gas per day vs ~$100 with one tx per call
<10 ms latency per call vs 1–2 s waiting for a block
~30 tx per user / month vs ~600,000 on-chain
120 tests 21 escrow · 81 TypeScript · 18 Foundry

Assumptions from the README: 20,000 calls per day, $0.005 of gas per L2 transaction.

The problem

An agent that calls a tool a thousand times a day can't send a thousand transactions.

Even on a cheap Layer 2 it would pay more in gas than for the service, and wait for a block on every call.

Pure on-chain

One transaction per call

  • ✕ Gas on every call, often more than the price of the tool
  • ✕ 1–2 seconds of block wait before each answer
  • ✕ Throughput bounded by the L2's blocks
  • ✕ Every call is a public transaction
L402-EL2

One channel, thousands of signatures

  • ✓ Each call costs one local signature: zero gas, under a millisecond
  • ✓ The server verifies it in memory in a few milliseconds
  • ✓ Scales with the server's CPU, not with the chain
  • ✓ The chain sees two transactions: open and collect
How it works

The L402 handshake, with a signature instead of a Lightning preimage.

L402 was born on the Lightning Network: the server answers 402 Payment Required with a credential and an invoice, the client pays and retries with a proof. Here the proof is a signed voucher on a payment channel — verifiable in memory, not a transaction hash to look up on-chain.

  1. 1on-chain · 1 tx

    Open a channel

    The agent deposits wrapped BTC in the L402Escrow contract in favour of the provider — for example openChannel(0.001 cbBTC). With ERC-4337, approve and open are one atomic operation.

  2. 2off-chain

    Call a tool, receive a 402 challenge

    The server answers with a macaroon and a payment request that already contains the exact cumulative amount to sign — the client needs no RPC call.

  3. 3local · 0 gas · <1 ms

    Sign a voucher

    The agent signs an EIP-712 voucher: “in total you may take up to N”. Its SpendingPolicy checks price, budget, provider, escrow and chain first.

  4. 4verified in ~5 ms

    Retry with the credentials

    Authorization: L402 <macaroon>:<voucher>. The Gatekeeper checks macaroon, signature, channel and amount, advances the voucher store atomically, and returns the result. Next calls pay in a single round trip.

  5. 5on-chain · 1 tx

    Settle in batch

    The settler turns the latest voucher of each channel into one settleBatch() transaction — most urgent deadlines first, never below a profitable threshold.

Core ideas

Four building blocks make it safe to pay off-chain.

The payment channel

A deposit by the payer, locked in the contract for one provider and one token. Money flows one way. The id is computable offline:

channelId = keccak256(abi.encode(payer, provider, token))

The payer can take back what wasn't spent — after a 24-hour window that lets the provider collect first.

The cumulative voucher

Each signature authorizes a total, not a delta. The provider only keeps the latest one.

#1 · 250 #2 · 500 #3 · 750 kept
  • No replay — the contract accepts only strictly increasing amounts.
  • Stateless-friendly — one Redis row per channel.
  • Bounded loss — at most the latest amount the agent signed.

Macaroons, not JWTs

A macaroon can be attenuated by whoever holds it, without the server's key. An agent can hand a sub-agent a token valid “only for search_web, for 60 seconds” — and the server still verifies it.

expires_atpayerchannel_idchain_idtokenmax_cumulativetoolservice

Caveats are verified fail-closed: an unknown key means the request is refused.

Session keys with an on-chain cap

“The agent may spend up to 0.0005 cbBTC” isn't a client rule — it is a line in the contract:

escrow.authorizeSigner(sessionKey, maxCumulative, validUntil);

If the key leaks, the loss is bounded. Delegations can be widened at any time, but narrowing or revoking takes effect after the same 24-hour grace period.

Try it here

Watch a channel work: calls pile up, transactions don't.

A browser simulation using the numbers of examples/e2e.ts: a 1,000,000-sat channel, search_web at 250 sat, heavy_analysis at 2,000 sat.

Paid calls0
On-chain transactions1channel open + settlements
Latest voucher (cumulative)0sat signed in total
Collected by provider00 sat waiting for the settler
Channel balance
0 of 1,000,000 sat spent
Gas so far ($0.005 / tx)
L402-EL2$0.005
One tx per call$0.000
Wire lognewest first
  1. tx approve + openChannel(provider, cbBTC, 1,000,000 sat, 30 days)
What's inside

A TypeScript monorepo and one Solidity contract.

core is shared by everyone; the server and the settler share the same voucher store (Redis).

packages/contracts

L402Escrow.sol

Unidirectional payment channel with cumulative EIP-712 vouchers, on-chain session keys and batch settlement.

packages/core

Shared primitives

Macaroons, EIP-712 types and digests, L402 header encoding, ABI and float-free unit conversions.

packages/server

Gatekeeper

Payment verification, HTTP and MCP middlewares, voucher stores (memory or Redis with atomic Lua scripts).

packages/client

Agent wallet

EOA, ERC-4337 or session-key wallet, a self-paying fetch interceptor and a paid MCP client.

packages/settler

Collector

A separate process that turns accumulated vouchers into L2 transactions. Ships with a CLI.

examples/

Runnable demos

A paid MCP server, a paying agent and an end-to-end run on an in-process EVM.

ActorRunsTouches the chain?
Agentclient, inside the agent's processOnly to open / top up the channel and register the session key
MCP serverserver (Express + MCP SDK)Reads only, with a short-TTL cache
Settlersettler, separate process + CLIYes — sends settleBatch
ContractL402Escrow on the L2—
Get started

From a local demo to production in five steps.

No chain, no account, no configuration. You'll see the contracts deployed on an in-process EVM, a paid MCP server start, an agent open a channel with a session key, pay four calls off-chain, and everything collected in a single transaction.

bash
npm install
npm run compile:contracts
npm run example:e2e

# tests
npm run test:contracts   # 21 escrow tests on a real in-process EVM
npx vitest run           # 81 tests, incl. attack scenarios
Security by design

Two lines of defence on spending, and a contract that can't freeze your funds.

A provider can't overcharge

It can collect at most the latest amount the payer signed.

No double spending

The same voucher can't be spent twice; parallel requests are serialized by an atomic store.

No interception

Only the channel's provider can settle; EIP-712 domains prevent cross-chain replay.

Bounded key leaks

A compromised session key costs at most its on-chain cap per channel.

No rug-pull on vouchers

Closes, revocations and narrowed delegations wait 24 hours, so the provider can collect.

Pausing doesn't freeze funds

pause() only blocks new deposits; settlements and withdrawals always work.

Why is the proof a signature and not a transaction hash?

It is the design choice of the protocol. A signature is verified in memory in milliseconds; a transaction hash would have to be looked up on-chain, bringing back the block wait and the RPC dependency that L402-EL2 exists to remove.

What stops the server from being abused as an RPC amplifier?

Challenges never read the chain — they only use cached state — and the on-chain read cache is bounded to 10,000 entries per kind, so random X-L402-Payer headers can't trigger RPC calls or grow memory.

What happens if the agent restarts before the provider settles?

Each challenge carries the last voucher the server accepted. The client verifies it's settleable (signed by the payer or a key it delegated), adopts it, and pays only the new increment — instead of refusing every payment.

How are smart accounts handled?

ERC-4337 smart accounts are verified through ERC-1271 (SignatureChecker) both on-chain and on the server. ERC-6492 signatures of not-yet-deployed accounts are refused, because the escrow couldn't verify them at settlement.

Not audited yet — testnet only

The contract is tested but has not been externally reviewed. Before mainnet: an audit, third-party caveats, the type: "tx" fallback, rate limiting tuned to your traffic, and idle MCP session cleanup.

Built on standards

No proprietary rails.

L402HTTP 402 scheme + macaroons
EIP-712Vouchers and delegations signed off-chain
ERC-4337Agent smart account, atomic batching
ERC-1271Smart-account signature verification
ERC-2612 / 3009Single-signature deposits
MCPThe tools exposed to the agent

Tokens: cbBTC · Base WBTC · Arbitrum / Optimism tBTC

Documentation

Everything above, in depth.