ResourcesPayment Schemes

Payment Schemes

T402 supports multiple payment schemes to accommodate different billing models and use cases.

What is a Scheme?

A scheme defines how payments are authorized, verified, and settled. Each scheme specifies:

  • Authorization format (how clients sign payment requests)
  • Verification requirements (what the facilitator checks)
  • Settlement mechanism (how funds are transferred on-chain)

Available Schemes

SchemeDescriptionUse Case
exactFixed amount paymentsOne-time purchases, subscriptions
uptoUsage-based billingAI tokens, API calls, streaming

Scheme Comparison

Featureexactupto
Amount fieldamountmaxAmount
SettlementFixed amountVariable (≤ max)
Client riskExact cost knownMaximum exposure known
Server complexityLowerHigher (usage tracking)
EVM implementationEIP-3009EIP-2612 + Router

Use exact when:

  • Price is known before the request
  • Simple pay-per-call model
  • Fixed content or service pricing
  • Lower implementation overhead desired

Use upto when:

  • Cost depends on request outcome
  • Usage-based or metered billing
  • Variable-length operations (streaming, generation)
  • Partial refunds may be needed

Network Support

Networkexactupto
EVM (Base, Ethereum, Arbitrum, etc.)✅ EIP-3009✅ EIP-2612
Solana✅ SPL Transfer
TON✅ Jetton Transfer
TRON✅ TRC-20 Transfer
NEAR✅ NEP-141 Transfer
Aptos✅ Fungible Asset Transfer
Tezos✅ FA2 Transfer
Polkadot✅ assets.transfer
Stacks✅ SIP-010 Transfer
Cosmos (Noble)✅ Bank MsgSend

SDK Support

All T402 SDKs support both schemes:

// TypeScript - exact scheme
import { ExactEvmScheme } from '@t402/evm';
 
// TypeScript - upto scheme
import { UptoEvmScheme } from '@t402/evm';
# Python - exact scheme
from t402.schemes import ExactEvmClientScheme
 
# Python - upto scheme
from t402.schemes import UptoEvmClientScheme
// Go - exact scheme
import "github.com/t402-io/t402/sdks/go/mechanisms/evm/exact"
 
// Go - upto scheme
import "github.com/t402-io/t402/sdks/go/mechanisms/evm/upto"
// Java - exact scheme
import io.t402.model.ExactSchemePayload;
 
// Java - upto scheme
import io.t402.schemes.upto.UptoPaymentRequirements;
import io.t402.schemes.evm.upto.UptoEIP2612Payload;

Exact Scheme

The exact scheme enables fixed-amount payments where the cost is known upfront. The client signs an authorization for a specific amount, and the facilitator executes the transfer on-chain.

Overview

The exact scheme is the primary payment scheme in T402. The client pre-signs a payment authorization off-chain, which the facilitator later submits to the blockchain for settlement. The client never pays gas.

Use Cases

  • API Monetization: Fixed price per API call ($0.01/request)
  • Content Purchases: One-time payment for articles, images, datasets
  • Subscription Tokens: Pre-pay for access periods
  • Data Marketplace: Fixed price per data record
  • IoT Micropayments: Sensor readings at known cost

Payment Requirements

The server specifies the exact amount required:

{
  "scheme": "exact",
  "network": "eip155:8453",
  "amount": "10000",
  "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
  "payTo": "0xMerchantAddress...",
  "maxTimeoutSeconds": 300,
  "extra": {
    "name": "USD Coin",
    "version": "2"
  }
}

Fields

FieldTypeRequiredDescription
schemestringYesAlways "exact"
networkstringYesCAIP-2 network ID (e.g., "eip155:8453")
amountstringYesExact payment amount (smallest units)
assetstringYesToken contract address or identifier
payTostringYesRecipient address
maxTimeoutSecondsnumberYesAuthorization validity period in seconds
extra.namestringEVM onlyToken EIP-712 domain name
extra.versionstringEVM onlyToken EIP-712 domain version
extra.feePayerstringSVM onlyFacilitator address for fee coverage

EVM Implementation (EIP-3009)

On EVM chains, the exact scheme uses EIP-3009 TransferWithAuthorization — a gasless transfer standard where the token holder signs a transfer authorization off-chain and a relayer executes it.

How It Works

  1. Client signs an EIP-712 typed data message authorizing a specific transfer
  2. Facilitator calls transferWithAuthorization(from, to, value, validAfter, validBefore, nonce, v, r, s) on the token contract
  3. The token contract verifies the signature and executes the transfer
  4. Client pays zero gas — the facilitator covers transaction costs

EIP-712 Typed Data

const domain = {
  name: "USD Coin",     // Token name (from extra.name)
  version: "2",         // Token version (from extra.version)
  chainId: 8453,        // From network ID
  verifyingContract: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
};
 
const types = {
  TransferWithAuthorization: [
    { name: "from", type: "address" },
    { name: "to", type: "address" },
    { name: "value", type: "uint256" },
    { name: "validAfter", type: "uint256" },
    { name: "validBefore", type: "uint256" },
    { name: "nonce", type: "bytes32" }
  ]
};
 
const message = {
  from: "0xClientAddress...",
  to: "0xMerchantAddress...",
  value: "10000",                    // Exact amount
  validAfter: "1700000000",          // Current time - 600s
  validBefore: "1700000300",         // Current time + maxTimeoutSeconds
  nonce: "0x1234567890abcdef..."     // Random 32-byte nonce
};

Payment Payload

{
  "t402Version": 2,
  "accepted": {
    "scheme": "exact",
    "network": "eip155:8453",
    "amount": "10000",
    "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
    "payTo": "0xMerchantAddress..."
  },
  "payload": {
    "signature": "0xabcdef1234...",
    "authorization": {
      "from": "0xClientAddress...",
      "to": "0xMerchantAddress...",
      "value": "10000",
      "validAfter": "1700000000",
      "validBefore": "1700000300",
      "nonce": "0x1234567890abcdef..."
    }
  }
}

Supported EVM Tokens

TokenEIP-3009Networks
USDT0Ethereum, Arbitrum, Ink, Berachain, Unichain
USDCEthereum, Base, Arbitrum, Optimism

EIP-3009 is supported by USDT0 (LayerZero OFT) and USDC. Both support gasless transferWithAuthorization. The extra.name and extra.version fields must match the token’s EIP-712 domain exactly.

Smart Wallet Support (EIP-6492)

The exact scheme supports smart contract wallets (Safe, ERC-4337 accounts) via EIP-6492 signature validation:

// Smart wallet signatures use EIP-6492 wrapper
// The facilitator automatically detects and validates:
// 1. Standard EOA signatures (ECDSA)
// 2. EIP-1271 smart contract signatures (deployed wallets)
// 3. EIP-6492 signatures (undeployed counterfactual wallets)
⚠️

For undeployed smart wallets, the facilitator must deploy the wallet before executing transferWithAuthorization. Set deployERC4337WithEIP6492: true in facilitator config.

Non-EVM Implementations

Non-EVM chains use the exact-direct variant where the client executes the transfer directly on-chain and provides the transaction hash as proof.

Exact vs Exact-Direct

Aspectexact (EVM)exact-direct (Non-EVM)
Who executes transferFacilitatorClient
Client pays gasNoYes
Authorization methodOff-chain signatureOn-chain transaction
ProofSigned EIP-712 messageTransaction hash
SettlementFacilitator calls contractAlready settled
Facilitator roleExecute + verifyVerify only

Solana (SPL Token Transfer)

{
  "scheme": "exact",
  "network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
  "amount": "10000",
  "asset": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
  "payTo": "FacilitatorPublicKey...",
  "extra": {
    "feePayer": "FacilitatorPublicKey..."
  }
}

The client creates a partially-signed TransferChecked instruction. The facilitator adds its signature (as fee payer) and submits.

TON (Jetton Transfer)

{
  "scheme": "exact",
  "network": "ton:mainnet",
  "amount": "10000",
  "asset": "EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs",
  "payTo": "EQRecipientAddress...",
  "maxTimeoutSeconds": 300
}

The client signs an external message containing a Jetton transfer operation. The facilitator submits the signed BOC to the TON network.

TRON (TRC-20 Transfer)

{
  "scheme": "exact",
  "network": "tron:mainnet",
  "amount": "10000",
  "asset": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
  "payTo": "TRecipientAddress...",
  "maxTimeoutSeconds": 300
}

The client signs a TRC-20 transfer transaction with reference block bytes for replay protection.

NEAR, Aptos, Tezos, Polkadot, Stacks, Cosmos (Direct Transfer)

These chains use exact-direct — the client executes the transfer and returns the transaction hash:

{
  "payload": {
    "txHash": "abc123...",
    "from": "sender-address",
    "to": "recipient-address",
    "amount": "10000"
  }
}

Verification

The facilitator performs these checks before approving a payment:

CheckDescription
Payload structureAll required fields present and correctly typed
Scheme matchscheme equals "exact"
Network matchPayload network matches requirements
Signature validityEIP-712 signature recovers to from address
Recipient matchto matches payTo in requirements
Time windowvalidAfter ≤ now and validBefore > now
Balance checkClient has sufficient token balance
Amount checkvalue ≥ amount in requirements

Error Codes

CodeDescriptionRecovery
invalid_payload_structureMissing or malformed fieldsCheck payload format
unsupported_schemeScheme is not "exact"Use correct scheme
network_mismatchNetwork doesn’t matchUse correct network
invalid_exact_evm_payload_signatureSignature verification failedRe-sign with correct key
invalid_exact_evm_payload_recipient_mismatchto doesn’t match payToUse address from requirements
invalid_exact_evm_payload_authorization_valid_beforeAuthorization expiredCreate new authorization
invalid_exact_evm_payload_authorization_valid_afterNot yet validWait or adjust validAfter
insufficient_fundsToken balance too lowFund the wallet
invalid_exact_evm_payload_authorization_valueAmount too lowUse amount from requirements

SDK Usage

import { t402Client } from '@t402/fetch'
import { ExactEvmScheme } from '@t402/evm/exact/client'
import { privateKeyToAccount } from 'viem/accounts'
 
// Client: Register scheme and make payment
const client = new t402Client()
const account = privateKeyToAccount('0x...')
 
client.register('eip155:*', new ExactEvmScheme(account))
 
// Automatic 402 handling
const fetchWithPay = wrapFetchWithPayment(fetch, client)
const response = await fetchWithPay('https://api.example.com/data')
// Server: Define requirements
import { paymentMiddleware } from '@t402/express'
import { ExactEvmScheme } from '@t402/evm/exact/server'
 
app.use(paymentMiddleware({
  'GET /api/data': {
    accepts: [{
      scheme: 'exact',
      network: 'eip155:8453',
      price: '$0.01',
      payTo: '0xMerchantAddress...'
    }],
    description: 'Premium API data'
  }
}, server))
// Facilitator: Verify and settle
import { ExactEvmScheme } from '@t402/evm/exact/facilitator'
 
const facilitator = new ExactEvmScheme(facilitatorSigner)
 
const verifyResult = await facilitator.verify(payload, requirements)
// { isValid: true, payer: "0x...", network: "eip155:8453" }
 
const settleResult = await facilitator.settle(payload, requirements)
// { success: true, transaction: "0xabc...", blockNumber: 12345 }

Security Considerations

Replay Protection

ChainMechanism
EVMRandom 32-byte nonce (unique per authorization)
SolanaRecent blockhash (expires after ~2 minutes)
TONWallet seqno + query ID
TRONReference block bytes/hash + expiration
NEAR/Aptos/Tezos/Polkadot/Stacks/CosmosTransaction hash uniqueness

Time Window

The validAfter and validBefore fields create a bounded time window:

validAfter = now - 600s    (10 minute grace for clock skew)
validBefore = now + maxTimeoutSeconds
⚠️

Set maxTimeoutSeconds appropriately for your use case. Too short may cause legitimate payments to expire. Too long increases the window for potential issues. 300 seconds (5 minutes) is recommended for most API calls.

Amount Validation

  • The facilitator verifies value >= amount (not exact equality)
  • This allows clients to slightly overpay without rejection
  • Settlement always transfers the exact value specified in the authorization

Exact Network Support

NetworkScheme VariantSignatureToken Standard
EVM (25 chains)exactEIP-712EIP-3009
SolanaexactEd25519SPL TransferChecked
TONexactEd25519Jetton transfer
TRONexactsecp256k1TRC-20 transfer
NEARexact-directEd25519NEP-141 ft_transfer
Aptosexact-directEd25519FA transfer
Tezosexact-directEd25519/secp256k1FA2 transfer
Polkadotexact-directSr25519assets.transfer
Stacksexact-directsecp256k1SIP-010 transfer
Cosmos (Noble)exact-directsecp256k1Bank MsgSend

Troubleshooting

“Signature verification failed”

  • Ensure extra.name and extra.version match the token’s EIP-712 domain
  • USDT0 uses name: "USDT0", version: "1"
  • USDC uses name: "USD Coin", version: "2"

“Authorization expired”

  • validBefore has passed — create a new payment payload
  • Check server/client clock synchronization

“Insufficient funds”

  • Client wallet needs sufficient token balance
  • Balance is checked at verification time, not signing time

“Recipient mismatch”

  • The to in authorization must exactly match payTo in requirements
  • Check for address checksum differences

“Nonce already used”

  • Each nonce can only be used once per (from, token) pair
  • The SDK generates random 32-byte nonces to avoid collisions

Up-To Scheme

The upto scheme enables usage-based billing by authorizing transfer of up to a maximum amount, with the actual settlement determined by usage.

Overview

Unlike the exact scheme where the full amount is known upfront, upto allows clients to authorize a maximum payment while only being charged for what they actually use.

Use Cases

  • LLM Token Generation: Authorize $1.00, pay $0.15 for 1,500 tokens
  • Metered API Usage: Pay per request or data volume
  • Streaming Services: Pay per minute/second consumed
  • Compute Resources: Pay per CPU/GPU second

Payment Requirements

The server specifies maximum amount and optional billing configuration:

{
  "scheme": "upto",
  "network": "eip155:8453",
  "maxAmount": "1000000",
  "minAmount": "10000",
  "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
  "payTo": "0x...",
  "maxTimeoutSeconds": 300,
  "extra": {
    "unit": "token",
    "unitPrice": "100",
    "name": "USD Coin",
    "version": "2"
  }
}

Fields

FieldTypeRequiredDescription
schemestringYesAlways "upto"
networkstringYesCAIP-2 network ID (e.g., "eip155:8453")
maxAmountstringYesMaximum authorized amount (smallest units)
minAmountstringNoMinimum settlement amount (prevents dust)
assetstringYesToken contract address
payTostringYesRecipient address
maxTimeoutSecondsnumberYesAuthorization validity period
extra.unitstringNoBilling unit ("token", "request", "second", etc.)
extra.unitPricestringNoPrice per unit

Supported Units

UnitDescriptionExample
tokenAI/LLM tokens1500 tokens @ $0.0001
requestAPI requests100 requests @ $0.01
secondTime-based60 seconds @ $0.001
minuteTime-based5 minutes @ $0.05
byteData volume1MB @ $0.001
kbKilobytes1024 KB
mbMegabytes10 MB

EVM Implementation

On EVM chains, the upto scheme uses EIP-2612 Permit for gasless authorization.

Payment Payload

{
  "t402Version": 2,
  "resource": {
    "url": "https://api.example.com/llm/generate",
    "description": "LLM token generation"
  },
  "accepted": {
    "scheme": "upto",
    "network": "eip155:8453",
    "maxAmount": "1000000",
    "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
    "payTo": "0x...",
    "maxTimeoutSeconds": 300,
    "extra": {
      "name": "USD Coin",
      "version": "2",
      "routerAddress": "0x..."
    }
  },
  "payload": {
    "signature": {
      "v": 28,
      "r": "0x1234567890abcdef...",
      "s": "0xfedcba0987654321..."
    },
    "authorization": {
      "owner": "0x...",
      "spender": "0x...",
      "value": "1000000",
      "deadline": "1740675689",
      "nonce": 5
    },
    "paymentNonce": "0xf374..."
  }
}

EIP-712 Domain

The permit signature uses EIP-712 typed data:

const domain = {
  name: "USD Coin",      // Token name
  version: "2",          // Token version
  chainId: 8453,         // Chain ID
  verifyingContract: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
};
 
const types = {
  Permit: [
    { name: "owner", type: "address" },
    { name: "spender", type: "address" },
    { name: "value", type: "uint256" },
    { name: "nonce", type: "uint256" },
    { name: "deadline", type: "uint256" }
  ]
};

The name and version fields in extra must match the token’s EIP-712 domain. These vary by token - USDC uses "USD Coin" and "2".

Supported Tokens

TokenEIP-2612Networks
USDCEthereum, Base, Arbitrum, Polygon
USDTNot supported (no permit)
DAIEthereum, Base, Arbitrum
⚠️

USDT does not support EIP-2612 Permit. For USDT payments, use the exact scheme instead.

Settlement

Settlement Request

After processing the request, the server sends the actual usage amount:

{
  "paymentPayload": "...",
  "paymentRequirements": {},
  "settleAmount": "150000",
  "usageDetails": {
    "unitsConsumed": 1500,
    "unitPrice": "100",
    "unitType": "token",
    "startTime": 1740672000,
    "endTime": 1740675600
  }
}

Settlement Rules

  1. settleAmount must be <= maxAmount
  2. settleAmount must be >= minAmount (if specified)
  3. If settleAmount is 0, no transfer occurs (nonce still consumed)
  4. Facilitator transfers exactly settleAmount, not maxAmount

Settlement Response

{
  "success": true,
  "transactionHash": "0x...",
  "settledAmount": "150000",
  "maxAmount": "1000000",
  "blockNumber": 12345678,
  "gasUsed": "85000"
}

SDK Usage

import { UptoEvmScheme, UptoPaymentRequirements } from '@t402/evm';
 
// Client: Create payment
const scheme = UptoEvmScheme.client({
  signer: walletSigner,
  network: 'eip155:8453'
});
 
const payload = await scheme.createPaymentPayload(requirements);
 
// Server: Define requirements
const requirements: UptoPaymentRequirements = {
  scheme: 'upto',
  network: 'eip155:8453',
  maxAmount: '1000000',
  minAmount: '10000',
  asset: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
  payTo: '0x...',
  maxTimeoutSeconds: 300,
  extra: {
    unit: 'token',
    unitPrice: '100',
    name: 'USD Coin',
    version: '2'
  }
};
 
// Server: Settle with actual usage
const settlement = await facilitator.settle({
  payload,
  requirements,
  settleAmount: '150000',
  usageDetails: {
    unitsConsumed: 1500,
    unitPrice: '100',
    unitType: 'token'
  }
});

Security Considerations

Client Protection

  • Client only authorizes maximum exposure (maxAmount)
  • Actual charge determined by server’s settlement request
  • Monitor settlement amounts vs. expected usage
  • Set appropriate maxAmount limits

Server Accountability

  • Accurately report usage in settlement requests
  • Settlement amounts should be auditable
  • Log usageDetails for dispute resolution
  • Provide transparent pricing (unit/unitPrice)

Facilitator Role

  • Must not settle more than authorized maxAmount
  • Must verify settlement amount is within bounds
  • Provides settlement receipts with details
  • Maintains audit trail

Partial Settlement

If a request is interrupted mid-processing:

  1. Server calculates partial usage
  2. Server settles for partial amount
  3. Client receives partial response with settlement header

Example: Client authorizes $1.00, request generates 500 tokens before timeout, server settles for $0.05.

Troubleshooting

“Permit signature invalid”

  • Check that name and version match the token’s EIP-712 domain
  • Verify the deadline hasn’t passed
  • Ensure nonce matches client’s current permit nonce

“Settlement amount exceeds maximum”

  • settleAmount must be <= maxAmount
  • Check for unit conversion errors

“Token does not support permit”

  • Use a token with EIP-2612 support (USDC, DAI)
  • For USDT, use exact scheme instead

Debug Logging

Enable debug logs to trace the payment flow:

// TypeScript
process.env.T402_DEBUG = 'true';
# Python
import logging
logging.getLogger('t402').setLevel(logging.DEBUG)