@faremeter/payment-evm 0.10.1 → 0.10.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.
@@ -0,0 +1,24 @@
1
+ import type { PaymentHandler } from "@faremeter/types/client";
2
+ import { type AssetNameOrContractInfo } from "@faremeter/info/evm";
3
+ import type { Hex } from "viem";
4
+ interface WalletForPayment {
5
+ chain: {
6
+ id: number;
7
+ name: string;
8
+ };
9
+ address: Hex;
10
+ account: {
11
+ signTypedData: (params: {
12
+ domain: Record<string, unknown>;
13
+ types: Record<string, unknown>;
14
+ primaryType: string;
15
+ message: Record<string, unknown>;
16
+ }) => Promise<Hex>;
17
+ };
18
+ }
19
+ export type CreatePaymentHandlerOpts = {
20
+ asset?: AssetNameOrContractInfo;
21
+ };
22
+ export declare function createPaymentHandler(wallet: WalletForPayment, opts?: CreatePaymentHandlerOpts): PaymentHandler;
23
+ export {};
24
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/exact/client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,cAAc,EAGf,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAGL,KAAK,uBAAuB,EAC7B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,MAAM,CAAC;AAWhC,UAAU,gBAAgB;IACxB,KAAK,EAAE;QACL,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IACF,OAAO,EAAE,GAAG,CAAC;IACb,OAAO,EAAE;QACP,aAAa,EAAE,CAAC,MAAM,EAAE;YACtB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;YAChC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;YAC/B,WAAW,EAAE,MAAM,CAAC;YACpB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;SAClC,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;KACpB,CAAC;CACH;AAED,MAAM,MAAM,wBAAwB,GAAG;IACrC,KAAK,CAAC,EAAE,uBAAuB,CAAC;CACjC,CAAC;AAEF,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,gBAAgB,EACxB,IAAI,GAAE,wBAA6B,GAClC,cAAc,CAkGhB"}
@@ -0,0 +1,81 @@
1
+ import { randomBytes } from "crypto";
2
+ import { lookupX402Network, findAssetInfo, } from "@faremeter/info/evm";
3
+ import { isAddress } from "viem";
4
+ import { type } from "arktype";
5
+ import { X402_EXACT_SCHEME, EIP712_TYPES, eip712Domain, } from "./constants.js";
6
+ export function createPaymentHandler(wallet, opts = {}) {
7
+ const x402Network = lookupX402Network(wallet.chain.id);
8
+ const assetInfo = findAssetInfo(x402Network, opts.asset ?? "USDC");
9
+ if (!assetInfo) {
10
+ throw new Error(`Couldn't look up USDC information on network '${x402Network}'`);
11
+ }
12
+ return async function handlePayment(context, accepts) {
13
+ const compatibleRequirements = accepts.filter((req) => req.scheme === X402_EXACT_SCHEME && req.network === x402Network);
14
+ return compatibleRequirements.map((requirements) => ({
15
+ requirements,
16
+ exec: async () => {
17
+ if (!isAddress(requirements.payTo)) {
18
+ throw new Error(`Invalid payTo address: ${requirements.payTo}`);
19
+ }
20
+ const payToAddress = requirements.payTo;
21
+ // Generate nonce for EIP-3009 authorization (32 bytes hex with 0x prefix)
22
+ const nonce = `0x${randomBytes(32).toString("hex")}`;
23
+ const now = Math.floor(Date.now() / 1000);
24
+ const validAfter = now - 60; // Valid from 60 seconds ago
25
+ const validBefore = now + requirements.maxTimeoutSeconds;
26
+ // Create the authorization parameters for EIP-3009
27
+ const authorization = {
28
+ from: wallet.address,
29
+ to: payToAddress,
30
+ value: requirements.maxAmountRequired, // String value of amount
31
+ validAfter: validAfter.toString(),
32
+ validBefore: validBefore.toString(),
33
+ nonce: nonce,
34
+ };
35
+ // Validate and extract EIP-712 domain parameters from requirements.extra
36
+ const extraResult = eip712Domain(requirements.extra ?? {});
37
+ if (extraResult instanceof type.errors) {
38
+ throw new Error(`Invalid EIP-712 domain parameters: ${extraResult.summary}`);
39
+ }
40
+ const verifyingContract = extraResult.verifyingContract ??
41
+ requirements.asset ??
42
+ assetInfo.address;
43
+ if (!isAddress(verifyingContract)) {
44
+ throw new Error(`Invalid verifying contract: ${verifyingContract}`);
45
+ }
46
+ const domain = {
47
+ name: extraResult.name ?? assetInfo.contractName,
48
+ version: extraResult.version ?? "2",
49
+ chainId: extraResult.chainId ?? wallet.chain.id,
50
+ verifyingContract,
51
+ };
52
+ const types = EIP712_TYPES;
53
+ // Message for EIP-712 signing (using BigInt for signing)
54
+ const message = {
55
+ from: wallet.address,
56
+ to: payToAddress,
57
+ value: BigInt(requirements.maxAmountRequired),
58
+ validAfter: BigInt(validAfter),
59
+ validBefore: BigInt(validBefore),
60
+ nonce: nonce,
61
+ };
62
+ // Sign the EIP-712 typed data
63
+ const signature = await wallet.account.signTypedData({
64
+ domain,
65
+ types,
66
+ primaryType: "TransferWithAuthorization",
67
+ message,
68
+ });
69
+ // Create the x402 exact scheme payload
70
+ const payload = {
71
+ signature: signature,
72
+ authorization: authorization,
73
+ };
74
+ // Return the EIP-3009 authorization payload
75
+ return {
76
+ payload,
77
+ };
78
+ },
79
+ }));
80
+ };
81
+ }
@@ -0,0 +1,18 @@
1
+ import type { PublicClient, Hex } from "viem";
2
+ export declare function generateDomain(publicClient: PublicClient, chainId: number, asset: Hex): Promise<{
3
+ name: string;
4
+ version: string;
5
+ chainId: number;
6
+ verifyingContract: `0x${string}`;
7
+ }>;
8
+ export declare function generateForwarderDomain(chainId: number, domainInfo: {
9
+ version: string;
10
+ name: string;
11
+ verifyingContract: `0x${string}`;
12
+ }): {
13
+ chainId: number;
14
+ version: string;
15
+ name: string;
16
+ verifyingContract: `0x${string}`;
17
+ };
18
+ //# sourceMappingURL=common.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../../src/exact/common.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,MAAM,CAAC;AAI9C,wBAAsB,cAAc,CAClC,YAAY,EAAE,YAAY,EAC1B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,GAAG;;;;;GA8BX;AAED,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,MAAM,EACf,UAAU,EAAE;IACV,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,iBAAiB,EAAE,KAAK,MAAM,EAAE,CAAC;CAClC;;aAHU,MAAM;UACT,MAAM;uBACO,KAAK,MAAM,EAAE;EAOnC"}
@@ -0,0 +1,36 @@
1
+ import { TRANSFER_WITH_AUTHORIZATION_ABI } from "./constants.js";
2
+ export async function generateDomain(publicClient, chainId, asset) {
3
+ // Read domain parameters from chain
4
+ let tokenName;
5
+ let tokenVersion;
6
+ try {
7
+ [tokenName, tokenVersion] = await Promise.all([
8
+ publicClient.readContract({
9
+ address: asset,
10
+ abi: TRANSFER_WITH_AUTHORIZATION_ABI,
11
+ functionName: "name",
12
+ }),
13
+ publicClient.readContract({
14
+ address: asset,
15
+ abi: TRANSFER_WITH_AUTHORIZATION_ABI,
16
+ functionName: "version",
17
+ }),
18
+ ]);
19
+ }
20
+ catch (cause) {
21
+ throw new Error("Failed to read contract parameters", { cause });
22
+ }
23
+ const domain = {
24
+ name: tokenName,
25
+ version: tokenVersion,
26
+ chainId,
27
+ verifyingContract: asset,
28
+ };
29
+ return domain;
30
+ }
31
+ export function generateForwarderDomain(chainId, domainInfo) {
32
+ return {
33
+ ...domainInfo,
34
+ chainId,
35
+ };
36
+ }
@@ -0,0 +1,119 @@
1
+ export declare const X402_EXACT_SCHEME = "exact";
2
+ export declare const TRANSFER_WITH_AUTHORIZATION_ABI: readonly [{
3
+ readonly name: "transferWithAuthorization";
4
+ readonly type: "function";
5
+ readonly stateMutability: "nonpayable";
6
+ readonly inputs: readonly [{
7
+ readonly name: "from";
8
+ readonly type: "address";
9
+ }, {
10
+ readonly name: "to";
11
+ readonly type: "address";
12
+ }, {
13
+ readonly name: "value";
14
+ readonly type: "uint256";
15
+ }, {
16
+ readonly name: "validAfter";
17
+ readonly type: "uint256";
18
+ }, {
19
+ readonly name: "validBefore";
20
+ readonly type: "uint256";
21
+ }, {
22
+ readonly name: "nonce";
23
+ readonly type: "bytes32";
24
+ }, {
25
+ readonly name: "v";
26
+ readonly type: "uint8";
27
+ }, {
28
+ readonly name: "r";
29
+ readonly type: "bytes32";
30
+ }, {
31
+ readonly name: "s";
32
+ readonly type: "bytes32";
33
+ }];
34
+ readonly outputs: readonly [];
35
+ }, {
36
+ readonly name: "authorizationState";
37
+ readonly type: "function";
38
+ readonly stateMutability: "view";
39
+ readonly inputs: readonly [{
40
+ readonly name: "authorizer";
41
+ readonly type: "address";
42
+ }, {
43
+ readonly name: "nonce";
44
+ readonly type: "bytes32";
45
+ }];
46
+ readonly outputs: readonly [{
47
+ readonly name: "";
48
+ readonly type: "bool";
49
+ }];
50
+ }, {
51
+ readonly name: "name";
52
+ readonly type: "function";
53
+ readonly stateMutability: "view";
54
+ readonly inputs: readonly [];
55
+ readonly outputs: readonly [{
56
+ readonly name: "";
57
+ readonly type: "string";
58
+ }];
59
+ }, {
60
+ readonly name: "version";
61
+ readonly type: "function";
62
+ readonly stateMutability: "view";
63
+ readonly inputs: readonly [];
64
+ readonly outputs: readonly [{
65
+ readonly name: "";
66
+ readonly type: "string";
67
+ }];
68
+ }, {
69
+ readonly name: "DOMAIN_SEPARATOR";
70
+ readonly type: "function";
71
+ readonly stateMutability: "view";
72
+ readonly inputs: readonly [];
73
+ readonly outputs: readonly [{
74
+ readonly name: "";
75
+ readonly type: "bytes32";
76
+ }];
77
+ }];
78
+ export declare const EIP712_TYPES: {
79
+ readonly TransferWithAuthorization: readonly [{
80
+ readonly name: "from";
81
+ readonly type: "address";
82
+ }, {
83
+ readonly name: "to";
84
+ readonly type: "address";
85
+ }, {
86
+ readonly name: "value";
87
+ readonly type: "uint256";
88
+ }, {
89
+ readonly name: "validAfter";
90
+ readonly type: "uint256";
91
+ }, {
92
+ readonly name: "validBefore";
93
+ readonly type: "uint256";
94
+ }, {
95
+ readonly name: "nonce";
96
+ readonly type: "bytes32";
97
+ }];
98
+ };
99
+ export declare const x402ExactPayload: import("arktype/internal/methods/object.ts").ObjectType<{
100
+ signature: (In: string) => import("arktype").Out<`0x${string}`>;
101
+ authorization: {
102
+ from: (In: string) => import("arktype").Out<`0x${string}`>;
103
+ to: (In: string) => import("arktype").Out<`0x${string}`>;
104
+ value: string;
105
+ validAfter: string;
106
+ validBefore: string;
107
+ nonce: (In: string) => import("arktype").Out<`0x${string}`>;
108
+ };
109
+ }, {}>;
110
+ export type x402ExactPayload = typeof x402ExactPayload.infer;
111
+ export type eip3009Authorization = x402ExactPayload["authorization"];
112
+ export declare const eip712Domain: import("arktype/internal/methods/object.ts").ObjectType<{
113
+ name?: string;
114
+ version?: string;
115
+ chainId?: number;
116
+ verifyingContract?: string;
117
+ }, {}>;
118
+ export type eip712Domain = typeof eip712Domain.infer;
119
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/exact/constants.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,iBAAiB,UAAU,CAAC;AAEzC,eAAO,MAAM,+BAA+B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiDlC,CAAC;AAEX,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;CASf,CAAC;AAEX,eAAO,MAAM,gBAAgB;;;;;;;;;;MAU3B,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,OAAO,gBAAgB,CAAC,KAAK,CAAC;AAC7D,MAAM,MAAM,oBAAoB,GAAG,gBAAgB,CAAC,eAAe,CAAC,CAAC;AAErE,eAAO,MAAM,YAAY;;;;;MAKvB,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,OAAO,YAAY,CAAC,KAAK,CAAC"}
@@ -0,0 +1,86 @@
1
+ import { type } from "arktype";
2
+ import { toHex, isHex } from "viem";
3
+ const prefixedHexString = type("string").pipe.try((x) => {
4
+ if (isHex(x)) {
5
+ return x;
6
+ }
7
+ return toHex(x);
8
+ });
9
+ export const X402_EXACT_SCHEME = "exact";
10
+ export const TRANSFER_WITH_AUTHORIZATION_ABI = [
11
+ {
12
+ name: "transferWithAuthorization",
13
+ type: "function",
14
+ stateMutability: "nonpayable",
15
+ inputs: [
16
+ { name: "from", type: "address" },
17
+ { name: "to", type: "address" },
18
+ { name: "value", type: "uint256" },
19
+ { name: "validAfter", type: "uint256" },
20
+ { name: "validBefore", type: "uint256" },
21
+ { name: "nonce", type: "bytes32" },
22
+ { name: "v", type: "uint8" },
23
+ { name: "r", type: "bytes32" },
24
+ { name: "s", type: "bytes32" },
25
+ ],
26
+ outputs: [],
27
+ },
28
+ {
29
+ name: "authorizationState",
30
+ type: "function",
31
+ stateMutability: "view",
32
+ inputs: [
33
+ { name: "authorizer", type: "address" },
34
+ { name: "nonce", type: "bytes32" },
35
+ ],
36
+ outputs: [{ name: "", type: "bool" }],
37
+ },
38
+ {
39
+ name: "name",
40
+ type: "function",
41
+ stateMutability: "view",
42
+ inputs: [],
43
+ outputs: [{ name: "", type: "string" }],
44
+ },
45
+ {
46
+ name: "version",
47
+ type: "function",
48
+ stateMutability: "view",
49
+ inputs: [],
50
+ outputs: [{ name: "", type: "string" }],
51
+ },
52
+ {
53
+ name: "DOMAIN_SEPARATOR",
54
+ type: "function",
55
+ stateMutability: "view",
56
+ inputs: [],
57
+ outputs: [{ name: "", type: "bytes32" }],
58
+ },
59
+ ];
60
+ export const EIP712_TYPES = {
61
+ TransferWithAuthorization: [
62
+ { name: "from", type: "address" },
63
+ { name: "to", type: "address" },
64
+ { name: "value", type: "uint256" },
65
+ { name: "validAfter", type: "uint256" },
66
+ { name: "validBefore", type: "uint256" },
67
+ { name: "nonce", type: "bytes32" },
68
+ ],
69
+ };
70
+ export const x402ExactPayload = type({
71
+ signature: prefixedHexString,
72
+ authorization: {
73
+ from: prefixedHexString,
74
+ to: prefixedHexString,
75
+ value: "string",
76
+ validAfter: "string",
77
+ validBefore: "string",
78
+ nonce: prefixedHexString,
79
+ },
80
+ });
81
+ export const eip712Domain = type({
82
+ "name?": "string",
83
+ "version?": "string",
84
+ "chainId?": "number",
85
+ "verifyingContract?": "string",
86
+ });
@@ -0,0 +1,10 @@
1
+ import { type FacilitatorHandler } from "@faremeter/types/facilitator";
2
+ import type { Chain, Transport } from "viem";
3
+ import { type KnownX402Network, type AssetNameOrContractInfo } from "@faremeter/info/evm";
4
+ type CreateFacilitatorHandlerOpts = {
5
+ network?: KnownX402Network;
6
+ transport?: Transport;
7
+ };
8
+ export declare function createFacilitatorHandler(chain: Chain, privateKey: string, assetNameOrInfo: AssetNameOrContractInfo, opts?: CreateFacilitatorHandlerOpts): Promise<FacilitatorHandler>;
9
+ export {};
10
+ //# sourceMappingURL=facilitator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"facilitator.d.ts","sourceRoot":"","sources":["../../../src/exact/facilitator.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAGvE,OAAO,KAAK,EAAgB,KAAK,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AAY3D,OAAO,EAEL,KAAK,gBAAgB,EAErB,KAAK,uBAAuB,EAC7B,MAAM,qBAAqB,CAAC;AA8B7B,KAAK,4BAA4B,GAAG;IAClC,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,SAAS,CAAC,EAAE,SAAS,CAAC;CACvB,CAAC;AACF,wBAAsB,wBAAwB,CAC5C,KAAK,EAAE,KAAK,EACZ,UAAU,EAAE,MAAM,EAClB,eAAe,EAAE,uBAAuB,EACxC,IAAI,GAAE,4BAAiC,GACtC,OAAO,CAAC,kBAAkB,CAAC,CA+S7B"}
@@ -0,0 +1,267 @@
1
+ import { isValidationError, caseInsensitiveLiteral } from "@faremeter/types";
2
+ import { isPrivateKey } from "@faremeter/types/evm";
3
+ import {} from "@faremeter/types/facilitator";
4
+ import { type } from "arktype";
5
+ import { createPublicClient, createWalletClient, http, verifyTypedData, encodeFunctionData, isAddress, } from "viem";
6
+ import { privateKeyToAccount } from "viem/accounts";
7
+ import { lookupX402Network, findAssetInfo, } from "@faremeter/info/evm";
8
+ import { X402_EXACT_SCHEME, TRANSFER_WITH_AUTHORIZATION_ABI, EIP712_TYPES, x402ExactPayload, } from "./constants.js";
9
+ import { generateDomain, generateForwarderDomain } from "./common.js";
10
+ function errorResponse(msg) {
11
+ return {
12
+ success: false,
13
+ error: msg,
14
+ txHash: null,
15
+ networkId: null,
16
+ };
17
+ }
18
+ const usedNonces = new Set();
19
+ function parseSignature(signature) {
20
+ const sig = signature.slice(2); // Remove 0x
21
+ const r = `0x${sig.slice(0, 64)}`;
22
+ const s = `0x${sig.slice(64, 128)}`;
23
+ const v = parseInt(sig.slice(128, 130), 16);
24
+ return { v, r, s };
25
+ }
26
+ export async function createFacilitatorHandler(chain, privateKey, assetNameOrInfo, opts = {}) {
27
+ if (!isPrivateKey(privateKey)) {
28
+ throw new Error(`Invalid private key: ${privateKey}`);
29
+ }
30
+ const network = opts.network ?? lookupX402Network(chain.id);
31
+ const chainId = chain.id;
32
+ const assetInfo = findAssetInfo(network, assetNameOrInfo);
33
+ const asset = assetInfo.address;
34
+ const useForwarder = assetInfo.forwarder !== undefined && assetInfo.forwarderName !== undefined;
35
+ if (!isAddress(asset)) {
36
+ throw new Error(`Invalid asset address: ${asset}`);
37
+ }
38
+ const transport = opts.transport ?? http(chain.rpcUrls.default.http[0]);
39
+ const account = privateKeyToAccount(privateKey);
40
+ const publicClient = createPublicClient({
41
+ chain,
42
+ transport,
43
+ });
44
+ const walletClient = createWalletClient({
45
+ account,
46
+ chain,
47
+ transport,
48
+ });
49
+ let domain;
50
+ if (useForwarder) {
51
+ if (!assetInfo.forwarder) {
52
+ throw new Error("Missing Forwarding Contract");
53
+ }
54
+ if (!assetInfo.forwarderVersion) {
55
+ throw new Error("Missing Forwarding Version");
56
+ }
57
+ if (!assetInfo.forwarderName) {
58
+ throw new Error("Missing Forwarding Name");
59
+ }
60
+ domain = generateForwarderDomain(chainId, {
61
+ version: assetInfo.forwarderVersion,
62
+ name: assetInfo.forwarderName,
63
+ verifyingContract: assetInfo.forwarder,
64
+ });
65
+ }
66
+ else {
67
+ domain = await generateDomain(publicClient, chainId, asset);
68
+ if (domain.name != assetInfo.contractName) {
69
+ throw new Error(`On chain contract name (${domain.name}) doesn't match configured asset name (${assetInfo.contractName})`);
70
+ }
71
+ }
72
+ const checkTuple = type({
73
+ scheme: caseInsensitiveLiteral(X402_EXACT_SCHEME),
74
+ network: caseInsensitiveLiteral(network),
75
+ });
76
+ const checkTupleAndAsset = checkTuple.and({
77
+ asset: caseInsensitiveLiteral(asset),
78
+ });
79
+ const getSupported = () => {
80
+ return [
81
+ Promise.resolve({
82
+ x402Version: 1,
83
+ network,
84
+ scheme: X402_EXACT_SCHEME,
85
+ }),
86
+ ];
87
+ };
88
+ const getRequirements = async (req) => {
89
+ return req
90
+ .filter((x) => !isValidationError(checkTupleAndAsset(x)))
91
+ .map((x) => ({
92
+ ...x,
93
+ asset,
94
+ maxTimeoutSeconds: 300,
95
+ // Provide EIP-712 domain parameters for client signing
96
+ extra: {
97
+ name: useForwarder ? assetInfo.forwarderName : assetInfo.contractName,
98
+ version: useForwarder ? assetInfo.forwarderVersion : "2",
99
+ chainId,
100
+ verifyingContract: useForwarder ? assetInfo.forwarder : asset,
101
+ },
102
+ }));
103
+ };
104
+ const handleSettle = async (requirements, payment) => {
105
+ const tupleMatches = checkTuple(payment);
106
+ if (isValidationError(tupleMatches)) {
107
+ return null; // Not for us, let another handler try
108
+ }
109
+ // For the exact scheme with EIP-3009, validate the authorization payload
110
+ const payloadResult = x402ExactPayload(payment.payload);
111
+ if (payloadResult instanceof type.errors) {
112
+ return errorResponse(`Invalid payload: ${payloadResult.summary}`);
113
+ }
114
+ const { authorization, signature } = payloadResult;
115
+ // Check if the payment is to the correct address
116
+ if (authorization.to.toLowerCase() !== requirements.payTo.toLowerCase()) {
117
+ return errorResponse("Payment authorized to wrong address");
118
+ }
119
+ // Check if the amount matches
120
+ if (authorization.value !== requirements.maxAmountRequired) {
121
+ return errorResponse("Incorrect payment amount");
122
+ }
123
+ // Check if the authorization is still valid (time-wise)
124
+ const now = Math.floor(Date.now() / 1000);
125
+ const validAfter = parseInt(authorization.validAfter);
126
+ const validBefore = parseInt(authorization.validBefore);
127
+ if (now < validAfter) {
128
+ return errorResponse("Authorization not yet valid");
129
+ }
130
+ if (now > validBefore) {
131
+ return errorResponse("Authorization expired");
132
+ }
133
+ // Verify the from address is valid
134
+ if (!isAddress(authorization.from)) {
135
+ return errorResponse("Invalid from address");
136
+ }
137
+ // Check nonce hasn't been used (local check)
138
+ const nonceKey = `${authorization.from}-${authorization.nonce}`;
139
+ if (usedNonces.has(nonceKey)) {
140
+ return errorResponse("Nonce already used");
141
+ }
142
+ // Check on-chain nonce status
143
+ let onChainUsed;
144
+ try {
145
+ onChainUsed = await publicClient.readContract({
146
+ address: assetInfo.forwarder ?? asset,
147
+ abi: TRANSFER_WITH_AUTHORIZATION_ABI,
148
+ functionName: "authorizationState",
149
+ args: [authorization.from, authorization.nonce],
150
+ });
151
+ }
152
+ catch (error) {
153
+ throw new Error("Failed to check authorization status", { cause: error });
154
+ }
155
+ if (onChainUsed) {
156
+ return errorResponse("Authorization already used on-chain");
157
+ }
158
+ let domain;
159
+ if (useForwarder) {
160
+ if (!assetInfo.forwarderVersion ||
161
+ !assetInfo.forwarderName ||
162
+ !assetInfo.forwarder) {
163
+ throw new Error("Secondary Forwardign Information Missing");
164
+ }
165
+ domain = generateForwarderDomain(chainId, {
166
+ version: assetInfo.forwarderVersion,
167
+ name: assetInfo.forwarderName,
168
+ verifyingContract: assetInfo.forwarder,
169
+ });
170
+ }
171
+ else {
172
+ domain = await generateDomain(publicClient, chainId, asset);
173
+ }
174
+ const types = EIP712_TYPES;
175
+ const message = {
176
+ from: authorization.from,
177
+ to: authorization.to,
178
+ value: BigInt(authorization.value),
179
+ validAfter: BigInt(validAfter),
180
+ validBefore: BigInt(validBefore),
181
+ nonce: authorization.nonce,
182
+ };
183
+ // Verify the signature
184
+ let isValidSignature;
185
+ try {
186
+ isValidSignature = await verifyTypedData({
187
+ address: authorization.from,
188
+ domain,
189
+ types,
190
+ primaryType: "TransferWithAuthorization",
191
+ message,
192
+ signature: signature,
193
+ });
194
+ }
195
+ catch (cause) {
196
+ throw new Error("Signature verification failed", { cause });
197
+ }
198
+ if (!isValidSignature) {
199
+ return errorResponse("Invalid signature");
200
+ }
201
+ // Verify contract supports EIP-712
202
+ try {
203
+ await publicClient.readContract({
204
+ address: useForwarder ? domain.verifyingContract : asset,
205
+ abi: TRANSFER_WITH_AUTHORIZATION_ABI,
206
+ functionName: "DOMAIN_SEPARATOR",
207
+ });
208
+ }
209
+ catch (cause) {
210
+ throw new Error("Contract does not support EIP-712", { cause });
211
+ }
212
+ const acct = walletClient.account;
213
+ if (!acct || acct.type !== "local") {
214
+ return errorResponse("Wallet client is not configured with a local account");
215
+ }
216
+ const { v, r, s } = parseSignature(signature);
217
+ const data = encodeFunctionData({
218
+ abi: TRANSFER_WITH_AUTHORIZATION_ABI,
219
+ functionName: "transferWithAuthorization",
220
+ args: [
221
+ authorization.from,
222
+ authorization.to,
223
+ BigInt(authorization.value),
224
+ BigInt(validAfter),
225
+ BigInt(validBefore),
226
+ authorization.nonce,
227
+ v,
228
+ r,
229
+ s,
230
+ ],
231
+ });
232
+ // Build and send the transaction
233
+ try {
234
+ const request = await walletClient.prepareTransactionRequest({
235
+ to: useForwarder ? domain.verifyingContract : asset,
236
+ data,
237
+ account: acct,
238
+ chain: undefined,
239
+ });
240
+ const serializedTransaction = await walletClient.signTransaction(request);
241
+ const txHash = await publicClient.sendRawTransaction({
242
+ serializedTransaction,
243
+ });
244
+ const receipt = await publicClient.waitForTransactionReceipt({
245
+ hash: txHash,
246
+ });
247
+ if (receipt.status !== "success") {
248
+ return errorResponse("Transaction failed");
249
+ }
250
+ usedNonces.add(nonceKey);
251
+ return {
252
+ success: true,
253
+ error: null,
254
+ txHash,
255
+ networkId: chainId.toString(),
256
+ };
257
+ }
258
+ catch (cause) {
259
+ throw new Error("Transaction execution failed", { cause });
260
+ }
261
+ };
262
+ return {
263
+ getSupported,
264
+ getRequirements,
265
+ handleSettle,
266
+ };
267
+ }
@@ -0,0 +1,4 @@
1
+ export { createPaymentHandler } from "./client.js";
2
+ export { createFacilitatorHandler } from "./facilitator.js";
3
+ export { X402_EXACT_SCHEME, TRANSFER_WITH_AUTHORIZATION_ABI, EIP712_TYPES, x402ExactPayload, eip712Domain, } from "./constants.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/exact/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EACL,iBAAiB,EACjB,+BAA+B,EAC/B,YAAY,EACZ,gBAAgB,EAChB,YAAY,GACb,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { createPaymentHandler } from "./client.js";
2
+ export { createFacilitatorHandler } from "./facilitator.js";
3
+ export { X402_EXACT_SCHEME, TRANSFER_WITH_AUTHORIZATION_ABI, EIP712_TYPES, x402ExactPayload, eip712Domain, } from "./constants.js";
@@ -0,0 +1,2 @@
1
+ export * as exact from "./exact/index.js";
2
+ //# sourceMappingURL=index.d.ts.map