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
payWithPermit2using witness + Permit2 signature. - Pull (EIP-2612): relayer submits
payWithEIP2612using permit signature (or skips permit if allowance already sufficient). - Pull (EIP-3009): relayer submits
payWithEIP3009using 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
permit2WitnessTypeStringfrom/networkswhen 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,
});
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/bundlefor gasless Permit2 approvals./relay/session/openand/relay/session/revokefor SessionGuard management./relay/session/open-claimableand/relay/session/claimfor 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.