Skip to content

Commit 5ad13ab

Browse files
add recurring payments guide
1 parent ef8566c commit 5ad13ab

4 files changed

Lines changed: 286 additions & 6 deletions

File tree

gator-sidebar.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,11 @@ const sidebar = {
122122
type: 'category',
123123
label: 'Buyer',
124124
collapsed: true,
125-
items: ['guides/x402/buyer/delegations', 'guides/x402/buyer/advanced-permissions'],
125+
items: [
126+
'guides/x402/buyer/delegations',
127+
'guides/x402/buyer/advanced-permissions',
128+
'guides/x402/buyer/recurring-payments',
129+
],
126130
},
127131
],
128132
},

smart-accounts-kit/guides/x402/buyer/advanced-permissions.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ For ERC-7710 (Smart Contract Delegation), x402 requires the payload fields `dele
224224
`permissionContext`, and `delegator`. The facilitator uses `permissionContext` to simulate
225225
during verification and then settle the payment.
226226

227-
Encode the full payment payload as base64, then send it in the `payment-signature` header.
227+
Encode the full payment payload as base64, then send it in the payment signature header.
228228

229229
<Tabs>
230230
<TabItem value="example.ts">
@@ -269,7 +269,7 @@ export type PaymentPayload = {
269269
270270
### 7. Make the paid request
271271
272-
Send the base64 encoded payload in the `payment-signature` header. If verification succeeds, the server returns the protected data.
272+
Send the base64 encoded payload in the payment signature header. If verification succeeds, the server returns the protected data.
273273
274274
```ts
275275
const apiResponse = await fetch('https://api.example.com/paid-endpoint', {

smart-accounts-kit/guides/x402/buyer/delegations.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ from the buyer smart account. With an open root delegation, the buyer delegates
138138
setting a specific delegate. Use [`createOpenDelegation`](../../../reference/delegation/index.md#createopendelegation) to create the open root delegation.
139139
140140
This example uses the [`erc20TransferAmount`](../../../guides/delegation/use-delegation-scopes/spending-limit.md#erc-20-transfer-scope)
141-
scope to allow USDC transfers up to the amount requested in `PAYMENT-REQUIRED`.
141+
scope to allow USDC transfers up to the amount requested in payment terms.
142142
It also uses the [`redeemer`](../../../reference/delegation/caveats.md#redeemer) caveat enforcer to restrict
143143
redemption to facilitator addresses provided by the server.
144144
@@ -193,7 +193,7 @@ Create a payment payload using the signed delegation and accepted requirements.
193193
For ERC-7710 (Smart Contract Delegation), x402 requires the payload fields `delegationManager`, `permissionContext`, and `delegator`.
194194
The facilitator uses `permissionContext` to simulate during verification and then settle the payment.
195195

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

198198
<Tabs>
199199
<TabItem value="example.ts">
@@ -237,7 +237,7 @@ export type PaymentPayload = {
237237
238238
### 5. Make the paid request
239239
240-
Send the encoded payment payload in the `payment-signature` header. If verification succeeds, the server returns the protected data.
240+
Send the encoded payment payload in the payment signature header. If verification succeeds, the server returns the protected data.
241241
242242
```ts
243243
const apiResponse = await fetch('https://api.example.com/paid-endpoint', {
Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
---
2+
description: Set up recurring x402 API payments using Advanced Permissions.
3+
sidebar_label: Recurring payments
4+
keywords:
5+
[
6+
x402,
7+
ERC-7715,
8+
ERC-7710,
9+
advanced permissions,
10+
recurring payments,
11+
periodic permission,
12+
session account,
13+
buyer,
14+
]
15+
---
16+
17+
import Tabs from '@theme/Tabs';
18+
import TabItem from '@theme/TabItem';
19+
import GlossaryTerm from '@theme/GlossaryTerm';
20+
21+
# Recurring x402 payments
22+
23+
In this guide, you set up recurring x402 payments by requesting an ERC-20 periodic
24+
<GlossaryTerm term="Advanced Permissions" /> permission from a user.
25+
26+
For example, a user gives your agent permission to spend up to 10 USDC per week.
27+
Later, when the agent needs access an x402 endpoint, it checks the price, uses the permission the user
28+
already gave it, and pays.
29+
30+
## Prerequisites
31+
32+
- [Install and set up the Smart Accounts Kit.](../../../get-started/install.md)
33+
34+
## Steps
35+
36+
### 1. Set up a Wallet Client
37+
38+
Set up a Wallet Client using Viem's [`createWalletClient`](https://viem.sh/docs/clients/wallet) function.
39+
This client will help you interact with MetaMask.
40+
41+
Then, extend the Wallet Client functionality using `erc7715ProviderActions`.
42+
These actions enable you to request <GlossaryTerm term="Advanced Permissions" /> from the user.
43+
44+
```typescript
45+
import { createWalletClient, custom } from 'viem'
46+
import { erc7715ProviderActions } from '@metamask/smart-accounts-kit/actions'
47+
48+
const walletClient = createWalletClient({
49+
transport: custom(window.ethereum),
50+
}).extend(erc7715ProviderActions())
51+
```
52+
53+
### 2. Set up an agent account
54+
55+
Set up an agent account. The requested permissions are granted to this account, which allows the agent
56+
to make x402 API calls on behalf of the user.
57+
58+
The agent account can be either a smart account or an EOA. This example uses an EOA as an agent account.
59+
60+
```typescript
61+
import { privateKeyToAccount } from 'viem/accounts'
62+
63+
const sessionAccount = privateKeyToAccount('0x...')
64+
```
65+
66+
### 3. Request Advanced Permisisons
67+
68+
Request Advanced Permissions from the user with the Wallet Client's `requestExecutionPermissions` action.
69+
70+
In this example, you request an [ERC-20 periodic permission](../../advanced-permissions/use-permissions/erc20-token.md#erc-20-periodic-permission) with a weekly allowance of 10 USDC. This creates
71+
a recurring payment budget that your agent can store and reuse for making x402 API call.
72+
73+
See the [`requestExecutionPermissions`](../../../reference/advanced-permissions/wallet-client.md#requestexecutionpermissions) API reference for more information.
74+
75+
```ts
76+
import { base as chain } from 'viem/chains'
77+
import { parseUnits } from 'viem'
78+
79+
// USDC address on Base.
80+
const tokenAddress = '0x...'
81+
82+
const currentTime = Math.floor(Date.now() / 1000)
83+
const expiry = currentTime + 60 * 60 * 24 * 30 // Permission expires in 30 days.
84+
85+
const grantedPermissions = await walletClient.requestExecutionPermissions([
86+
{
87+
chainId: chain.id,
88+
expiry,
89+
to: sessionAccount.address,
90+
permission: {
91+
type: 'erc20-token-periodic',
92+
data: {
93+
tokenAddress: accepted.asset,
94+
periodAmount: parseUnits('10', 6),
95+
periodDuration: 604800,
96+
startTime: currentTime,
97+
justification:
98+
'Permission for agent to spend up to 10 USDC every week for making x402 API calls',
99+
},
100+
isAdjustmentAllowed: false,
101+
},
102+
},
103+
])
104+
```
105+
106+
### 4. Get payment requirements
107+
108+
Once your agent has been granted permission, when it wants to access an x402-protected endpoint, it
109+
calls the endpoint without a payment header to request payment terms.
110+
111+
The server returns `402` with the payment terms (`PAYMENT-REQUIRED`) in the response, which agent can use to
112+
build the payment payload.
113+
114+
```ts
115+
import { PaymentRequirements } from './types'
116+
117+
// Update the URL
118+
const challengeResponse = await fetch('https://api.example.com/paid-endpoint')
119+
if (challengeResponse.status !== 402) {
120+
console.error('Expected 402 challenge from protected route')
121+
// Handle error
122+
}
123+
124+
const paymentRequiredHeader = challengeResponse.headers.get('PAYMENT-REQUIRED')
125+
if (!paymentRequiredHeader) {
126+
console.error('PAYMENT-REQUIRED header is missing')
127+
// Handle error
128+
}
129+
130+
const decodedPaymentRequired = Buffer.from(paymentRequiredHeader, 'base64').toString('utf-8')
131+
const paymentRequired = JSON.parse(decodedPaymentRequired) as {
132+
accepts: PaymentRequirements[]
133+
}
134+
135+
const accepted = paymentRequired.accepts[0]
136+
if (!accepted) {
137+
console.error('Server did not provide accepted payment requirements')
138+
// Handle error
139+
}
140+
141+
if (accepted.extra.assetTransferMethod !== 'erc7710') {
142+
console.error('Server does not support ERC-7710 delegation payments')
143+
// Handle error
144+
}
145+
146+
if (accepted.asset.toLowerCase() !== tokenAddress.toLowerCase()) {
147+
console.error('Requested payment asset does not match recurring payment token')
148+
// Handle error
149+
}
150+
```
151+
152+
### 5. Create a redelegation
153+
154+
The granted advanced permission is delegated to the agent account.
155+
For each protected API call, create a redelegation from the agent account so facilitator addresses
156+
can redeem the permission context for x402 settlement.
157+
158+
This example uses the [`erc20TransferAmount`](../../../guides/delegation/use-delegation-scopes/spending-limit.md#erc-20-transfer-scope)
159+
scope while creating redelegation to allow USDC transfers up to the amount requested in payment terms. It also uses
160+
the [`redeemer`](../../../reference/delegation/caveats.md#redeemer) enforcer to restrict
161+
redemption to facilitator addresses provided by the server.
162+
163+
Use the Wallet Client's [`redelegatePermissionContext`](../../../reference/erc7710/wallet-client.md#redelegatepermissioncontextopen) action to create a redelegated permission
164+
context.
165+
166+
<Tabs>
167+
<TabItem value="example.ts">
168+
169+
```ts
170+
import { environment, sessionAccountWalletClient } from './config.ts'
171+
import { ScopeType, CaveatType } from '@metamask/smart-accounts-kit'
172+
173+
const permission = grantedPermissions[0]
174+
if (!permission) {
175+
console.error('No permission response returned by requestExecutionPermissions')
176+
// Handle error
177+
}
178+
179+
const facilitators = accepted.extra.facilitators
180+
if (!facilitators || facilitators.length === 0) {
181+
console.error('No facilitators found in payment terms')
182+
// Handle error
183+
}
184+
185+
const { permissionContext: redelegatedPermissionContext } =
186+
await sessionAccountWalletClient.redelegatePermissionContext({
187+
environment,
188+
permissionContext: permission.context,
189+
scope: {
190+
type: ScopeType.Erc20TransferAmount,
191+
tokenAddress: accepted.asset,
192+
maxAmount: BigInt(accepted.amount),
193+
},
194+
caveats: [
195+
{
196+
type: CaveatType.Redeemer,
197+
redeemers: facilitators,
198+
},
199+
],
200+
})
201+
```
202+
203+
</TabItem>
204+
<TabItem value="config.ts">
205+
206+
```ts
207+
import { createWalletClient, http } from 'viem'
208+
import { base as chain } from 'viem/chains'
209+
import { getSmartAccountsEnvironment } from '@metamask/smart-accounts-kit'
210+
import { erc7710WalletActions } from '@metamask/smart-accounts-kit/actions'
211+
212+
export const environment = getSmartAccountsEnvironment(chain.id)
213+
214+
// Use sessionAccount from previous step
215+
export const sessionAccountWalletClient = createWalletClient({
216+
account: sessionAccount,
217+
chain,
218+
transport: http(),
219+
}).extend(erc7710WalletActions())
220+
```
221+
222+
</TabItem>
223+
</Tabs>
224+
225+
### 6. Make the payment payload
226+
227+
For each protected API call, create a payment payload with the redelegated permission context.
228+
229+
For ERC-7710 (Smart Contract Delegation), x402 requires the payload fields `delegationManager`,
230+
`permissionContext`, and `delegator`. The facilitator uses `permissionContext` to simulate
231+
during verification and then settle the payment.
232+
233+
Encode the full payment payload as base64, then send it in the payment signature header.
234+
235+
```ts
236+
import { PaymentPayload } from './types'
237+
238+
const permission = grantedPermissions[0]
239+
240+
const paymentPayload: PaymentPayload = {
241+
x402Version: 2,
242+
accepted,
243+
payload: {
244+
delegationManager: permission.delegationManager,
245+
permissionContext: redelegatedPermissionContext,
246+
delegator: permission.from,
247+
},
248+
}
249+
250+
const encodedPayment = Buffer.from(JSON.stringify(paymentPayload)).toString('base64')
251+
```
252+
253+
### 7. Make the paid request
254+
255+
Send the base64 encoded payload in the payment signature header.
256+
257+
```ts
258+
const apiResponse = await fetch('https://api.example.com/paid-endpoint', {
259+
headers: {
260+
'payment-signature': encodedPayment,
261+
},
262+
})
263+
264+
if (!apiResponse.ok) {
265+
const errorBody = await apiResponse.json()
266+
console.error(errorBody.error ?? 'API request failed')
267+
// Handle error
268+
}
269+
270+
const data = await apiResponse.json()
271+
console.log('Protected API response:', data)
272+
```
273+
274+
Reuse the same weekly granted permission for additional protected routes and providers in your agent flow.
275+
Your agent can continue paying until the weekly cap is reached, then continue after the next weekly
276+
period starts.

0 commit comments

Comments
 (0)