@armory-sh/client-ethers 0.2.9 → 0.2.13

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 +164 -36
  2. package/dist/index.js +347 -137
  3. package/package.json +3 -3
package/dist/index.d.ts CHANGED
@@ -1,40 +1,80 @@
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
+ interface ParsedPaymentRequirements {
18
+ version: 1 | 2;
19
+ requirements: X402PaymentRequirementsV1 | PaymentRequirementsV2;
20
+ }
21
+ /**
22
+ * Parse x402 PAYMENT-REQUIRED header from response
23
+ * Automatically detects V1 or V2 format
24
+ */
25
+ declare function parsePaymentRequired(response: Response): ParsedPaymentRequirements;
26
+ /**
27
+ * Create x402 V1 payment payload
28
+ */
29
+ declare function createX402V1Payment(signer: Signer, requirements: X402PaymentRequirementsV1, fromAddress: Address, nonce: `0x${string}`, validBefore: number, domainName?: string, domainVersion?: string): Promise<X402PaymentPayloadV1>;
30
+ /**
31
+ * Create x402 V2 payment payload
32
+ */
33
+ declare function createX402V2Payment(signer: Signer, requirements: PaymentRequirementsV2, fromAddress: Address, nonce: `0x${string}`, validBefore: number, domainName?: string, domainVersion?: string): Promise<PaymentPayloadV2>;
34
+ /**
35
+ * Create x402 payment payload (auto-detects version)
36
+ */
37
+ declare function createX402Payment(signer: Signer, parsed: ParsedPaymentRequirements, fromAddress: Address, nonce?: `0x${string}`, validBefore?: number, domainName?: string, domainVersion?: string): Promise<X402PaymentPayloadV1 | PaymentPayloadV2>;
38
+ /**
39
+ * Encode x402 payment payload to Base64 for transport
40
+ */
41
+ declare function encodeX402Payment(payload: X402PaymentPayloadV1 | PaymentPayloadV2): string;
42
+ /**
43
+ * Get the correct header name for payment based on version
44
+ */
45
+ declare function getPaymentHeaderName(version: 1 | 2): string;
46
+ /**
47
+ * @deprecated Use createX402Payment instead
48
+ */
49
+ declare function createPaymentPayload(requirements: unknown, signer: Signer, from: string): Promise<[string, string]>;
50
+ /**
51
+ * @deprecated Use parsePaymentRequired instead
52
+ */
53
+ declare function parsePaymentRequirements(response: Response): Promise<unknown>;
54
+ /**
55
+ * @deprecated Use detectX402Version instead
56
+ */
57
+ declare function detectProtocolVersion(requirements: unknown): 1 | 2;
58
+
59
+ declare class X402ClientError extends Error {
60
+ readonly cause?: unknown;
61
+ constructor(message: string, cause?: unknown);
62
+ }
63
+ declare class SigningError extends X402ClientError {
64
+ constructor(message: string, cause?: unknown);
65
+ }
66
+ declare class PaymentError extends X402ClientError {
67
+ constructor(message: string, cause?: unknown);
68
+ }
69
+ declare class SignerRequiredError extends X402ClientError {
70
+ constructor(message?: string);
71
+ }
72
+ declare class AuthorizationError extends X402ClientError {
73
+ constructor(message: string, cause?: unknown);
74
+ }
75
+ declare class ProviderRequiredError extends X402ClientError {
76
+ constructor(message?: string);
77
+ }
38
78
 
39
79
  /** Token configuration - can use pre-configured tokens from @armory-sh/tokens */
40
80
  type Token = CustomToken;
@@ -76,6 +116,20 @@ interface X402RequestInit extends Omit<RequestInit, "headers"> {
76
116
  skipAutoPay?: boolean;
77
117
  forceProtocolVersion?: 1 | 2;
78
118
  }
119
+ interface TransferWithAuthorizationParams {
120
+ from: `0x${string}`;
121
+ to: `0x${string}`;
122
+ value: bigint | number;
123
+ validAfter: bigint | number;
124
+ validBefore: bigint | number;
125
+ nonce: bigint | number;
126
+ }
127
+ interface EIP712Domain {
128
+ name: string;
129
+ version: string;
130
+ chainId: number;
131
+ verifyingContract: `0x${string}`;
132
+ }
79
133
 
80
134
  interface X402Transport {
81
135
  fetch(url: string, init?: X402RequestInit): Promise<Response>;
@@ -89,4 +143,78 @@ interface X402Transport {
89
143
  }
90
144
  declare const createX402Transport: (config?: X402TransportConfig) => X402Transport;
91
145
 
92
- export { type ClientConfig, type X402ClientConfig, createPaymentPayload, createPaymentPayloadV1, createPaymentPayloadV2, createX402Transport, detectProtocolVersion, parsePaymentRequirements };
146
+ declare function signEIP3009(signer: Signer, params: TransferWithAuthorizationParams, domain: EIP712Domain): Promise<{
147
+ v: number;
148
+ r: string;
149
+ s: string;
150
+ }>;
151
+ declare function signEIP3009WithDomain(signer: Signer, params: TransferWithAuthorizationParams, chainId: number, verifyingContract: `0x${string}`, domainName?: string, domainVersion?: string): Promise<{
152
+ v: number;
153
+ r: string;
154
+ s: string;
155
+ }>;
156
+ 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<{
157
+ v: number;
158
+ r: string;
159
+ s: string;
160
+ nonce: bigint;
161
+ }>;
162
+ declare function recoverEIP3009Signer(params: TransferWithAuthorizationParams, signature: {
163
+ v: number;
164
+ r: string;
165
+ s: string;
166
+ }, domain: EIP712Domain): Promise<`0x${string}`>;
167
+
168
+ /**
169
+ * X402 Client Factory for Ethers
170
+ *
171
+ * Creates a configured client instance for making X-402 payments
172
+ */
173
+
174
+ /**
175
+ * X402 Client instance
176
+ * Provides a high-level API for making X-402 authenticated requests
177
+ */
178
+ interface X402Client {
179
+ /**
180
+ * Make a GET request
181
+ */
182
+ get(url: string, init?: X402RequestInit): Promise<Response>;
183
+ /**
184
+ * Make a POST request
185
+ */
186
+ post(url: string, body?: unknown, init?: X402RequestInit): Promise<Response>;
187
+ /**
188
+ * Make a PUT request
189
+ */
190
+ put(url: string, body?: unknown, init?: X402RequestInit): Promise<Response>;
191
+ /**
192
+ * Make a DELETE request
193
+ */
194
+ del(url: string, init?: X402RequestInit): Promise<Response>;
195
+ /**
196
+ * Make a PATCH request
197
+ */
198
+ patch(url: string, body?: unknown, init?: X402RequestInit): Promise<Response>;
199
+ /**
200
+ * Set the signer for payment signing
201
+ */
202
+ setSigner(signer: Signer): void;
203
+ /**
204
+ * Get the current signer
205
+ */
206
+ getSigner(): Signer | undefined;
207
+ }
208
+
209
+ /**
210
+ * Create an X402 client
211
+ *
212
+ * @param config - Client configuration
213
+ * @returns Configured X402 client
214
+ */
215
+ declare function createX402Client(config: X402ClientConfig & SignerClientConfig & {
216
+ signer: Signer;
217
+ }): X402Client;
218
+ declare function createX402Client(config: X402ClientConfig & ProviderClientConfig): X402Client;
219
+
220
+ export { AuthorizationError, type ClientConfig, type ParsedPaymentRequirements, PaymentError, ProviderRequiredError, SignerRequiredError, SigningError, type X402Client, type X402ClientConfig, X402ClientError, type X402RequestInit, type X402Transport, type X402TransportConfig, createPaymentPayload, createX402Client, 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,257 @@ 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
+ scheme: requirements.scheme,
336
+ network: requirements.network,
337
+ payload
179
338
  };
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
339
  }
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({
340
+ async function createX402Payment(signer, parsed, fromAddress, nonce, validBefore, domainName, domainVersion) {
341
+ const effectiveNonce = nonce ?? createNonce();
342
+ const effectiveValidBefore = validBefore ?? Math.floor(Date.now() / 1e3) + 3600;
343
+ if (parsed.version === 1) {
344
+ return createX402V1Payment(
199
345
  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];
346
+ parsed.requirements,
347
+ fromAddress,
348
+ effectiveNonce,
349
+ effectiveValidBefore,
350
+ domainName,
351
+ domainVersion
352
+ );
210
353
  }
211
- const req = requirements;
212
- const payload = await createPaymentPayloadV2({
354
+ return createX402V2Payment(
213
355
  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];
356
+ parsed.requirements,
357
+ fromAddress,
358
+ effectiveNonce,
359
+ effectiveValidBefore,
360
+ domainName,
361
+ domainVersion
362
+ );
363
+ }
364
+ function encodeX402Payment(payload) {
365
+ return Buffer.from(JSON.stringify(payload)).toString("base64");
366
+ }
367
+ function getPaymentHeaderName(version) {
368
+ return version === 1 ? V1_HEADERS.PAYMENT : V2_HEADERS.PAYMENT_SIGNATURE;
369
+ }
370
+ async function createPaymentPayload(requirements, signer, from) {
371
+ const parsed = isX402V1Requirements(requirements) ? { version: 1, requirements } : { version: 2, requirements };
372
+ const payload = await createX402Payment(signer, parsed, from);
373
+ const encoded = encodeX402Payment(payload);
374
+ return [encoded, getPaymentHeaderName(parsed.version)];
223
375
  }
224
376
  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");
377
+ const parsed = parsePaymentRequired(response);
378
+ return parsed.requirements;
379
+ }
380
+ function detectProtocolVersion(requirements) {
381
+ if (typeof requirements === "object" && requirements !== null) {
382
+ if ("x402Version" in requirements && requirements.x402Version === 2) {
383
+ return 2;
384
+ }
385
+ if ("chainId" in requirements && "assetId" in requirements && !("contractAddress" in requirements)) {
386
+ return 2;
387
+ }
233
388
  }
389
+ return 1;
234
390
  }
235
391
 
236
392
  // src/transport.ts
@@ -274,26 +430,29 @@ var handlePaymentRequired = async (state, response) => {
274
430
  throw new SignerRequiredError("Cannot handle payment: no signer configured.");
275
431
  }
276
432
  try {
277
- const requirements = await parsePaymentRequirements(response);
433
+ const parsed = parsePaymentRequired(response);
278
434
  const from = await state.signer.getAddress();
279
- const [payload, headerName] = await createPaymentPayload(requirements, state.signer, from);
435
+ const payload = await createX402Payment(state.signer, parsed, from);
436
+ const encoded = encodeX402Payment(payload);
437
+ const headerName = getPaymentHeaderName(parsed.version);
280
438
  const paymentResponse = await fetchWithTimeout(
281
439
  response.url,
282
440
  {
283
441
  method: "GET",
284
- headers: { ...state.config.headers, [headerName]: payload }
442
+ headers: { ...state.config.headers, [headerName]: encoded }
285
443
  },
286
444
  state.config.timeout
287
445
  );
288
- return { success: true, settlement: decodeSettlementLegacy(paymentResponse.headers) };
446
+ const settlement = decodeSettlementLegacy(paymentResponse.headers);
447
+ return { success: true, settlement };
289
448
  } catch (error) {
290
449
  return { success: false, error: error instanceof Error ? error : new Error(String(error)) };
291
450
  }
292
451
  };
293
452
  var shouldRetryPayment = async (state, response) => {
294
453
  if (!state.config.autoPay) return false;
295
- const requirements = await parsePaymentRequirements(response);
296
- return await state.config.onPaymentRequired(requirements);
454
+ const parsed = parsePaymentRequired(response);
455
+ return await state.config.onPaymentRequired(parsed.requirements);
297
456
  };
298
457
  var x402Fetch = async (state, url, init = {}) => {
299
458
  const fullUrl = state.config.baseURL ? new URL(url, state.config.baseURL).toString() : url;
@@ -344,18 +503,58 @@ var createX402Transport = (config) => {
344
503
  getSigner: () => state.signer
345
504
  };
346
505
  };
506
+
507
+ // src/client.ts
508
+ function createX402Client(config) {
509
+ const transport = createX402Transport(void 0);
510
+ if ("signer" in config && config.signer) {
511
+ transport.setSigner(config.signer);
512
+ } else if ("provider" in config && config.provider) {
513
+ const provider = config.provider;
514
+ if (provider.getSigner) {
515
+ try {
516
+ const signer = provider.getSigner();
517
+ if (signer) {
518
+ transport.setSigner(signer);
519
+ }
520
+ } catch {
521
+ }
522
+ }
523
+ } else {
524
+ throw new SignerRequiredError(
525
+ "Either 'signer' or 'provider' with getSigner() must be provided"
526
+ );
527
+ }
528
+ return {
529
+ get: (url, init) => transport.get(url, init),
530
+ post: (url, body, init) => transport.post(url, body, init),
531
+ put: (url, body, init) => transport.put(url, body, init),
532
+ del: (url, init) => transport.del(url, init),
533
+ patch: (url, body, init) => transport.patch(url, body, init),
534
+ setSigner: (signer) => transport.setSigner(signer),
535
+ getSigner: () => transport.getSigner()
536
+ };
537
+ }
347
538
  export {
348
- EIP712_TYPES,
539
+ AuthorizationError,
540
+ EIP712_TYPES2 as EIP712_TYPES,
349
541
  ERC20_ABI,
350
542
  NETWORKS,
351
- V1_HEADERS2 as V1_HEADERS,
352
- V2_HEADERS2 as V2_HEADERS,
353
- createEIP712Domain2 as createEIP712Domain,
543
+ PaymentError,
544
+ ProviderRequiredError,
545
+ SignerRequiredError,
546
+ SigningError,
547
+ V1_HEADERS3 as V1_HEADERS,
548
+ V2_HEADERS3 as V2_HEADERS,
549
+ X402ClientError,
550
+ createEIP712Domain3 as createEIP712Domain,
354
551
  createPaymentPayload,
355
- createPaymentPayloadV1,
356
- createPaymentPayloadV2,
357
- createTransferWithAuthorization,
552
+ createTransferWithAuthorization2 as createTransferWithAuthorization,
553
+ createX402Client,
554
+ createX402Payment,
358
555
  createX402Transport,
556
+ createX402V1Payment,
557
+ createX402V2Payment,
359
558
  decodePayment,
360
559
  decodePaymentV1,
361
560
  decodePaymentV2,
@@ -364,14 +563,17 @@ export {
364
563
  decodeSettlementV2,
365
564
  detectPaymentVersion,
366
565
  detectProtocolVersion,
367
- encodePaymentV12 as encodePaymentV1,
368
- encodePaymentV22 as encodePaymentV2,
566
+ detectX402Version,
567
+ encodePaymentV1,
568
+ encodePaymentV2,
369
569
  encodeSettlementV1,
370
570
  encodeSettlementV2,
571
+ encodeX402Payment,
371
572
  getMainnets,
372
573
  getNetworkByChainId2 as getNetworkByChainId,
373
- getNetworkConfig,
574
+ getNetworkConfig2 as getNetworkConfig,
374
575
  getPaymentHeaderName,
576
+ getPaymentHeaderName2 as getPaymentHeaderNameBase,
375
577
  getPaymentRequiredHeaderName,
376
578
  getPaymentResponseHeaderName,
377
579
  getPaymentVersion,
@@ -379,16 +581,24 @@ export {
379
581
  getSettlementVersion,
380
582
  getTestnets,
381
583
  getTxHash,
382
- isAddress,
383
584
  isCAIP2ChainId,
384
585
  isCAIPAssetId,
385
586
  isPaymentV1,
386
587
  isPaymentV2,
387
- isSettlementSuccessful,
588
+ isSettlementSuccessful2 as isSettlementSuccessful,
388
589
  isSettlementV1,
389
590
  isSettlementV2,
390
591
  isV1,
391
592
  isV2,
593
+ isX402V1PaymentRequired2 as isX402V1PaymentRequired,
594
+ isX402V1Requirements2 as isX402V1Requirements,
595
+ isX402V2PaymentRequired2 as isX402V2PaymentRequired,
596
+ parsePaymentRequired,
392
597
  parsePaymentRequirements,
598
+ recoverEIP3009Signer,
599
+ safeBase64Decode2 as safeBase64Decode,
600
+ signEIP3009,
601
+ signEIP3009WithDomain,
602
+ signPayment,
393
603
  validateTransferWithAuthorization
394
604
  };
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.13",
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.13",
31
31
  "ethers": "6.16.0"
32
32
  },
33
33
  "devDependencies": {
@@ -35,7 +35,7 @@
35
35
  "bun-types": "latest"
36
36
  },
37
37
  "scripts": {
38
- "build": "tsup",
38
+ "build": "rm -rf dist && tsup",
39
39
  "test": "bun test",
40
40
  "example": "bun run examples/"
41
41
  }