Skip to main content

Payment Flows

All flows settle through X402FlexRouter → X402FlexRegistry, so PaymentSettledV2 is always the source of truth.

Patterns

  • Push (native): payer submits depositAndSettleNative.
  • Push (ERC-20): payer submits depositAndSettleToken.
  • Push (AA4337): UserOp calls the same deposit entry points with optional witness.
  • Pull (Permit2): relayer submits payWithPermit2 using witness + Permit2 signature.
  • Pull (EIP-2612): relayer submits payWithEIP2612 using permit signature (or skips permit if allowance already sufficient).
  • Pull (EIP-3009): relayer submits payWithEIP3009 using ReceiveWithAuthorization with intent-derived nonce.
  • Session (any scheme): add SessionSpendAuth + SessionContext.
  • Relay: /relay/payment, /relay/permit2/bundle, /relay/session/* endpoints.

Push (native / ERC-20)

Use this when the payer can submit a transaction directly.

import { createFlexIntent, sendRouterPayment, RpcTransport } from '@pepaylabs/bnbpay';
import { ethers } from 'ethers';

const intent = createFlexIntent({
merchant: MERCHANT,
token: ethers.ZeroAddress,
amount: ethers.parseEther('0.5'),
chainId: 56,
referenceId: 'order-125',
scheme: 'push:evm:direct',
});

await sendRouterPayment({
transport: new RpcTransport(signer),
routerAddress: ROUTER_ADDRESS,
intent: intent.intent,
witness: intent.witness,
reference: intent.referenceId,
});

AA4337 uses the same router entry points; the UserOp target should be depositAndSettleNative/Token with the same PaymentIntent + optional FlexWitness.

Pull (Permit2 / EIP-2612 / EIP-3009)

Use this when the payer signs a permit + witness and a relayer submits the transaction.

import { hashPaymentIntent, getFlexSchemeId } from '@pepaylabs/bnbpay';

const witness = {
schemeId: getFlexSchemeId('exact:evm:permit2'),
intentHash: hashPaymentIntent(intent),
payer: payerAddress,
salt: saltHex,
};

// Sign witness + permit, then call router.payWithPermit2(...)
  • Use permit2WitnessTypeString from /networks when constructing Permit2 typed data.
  • For EIP-2612, you can skip signing a permit if allowance already covers the amount.
  • For EIP-3009, sign ReceiveWithAuthorization with to = router (payee = router) and an intent-derived nonce.
  • Fee-on-transfer / deflationary tokens are rejected by router balance-delta checks for pull schemes.

EIP-3009 nonce binding:

import { deriveEip3009Nonce, hashPaymentIntent } from '@pepaylabs/bnbpay';

const intentHash = hashPaymentIntent(intent);
const authNonce = deriveEip3009Nonce({
intentHash,
router: ROUTER_ADDRESS,
chainId: 56,
});
MEV-sensitive pull flows

Permit2 submissions can be copied in the public mempool. EIP-3009 uses receiveWithAuthorization (payee = router) which prevents nonce-consumption front-runs, but private relays/bundlers are still recommended for reliability.

Session-based payments (SessionGuard)

Use sessions to authorize an agent or service to spend under a budget. The session ID must be tagged into the reference string.

import { buildSessionContext, formatSessionReference } from '@pepaylabs/bnbpay';

const reference = formatSessionReference('order-126', sessionId, resourceId);
const ctx = buildSessionContext({ sessionId }, { defaultAgent: agentAddress });

await router.depositAndSettleTokenSession(intent, witness, '0x', ctx, reference);

Session spends require a SessionSpendAuth signed by the agent, bound to the current session epoch:

import { getFlexSchemeId, hashPaymentIntent } from '@pepaylabs/bnbpay';

const state = await sessionStore.getSessionState(sessionId);
const spendNonce = await sessionStore.getSessionSpendNonce(sessionId);

const sessionAuth = {
sessionId,
intentHash: hashPaymentIntent(intent),
schemeId: getFlexSchemeId('exact:evm:eip3009'),
spendNonce,
expiresAt: Math.floor(Date.now() / 1000) + 3600,
epoch: state.epoch,
};
// sign SessionSpendAuth typed data with the session agent

For claimable sessions (gift cards), use /relay/session/open-claimable and /relay/session/claim before spending.

Relay (server-assisted)

Use the API relay when you want the BNBPay relayer to submit transactions on behalf of the payer.

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

const api = createApiClient({ apiKey: process.env.BNBPAY_RELAY_KEY });

await api.relay.payment({
network: 'bnb',
scheme: 'permit2',
intent,
witness,
witnessSignature,
permit2,
reference,
});

Relay also supports:

  • /relay/permit2/bundle for gasless Permit2 approvals.
  • /relay/session/open and /relay/session/revoke for SessionGuard management.
  • /relay/session/open-claimable and /relay/session/claim for claimable sessions.

Reference tagging

Always hash the final referenceData after tags are applied. The SDK helpers do this automatically when you pass session or use formatSessionReference.