Skip to main content

Payments Architecture

Every x402Flex settlement must route Client → X402FlexRouter → X402FlexRegistry. Direct contract calls are blocked so a single PaymentSettledV2 schema powers analytics, compliance, and MCP agents.

Router Flow

  1. Client (wallet, POS, agent, or MCP server) creates a PaymentIntent + optional FlexWitness using @pepaylabs/bnbpay.
  2. X402FlexRouter validates the schemeId (Permit2, EIP-2612, EIP-3009, push, AA4337, etc.) and moves funds into custody. Pull schemes enforce exact balance deltas; fee-on-transfer tokens are unsupported.
  3. Router calls X402FlexRegistry.settleFromRouter(intent, schemeId, referenceData).
  4. Registry transfers funds to the merchant/collector, applies fees/allowlists, and emits PaymentSettledV2.

x402Flex router flow diagram

Canonical Event

event PaymentSettledV2(
bytes32 indexed paymentId,
address indexed payer,
address indexed merchant,
address token,
uint256 amount,
uint256 feeAmount,
bytes32 schemeId,
string referenceData,
bytes32 referenceHash,
bytes32 resourceId,
uint256 timestamp
);
FieldDescription
paymentIdDeterministic keccak derived alongside resourceId via createFlexIntent.
payerWallet or contract that ultimately funded the router transfer.
merchantSettlement recipient (can be router collector).
schemeIdkeccak256("exact:evm:permit2"), keccak256("push:evm:direct"), etc.
referenceDataHuman-readable reference string (invoice/session tags appended).
resourceIdInvoice fingerprint; index this when correlating HTTP 402 challenges.
referenceHashkeccak256(referenceData) after tags are applied.
timestampBlock timestamp logged by registry for off-chain SLAs.

PaymentId binding

paymentId is bound on-chain to the intent terms:

paymentId = keccak256(abi.encode(
PAYMENT_ID_TYPEHASH,
token,
amount,
deadline,
resourceId,
referenceHash,
nonce
))

Always generate a unique per-invoice nonce and persist it alongside paymentId.

SDK Helpers

import { createFlexIntent, buildPaymentTransaction, decodePaymentSettledEvent, X402FlexRouter__factory } from '@pepaylabs/bnbpay';
import { ethers } from 'ethers';

const intent = createFlexIntent({
merchant: '0xMerchant',
token: ethers.ZeroAddress,
amount: ethers.parseEther('0.25'),
chainId: 56,
referenceId: 'order-402-045',
});

const tx = await buildPaymentTransaction(provider, {
recipient: intent.intent.merchant,
chainId: 56,
token: intent.intent.token,
referenceId: intent.referenceId,
}, intent.intent.amount, 'contract', ROUTER_ADDRESS, {
paymentId: intent.paymentId,
resourceId: intent.resourceId,
schemeId: intent.schemeId,
});

const receipt = await signer.sendTransaction(tx);
const settlement = await decodePaymentSettledEvent(receipt.logs[0]);

const router = X402FlexRouter__factory.connect(ROUTER_ADDRESS, signer);
const router = X402FlexRouter__factory.connect(ROUTER_ADDRESS, signer);
await router.depositAndSettleNative(
intent.intent,
intent.witness,
ethers.ZeroBytes,
intent.referenceId,
{ value: intent.intent.amount }
);

Event Indexing

import { decodePaymentSettledEvent } from '@pepaylabs/bnbpay';

provider.on({ address: PAYMENT_REGISTRY_ADDRESS }, (log) => {
const payment = decodePaymentSettledEvent(log);
console.log('invoice paid', payment.paymentId, payment.resourceId, payment.schemeId);
});
  • Store both paymentId and resourceId so HTTP 402 flows, MCP agents, and merchant dashboards can correlate states.
  • schemeId and referenceData let you differentiate Permit2 pulls, AA pushes, MCP-supplied metadata, and agent-to-agent transfers.
  • Use bnbpay-api or your own indexer to fan events into analytics and CRM systems.

Best Practices

  • Derive references deterministically; never reuse paymentId or resourceId.
  • Keep the nonce that produced the invoice’s paymentId; reuse will revert.
  • For EIP-3009, derive authNonce from the intent hash and router address to prevent replay across intents.
  • Fee-on-transfer / deflationary tokens are rejected by the router’s balance-delta checks for Permit2/EIP-2612/EIP-3009.
  • Apply token allowlists + fee caps from X402FlexRegistry even when building off-chain orchestrators.
  • For AA/push flows, ensure the router address is granted minimal permissions (collector role only).
  • Capture gas data + txHash in your telemetry to debug agent-to-agent payments.