Skip to content

Commit c612e1d

Browse files
committed
Add support for multiple payment methods eip-3009 | erc-7710
1 parent f5e6163 commit c612e1d

2 files changed

Lines changed: 77 additions & 8 deletions

File tree

packages/x402-example/src/client.ts

Lines changed: 71 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { config } from 'dotenv';
22
import { x402Client, x402HTTPClient } from '@x402/core/client';
3+
import { toClientEvmSigner } from '@x402/evm';
4+
import { ExactEvmScheme } from '@x402/evm/exact/client';
35
import { wrapFetchWithPayment } from '@x402/fetch';
46
import {
57
CaveatType,
@@ -12,7 +14,9 @@ import {
1214
import { encodeDelegations } from '@metamask/smart-accounts-kit/utils';
1315
import { x402Erc7710Client } from '@metamask/smart-accounts-kit-x402';
1416
import {
17+
createPublicClient,
1518
getAddress,
19+
http,
1620
type Hex,
1721
} from 'viem';
1822
import { privateKeyToAccount } from 'viem/accounts';
@@ -23,12 +27,16 @@ config();
2327
type CliConfig = {
2428
privateKey: Hex;
2529
url: string;
30+
paymentMethod: 'erc7710' | 'eip3009';
31+
rpcUrl?: string;
2632
};
2733

2834
function parseCliConfig(): CliConfig {
2935
const args = process.argv.slice(2);
3036
const keyArgIndex = args.findIndex((arg) => arg === '--private-key');
3137
const urlArgIndex = args.findIndex((arg) => arg === '--url');
38+
const methodArgIndex = args.findIndex((arg) => arg === '--payment-method');
39+
const rpcUrlArgIndex = args.findIndex((arg) => arg === '--rpc-url');
3240

3341
const privateKeyFromArg =
3442
keyArgIndex >= 0 ? (args[keyArgIndex + 1] as Hex | undefined) : undefined;
@@ -44,7 +52,22 @@ function parseCliConfig(): CliConfig {
4452
const urlFromArg = urlArgIndex >= 0 ? args[urlArgIndex + 1] : undefined;
4553
const url = urlFromArg ?? process.env.X402_URL ?? 'http://localhost:4021/random';
4654

47-
return { privateKey, url };
55+
const paymentMethodFromArg =
56+
methodArgIndex >= 0 ? args[methodArgIndex + 1] : undefined;
57+
const paymentMethod =
58+
paymentMethodFromArg ?? process.env.X402_PAYMENT_METHOD ?? 'erc7710';
59+
60+
if (paymentMethod !== 'erc7710' && paymentMethod !== 'eip3009') {
61+
throw new Error(
62+
`Unsupported payment method "${paymentMethod}". Expected "erc7710" or "eip3009"`,
63+
);
64+
}
65+
66+
const rpcUrlFromArg =
67+
rpcUrlArgIndex >= 0 ? args[rpcUrlArgIndex + 1] : undefined;
68+
const rpcUrl = rpcUrlFromArg ?? process.env.X402_RPC_URL ?? process.env.RPC_URL;
69+
70+
return { privateKey, url, paymentMethod, rpcUrl };
4871
}
4972

5073
function parseChainIdFromNetwork(network: string): number {
@@ -64,8 +87,21 @@ function parseChainIdFromNetwork(network: string): number {
6487

6588

6689
async function main() {
67-
const { privateKey, url } = parseCliConfig();
90+
const { privateKey, url, paymentMethod, rpcUrl } = parseCliConfig();
6891
const account = privateKeyToAccount(privateKey);
92+
const permit2Signer = (() => {
93+
if (!rpcUrl) {
94+
return account;
95+
}
96+
97+
const publicClient = createPublicClient({
98+
transport: http(rpcUrl),
99+
});
100+
101+
return toClientEvmSigner(account, publicClient);
102+
})();
103+
104+
const exactEvmClient = new ExactEvmScheme(permit2Signer);
69105

70106
const erc7710Client = new x402Erc7710Client({
71107
delegationProvider: async (requirements) => {
@@ -114,10 +150,39 @@ async function main() {
114150
},
115151
});
116152

117-
const httpClient = new x402HTTPClient(new x402Client().register(
118-
'eip155:*',
119-
erc7710Client
120-
));
153+
const selectedClient =
154+
paymentMethod === 'erc7710' ? erc7710Client : exactEvmClient;
155+
156+
const coreClient = new x402Client()
157+
.register('eip155:*', selectedClient)
158+
.registerPolicy((_x402Version, paymentRequirements) => {
159+
const filtered = paymentRequirements.filter(
160+
(requirements) =>
161+
requirements.extra?.assetTransferMethod === paymentMethod,
162+
);
163+
164+
if (filtered.length > 0) {
165+
return filtered;
166+
}
167+
168+
const availableMethods = [
169+
...new Set(
170+
paymentRequirements
171+
.map((requirements) => requirements.extra?.assetTransferMethod)
172+
.filter((method): method is string => typeof method === 'string'),
173+
),
174+
];
175+
176+
throw new Error(
177+
`Requested payment method "${paymentMethod}" was not offered by the server. Available methods: ${
178+
availableMethods.length > 0
179+
? availableMethods.join(', ')
180+
: 'none'
181+
}`,
182+
);
183+
});
184+
185+
const httpClient = new x402HTTPClient(coreClient);
121186

122187
const fetchWithPayment = wrapFetchWithPayment(fetch, httpClient);
123188

packages/x402-example/src/server.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { config } from 'dotenv';
22
import express from 'express';
33
import { HTTPFacilitatorClient } from '@x402/core/server';
44
import { paymentMiddleware, x402ResourceServer } from '@x402/express';
5-
import { ExactEvmScheme } from '@x402/evm/exact/server';
65
import {
76
type Network,
87
} from '@x402/core/types';
@@ -24,6 +23,10 @@ const network = (process.env.NETWORK ?? 'eip155:84532') as Network;
2423
const port = Number(process.env.PORT ?? 4021);
2524
const price = '1000' as const;
2625
const acceptedToken = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' as const;
26+
const acceptedTokenEip712Domain = {
27+
name: 'USD Coin',
28+
version: '2',
29+
} as const;
2730

2831
const facilitatorClient = new HTTPFacilitatorClient({ url: facilitatorUrl });
2932
const app = express();
@@ -55,6 +58,7 @@ app.use(
5558
},
5659
extra: {
5760
assetTransferMethod: 'eip3009',
61+
...acceptedTokenEip712Domain,
5862
},
5963
},
6064
],
@@ -76,6 +80,6 @@ app.get('/random', (_req: unknown, res: { type: (value: string) => { send: (valu
7680

7781
app.listen(port, () => {
7882
console.log(
79-
`x402 ERC-7710 example server listening on http://localhost:${port}/random`,
83+
`x402 ERC-7710 + EIP-3009 example server listening on http://localhost:${port}/random`,
8084
);
8185
});

0 commit comments

Comments
 (0)