@agentokratia/x402-escrow 2.1.1 → 2.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +165 -151
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,208 +5,219 @@ Escrow payment scheme for the x402 protocol. Supports direct USDC payments and c
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
7
|
- **Direct USDC payments** - Gasless via ERC-3009 ReceiveWithAuthorization
|
|
8
|
-
- **Multi-token support** - Pay with WETH, DAI, USDT
|
|
8
|
+
- **Multi-token support** - Pay with WETH, DAI, USDT. Receiver gets USDC.
|
|
9
9
|
- **Automatic swap quotes** - Server fetches DEX quotes, client just signs
|
|
10
10
|
- **Gzip compression** - Aggregator calldata compressed for smaller payloads
|
|
11
11
|
|
|
12
12
|
## Installation
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
|
-
npm install @agentokratia/x402-escrow
|
|
15
|
+
npm install @x402/core @x402/next @x402/fetch @x402/evm @agentokratia/x402-escrow viem
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
##
|
|
18
|
+
## Server Integration
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
### Basic Setup
|
|
20
|
+
### Next.js with paymentProxy
|
|
23
21
|
|
|
24
22
|
```typescript
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import
|
|
29
|
-
|
|
30
|
-
// Convert wagmi WalletClient to x402 signer
|
|
31
|
-
function walletClientToSigner(walletClient: WalletClient) {
|
|
32
|
-
return {
|
|
33
|
-
address: walletClient.account!.address,
|
|
34
|
-
signTypedData: async (message) =>
|
|
35
|
-
walletClient.signTypedData({
|
|
36
|
-
account: walletClient.account!,
|
|
37
|
-
domain: message.domain,
|
|
38
|
-
types: message.types,
|
|
39
|
-
primaryType: message.primaryType,
|
|
40
|
-
message: message.message,
|
|
41
|
-
}),
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Create x402 client
|
|
46
|
-
const client = new x402Client();
|
|
47
|
-
|
|
48
|
-
// Register exact scheme for direct USDC payments
|
|
49
|
-
const exactScheme = new ExactEvmScheme(walletClientToSigner(walletClient));
|
|
50
|
-
client.register('eip155:8453', exactScheme);
|
|
51
|
-
|
|
52
|
-
// Register escrow scheme for swap payments
|
|
53
|
-
const escrowScheme = new EscrowScheme(walletClient);
|
|
54
|
-
client.register('eip155:8453', escrowScheme);
|
|
55
|
-
|
|
56
|
-
// Create payment payload from 402 response
|
|
57
|
-
const paymentPayload = await client.createPaymentPayload(paymentRequired);
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
### With Policies (Token Selection)
|
|
23
|
+
import { x402ResourceServer, HTTPFacilitatorClient } from '@x402/core/server';
|
|
24
|
+
import { ExactEvmScheme } from '@x402/evm/exact/server';
|
|
25
|
+
import { EscrowScheme } from '@agentokratia/x402-escrow/server';
|
|
26
|
+
import { paymentProxy } from '@x402/next';
|
|
61
27
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
28
|
+
const facilitator = new HTTPFacilitatorClient({
|
|
29
|
+
url: 'https://facilitator.agentokratia.com',
|
|
30
|
+
createAuthHeaders: async () => ({
|
|
31
|
+
verify: { Authorization: `Bearer ${process.env.X402_API_KEY}` },
|
|
32
|
+
settle: { Authorization: `Bearer ${process.env.X402_API_KEY}` },
|
|
33
|
+
}),
|
|
34
|
+
});
|
|
65
35
|
|
|
66
|
-
|
|
67
|
-
const preferExactSelector = (version, requirements) => {
|
|
68
|
-
const exact = requirements.find((r) => r.scheme === 'exact');
|
|
69
|
-
return exact || requirements[0];
|
|
70
|
-
};
|
|
36
|
+
const escrow = new EscrowScheme({ facilitator });
|
|
71
37
|
|
|
72
|
-
|
|
73
|
-
function createInputTokenPolicy(inputToken: string) {
|
|
74
|
-
return (version, requirements) => {
|
|
75
|
-
return requirements.filter((req) => {
|
|
76
|
-
if (req.scheme === 'exact') {
|
|
77
|
-
return req.asset?.toLowerCase() === inputToken.toLowerCase();
|
|
78
|
-
}
|
|
79
|
-
// For escrow, check swapData.inputToken
|
|
80
|
-
const swapData = req.extra?.swapData;
|
|
81
|
-
if (swapData?.inputToken) {
|
|
82
|
-
return swapData.inputToken.toLowerCase() === inputToken.toLowerCase();
|
|
83
|
-
}
|
|
84
|
-
return req.asset?.toLowerCase() === inputToken.toLowerCase();
|
|
85
|
-
});
|
|
86
|
-
};
|
|
87
|
-
}
|
|
38
|
+
const USDC = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
|
|
88
39
|
|
|
89
|
-
|
|
90
|
-
|
|
40
|
+
const server = new x402ResourceServer(facilitator)
|
|
41
|
+
.register('eip155:8453', new ExactEvmScheme())
|
|
42
|
+
.register('eip155:8453', escrow);
|
|
91
43
|
|
|
92
|
-
//
|
|
93
|
-
|
|
44
|
+
// Auto-discovers supported tokens (USDC, WETH, DAI...)
|
|
45
|
+
const escrowAccepts = await escrow.buildAccepts({
|
|
46
|
+
network: 'eip155:8453',
|
|
47
|
+
price: '$0.01',
|
|
48
|
+
payTo: '0xYourWallet...',
|
|
49
|
+
asset: USDC, // settlement token (default: auto-detect)
|
|
50
|
+
});
|
|
94
51
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
52
|
+
export const middleware = paymentProxy(
|
|
53
|
+
{
|
|
54
|
+
'/api/premium': {
|
|
55
|
+
accepts: [
|
|
56
|
+
{ scheme: 'exact', network: 'eip155:8453', price: '$0.01', payTo: '0xYourWallet...' },
|
|
57
|
+
...escrowAccepts,
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
server
|
|
62
|
+
);
|
|
98
63
|
```
|
|
99
64
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
For APIs accepting payments. Config is auto-discovered from facilitator.
|
|
103
|
-
|
|
104
|
-
### Basic Setup
|
|
65
|
+
### Express with paymentMiddleware
|
|
105
66
|
|
|
106
67
|
```typescript
|
|
68
|
+
import { paymentMiddleware } from '@x402/express';
|
|
107
69
|
import { x402ResourceServer, HTTPFacilitatorClient } from '@x402/core/server';
|
|
108
70
|
import { EscrowScheme } from '@agentokratia/x402-escrow/server';
|
|
109
|
-
import { ExactEvmScheme } from '@x402/evm/exact/server';
|
|
110
71
|
|
|
111
|
-
// 1. Create facilitator client
|
|
112
72
|
const facilitator = new HTTPFacilitatorClient({
|
|
113
73
|
url: 'https://facilitator.agentokratia.com',
|
|
114
74
|
createAuthHeaders: async () => ({
|
|
115
75
|
verify: { Authorization: `Bearer ${process.env.X402_API_KEY}` },
|
|
116
76
|
settle: { Authorization: `Bearer ${process.env.X402_API_KEY}` },
|
|
117
|
-
supported: { Authorization: `Bearer ${process.env.X402_API_KEY}` },
|
|
118
77
|
}),
|
|
119
78
|
});
|
|
120
79
|
|
|
121
|
-
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
80
|
+
const escrow = new EscrowScheme({ facilitator });
|
|
81
|
+
const accepts = await escrow.buildAccepts({
|
|
82
|
+
network: 'eip155:8453',
|
|
83
|
+
price: '$0.01',
|
|
84
|
+
payTo: '0xYourWallet',
|
|
125
85
|
});
|
|
126
86
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
87
|
+
app.use(
|
|
88
|
+
paymentMiddleware(
|
|
89
|
+
{
|
|
90
|
+
'GET /api/analyze': { accepts },
|
|
91
|
+
},
|
|
92
|
+
new x402ResourceServer(facilitator)
|
|
93
|
+
)
|
|
94
|
+
);
|
|
131
95
|
```
|
|
132
96
|
|
|
133
|
-
###
|
|
97
|
+
### Dynamic Pricing (Custom API Routes)
|
|
98
|
+
|
|
99
|
+
For endpoints with per-request pricing (like tip-app):
|
|
134
100
|
|
|
135
101
|
```typescript
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
server.buildPaymentRequirements({
|
|
140
|
-
scheme: 'exact',
|
|
141
|
-
network: 'eip155:8453',
|
|
142
|
-
price: '$1.00',
|
|
143
|
-
payTo: recipientAddress,
|
|
144
|
-
maxTimeoutSeconds: 600,
|
|
145
|
-
}),
|
|
146
|
-
// Escrow scheme (swaps) - fetches DEX quotes
|
|
147
|
-
escrow.buildAcceptsResolved({
|
|
148
|
-
network: 'eip155:8453',
|
|
149
|
-
price: '$1.00',
|
|
150
|
-
payTo: recipientAddress,
|
|
151
|
-
}),
|
|
152
|
-
]);
|
|
102
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
103
|
+
import { HTTPFacilitatorClient } from '@x402/core/server';
|
|
104
|
+
import { EscrowScheme, preprocessSwapPayload } from '@agentokratia/x402-escrow/server';
|
|
153
105
|
|
|
154
|
-
|
|
155
|
-
|
|
106
|
+
const facilitator = new HTTPFacilitatorClient({
|
|
107
|
+
/* config */
|
|
108
|
+
});
|
|
109
|
+
const escrow = new EscrowScheme({ facilitator, apiKey: process.env.X402_API_KEY });
|
|
110
|
+
|
|
111
|
+
export async function GET(request: NextRequest) {
|
|
112
|
+
const amount = request.nextUrl.searchParams.get('amount');
|
|
113
|
+
const recipient = request.nextUrl.searchParams.get('to');
|
|
114
|
+
const paymentSignature = request.headers.get('payment-signature');
|
|
115
|
+
|
|
116
|
+
if (!paymentSignature) {
|
|
117
|
+
// Fetch fresh DEX quotes with buildAcceptsResolved
|
|
118
|
+
const accepts = await escrow.buildAcceptsResolved({
|
|
119
|
+
network: 'eip155:8453',
|
|
120
|
+
price: `$${amount}`,
|
|
121
|
+
payTo: recipient,
|
|
122
|
+
});
|
|
156
123
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
124
|
+
const requirements = accepts.map((a) => ({
|
|
125
|
+
scheme: a.scheme,
|
|
126
|
+
network: a.network,
|
|
127
|
+
asset: a.price.asset,
|
|
128
|
+
amount: a.price.amount,
|
|
129
|
+
payTo: a.payTo,
|
|
130
|
+
maxTimeoutSeconds: 600,
|
|
131
|
+
extra: a.price.extra || {},
|
|
132
|
+
}));
|
|
133
|
+
|
|
134
|
+
const response = NextResponse.json({ message: 'Payment required' }, { status: 402 });
|
|
135
|
+
response.headers.set(
|
|
136
|
+
'PAYMENT-REQUIRED',
|
|
137
|
+
Buffer.from(JSON.stringify(requirements)).toString('base64')
|
|
138
|
+
);
|
|
139
|
+
return response;
|
|
140
|
+
}
|
|
160
141
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
payTo: accept.payTo,
|
|
169
|
-
maxTimeoutSeconds: accept.maxTimeoutSeconds || 600,
|
|
170
|
-
extra: accept.price.extra || {},
|
|
171
|
-
});
|
|
142
|
+
// Process payment
|
|
143
|
+
const payload = JSON.parse(Buffer.from(paymentSignature, 'base64').toString());
|
|
144
|
+
const processed = preprocessSwapPayload({ x402Version: 2, ...payload });
|
|
145
|
+
|
|
146
|
+
const verifyResult = await facilitator.verify(processed, processed.accepted);
|
|
147
|
+
if (!verifyResult.isValid) {
|
|
148
|
+
return NextResponse.json({ error: verifyResult.invalidReason }, { status: 402 });
|
|
172
149
|
}
|
|
173
|
-
}
|
|
174
150
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
151
|
+
const settleResult = await facilitator.settle(processed, processed.accepted);
|
|
152
|
+
return NextResponse.json({ success: true, transaction: settleResult.transaction });
|
|
153
|
+
}
|
|
178
154
|
```
|
|
179
155
|
|
|
180
|
-
|
|
156
|
+
## Client Integration
|
|
157
|
+
|
|
158
|
+
### Balance-Aware Token Selection (Recommended)
|
|
181
159
|
|
|
182
160
|
```typescript
|
|
183
|
-
import {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
161
|
+
import { wrapFetchWithPayment, x402Client } from '@x402/fetch';
|
|
162
|
+
import { ExactEvmScheme } from '@x402/evm/exact/client';
|
|
163
|
+
import { createWalletClient, createPublicClient, http } from 'viem';
|
|
164
|
+
import { privateKeyToAccount } from 'viem/accounts';
|
|
165
|
+
import { base } from 'viem/chains';
|
|
166
|
+
import {
|
|
167
|
+
EscrowScheme,
|
|
168
|
+
createBalanceSelector,
|
|
169
|
+
preferTokenPolicy,
|
|
170
|
+
} from '@agentokratia/x402-escrow/client';
|
|
171
|
+
|
|
172
|
+
const WETH = '0x4200000000000000000000000000000000000006';
|
|
173
|
+
const USDC = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
|
|
174
|
+
|
|
175
|
+
const account = privateKeyToAccount(process.env.PRIVATE_KEY);
|
|
176
|
+
|
|
177
|
+
const walletClient = createWalletClient({
|
|
178
|
+
account,
|
|
179
|
+
chain: base,
|
|
180
|
+
transport: http(),
|
|
195
181
|
});
|
|
196
182
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}
|
|
183
|
+
const publicClient = createPublicClient({
|
|
184
|
+
chain: base,
|
|
185
|
+
transport: http(),
|
|
186
|
+
});
|
|
202
187
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
188
|
+
// Balance-aware: auto-picks USDC (gasless) or WETH/DAI (swap)
|
|
189
|
+
const client = new x402Client(createBalanceSelector(publicClient, account.address))
|
|
190
|
+
.register('eip155:8453', new ExactEvmScheme(account))
|
|
191
|
+
.register('eip155:8453', new EscrowScheme(walletClient))
|
|
192
|
+
.registerPolicy(preferTokenPolicy([WETH, USDC]));
|
|
193
|
+
|
|
194
|
+
const paidFetch = wrapFetchWithPayment(fetch, client);
|
|
195
|
+
|
|
196
|
+
const res = await paidFetch('https://api.example.com/premium');
|
|
197
|
+
const data = await res.json();
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Simple Setup (Browser with wagmi)
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
import { wrapFetchWithPayment, x402Client } from '@x402/fetch';
|
|
204
|
+
import { ExactEvmScheme } from '@x402/evm/exact/client';
|
|
205
|
+
import { EscrowScheme } from '@agentokratia/x402-escrow/client';
|
|
206
|
+
|
|
207
|
+
// With wagmi's useWalletClient
|
|
208
|
+
const { data: walletClient } = useWalletClient();
|
|
209
|
+
|
|
210
|
+
const signer = {
|
|
211
|
+
address: walletClient.account.address,
|
|
212
|
+
signTypedData: (msg) => walletClient.signTypedData({ account: walletClient.account, ...msg }),
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const client = new x402Client()
|
|
216
|
+
.register('eip155:8453', new ExactEvmScheme(signer))
|
|
217
|
+
.register('eip155:8453', new EscrowScheme(walletClient));
|
|
207
218
|
|
|
208
|
-
|
|
209
|
-
|
|
219
|
+
const paidFetch = wrapFetchWithPayment(fetch, client);
|
|
220
|
+
const res = await paidFetch('https://api.example.com/premium');
|
|
210
221
|
```
|
|
211
222
|
|
|
212
223
|
## How It Works
|
|
@@ -250,6 +261,9 @@ Client Server Facilitator
|
|
|
250
261
|
| Export | Description |
|
|
251
262
|
| ------------------------- | ------------------------------------------------- |
|
|
252
263
|
| `EscrowScheme` | Client scheme for x402Client (takes WalletClient) |
|
|
264
|
+
| `createBalanceSelector` | Async selector that checks on-chain balances |
|
|
265
|
+
| `preferTokenPolicy` | Sync policy that reorders by token preference |
|
|
266
|
+
| `checkBalance` | Utility for custom balance checks |
|
|
253
267
|
| `signERC3009` | Sign ERC-3009 authorization |
|
|
254
268
|
| `signPermit2TransferFrom` | Sign Permit2 transfer |
|
|
255
269
|
| `computePaymentNonce` | Derive deterministic nonce from payment params |
|
package/package.json
CHANGED