Reliable paid or quota-limited tool calls.

Tollgate is an open-source runtime for MCP and agent tools. It handles fallback, idempotent retries, metering, recovery, and execution traces so retries and edge cases do not double-charge or hard-fail.

Rail-agnostic: Prepaid Stripe x402 · EVM x402 · SolanaNew MPP

Run the repo demo

Clone the repository to run the local demo. The first-run path uses the in-memory ledger and does not require Stripe, x402, MPP, wallets, webhooks, hosted APIs, or environment variables.

git clone https://github.com/niceberginc/tollgate.git
cd tollgate
npm install
npm run build
npm run example:local

Runtime behavior

Tollgate wraps the call boundary where payment, quota, retries, execution, and recovery meet.

Fallback

Return a degraded result instead of forcing every unpaid call into a hard failure.

Idempotency

Replay completed results for duplicate request keys without charging twice.

Integer money

Use minor-unit accounting through `Money`, `usd()`, and ledger adapters.

Metering

Record usage and optionally price after execution based on actual metrics.

Recovery

Credit back prepaid calls when execution fails — and retry, queue, and reconcile uncertain on-chain settlements. More →

Traces

Inspect decisions, charge status, fallback usage, handler status, and recovery actions.

Minimal usage

Start with `TollGate`, `InMemoryLedger`, and `usd()`; add rail adapters later.

import { TollGate, InMemoryLedger, usd } from "@niceberglabs/tollgate";

const ledger = new InMemoryLedger();
const gate = new TollGate({ publisherKey: "tg_local_demo", ledger });

const search = gate.paidTool({
  name: "premium_search",
  price: usd("0.05"),
  onPaymentFailed: "fallback",
  idempotencyKey: (input) => `premium_search:${input.requestId}`,
  handler: async (input) => ({ tier: "premium", query: input.query }),
  fallback: async (input) => ({ tier: "free", query: input.query }),
});

await ledger.credit("caller-1", usd("1.00"), {
  source: "manual",
  reference: "dev-credit",
});

const result = await search({ query: "vector dbs", requestId: "r1" }, "caller-1");
console.log(result.receipt);

MCP wrapper

Use `createMcpAdapter()` to expose a paid tool through an MCP server. `_meta.tollgate` carries receipt, fallback, and payment-required metadata.

See minimal usage →

import { TollGate, createMcpAdapter, usd } from "@niceberglabs/tollgate";

const gate = new TollGate({ publisherKey: "tg_local_demo" });
const mcp = createMcpAdapter(gate, {
  getCallerId: (_args, extra) => extra?.sessionId ?? "demo-user",
});

mcp.paidTool("premium_search", {
  price: usd("0.05"),
  onPaymentFailed: "fallback",
  inputSchema: { type: "object", properties: { query: { type: "string" } } },
  handler: async (args) => ({ results: [`deep results for ${args.query}`] }),
  fallback: async (args) => ({ results: [`basic result for ${args.query}`] }),
});

Payment rails & networks

Tollgate is rail-agnostic: the runtime — fallback, idempotency, recovery, traces — stays identical while the rail is a pluggable adapter. x402 spans EVM and, now, Solana.

Beta · HTTP 402

x402 on EVM

USDC micropayments via EIP-3009 transferWithAuthorization, validated and settled through a facilitator. Gasless — the facilitator pays gas.

  • Base & Base Sepolia
  • Polygon · Arbitrum · Optimism
  • Ethereum mainnet
Verified on Base mainnet ↗
Beta · SVM exact scheme

x402 on Solana

Gasless: the agent partial-signs an SPL USDC transfer, the facilitator sponsors the fee and settles. Sub-cent, sub-second.

  • Solana mainnet & devnet
  • Facilitators: PayAI, Coinbase CDP
  • Agent holds zero SOL
Verified on Solana mainnet ↗
Fiat & ledger

Stripe · Prepaid · MPP

Top up with Stripe, run on a local prepaid ledger (in-memory / SQLite / D1), or settle through MPP — same runtime, same traces across every rail.

Settlement recovery

On-chain rails settle after execution — and settlement can fail on its own (RPC blip, facilitator timeout) while the payment already verified. Tollgate never lets that payment vanish.

The hard part

No silently lost payments

Verified, executed, but settlement didn't confirm? Most setups drop it. Tollgate recovers it — and never double-settles a tx that already landed.

The loop

retry → queue → reconcile

  • Retry settlement with backoff
  • Durable queue (in-memory or SQLite/D1)
  • On-chain confirmation before dequeue
  • Scheduled reconcileSettlements()
Always visible

Every state traced

`settlement_uncertain`, attempts, queued, and final settlement land in the execution trace — partial failure is explicit, never collapsed into one success bit.

Status

Payment rails are optional adapters. Multi-instance production requires durable idempotency, which is future work.

AreaStatus
Core runtimeDeveloper preview
In-memory ledger and idempotencyLocal development and single-process prototypes
SQLite / D1 ledgerLocal and single-process paths
Stripe test modeValidated with configured test credentials
Stripe productionBeta; validate your webhook and deployment path
x402 — EVM (Base, Polygon, Arbitrum, Optimism, Ethereum)Beta; EIP-712 USDC domain auto-injected, verified on Base mainnet (gasless)
x402 — Solana / SVMBeta; packaged signer + full MCP verify/settle e2e, verified on devnet and mainnet (gasless via x402 facilitators)
Settlement recoveryRetry + durable queue + reconcile + on-chain confirmation; in-memory or SQLite/D1
MPPMocked / spec-path unless verified with real mppx integration