VetoVetoDocs
Concepts

Rails

The pluggable settlement interface — x402 (HTTP-402 + stablecoin), mock (offline tests), and the card stub. Bring your own receiving destination per rail; Veto never holds funds.

A rail is how money actually moves. It's a pluggable interface: quote() turns a cart total into a self-describing PaymentRequirement; settle() verifies and captures.

interface Rail {
  name: 'x402' | 'card' | 'mock';
  quote(input): PaymentRequirement | Promise<PaymentRequirement>;
  settle(input): SettlementResult | Promise<SettlementResult>;
}

When you omit rails, the SDK defaults to [x402, mock]: x402 is the v1 first-class rail and mock keeps the whole flow runnable offline.

v1 rails

RailStatusUse
x402First-classHTTP-402 + stablecoin (USDC). Structural EIP-3009 verification in core; on-chain settlement via an injected facilitator (see below).
mockFirst-classOffline structural validation; always "captures". For tests & demos. Never moves money.
cardTyped stubReturns RAIL_NOT_IMPLEMENTED. Stripe Connect / ACP delegated token is the planned, non-custodial path.

Non-custodial receiving

You bring your own receiving destination per rail — Veto never holds funds:

receiving: {
  x402: { chain: 'base', address: '0xYourAddress', asset: 'USDC' },
  mock: { account: 'acct_test' },
}

x402 settlement (the injected seam)

The zero-dependency checkout core never imports viem / @coinbase/x402. By default the x402 rail does structural EIP-3009 verification only. To settle on-chain, you inject a Facilitator (e.g. the viem/CDP one wired in apps/api):

import { createCheckout, x402Rail } from '@veto-protocol/checkout';

const checkout = createCheckout({
  // …
  rails: [x402Rail({ facilitator: myFacilitator })],
});

When a real facilitator is present, the on-chain txHash it returns is the authoritative settlement reference. A facilitator returns ok:false with reason codes (SIGNER_MISMATCH, INSUFFICIENT_FUNDS, NONCE_ALREADY_USED, …) rather than throwing.

See Go live with x402 + CDP for a full walkthrough.