Skip to main content

Pay for an x402 API with delegation

In this guide, you use a buyer smart accountMetaMask smart account A smart contract account created using the Smart Accounts Kit that supports programmable behavior, flexible signing options, and ERC-7710 delegations. to access API data from an x402 server.

You create an open delegation, restrict redemption to the facilitator, encode the delegation chain, and send it as the payment payload when calling a protected API route.

Prerequisites

Steps

1. Create a buyer account

Create an account to represent the buyer, the delegatorDelegator account The account that creates and signs a delegation to grant limited authority to another account. who will create a delegation.

The delegator must be a MetaMask smart accountMetaMask smart account A smart contract account created using the Smart Accounts Kit that supports programmable behavior, flexible signing options, and ERC-7710 delegations.; use the toolkit's toMetaMaskSmartAccount method to create the buyer account.

Important

Fund the smart account with USDC for the requested payment.

import { Implementation, toMetaMaskSmartAccount } from '@metamask/smart-accounts-kit'
import { publicClient, buyerAccount } from './config'

export const buyerSmartAccount = await toMetaMaskSmartAccount({
client: publicClient,
implementation: Implementation.Hybrid,
deployParams: [buyerAccount.address, [], [], []],
deploySalt: '0x',
signer: { account: buyerAccount },
})

2. Get payment requirements

Call the protected API route once without a payment header.

The server returns 402 with the payment terms (PAYMENT-REQUIRED) in the response, which you use to build the payment payload.

import { PaymentRequirements } from './types'

// Update the URL
const challengeResponse = await fetch('https://api.example.com/paid-endpoint')
if (challengeResponse.status !== 402) {
console.error('Expected 402 challenge from protected route')
// Handle error
}

const paymentRequiredHeader = challengeResponse.headers.get('PAYMENT-REQUIRED')
if (!paymentRequiredHeader) {
console.error('PAYMENT-REQUIRED header is missing')
// Handle error
}

const decodedPaymentRequired = Buffer.from(paymentRequiredHeader, 'base64').toString('utf-8')
const paymentRequired = JSON.parse(decodedPaymentRequired) as {
accepts: PaymentRequirements[]
}

const accepted = paymentRequired.accepts[0]
if (!accepted) {
console.error('Server did not provide accepted payment requirements')
// Handle error
}

if (accepted.extra.assetTransferMethod !== 'erc7710') {
console.error('Server does not support ERC-7710 delegation payments')
// Handle error
}

3. Create a delegation

Create an open root delegation from the buyer smart account. With an open root delegation, the buyer delegates authority without setting a specific delegate. Use createOpenDelegation to create the open root delegation.

This example uses the erc20TransferAmount scope to allow USDC transfers up to the amount requested in payment terms. It also uses the redeemer caveat enforcer to restrict redemption to facilitator addresses provided by the server.

warning

Before creating the delegation, make sure your buyer smart account is deployed. If it is not deployed, delegation redemption will fail.

import { CaveatType, createOpenDelegation, ScopeType } from '@metamask/smart-accounts-kit'

const facilitators = accepted.extra.facilitators
if (!facilitators || facilitators.length === 0) {
console.log('No facilitators found in PAYMENT-REQUIRED')
// Handle the error
}

// Use the amount requested by the seller.
const maxAmount = BigInt(accepted.amount)

const caveats = [
{
type: CaveatType.Redeemer,
redeemers: facilitators,
},
]

const delegation = createOpenDelegation({
from: buyerSmartAccount.address,
environment: buyerSmartAccount.environment,
scope: {
type: ScopeType.Erc20TransferAmount,
tokenAddress: accepted.asset,
maxAmount,
},
caveats,
})

const signature = await buyerSmartAccount.signDelegation({
delegation,
})

const signedDelegation = {
...delegation,
signature,
}

4. Create the payment payload

Create a payment payload using the signed delegation and accepted requirements. For ERC-7710 (Smart Contract Delegation), x402 requires the payload fields delegationManager, permissionContext, and delegator. The facilitator uses permissionContext to simulate during verification and then settle the payment.

Use encodeDelegations to encode the delegation chain. Then base64 encode the full x402 payment payload before sending it in the payment-signature header.

import { encodeDelegations } from '@metamask/smart-accounts-kit/utils'
import { PaymentPayload } from './types'

const permissionContext = encodeDelegations([signedDelegation])

const paymentPayload: PaymentPayload = {
x402Version: 2,
accepted,
payload: {
delegationManager: buyerSmartAccount.environment.DelegationManager,
permissionContext,
delegator: buyerSmartAccount.address,
},
}

const encodedPayment = Buffer.from(JSON.stringify(paymentPayload)).toString('base64')

5. Make the paid request

Send the encoded x402 payment payload in the payment-signature header. If verification succeeds, the server returns the protected data.

const apiResponse = await fetch('https://api.example.com/paid-endpoint', {
headers: {
'payment-signature': encodedPayment,
},
})

if (!apiResponse.ok) {
const errorBody = await apiResponse.json()
throw new Error(errorBody.error ?? 'API request failed')
}

const data = await apiResponse.json()
console.log('Protected API response:', data)