Skip to content

Commit ef8566c

Browse files
add advanced permission buyer guide
1 parent e7ae56e commit ef8566c

3 files changed

Lines changed: 306 additions & 12 deletions

File tree

gator-sidebar.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ const sidebar = {
122122
type: 'category',
123123
label: 'Buyer',
124124
collapsed: true,
125-
items: ['guides/x402/buyer/delegations'],
125+
items: ['guides/x402/buyer/delegations', 'guides/x402/buyer/advanced-permissions'],
126126
},
127127
],
128128
},
Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
---
2+
description: Access an x402-protected API data using Advanced Permissions with a fixed allowance.
3+
sidebar_label: Advanced Permissions
4+
keywords:
5+
[x402, ERC-7715, ERC-7710, advanced permissions, redeemers, allowance, session account, buyer]
6+
---
7+
8+
import Tabs from '@theme/Tabs';
9+
import TabItem from '@theme/TabItem';
10+
import GlossaryTerm from '@theme/GlossaryTerm';
11+
12+
# Pay for an x402 API with Advanced Permissions
13+
14+
In this guide, you request <GlossaryTerm term="Advanced Permissions" /> with a fixed ERC-20 allowance
15+
to pay for a specific x402-protected resource.
16+
17+
## Prerequisites
18+
19+
- [Install and set up the Smart Accounts Kit.](../../../get-started/install.md)
20+
21+
## Steps
22+
23+
### 1. Set up a Wallet Client
24+
25+
Set up a Wallet Client using Viem's [`createWalletClient`](https://viem.sh/docs/clients/wallet) function. This client will
26+
help you interact with MetaMask.
27+
28+
Then, extend the Wallet Client functionality using `erc7715ProviderActions`.
29+
These actions enable you to request <GlossaryTerm term="Advanced Permissions" /> from the user.
30+
31+
```typescript
32+
import { createWalletClient, custom } from 'viem'
33+
import { erc7715ProviderActions } from '@metamask/smart-accounts-kit/actions'
34+
35+
const walletClient = createWalletClient({
36+
transport: custom(window.ethereum),
37+
}).extend(erc7715ProviderActions())
38+
```
39+
40+
### 2. Set up a session account
41+
42+
Set up a session account. The requested permissions are granted to the session account, which is responsible for making x402 API calls.
43+
44+
The session account can be either a smart account or an EOA. This examples uses EOA as a session account.
45+
46+
```typescript
47+
import { privateKeyToAccount } from 'viem/accounts'
48+
import { sepolia as chain } from 'viem/chains'
49+
import { createWalletClient, http } from 'viem'
50+
51+
const sessionAccount = privateKeyToAccount('0x...')
52+
```
53+
54+
### 3. Get payment requirements
55+
56+
Call the protected API route once without a payment header.
57+
58+
The server returns `402` with the payment terms (`PAYMENT-REQUIRED`) in the response, which you use
59+
to build the payment payload.
60+
61+
<Tabs>
62+
<TabItem value="example.ts">
63+
64+
```ts
65+
import { PaymentRequirements } from './types'
66+
67+
// Update the URL
68+
const challengeResponse = await fetch('https://api.example.com/paid-endpoint')
69+
if (challengeResponse.status !== 402) {
70+
console.error('Expected 402 challenge from protected route')
71+
// Handle error
72+
}
73+
74+
const paymentRequiredHeader = challengeResponse.headers.get('PAYMENT-REQUIRED')
75+
if (!paymentRequiredHeader) {
76+
console.error('PAYMENT-REQUIRED header is missing')
77+
// Handle error
78+
}
79+
80+
const decodedPaymentRequired = Buffer.from(paymentRequiredHeader, 'base64').toString('utf-8')
81+
const paymentRequired = JSON.parse(decodedPaymentRequired) as {
82+
accepts: PaymentRequirements[]
83+
}
84+
85+
const accepted = paymentRequired.accepts[0]
86+
if (!accepted) {
87+
console.error('Server did not provide accepted payment requirements')
88+
// Handle error
89+
}
90+
91+
if (accepted.extra.assetTransferMethod !== 'erc7710') {
92+
console.error('Server does not support ERC-7710 delegation payments')
93+
// Handle error
94+
}
95+
```
96+
97+
</TabItem>
98+
<TabItem value="types.ts">
99+
100+
```ts
101+
import { Address } from 'viem'
102+
103+
export type PaymentRequirements = {
104+
scheme: string
105+
network: string
106+
amount: string
107+
asset: Address
108+
payTo: Address
109+
maxTimeoutSeconds: number
110+
extra: {
111+
assetTransferMethod: string
112+
facilitators?: Address[]
113+
}
114+
}
115+
```
116+
117+
</TabItem>
118+
</Tabs>
119+
120+
### 4. Request Advanced Permissions
121+
122+
Request Advanced Permissions from the user with the Wallet Client's `requestExecutionPermissions` action.
123+
124+
In this example, you request an ERC-20 allowance permission with a fixed allowance equal to the
125+
resource cost. Use the `redeemers` rule to restrict redemption to facilitator addresses from
126+
the payment requirements.
127+
128+
See the [`requestExecutionPermissions`](../../../reference/advanced-permissions/wallet-client.md#requestexecutionpermissions) API reference for more information.
129+
130+
```ts
131+
import { base as chain } from 'viem/chains'
132+
133+
const facilitators = accepted.extra.facilitators
134+
if (!facilitators || facilitators.length === 0) {
135+
console.error('No facilitators found in PAYMENT-REQUIRED')
136+
// Handle error
137+
}
138+
139+
const supportedPermissions = await walletClient.getSupportedExecutionPermissions()
140+
const currentTime = Math.floor(Date.now() / 1000)
141+
const expiry = currentTime + 3600
142+
143+
const grantedPermissions = await walletClient.requestExecutionPermissions([
144+
{
145+
chainId: chain.id,
146+
expiry,
147+
to: sessionAccount.address,
148+
permission: {
149+
type: 'erc20-token-allowance',
150+
data: {
151+
tokenAddress: accepted.asset,
152+
// Fixed allowance for this resource.
153+
allowanceAmount: BigInt(accepted.amount),
154+
justification: 'Permission to pay for a specific x402-protected API resource',
155+
},
156+
isAdjustmentAllowed: false,
157+
},
158+
rules: [
159+
{
160+
type: 'redeemers',
161+
data: {
162+
addresses: facilitators!,
163+
},
164+
},
165+
],
166+
},
167+
])
168+
```
169+
170+
### 5. Create a redelegation
171+
172+
The granted advanced permisison is delegated to the session account. To let facilitator addresses
173+
redeem this permission context for x402 settlement, create an open redelegation from the session account.
174+
175+
Use the Wallet Client's [`redelegatePermissionContextOpen`](../../../reference/erc7710/wallet-client.md#redelegatepermissioncontextopen)
176+
action to create a redelegated permission context. The granted permission already includes
177+
a [redeemer enforcer](../../../reference/delegation/caveats.md#redeemer), so you do not add extra caveats here.
178+
179+
<Tabs>
180+
<TabItem value="example.ts">
181+
182+
```ts
183+
import { environment, sessionAccountWalletClient } from './config.ts'
184+
185+
const permission = grantedPermissions[0]
186+
if (!permission) {
187+
console.error('No permission response returned by requestExecutionPermissions')
188+
// Handle error
189+
}
190+
191+
const { permissionContext: redelegatedPermissionContext } =
192+
await sessionAccountWalletClient.redelegatePermissionContextOpen({
193+
environment,
194+
permissionContext: permission!.context,
195+
})
196+
```
197+
198+
</TabItem>
199+
<TabItem value="config.ts">
200+
201+
```ts
202+
import { createWalletClient, http } from 'viem'
203+
import { base as chain } from 'viem/chains'
204+
import { getSmartAccountsEnvironment } from '@metamask/smart-accounts-kit'
205+
import { erc7710WalletActions } from '@metamask/smart-accounts-kit/actions'
206+
import { sessionAccount } from './session-account.ts'
207+
208+
export const environment = getSmartAccountsEnvironment(chain.id)
209+
210+
export const sessionAccountWalletClient = createWalletClient({
211+
account: sessionAccount,
212+
chain,
213+
transport: http(),
214+
}).extend(erc7710WalletActions())
215+
```
216+
217+
</TabItem>
218+
</Tabs>
219+
220+
### 6. Create the payment payload
221+
222+
Create a payment payload using the redelegated permission context and accepted requirements.
223+
For ERC-7710 (Smart Contract Delegation), x402 requires the payload fields `delegationManager`,
224+
`permissionContext`, and `delegator`. The facilitator uses `permissionContext` to simulate
225+
during verification and then settle the payment.
226+
227+
Encode the full payment payload as base64, then send it in the `payment-signature` header.
228+
229+
<Tabs>
230+
<TabItem value="example.ts">
231+
232+
```ts
233+
import { PaymentPayload } from './types'
234+
235+
const permission = grantedPermissions[0]
236+
237+
const paymentPayload: PaymentPayload = {
238+
x402Version: 2,
239+
accepted,
240+
payload: {
241+
delegationManager: permission.delegationManager,
242+
permissionContext: redelegatedPermissionContext,
243+
delegator: permission.from,
244+
},
245+
}
246+
247+
const encodedPayment = Buffer.from(JSON.stringify(paymentPayload)).toString('base64')
248+
```
249+
250+
</TabItem>
251+
<TabItem value="types.ts">
252+
253+
```ts
254+
import { Address, Hex } from 'viem'
255+
256+
export type PaymentPayload = {
257+
x402Version: 2
258+
accepted: PaymentRequirements
259+
payload: {
260+
delegationManager: Address
261+
permissionContext: Hex
262+
delegator: Address
263+
}
264+
}
265+
```
266+
267+
</TabItem>
268+
</Tabs>
269+
270+
### 7. Make the paid request
271+
272+
Send the base64 encoded payload in the `payment-signature` header. If verification succeeds, the server returns the protected data.
273+
274+
```ts
275+
const apiResponse = await fetch('https://api.example.com/paid-endpoint', {
276+
headers: {
277+
'payment-signature': encodedPayment,
278+
},
279+
})
280+
281+
if (!apiResponse.ok) {
282+
const errorBody = await apiResponse.json()
283+
console.error(errorBody.error ?? 'API request failed')
284+
// Handle error
285+
}
286+
287+
const data = await apiResponse.json()
288+
console.log('Protected API response:', data)
289+
```

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

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ sidebar_label: Delegations
44
keywords: [x402, ERC-7710, delegation, smart account, facilitator, buyer, API]
55
---
66

7-
import Tabs from '@theme/Tabs'
8-
import TabItem from '@theme/TabItem'
9-
import GlossaryTerm from '@theme/GlossaryTerm'
7+
import Tabs from '@theme/Tabs';
8+
import TabItem from '@theme/TabItem';
9+
import GlossaryTerm from '@theme/GlossaryTerm';
1010

11-
# Pay for x402 API with delegations
11+
# Pay for an x402 API with delegation
1212

1313
In this guide, you use a buyer <GlossaryTerm term="MetaMask smart account">smart account</GlossaryTerm>
1414
to access API data from an x402 server.
@@ -19,16 +19,19 @@ and send it as the payment payload when calling a protected API route.
1919
## Prerequisites
2020

2121
- [Install and set up the Smart Accounts Kit.](../../../get-started/install.md)
22-
- Fund smart account with USDC for the requested payment.
2322

2423
## Steps
2524

2625
### 1. Create a buyer account
2726

28-
Create an account to represent buyer, the <GlossaryTerm term="Delegator account">delegator</GlossaryTerm> who will create a delegation.
27+
Create an account to represent the buyer, the <GlossaryTerm term="Delegator account">delegator</GlossaryTerm> who will create a delegation.
2928

3029
The delegator must be a <GlossaryTerm term="MetaMask smart account" />; use the toolkit's [`toMetaMaskSmartAccount`](../../../reference/smart-account.md#tometamasksmartaccount) method to create the buyer account.
3130

31+
:::note Important
32+
Fund the smart account with USDC for the requested payment.
33+
:::
34+
3235
<Tabs>
3336
<TabItem value="example.ts">
3437

@@ -68,8 +71,8 @@ export const buyerAccount = privateKeyToAccount('0x<BUYER_PRIVATE_KEY>')
6871

6972
Call the protected API route once without a payment header.
7073

71-
The server returns `402` and includes `PAYMENT-REQUIRED`, which gives you the payment terms needed
72-
to create an appropriate payment payload.
74+
The server returns `402` with the payment terms (`PAYMENT-REQUIRED`) in the response, which you use
75+
to build the payment payload.
7376

7477
<Tabs>
7578
<TabItem value="example.ts">
@@ -131,16 +134,18 @@ export type PaymentRequirements = {
131134
### 3. Create a delegation
132135
133136
Create an [open root delegation](../../../concepts/delegation/overview.md#open-root-delegation)
134-
from the buyer's smart account. With an open root delegation, the buyer delegates authority without
137+
from the buyer smart account. With an open root delegation, the buyer delegates authority without
135138
setting a specific delegate. Use [`createOpenDelegation`](../../../reference/delegation/index.md#createopendelegation) to create the open root delegation.
136139
137140
This example uses the [`erc20TransferAmount`](../../../guides/delegation/use-delegation-scopes/spending-limit.md#erc-20-transfer-scope)
138141
scope to allow USDC transfers up to the amount requested in `PAYMENT-REQUIRED`.
139142
It also uses the [`redeemer`](../../../reference/delegation/caveats.md#redeemer) caveat enforcer to restrict
140143
redemption to facilitator addresses provided by the server.
141144
145+
:::warning
142146
Before creating the delegation, make sure your buyer smart account is deployed. If it is not
143147
deployed, delegation redemption will fail.
148+
:::
144149
145150
```ts
146151
import { CaveatType, createOpenDelegation, ScopeType } from '@metamask/smart-accounts-kit'
@@ -185,7 +190,7 @@ const signedDelegation = {
185190
### 4. Create the payment payload
186191

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

191196
Use `encodeDelegations` to encode the delegation chain. Then base64 encode the full payment payload before sending it in the `payment-signature` header.
@@ -230,7 +235,7 @@ export type PaymentPayload = {
230235
</TabItem>
231236
</Tabs>
232237
233-
### 5. Make paid request
238+
### 5. Make the paid request
234239
235240
Send the encoded payment payload in the `payment-signature` header. If verification succeeds, the server returns the protected data.
236241

0 commit comments

Comments
 (0)