@armory-sh/client-ethers 0.2.9 → 0.2.12

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.
Files changed (3) hide show
  1. package/dist/index.d.ts +115 -36
  2. package/dist/index.js +313 -137
  3. package/package.json +2 -2
package/dist/index.d.ts CHANGED
@@ -1,40 +1,83 @@
1
- import { PaymentRequirements, CAIP2ChainId, CAIPAssetId, CustomToken } from '@armory-sh/base';
2
- export { Address, BalanceOfParams, CAIP2ChainId, CAIPAssetId, EIP712_TYPES, ERC20_ABI, Extensions, NETWORKS, NetworkConfig, PayToV2, PaymentPayload, PaymentPayloadV1, PaymentPayloadV2, PaymentRequirements, PaymentRequirementsV1, PaymentRequirementsV2, SettlementResponse, SettlementResponseV1, SettlementResponseV2, Signature, TransferWithAuthorizationParams, V1_HEADERS, V2_HEADERS, createEIP712Domain, createTransferWithAuthorization, decodePayment, decodePaymentV1, decodePaymentV2, decodeSettlementLegacy, decodeSettlementV1, decodeSettlementV2, detectPaymentVersion, encodePaymentV1, encodePaymentV2, encodeSettlementV1, encodeSettlementV2, getMainnets, getNetworkByChainId, getNetworkConfig, getPaymentHeaderName, getPaymentRequiredHeaderName, getPaymentResponseHeaderName, getPaymentVersion, getRequirementsVersion, getSettlementVersion, getTestnets, getTxHash, isAddress, isCAIP2ChainId, isCAIPAssetId, isPaymentV1, isPaymentV2, isSettlementSuccessful, isSettlementV1, isSettlementV2, isV1, isV2, validateTransferWithAuthorization } from '@armory-sh/base';
1
+ import { X402PaymentRequirementsV1, PaymentRequirementsV2, Address, X402PaymentPayloadV1, PaymentPayloadV2, CustomToken } from '@armory-sh/base';
2
+ export { Address, BalanceOfParams, CAIP2ChainId, CAIPAssetId, EIP712_TYPES, ERC20_ABI, Extensions, NETWORKS, NetworkConfig, PayToV2, PaymentPayload, PaymentPayloadV1, PaymentPayloadV2, PaymentRequirements, PaymentRequirementsV1, PaymentRequirementsV2, SettlementResponse, SettlementResponseV1, SettlementResponseV2, Signature, TransferWithAuthorizationParams, V1_HEADERS, V2_HEADERS, createEIP712Domain, createTransferWithAuthorization, decodePayment, decodePaymentV1, decodePaymentV2, decodeSettlementLegacy, decodeSettlementV1, decodeSettlementV2, detectPaymentVersion, encodePaymentV1, encodePaymentV2, encodeSettlementV1, encodeSettlementV2, getMainnets, getNetworkByChainId, getNetworkConfig, getPaymentHeaderName as getPaymentHeaderNameBase, getPaymentRequiredHeaderName, getPaymentResponseHeaderName, getPaymentVersion, getRequirementsVersion, getSettlementVersion, getTestnets, getTxHash, isCAIP2ChainId, isCAIPAssetId, isPaymentV1, isPaymentV2, isSettlementSuccessful, isSettlementV1, isSettlementV2, isV1, isV2, isX402V1PaymentRequired, isX402V1Requirements, isX402V2PaymentRequired, safeBase64Decode, validateTransferWithAuthorization } from '@armory-sh/base';
3
3
  import { Signer, Provider } from 'ethers';
4
- export { Signer } from 'ethers';
4
+ export { Provider, Signer } from 'ethers';
5
5
 
6
- declare function createPaymentPayloadV1(params: {
7
- signer: Signer;
8
- from: string;
9
- to: string;
10
- amount: string;
11
- nonce: string;
12
- expiry: number;
13
- chainId: number;
14
- contractAddress: string;
15
- network: string;
16
- domainName?: string;
17
- domainVersion?: string;
18
- }): Promise<string>;
19
- declare function createPaymentPayloadV2(params: {
20
- signer: Signer;
21
- from: `0x${string}`;
22
- to: `0x${string}` | {
23
- role?: string;
24
- callback?: string;
25
- };
26
- amount: string;
27
- nonce: string;
28
- expiry: number;
29
- chainId: CAIP2ChainId;
30
- assetId: CAIPAssetId;
31
- extensions?: Record<string, unknown>;
32
- domainName?: string;
33
- domainVersion?: string;
34
- }): Promise<string>;
35
- declare function detectProtocolVersion(requirements: PaymentRequirements): 1 | 2;
36
- declare function createPaymentPayload(requirements: PaymentRequirements, signer: Signer, from: string): Promise<[string, string]>;
37
- declare function parsePaymentRequirements(response: Response): Promise<PaymentRequirements>;
6
+ /**
7
+ * X402 Protocol Implementation for Ethers Client
8
+ *
9
+ * Handles parsing x402 V1 and V2 PAYMENT-REQUIRED headers
10
+ * and generating x402 V1 and V2 PAYMENT-SIGNATURE payloads
11
+ */
12
+
13
+ /**
14
+ * Detect x402 protocol version from response headers
15
+ */
16
+ declare function detectX402Version(response: Response): 1 | 2;
17
+ /**
18
+ * Parsed payment requirements with version info
19
+ */
20
+ interface ParsedPaymentRequirements {
21
+ version: 1 | 2;
22
+ requirements: X402PaymentRequirementsV1 | PaymentRequirementsV2;
23
+ }
24
+ /**
25
+ * Parse x402 PAYMENT-REQUIRED header from response
26
+ * Automatically detects V1 or V2 format
27
+ */
28
+ declare function parsePaymentRequired(response: Response): ParsedPaymentRequirements;
29
+ /**
30
+ * Create x402 V1 payment payload
31
+ */
32
+ declare function createX402V1Payment(signer: Signer, requirements: X402PaymentRequirementsV1, fromAddress: Address, nonce: `0x${string}`, validBefore: number, domainName?: string, domainVersion?: string): Promise<X402PaymentPayloadV1>;
33
+ /**
34
+ * Create x402 V2 payment payload
35
+ */
36
+ declare function createX402V2Payment(signer: Signer, requirements: PaymentRequirementsV2, fromAddress: Address, nonce: `0x${string}`, validBefore: number, domainName?: string, domainVersion?: string): Promise<PaymentPayloadV2>;
37
+ /**
38
+ * Create x402 payment payload (auto-detects version)
39
+ */
40
+ declare function createX402Payment(signer: Signer, parsed: ParsedPaymentRequirements, fromAddress: Address, nonce?: `0x${string}`, validBefore?: number, domainName?: string, domainVersion?: string): Promise<X402PaymentPayloadV1 | PaymentPayloadV2>;
41
+ /**
42
+ * Encode x402 payment payload to Base64 for transport
43
+ */
44
+ declare function encodeX402Payment(payload: X402PaymentPayloadV1 | PaymentPayloadV2): string;
45
+ /**
46
+ * Get the correct header name for payment based on version
47
+ */
48
+ declare function getPaymentHeaderName(version: 1 | 2): string;
49
+ /**
50
+ * @deprecated Use createX402Payment instead
51
+ */
52
+ declare function createPaymentPayload(requirements: unknown, signer: Signer, from: string): Promise<[string, string]>;
53
+ /**
54
+ * @deprecated Use parsePaymentRequired instead
55
+ */
56
+ declare function parsePaymentRequirements(response: Response): Promise<unknown>;
57
+ /**
58
+ * @deprecated Use detectX402Version instead
59
+ */
60
+ declare function detectProtocolVersion(requirements: unknown): 1 | 2;
61
+
62
+ declare class X402ClientError extends Error {
63
+ readonly cause?: unknown;
64
+ constructor(message: string, cause?: unknown);
65
+ }
66
+ declare class SigningError extends X402ClientError {
67
+ constructor(message: string, cause?: unknown);
68
+ }
69
+ declare class PaymentError extends X402ClientError {
70
+ constructor(message: string, cause?: unknown);
71
+ }
72
+ declare class SignerRequiredError extends X402ClientError {
73
+ constructor(message?: string);
74
+ }
75
+ declare class AuthorizationError extends X402ClientError {
76
+ constructor(message: string, cause?: unknown);
77
+ }
78
+ declare class ProviderRequiredError extends X402ClientError {
79
+ constructor(message?: string);
80
+ }
38
81
 
39
82
  /** Token configuration - can use pre-configured tokens from @armory-sh/tokens */
40
83
  type Token = CustomToken;
@@ -76,6 +119,20 @@ interface X402RequestInit extends Omit<RequestInit, "headers"> {
76
119
  skipAutoPay?: boolean;
77
120
  forceProtocolVersion?: 1 | 2;
78
121
  }
122
+ interface TransferWithAuthorizationParams {
123
+ from: `0x${string}`;
124
+ to: `0x${string}`;
125
+ value: bigint | number;
126
+ validAfter: bigint | number;
127
+ validBefore: bigint | number;
128
+ nonce: bigint | number;
129
+ }
130
+ interface EIP712Domain {
131
+ name: string;
132
+ version: string;
133
+ chainId: number;
134
+ verifyingContract: `0x${string}`;
135
+ }
79
136
 
80
137
  interface X402Transport {
81
138
  fetch(url: string, init?: X402RequestInit): Promise<Response>;
@@ -89,4 +146,26 @@ interface X402Transport {
89
146
  }
90
147
  declare const createX402Transport: (config?: X402TransportConfig) => X402Transport;
91
148
 
92
- export { type ClientConfig, type X402ClientConfig, createPaymentPayload, createPaymentPayloadV1, createPaymentPayloadV2, createX402Transport, detectProtocolVersion, parsePaymentRequirements };
149
+ declare function signEIP3009(signer: Signer, params: TransferWithAuthorizationParams, domain: EIP712Domain): Promise<{
150
+ v: number;
151
+ r: string;
152
+ s: string;
153
+ }>;
154
+ declare function signEIP3009WithDomain(signer: Signer, params: TransferWithAuthorizationParams, chainId: number, verifyingContract: `0x${string}`, domainName?: string, domainVersion?: string): Promise<{
155
+ v: number;
156
+ r: string;
157
+ s: string;
158
+ }>;
159
+ declare function signPayment(signer: Signer, from: `0x${string}`, to: `0x${string}`, value: bigint, chainId: number, verifyingContract: `0x${string}`, expirySeconds?: number, domainName?: string, domainVersion?: string): Promise<{
160
+ v: number;
161
+ r: string;
162
+ s: string;
163
+ nonce: bigint;
164
+ }>;
165
+ declare function recoverEIP3009Signer(params: TransferWithAuthorizationParams, signature: {
166
+ v: number;
167
+ r: string;
168
+ s: string;
169
+ }, domain: EIP712Domain): Promise<`0x${string}`>;
170
+
171
+ export { AuthorizationError, type ClientConfig, type ParsedPaymentRequirements, PaymentError, ProviderRequiredError, SignerRequiredError, SigningError, type X402ClientConfig, X402ClientError, type X402RequestInit, type X402Transport, type X402TransportConfig, createPaymentPayload, createX402Payment, createX402Transport, createX402V1Payment, createX402V2Payment, detectProtocolVersion, detectX402Version, encodeX402Payment, getPaymentHeaderName, parsePaymentRequired, parsePaymentRequirements, recoverEIP3009Signer, signEIP3009, signEIP3009WithDomain, signPayment };
package/dist/index.js CHANGED
@@ -1,28 +1,28 @@
1
1
  // src/index.ts
2
2
  import {
3
- V1_HEADERS as V1_HEADERS2,
4
- V2_HEADERS as V2_HEADERS2,
3
+ V1_HEADERS as V1_HEADERS3,
4
+ V2_HEADERS as V2_HEADERS3,
5
5
  isV1,
6
6
  isV2,
7
7
  getPaymentVersion,
8
8
  getRequirementsVersion,
9
9
  getSettlementVersion,
10
- getPaymentHeaderName,
10
+ getPaymentHeaderName as getPaymentHeaderName2,
11
11
  getPaymentResponseHeaderName,
12
12
  getPaymentRequiredHeaderName,
13
- isSettlementSuccessful,
13
+ isSettlementSuccessful as isSettlementSuccessful2,
14
14
  getTxHash,
15
15
  NETWORKS,
16
- getNetworkConfig,
16
+ getNetworkConfig as getNetworkConfig2,
17
17
  getNetworkByChainId as getNetworkByChainId2,
18
18
  getMainnets,
19
19
  getTestnets,
20
20
  ERC20_ABI,
21
- encodePaymentV1 as encodePaymentV12,
21
+ encodePaymentV1,
22
22
  decodePaymentV1,
23
23
  encodeSettlementV1,
24
24
  decodeSettlementV1,
25
- encodePaymentV2 as encodePaymentV22,
25
+ encodePaymentV2,
26
26
  decodePaymentV2,
27
27
  encodeSettlementV2,
28
28
  decodeSettlementV2,
@@ -33,30 +33,39 @@ import {
33
33
  isPaymentV2,
34
34
  isSettlementV1,
35
35
  isSettlementV2,
36
- EIP712_TYPES,
37
- createEIP712Domain as createEIP712Domain2,
38
- createTransferWithAuthorization,
36
+ isX402V1PaymentRequired as isX402V1PaymentRequired2,
37
+ isX402V2PaymentRequired as isX402V2PaymentRequired2,
38
+ isX402V1Requirements as isX402V1Requirements2,
39
+ EIP712_TYPES as EIP712_TYPES2,
40
+ createEIP712Domain as createEIP712Domain3,
41
+ createTransferWithAuthorization as createTransferWithAuthorization2,
39
42
  validateTransferWithAuthorization,
40
43
  isCAIP2ChainId,
41
44
  isCAIPAssetId,
42
- isAddress
45
+ safeBase64Decode as safeBase64Decode2
43
46
  } from "@armory-sh/base";
44
47
 
45
48
  // src/protocol.ts
46
49
  import {
47
- encodePaymentV1,
48
- encodePaymentV2,
49
50
  V1_HEADERS,
50
51
  V2_HEADERS,
51
- getNetworkByChainId
52
+ safeBase64Decode,
53
+ isX402V1PaymentRequired,
54
+ isX402V2PaymentRequired,
55
+ isX402V1Requirements,
56
+ getNetworkByChainId,
57
+ getNetworkConfig,
58
+ createEIP712Domain as createEIP712Domain2,
59
+ normalizeNetworkName
52
60
  } from "@armory-sh/base";
53
61
 
54
62
  // src/eip3009.ts
63
+ import { ethers } from "ethers";
55
64
  import {
56
65
  createEIP712Domain
57
66
  } from "@armory-sh/base";
58
67
 
59
- // src/types.ts
68
+ // src/errors.ts
60
69
  var X402ClientError = class extends Error {
61
70
  cause;
62
71
  constructor(message, cause) {
@@ -65,6 +74,18 @@ var X402ClientError = class extends Error {
65
74
  this.cause = cause;
66
75
  }
67
76
  };
77
+ var SigningError = class extends X402ClientError {
78
+ constructor(message, cause) {
79
+ super(`Signing failed: ${message}`, cause);
80
+ this.name = "SigningError";
81
+ }
82
+ };
83
+ var PaymentError = class extends X402ClientError {
84
+ constructor(message, cause) {
85
+ super(`Payment failed: ${message}`, cause);
86
+ this.name = "PaymentError";
87
+ }
88
+ };
68
89
  var SignerRequiredError = class extends X402ClientError {
69
90
  constructor(message = "Signer is required for this operation") {
70
91
  super(message);
@@ -77,6 +98,12 @@ var AuthorizationError = class extends X402ClientError {
77
98
  this.name = "AuthorizationError";
78
99
  }
79
100
  };
101
+ var ProviderRequiredError = class extends X402ClientError {
102
+ constructor(message = "Provider is required for this operation") {
103
+ super(message);
104
+ this.name = "ProviderRequiredError";
105
+ }
106
+ };
80
107
 
81
108
  // src/eip3009.ts
82
109
  var EIP712_TYPES_ETHERS = {
@@ -109,128 +136,256 @@ async function signEIP3009(signer, params, domain) {
109
136
  );
110
137
  }
111
138
  }
139
+ async function signEIP3009WithDomain(signer, params, chainId, verifyingContract, domainName, domainVersion) {
140
+ const domain = createEIP712Domain(chainId, verifyingContract);
141
+ const customDomain = domainName || domainVersion ? { ...domain, name: domainName ?? domain.name, version: domainVersion ?? domain.version } : domain;
142
+ return signEIP3009(signer, params, customDomain);
143
+ }
144
+ async function signPayment(signer, from, to, value, chainId, verifyingContract, expirySeconds = 3600, domainName, domainVersion) {
145
+ const now = Math.floor(Date.now() / 1e3);
146
+ const nonce = BigInt(now * 1e3);
147
+ const signature = await signEIP3009WithDomain(
148
+ signer,
149
+ { from, to, value, validAfter: 0n, validBefore: BigInt(now + expirySeconds), nonce },
150
+ chainId,
151
+ verifyingContract,
152
+ domainName,
153
+ domainVersion
154
+ );
155
+ return { ...signature, nonce };
156
+ }
157
+ async function recoverEIP3009Signer(params, signature, domain) {
158
+ const message = {
159
+ from: params.from,
160
+ to: params.to,
161
+ value: BigInt(params.value),
162
+ validAfter: BigInt(params.validAfter),
163
+ validBefore: BigInt(params.validBefore),
164
+ nonce: BigInt(params.nonce)
165
+ };
166
+ const sig = ethers.Signature.from({
167
+ v: signature.v,
168
+ r: signature.r,
169
+ s: signature.s
170
+ });
171
+ const address = ethers.verifyTypedData(domain, EIP712_TYPES_ETHERS, message, sig);
172
+ return address;
173
+ }
112
174
 
113
175
  // src/protocol.ts
114
- async function createPaymentPayloadV1(params) {
115
- const { signer, from, to, amount, nonce, expiry, chainId, contractAddress, network, domainName, domainVersion } = params;
116
- const authParams = {
117
- from,
118
- to,
119
- value: BigInt(Math.floor(parseFloat(amount) * 1e6)),
120
- validAfter: 0n,
121
- validBefore: BigInt(expiry),
122
- nonce: BigInt(nonce)
176
+ function detectX402Version(response) {
177
+ const v2Header = response.headers.get(V2_HEADERS.PAYMENT_REQUIRED);
178
+ if (v2Header) {
179
+ try {
180
+ const parsed = JSON.parse(v2Header);
181
+ if (parsed.x402Version === 2) return 2;
182
+ } catch {
183
+ }
184
+ }
185
+ const v1Header = response.headers.get(V1_HEADERS.PAYMENT_REQUIRED);
186
+ if (v1Header) {
187
+ try {
188
+ const decoded = safeBase64Decode(v1Header);
189
+ const parsed = JSON.parse(decoded);
190
+ if (parsed.x402Version === 1) return 1;
191
+ return 1;
192
+ } catch {
193
+ }
194
+ }
195
+ return 2;
196
+ }
197
+ function parsePaymentRequired(response) {
198
+ const version = detectX402Version(response);
199
+ if (version === 2) {
200
+ const v2Header = response.headers.get(V2_HEADERS.PAYMENT_REQUIRED);
201
+ if (!v2Header) {
202
+ throw new PaymentError("No PAYMENT-REQUIRED header found in 402 response");
203
+ }
204
+ try {
205
+ const parsed = JSON.parse(v2Header);
206
+ if (!isX402V2PaymentRequired(parsed)) {
207
+ throw new PaymentError("Invalid x402 V2 payment required format");
208
+ }
209
+ if (!parsed.accepts || parsed.accepts.length === 0) {
210
+ throw new PaymentError("No payment requirements found in accepts array");
211
+ }
212
+ return {
213
+ version: 2,
214
+ requirements: parsed.accepts[0]
215
+ };
216
+ } catch (error) {
217
+ if (error instanceof PaymentError) throw error;
218
+ throw new PaymentError(`Failed to parse V2 PAYMENT-REQUIRED header: ${error}`);
219
+ }
220
+ }
221
+ const v1Header = response.headers.get(V1_HEADERS.PAYMENT_REQUIRED);
222
+ if (!v1Header) {
223
+ throw new PaymentError("No X-PAYMENT-REQUIRED header found in 402 response");
224
+ }
225
+ try {
226
+ const decoded = safeBase64Decode(v1Header);
227
+ const parsed = JSON.parse(decoded);
228
+ if (!isX402V1PaymentRequired(parsed)) {
229
+ throw new PaymentError("Invalid x402 V1 payment required format");
230
+ }
231
+ if (!parsed.accepts || parsed.accepts.length === 0) {
232
+ throw new PaymentError("No payment requirements found in accepts array");
233
+ }
234
+ return {
235
+ version: 1,
236
+ requirements: parsed.accepts[0]
237
+ };
238
+ } catch (error) {
239
+ if (error instanceof PaymentError) throw error;
240
+ throw new PaymentError(`Failed to parse V1 PAYMENT-REQUIRED header: ${error}`);
241
+ }
242
+ }
243
+ function toAtomicUnits(amount) {
244
+ return Math.floor(parseFloat(amount) * 1e6).toString();
245
+ }
246
+ function extractChainId(network) {
247
+ if (network.startsWith("eip155:")) {
248
+ return parseInt(network.split(":")[1], 10);
249
+ }
250
+ const net = getNetworkConfig(normalizeNetworkName(network));
251
+ if (net) {
252
+ return net.chainId;
253
+ }
254
+ throw new PaymentError(`Unsupported network: ${network}`);
255
+ }
256
+ function getNetworkSlug(network) {
257
+ if (network.startsWith("eip155:")) {
258
+ const chainId = parseInt(network.split(":")[1], 10);
259
+ const net = getNetworkByChainId(chainId);
260
+ if (!net) {
261
+ throw new PaymentError(`No network config found for chainId: ${chainId}`);
262
+ }
263
+ return normalizeNetworkName(net.name);
264
+ }
265
+ return normalizeNetworkName(network);
266
+ }
267
+ function createNonce() {
268
+ const now = Math.floor(Date.now() / 1e3);
269
+ return `0x${(now * 1e3).toString(16).padStart(64, "0")}`;
270
+ }
271
+ async function createX402V1Payment(signer, requirements, fromAddress, nonce, validBefore, domainName, domainVersion) {
272
+ const network = getNetworkSlug(requirements.network);
273
+ const contractAddress = requirements.asset;
274
+ const chainId = extractChainId(requirements.network);
275
+ const domain = createEIP712Domain2(chainId, contractAddress);
276
+ const customDomain = domainName || domainVersion ? { ...domain, name: domainName ?? domain.name, version: domainVersion ?? domain.version } : domain;
277
+ const authorization = {
278
+ from: fromAddress,
279
+ to: requirements.payTo,
280
+ value: toAtomicUnits(requirements.maxAmountRequired),
281
+ validAfter: "0",
282
+ validBefore: validBefore.toString(),
283
+ nonce
123
284
  };
124
- const domain = {
125
- name: domainName ?? "USD Coin",
126
- version: domainVersion ?? "2",
127
- chainId,
128
- verifyingContract: contractAddress
285
+ const authParams = {
286
+ from: authorization.from,
287
+ to: authorization.to,
288
+ value: BigInt(authorization.value),
289
+ validAfter: BigInt(authorization.validAfter),
290
+ validBefore: BigInt(authorization.validBefore),
291
+ nonce: BigInt(authorization.nonce)
129
292
  };
130
- const signature = await signEIP3009(signer, authParams, domain);
293
+ const signature = await signEIP3009(signer, authParams, customDomain);
294
+ const combinedSignature = `0x${signature.r.slice(2)}${signature.s.slice(2)}${signature.v.toString(16).padStart(2, "0")}`;
131
295
  const payload = {
132
- from,
133
- to,
134
- amount,
135
- nonce,
136
- expiry,
137
- v: signature.v,
138
- r: signature.r,
139
- s: signature.s,
140
- chainId,
141
- contractAddress,
142
- network
296
+ signature: combinedSignature,
297
+ authorization
298
+ };
299
+ return {
300
+ x402Version: 1,
301
+ scheme: "exact",
302
+ network,
303
+ payload
143
304
  };
144
- return encodePaymentV1(payload);
145
305
  }
146
- async function createPaymentPayloadV2(params) {
147
- const { signer, from, to, amount, nonce, expiry, chainId, assetId, extensions, domainName, domainVersion } = params;
148
- const chainIdNum = parseInt(chainId.split(":")[1], 10);
149
- const contractAddress = assetId.split(":")[2];
150
- const authParams = {
151
- from,
152
- to: typeof to === "string" ? to : "0x0000000000000000000000000000000000000000",
153
- value: BigInt(Math.floor(parseFloat(amount) * 1e6)),
154
- validAfter: 0n,
155
- validBefore: BigInt(expiry),
156
- nonce: BigInt(nonce)
306
+ async function createX402V2Payment(signer, requirements, fromAddress, nonce, validBefore, domainName, domainVersion) {
307
+ const contractAddress = requirements.asset;
308
+ const chainId = extractChainId(requirements.network);
309
+ const domain = createEIP712Domain2(chainId, contractAddress);
310
+ const customDomain = domainName || domainVersion ? { ...domain, name: domainName ?? domain.name, version: domainVersion ?? domain.version } : domain;
311
+ const authorization = {
312
+ from: fromAddress,
313
+ to: requirements.payTo,
314
+ value: toAtomicUnits(requirements.amount),
315
+ validAfter: "0",
316
+ validBefore: validBefore.toString(),
317
+ nonce
157
318
  };
158
- const domain = {
159
- name: domainName ?? "USD Coin",
160
- version: domainVersion ?? "2",
161
- chainId: chainIdNum,
162
- verifyingContract: contractAddress
319
+ const authParams = {
320
+ from: authorization.from,
321
+ to: authorization.to,
322
+ value: BigInt(authorization.value),
323
+ validAfter: BigInt(authorization.validAfter),
324
+ validBefore: BigInt(authorization.validBefore),
325
+ nonce: BigInt(authorization.nonce)
163
326
  };
164
- const signature = await signEIP3009(signer, authParams, domain);
327
+ const signature = await signEIP3009(signer, authParams, customDomain);
328
+ const combinedSignature = `0x${signature.r.slice(2)}${signature.s.slice(2)}${signature.v.toString(16).padStart(2, "0")}`;
165
329
  const payload = {
166
- from,
167
- to,
168
- amount,
169
- nonce,
170
- expiry,
171
- signature: {
172
- v: signature.v,
173
- r: signature.r,
174
- s: signature.s
175
- },
176
- chainId,
177
- assetId,
178
- extensions
330
+ signature: combinedSignature,
331
+ authorization
332
+ };
333
+ return {
334
+ x402Version: 2,
335
+ accepted: requirements,
336
+ payload
179
337
  };
180
- return encodePaymentV2(payload);
181
- }
182
- function detectProtocolVersion(requirements) {
183
- if ("contractAddress" in requirements && "network" in requirements) {
184
- return 1;
185
- }
186
- return 2;
187
338
  }
188
- async function createPaymentPayload(requirements, signer, from) {
189
- const version = detectProtocolVersion(requirements);
190
- const now = Math.floor(Date.now() / 1e3);
191
- const nonce = now.toString();
192
- const expiry = now + 3600;
193
- if (version === 1) {
194
- const req2 = requirements;
195
- const networkConfig = getNetworkByChainId(
196
- parseInt(req2.contractAddress.slice(0, 10), 16)
197
- );
198
- const payload2 = await createPaymentPayloadV1({
339
+ async function createX402Payment(signer, parsed, fromAddress, nonce, validBefore, domainName, domainVersion) {
340
+ const effectiveNonce = nonce ?? createNonce();
341
+ const effectiveValidBefore = validBefore ?? Math.floor(Date.now() / 1e3) + 3600;
342
+ if (parsed.version === 1) {
343
+ return createX402V1Payment(
199
344
  signer,
200
- from,
201
- to: req2.payTo,
202
- amount: req2.amount,
203
- nonce,
204
- expiry,
205
- chainId: networkConfig?.chainId || 1,
206
- contractAddress: req2.contractAddress,
207
- network: req2.network
208
- });
209
- return [payload2, V1_HEADERS.PAYMENT];
345
+ parsed.requirements,
346
+ fromAddress,
347
+ effectiveNonce,
348
+ effectiveValidBefore,
349
+ domainName,
350
+ domainVersion
351
+ );
210
352
  }
211
- const req = requirements;
212
- const payload = await createPaymentPayloadV2({
353
+ return createX402V2Payment(
213
354
  signer,
214
- from,
215
- to: req.to,
216
- amount: req.amount,
217
- nonce,
218
- expiry,
219
- chainId: req.chainId,
220
- assetId: req.assetId
221
- });
222
- return [payload, V2_HEADERS.PAYMENT_SIGNATURE];
355
+ parsed.requirements,
356
+ fromAddress,
357
+ effectiveNonce,
358
+ effectiveValidBefore,
359
+ domainName,
360
+ domainVersion
361
+ );
362
+ }
363
+ function encodeX402Payment(payload) {
364
+ return Buffer.from(JSON.stringify(payload)).toString("base64");
365
+ }
366
+ function getPaymentHeaderName(version) {
367
+ return version === 1 ? V1_HEADERS.PAYMENT : V2_HEADERS.PAYMENT_SIGNATURE;
368
+ }
369
+ async function createPaymentPayload(requirements, signer, from) {
370
+ const parsed = isX402V1Requirements(requirements) ? { version: 1, requirements } : { version: 2, requirements };
371
+ const payload = await createX402Payment(signer, parsed, from);
372
+ const encoded = encodeX402Payment(payload);
373
+ return [encoded, getPaymentHeaderName(parsed.version)];
223
374
  }
224
375
  async function parsePaymentRequirements(response) {
225
- const v2Header = response.headers.get(V2_HEADERS.PAYMENT_REQUIRED);
226
- if (v2Header) {
227
- return JSON.parse(v2Header);
228
- }
229
- try {
230
- return await response.json();
231
- } catch {
232
- throw new Error("No payment requirements found in response");
376
+ const parsed = parsePaymentRequired(response);
377
+ return parsed.requirements;
378
+ }
379
+ function detectProtocolVersion(requirements) {
380
+ if (typeof requirements === "object" && requirements !== null) {
381
+ if ("x402Version" in requirements && requirements.x402Version === 2) {
382
+ return 2;
383
+ }
384
+ if ("chainId" in requirements && "assetId" in requirements && !("contractAddress" in requirements)) {
385
+ return 2;
386
+ }
233
387
  }
388
+ return 1;
234
389
  }
235
390
 
236
391
  // src/transport.ts
@@ -274,26 +429,29 @@ var handlePaymentRequired = async (state, response) => {
274
429
  throw new SignerRequiredError("Cannot handle payment: no signer configured.");
275
430
  }
276
431
  try {
277
- const requirements = await parsePaymentRequirements(response);
432
+ const parsed = parsePaymentRequired(response);
278
433
  const from = await state.signer.getAddress();
279
- const [payload, headerName] = await createPaymentPayload(requirements, state.signer, from);
434
+ const payload = await createX402Payment(state.signer, parsed, from);
435
+ const encoded = encodeX402Payment(payload);
436
+ const headerName = getPaymentHeaderName(parsed.version);
280
437
  const paymentResponse = await fetchWithTimeout(
281
438
  response.url,
282
439
  {
283
440
  method: "GET",
284
- headers: { ...state.config.headers, [headerName]: payload }
441
+ headers: { ...state.config.headers, [headerName]: encoded }
285
442
  },
286
443
  state.config.timeout
287
444
  );
288
- return { success: true, settlement: decodeSettlementLegacy(paymentResponse.headers) };
445
+ const settlement = decodeSettlementLegacy(paymentResponse.headers);
446
+ return { success: true, settlement };
289
447
  } catch (error) {
290
448
  return { success: false, error: error instanceof Error ? error : new Error(String(error)) };
291
449
  }
292
450
  };
293
451
  var shouldRetryPayment = async (state, response) => {
294
452
  if (!state.config.autoPay) return false;
295
- const requirements = await parsePaymentRequirements(response);
296
- return await state.config.onPaymentRequired(requirements);
453
+ const parsed = parsePaymentRequired(response);
454
+ return await state.config.onPaymentRequired(parsed.requirements);
297
455
  };
298
456
  var x402Fetch = async (state, url, init = {}) => {
299
457
  const fullUrl = state.config.baseURL ? new URL(url, state.config.baseURL).toString() : url;
@@ -345,17 +503,24 @@ var createX402Transport = (config) => {
345
503
  };
346
504
  };
347
505
  export {
348
- EIP712_TYPES,
506
+ AuthorizationError,
507
+ EIP712_TYPES2 as EIP712_TYPES,
349
508
  ERC20_ABI,
350
509
  NETWORKS,
351
- V1_HEADERS2 as V1_HEADERS,
352
- V2_HEADERS2 as V2_HEADERS,
353
- createEIP712Domain2 as createEIP712Domain,
510
+ PaymentError,
511
+ ProviderRequiredError,
512
+ SignerRequiredError,
513
+ SigningError,
514
+ V1_HEADERS3 as V1_HEADERS,
515
+ V2_HEADERS3 as V2_HEADERS,
516
+ X402ClientError,
517
+ createEIP712Domain3 as createEIP712Domain,
354
518
  createPaymentPayload,
355
- createPaymentPayloadV1,
356
- createPaymentPayloadV2,
357
- createTransferWithAuthorization,
519
+ createTransferWithAuthorization2 as createTransferWithAuthorization,
520
+ createX402Payment,
358
521
  createX402Transport,
522
+ createX402V1Payment,
523
+ createX402V2Payment,
359
524
  decodePayment,
360
525
  decodePaymentV1,
361
526
  decodePaymentV2,
@@ -364,14 +529,17 @@ export {
364
529
  decodeSettlementV2,
365
530
  detectPaymentVersion,
366
531
  detectProtocolVersion,
367
- encodePaymentV12 as encodePaymentV1,
368
- encodePaymentV22 as encodePaymentV2,
532
+ detectX402Version,
533
+ encodePaymentV1,
534
+ encodePaymentV2,
369
535
  encodeSettlementV1,
370
536
  encodeSettlementV2,
537
+ encodeX402Payment,
371
538
  getMainnets,
372
539
  getNetworkByChainId2 as getNetworkByChainId,
373
- getNetworkConfig,
540
+ getNetworkConfig2 as getNetworkConfig,
374
541
  getPaymentHeaderName,
542
+ getPaymentHeaderName2 as getPaymentHeaderNameBase,
375
543
  getPaymentRequiredHeaderName,
376
544
  getPaymentResponseHeaderName,
377
545
  getPaymentVersion,
@@ -379,16 +547,24 @@ export {
379
547
  getSettlementVersion,
380
548
  getTestnets,
381
549
  getTxHash,
382
- isAddress,
383
550
  isCAIP2ChainId,
384
551
  isCAIPAssetId,
385
552
  isPaymentV1,
386
553
  isPaymentV2,
387
- isSettlementSuccessful,
554
+ isSettlementSuccessful2 as isSettlementSuccessful,
388
555
  isSettlementV1,
389
556
  isSettlementV2,
390
557
  isV1,
391
558
  isV2,
559
+ isX402V1PaymentRequired2 as isX402V1PaymentRequired,
560
+ isX402V1Requirements2 as isX402V1Requirements,
561
+ isX402V2PaymentRequired2 as isX402V2PaymentRequired,
562
+ parsePaymentRequired,
392
563
  parsePaymentRequirements,
564
+ recoverEIP3009Signer,
565
+ safeBase64Decode2 as safeBase64Decode,
566
+ signEIP3009,
567
+ signEIP3009WithDomain,
568
+ signPayment,
393
569
  validateTransferWithAuthorization
394
570
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@armory-sh/client-ethers",
3
- "version": "0.2.9",
3
+ "version": "0.2.12",
4
4
  "license": "MIT",
5
5
  "author": "Sawyer Cutler <sawyer@dirtroad.dev>",
6
6
  "type": "module",
@@ -27,7 +27,7 @@
27
27
  "directory": "packages/client-ethers"
28
28
  },
29
29
  "dependencies": {
30
- "@armory-sh/base": "^0.2.9",
30
+ "@armory-sh/base": "^0.2.12",
31
31
  "ethers": "6.16.0"
32
32
  },
33
33
  "devDependencies": {