@armory-sh/client-ethers 0.2.27 → 0.2.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,42 +8,63 @@ Armory x402 SDK — Payment client for ethers.js v6. Make payments from any ethe
8
8
 
9
9
  ```bash
10
10
  bun add @armory-sh/client-ethers
11
+ bun add @armory-sh/client-hooks # optional preference/logger hooks
11
12
  ```
12
13
 
13
14
  ## Why Armory?
14
15
 
15
16
  Armory enables HTTP API payments via EIP-3009 `transferWithAuthorization`. Let your users pay with USDC directly from their wallet—no credit cards, no middlemen, no gas for payers.
16
17
 
17
- ## Key Exports
18
+ ## API Reference
19
+
20
+ ### Client Creation
18
21
 
19
22
  ```typescript
20
23
  import {
21
- // Client Creation
22
24
  createX402Client,
23
25
  createX402Transport,
24
26
 
25
- // Protocol
27
+ // Types
28
+ type X402Client,
29
+ type X402ClientConfig,
30
+ type X402TransportConfig,
31
+ } from '@armory-sh/client-ethers';
32
+ ```
33
+
34
+ ### Protocol Detection & Parsing
35
+
36
+ ```typescript
37
+ import {
26
38
  detectX402Version,
27
39
  parsePaymentRequired,
40
+ } from '@armory-sh/client-ethers';
41
+ ```
42
+
43
+ ### Signing Functions
28
44
 
29
- // Signing
45
+ ```typescript
46
+ import {
30
47
  signPayment,
31
48
  signEIP3009,
32
49
  recoverEIP3009Signer,
33
50
 
34
51
  // Types
35
- type X402Client,
36
- type X402ClientConfig,
37
- type X402TransportConfig,
38
52
  type SignPaymentOptions,
53
+ } from '@armory-sh/client-ethers';
54
+ ```
55
+
56
+ ### Error Classes
39
57
 
40
- // Errors
58
+ ```typescript
59
+ import {
41
60
  X402ClientError,
42
61
  SigningError,
43
62
  PaymentError,
44
63
  } from '@armory-sh/client-ethers';
45
64
  ```
46
65
 
66
+ ---
67
+
47
68
  ## Quick Start
48
69
 
49
70
  ```typescript
@@ -60,9 +81,30 @@ const response = await client.fetch('https://api.example.com/protected')
60
81
  const data = await response.json()
61
82
  ```
62
83
 
84
+ ## Hook Pipeline
85
+
86
+ ```typescript
87
+ import { createX402Client } from '@armory-sh/client-ethers'
88
+ import { PaymentPreference, Logger } from '@armory-sh/client-hooks'
89
+
90
+ const client = createX402Client({
91
+ signer,
92
+ hooks: [
93
+ PaymentPreference.chain(['base', 'ethereum', 'skale-base']),
94
+ PaymentPreference.token(['USDT', 'USDC', 'WBTC']),
95
+ PaymentPreference.cheapest(),
96
+ Logger.console(),
97
+ ],
98
+ })
99
+ ```
100
+
101
+ `parsePaymentRequired` returns `accepts[]` (x402 v2 challenge options). Clients select from this list.
102
+ `hooks` are lifecycle callbacks. `extensions` are protocol payload fields. Hooks can drive selection and payload behavior, but they are not extensions.
103
+
63
104
  ## Features
64
105
 
65
106
  - **Auto 402 Handling**: Automatically intercepts and pays for 402 responses
107
+ - **Detailed Verification Errors**: 402 retry failures include server details (for example `insufficient_funds`)
66
108
  - **EIP-3009 Signing**: Full support for EIP-3009 TransferWithAuthorization
67
109
  - **Multi-Network**: Ethereum, Base, SKALE support
68
110
  - **Multi-Token**: USDC, EURC, USDT, WBTC, WETH, SKL
@@ -79,6 +121,7 @@ const data = await response.json()
79
121
  | Base Sepolia | 84532 |
80
122
  | SKALE Base | 1187947933 |
81
123
  | SKALE Base Sepolia | 324705682 |
124
+ | Ethereum Sepolia | 11155111 |
82
125
 
83
126
  ## License
84
127
 
package/dist/index.d.ts CHANGED
@@ -1,52 +1,121 @@
1
- import { PaymentRequirementsV2, Address, PaymentPayloadV2, CustomToken, NetworkId, TokenId, ArmoryPaymentResult, ValidationError } from '@armory-sh/base';
2
- export { Address, BalanceOfParams, CAIP2ChainId, CAIPAssetId, EIP712_TYPES, ERC20_ABI, Extensions, NETWORKS, NetworkConfig, PayToV2, PaymentPayload, PaymentPayloadV2, PaymentRequirements, PaymentRequirementsV2, SettlementResponse, SettlementResponseV2, Signature, TransferWithAuthorizationParams, V2_HEADERS, createEIP712Domain, createTransferWithAuthorization, decodePayment, decodePaymentV2, decodeSettlementV2, detectPaymentVersion, encodePaymentV2, encodeSettlementV2, getMainnets, getNetworkByChainId, getNetworkConfig, getTestnets, getTxHash, isCAIP2ChainId, isCAIPAssetId, isPaymentV2, isSettlementSuccessful, isSettlementV2, isX402V2PaymentRequired, safeBase64Decode, validateTransferWithAuthorization } from '@armory-sh/base';
1
+ import { NetworkId, TokenId, ArmoryPaymentResult, ValidationError, X402ClientError, CustomToken, PaymentRequirementsV2, Address, PaymentPayloadV2 } from '@armory-sh/base';
2
+ export { PaymentException as PaymentError, SigningError, X402ClientError } from '@armory-sh/base';
3
3
  import { Signer, Provider } from 'ethers';
4
4
  export { Provider, Signer } from 'ethers';
5
+ import { ClientHook } from '@armory-sh/base/types/hooks';
5
6
 
6
7
  /**
7
- * X402 Protocol Implementation for Ethers Client (V2 Only)
8
- *
9
- * Handles parsing x402 V2 PAYMENT-REQUIRED headers
10
- * and generating x402 V2 PAYMENT-SIGNATURE payloads
8
+ * Simple one-line payment API for Armory (Ethers)
9
+ * Focus on DX/UX - "everything just magically works"
11
10
  */
12
11
 
13
12
  /**
14
- * Detect x402 protocol version from response headers
15
- * V2-only: Always returns 2
13
+ * Simple wallet input - accepts wallet directly or wrapped for backward compatibility
16
14
  */
17
- declare function detectX402Version(_response: Response): 2;
15
+ type SimpleWalletInput = Signer | {
16
+ signer: Signer;
17
+ };
18
18
  /**
19
- * Get payment header name for protocol version
19
+ * Normalized wallet (internal use)
20
20
  */
21
- declare function getPaymentHeaderName(_version: 2): string;
22
- interface ParsedPaymentRequirements {
23
- version: 2;
24
- requirements: PaymentRequirementsV2;
25
- }
21
+ type NormalizedWallet = Signer;
26
22
  /**
27
- * Parse x402 PAYMENT-REQUIRED header from response
28
- * V2 only
23
+ * Normalize wallet input to internal format
29
24
  */
30
- declare function parsePaymentRequired(response: Response): ParsedPaymentRequirements;
25
+ declare const normalizeWallet: (wallet: SimpleWalletInput) => NormalizedWallet;
31
26
  /**
32
- * Create x402 payment payload (V2-only wrapper)
27
+ * Make a payment-protected API request with one line of code
28
+ */
29
+ declare const armoryPay: <T = unknown>(wallet: SimpleWalletInput, url: string, network: NetworkId, token: TokenId, options?: {
30
+ /** Request method (default: GET) */
31
+ method?: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
32
+ /** Request body (for POST/PUT/PATCH) */
33
+ body?: unknown;
34
+ /** Request headers */
35
+ headers?: Record<string, string>;
36
+ /** Protocol version (V2 only) */
37
+ version?: 2;
38
+ /** Payment amount in token units (default: from 402 header) */
39
+ amount?: string;
40
+ /** Enable debug logging */
41
+ debug?: boolean;
42
+ }) => Promise<ArmoryPaymentResult<T>>;
43
+ /**
44
+ * Make a GET request with payment
33
45
  */
34
- declare function createX402Payment(signer: Signer, parsed: ParsedPaymentRequirements, fromAddress: Address, nonce?: `0x${string}`, validBefore?: number, domainName?: string, domainVersion?: string): Promise<PaymentPayloadV2>;
46
+ declare const armoryGet: <T = unknown>(wallet: SimpleWalletInput, url: string, network: NetworkId, token: TokenId, options?: Omit<Parameters<typeof armoryPay>[4], "method">) => Promise<ArmoryPaymentResult<T>>;
35
47
  /**
36
- * Encode x402 payment payload to Base64 for transport
48
+ * Make a POST request with payment
37
49
  */
38
- declare function encodeX402Payment(payload: PaymentPayloadV2): string;
50
+ declare const armoryPost: <T = unknown>(wallet: SimpleWalletInput, url: string, network: NetworkId, token: TokenId, body?: unknown, options?: Omit<Parameters<typeof armoryPay>[4], "method" | "body">) => Promise<ArmoryPaymentResult<T>>;
51
+ /**
52
+ * Make a PUT request with payment
53
+ */
54
+ declare const armoryPut: <T = unknown>(wallet: SimpleWalletInput, url: string, network: NetworkId, token: TokenId, body?: unknown, options?: Omit<Parameters<typeof armoryPay>[4], "method" | "body">) => Promise<ArmoryPaymentResult<T>>;
55
+ /**
56
+ * Make a DELETE request with payment
57
+ */
58
+ declare const armoryDelete: <T = unknown>(wallet: SimpleWalletInput, url: string, network: NetworkId, token: TokenId, options?: Omit<Parameters<typeof armoryPay>[4], "method">) => Promise<ArmoryPaymentResult<T>>;
59
+ /**
60
+ * Make a PATCH request with payment
61
+ */
62
+ declare const armoryPatch: <T = unknown>(wallet: SimpleWalletInput, url: string, network: NetworkId, token: TokenId, body?: unknown, options?: Omit<Parameters<typeof armoryPay>[4], "method" | "body">) => Promise<ArmoryPaymentResult<T>>;
63
+ /**
64
+ * Get the wallet address from a SimpleWalletInput
65
+ */
66
+ declare const getWalletAddress: (wallet: SimpleWalletInput) => Promise<string>;
67
+ /**
68
+ * Validate a network identifier without making a request
69
+ */
70
+ declare const validateNetwork: (network: NetworkId) => ValidationError | {
71
+ success: true;
72
+ network: string;
73
+ };
74
+ /**
75
+ * Validate a token identifier without making a request
76
+ */
77
+ declare const validateToken: (token: TokenId, network?: NetworkId) => ValidationError | {
78
+ success: true;
79
+ token: string;
80
+ network: string;
81
+ };
82
+ /**
83
+ * Get list of available networks
84
+ */
85
+ declare const getNetworks: () => string[];
86
+ /**
87
+ * Get list of available tokens
88
+ */
89
+ declare const getTokens: () => string[];
39
90
 
40
- declare class X402ClientError extends Error {
41
- readonly cause?: unknown;
42
- constructor(message: string, cause?: unknown);
91
+ /**
92
+ * Armory API - Simplified payment interface (Ethers)
93
+ * Provides a configurable object with method-based payment functions
94
+ */
95
+
96
+ type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
97
+ interface ArmoryConfig {
98
+ wallet: SimpleWalletInput;
99
+ methods?: HttpMethod[] | HttpMethod;
100
+ tokens?: TokenId[] | TokenId;
101
+ chains?: NetworkId[] | NetworkId;
102
+ debug?: boolean;
43
103
  }
44
- declare class SigningError extends X402ClientError {
45
- constructor(message: string, cause?: unknown);
104
+ interface PaymentOptions {
105
+ method?: HttpMethod;
106
+ body?: unknown;
46
107
  }
47
- declare class PaymentError extends X402ClientError {
48
- constructor(message: string, cause?: unknown);
108
+ interface ArmoryInstance {
109
+ get<T>(url: string, body?: unknown): Promise<ArmoryPaymentResult<T>>;
110
+ post<T>(url: string, body?: unknown): Promise<ArmoryPaymentResult<T>>;
111
+ put<T>(url: string, body?: unknown): Promise<ArmoryPaymentResult<T>>;
112
+ delete<T>(url: string): Promise<ArmoryPaymentResult<T>>;
113
+ patch<T>(url: string, body?: unknown): Promise<ArmoryPaymentResult<T>>;
114
+ pay<T>(url: string, options?: PaymentOptions): Promise<ArmoryPaymentResult<T>>;
115
+ call<T>(url: string): Promise<ArmoryPaymentResult<T>>;
49
116
  }
117
+ declare const createArmory: (config: ArmoryConfig) => ArmoryInstance;
118
+
50
119
  declare class SignerRequiredError extends X402ClientError {
51
120
  constructor(message?: string);
52
121
  }
@@ -72,6 +141,7 @@ interface X402ClientConfig {
72
141
  domainName?: string;
73
142
  /** Override EIP-712 domain version for custom tokens */
74
143
  domainVersion?: string;
144
+ hooks?: ClientHook<Signer>[];
75
145
  }
76
146
  interface SignerClientConfig extends X402ClientConfig {
77
147
  signer: Signer;
@@ -91,6 +161,7 @@ interface X402TransportConfig {
91
161
  onPaymentRequired?: (requirements: unknown) => boolean | Promise<boolean>;
92
162
  onPaymentSuccess?: (settlement: unknown) => void;
93
163
  onPaymentError?: (error: Error) => void;
164
+ hooks?: ClientHook<Signer>[];
94
165
  }
95
166
  interface X402RequestInit extends Omit<RequestInit, "headers"> {
96
167
  headers?: Record<string, string> | Headers;
@@ -112,40 +183,6 @@ interface EIP712Domain {
112
183
  verifyingContract: `0x${string}`;
113
184
  }
114
185
 
115
- interface X402Transport {
116
- fetch(url: string, init?: X402RequestInit): Promise<Response>;
117
- get(url: string, init?: X402RequestInit): Promise<Response>;
118
- post(url: string, body?: unknown, init?: X402RequestInit): Promise<Response>;
119
- put(url: string, body?: unknown, init?: X402RequestInit): Promise<Response>;
120
- del(url: string, init?: X402RequestInit): Promise<Response>;
121
- patch(url: string, body?: unknown, init?: X402RequestInit): Promise<Response>;
122
- setSigner(signer: Signer): void;
123
- getSigner(): Signer | undefined;
124
- }
125
- declare const createX402Transport: (config?: X402TransportConfig) => X402Transport;
126
-
127
- declare function signEIP3009(signer: Signer, params: TransferWithAuthorizationParams, domain: EIP712Domain): Promise<{
128
- v: number;
129
- r: string;
130
- s: string;
131
- }>;
132
- declare function signEIP3009WithDomain(signer: Signer, params: TransferWithAuthorizationParams, chainId: number, verifyingContract: `0x${string}`, domainName?: string, domainVersion?: string): Promise<{
133
- v: number;
134
- r: string;
135
- s: string;
136
- }>;
137
- 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<{
138
- v: number;
139
- r: string;
140
- s: string;
141
- nonce: `0x${string}`;
142
- }>;
143
- declare function recoverEIP3009Signer(params: TransferWithAuthorizationParams, signature: {
144
- v: number;
145
- r: string;
146
- s: string;
147
- }, domain: EIP712Domain): Promise<`0x${string}`>;
148
-
149
186
  /**
150
187
  * X402 Client Factory for Ethers
151
188
  *
@@ -198,116 +235,68 @@ declare function createX402Client(config: X402ClientConfig & SignerClientConfig
198
235
  }): X402Client;
199
236
  declare function createX402Client(config: X402ClientConfig & ProviderClientConfig): X402Client;
200
237
 
201
- /**
202
- * Simple one-line payment API for Armory (Ethers)
203
- * Focus on DX/UX - "everything just magically works"
204
- */
238
+ declare function signEIP3009(signer: Signer, params: TransferWithAuthorizationParams, domain: EIP712Domain): Promise<{
239
+ v: number;
240
+ r: string;
241
+ s: string;
242
+ }>;
243
+ declare function signEIP3009WithDomain(signer: Signer, params: TransferWithAuthorizationParams, chainId: number, verifyingContract: `0x${string}`, domainName?: string, domainVersion?: string): Promise<{
244
+ v: number;
245
+ r: string;
246
+ s: string;
247
+ }>;
248
+ 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<{
249
+ v: number;
250
+ r: string;
251
+ s: string;
252
+ nonce: `0x${string}`;
253
+ }>;
254
+ declare function recoverEIP3009Signer(params: TransferWithAuthorizationParams, signature: {
255
+ v: number;
256
+ r: string;
257
+ s: string;
258
+ }, domain: EIP712Domain): Promise<`0x${string}`>;
205
259
 
206
260
  /**
207
- * Simple wallet input - accepts wallet directly or wrapped for backward compatibility
208
- */
209
- type SimpleWalletInput = Signer | {
210
- signer: Signer;
211
- };
212
- /**
213
- * Normalized wallet (internal use)
214
- */
215
- type NormalizedWallet = Signer;
216
- /**
217
- * Normalize wallet input to internal format
218
- */
219
- declare const normalizeWallet: (wallet: SimpleWalletInput) => NormalizedWallet;
220
- /**
221
- * Make a payment-protected API request with one line of code
222
- */
223
- declare const armoryPay: <T = unknown>(wallet: SimpleWalletInput, url: string, network: NetworkId, token: TokenId, options?: {
224
- /** Request method (default: GET) */
225
- method?: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
226
- /** Request body (for POST/PUT/PATCH) */
227
- body?: unknown;
228
- /** Request headers */
229
- headers?: Record<string, string>;
230
- /** Protocol version (V2 only) */
231
- version?: 2;
232
- /** Payment amount in token units (default: from 402 header) */
233
- amount?: string;
234
- /** Enable debug logging */
235
- debug?: boolean;
236
- }) => Promise<ArmoryPaymentResult<T>>;
237
- /**
238
- * Make a GET request with payment
239
- */
240
- declare const armoryGet: <T = unknown>(wallet: SimpleWalletInput, url: string, network: NetworkId, token: TokenId, options?: Omit<Parameters<typeof armoryPay>[4], "method">) => Promise<ArmoryPaymentResult<T>>;
241
- /**
242
- * Make a POST request with payment
243
- */
244
- declare const armoryPost: <T = unknown>(wallet: SimpleWalletInput, url: string, network: NetworkId, token: TokenId, body?: unknown, options?: Omit<Parameters<typeof armoryPay>[4], "method" | "body">) => Promise<ArmoryPaymentResult<T>>;
245
- /**
246
- * Make a PUT request with payment
247
- */
248
- declare const armoryPut: <T = unknown>(wallet: SimpleWalletInput, url: string, network: NetworkId, token: TokenId, body?: unknown, options?: Omit<Parameters<typeof armoryPay>[4], "method" | "body">) => Promise<ArmoryPaymentResult<T>>;
249
- /**
250
- * Make a DELETE request with payment
251
- */
252
- declare const armoryDelete: <T = unknown>(wallet: SimpleWalletInput, url: string, network: NetworkId, token: TokenId, options?: Omit<Parameters<typeof armoryPay>[4], "method">) => Promise<ArmoryPaymentResult<T>>;
253
- /**
254
- * Make a PATCH request with payment
255
- */
256
- declare const armoryPatch: <T = unknown>(wallet: SimpleWalletInput, url: string, network: NetworkId, token: TokenId, body?: unknown, options?: Omit<Parameters<typeof armoryPay>[4], "method" | "body">) => Promise<ArmoryPaymentResult<T>>;
257
- /**
258
- * Get the wallet address from a SimpleWalletInput
259
- */
260
- declare const getWalletAddress: (wallet: SimpleWalletInput) => Promise<string>;
261
- /**
262
- * Validate a network identifier without making a request
261
+ * X402 Protocol Implementation for Ethers Client (V2 Only)
262
+ *
263
+ * Handles parsing x402 V2 PAYMENT-REQUIRED headers
264
+ * and generating x402 V2 PAYMENT-SIGNATURE payloads
263
265
  */
264
- declare const validateNetwork: (network: NetworkId) => ValidationError | {
265
- success: true;
266
- network: string;
267
- };
266
+
268
267
  /**
269
- * Validate a token identifier without making a request
268
+ * Detect x402 protocol version from response headers
269
+ * V2-only: Always returns 2
270
270
  */
271
- declare const validateToken: (token: TokenId, network?: NetworkId) => ValidationError | {
272
- success: true;
273
- token: string;
274
- network: string;
275
- };
271
+ declare function detectX402Version(_response: Response): 2;
276
272
  /**
277
- * Get list of available networks
273
+ * Get payment header name for protocol version
278
274
  */
279
- declare const getNetworks: () => string[];
275
+ declare function getPaymentHeaderName(_version: 2): string;
276
+ interface ParsedPaymentRequirements {
277
+ version: 2;
278
+ accepts: PaymentRequirementsV2[];
279
+ }
280
280
  /**
281
- * Get list of available tokens
281
+ * Parse x402 PAYMENT-REQUIRED header from response
282
+ * V2 only
282
283
  */
283
- declare const getTokens: () => string[];
284
-
284
+ declare function parsePaymentRequired(response: Response): ParsedPaymentRequirements;
285
285
  /**
286
- * Armory API - Simplified payment interface (Ethers)
287
- * Provides a configurable object with method-based payment functions
286
+ * Create x402 payment payload (V2-only wrapper)
288
287
  */
288
+ declare function createX402Payment(signer: Signer, requirement: PaymentRequirementsV2, fromAddress: Address, nonce?: `0x${string}`, validBefore?: number, domainName?: string, domainVersion?: string): Promise<PaymentPayloadV2>;
289
289
 
290
- type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
291
- interface ArmoryConfig {
292
- wallet: SimpleWalletInput;
293
- methods?: HttpMethod[] | HttpMethod;
294
- tokens?: TokenId[] | TokenId;
295
- chains?: NetworkId[] | NetworkId;
296
- debug?: boolean;
297
- }
298
- interface PaymentOptions {
299
- method?: HttpMethod;
300
- body?: unknown;
301
- }
302
- interface ArmoryInstance {
303
- get<T>(url: string, body?: unknown): Promise<ArmoryPaymentResult<T>>;
304
- post<T>(url: string, body?: unknown): Promise<ArmoryPaymentResult<T>>;
305
- put<T>(url: string, body?: unknown): Promise<ArmoryPaymentResult<T>>;
306
- delete<T>(url: string): Promise<ArmoryPaymentResult<T>>;
307
- patch<T>(url: string, body?: unknown): Promise<ArmoryPaymentResult<T>>;
308
- pay<T>(url: string, options?: PaymentOptions): Promise<ArmoryPaymentResult<T>>;
309
- call<T>(url: string): Promise<ArmoryPaymentResult<T>>;
290
+ interface X402Transport {
291
+ fetch(url: string, init?: X402RequestInit): Promise<Response>;
292
+ get(url: string, init?: X402RequestInit): Promise<Response>;
293
+ post(url: string, body?: unknown, init?: X402RequestInit): Promise<Response>;
294
+ put(url: string, body?: unknown, init?: X402RequestInit): Promise<Response>;
295
+ del(url: string, init?: X402RequestInit): Promise<Response>;
296
+ patch(url: string, body?: unknown, init?: X402RequestInit): Promise<Response>;
297
+ setSigner(signer: Signer): void;
298
+ getSigner(): Signer | undefined;
310
299
  }
311
- declare const createArmory: (config: ArmoryConfig) => ArmoryInstance;
300
+ declare const createX402Transport: (config?: X402TransportConfig) => X402Transport;
312
301
 
313
- export { type ArmoryConfig, type ArmoryInstance, AuthorizationError, type ClientConfig, type HttpMethod, type NormalizedWallet, type ParsedPaymentRequirements, PaymentError, type PaymentOptions, ProviderRequiredError, SignerRequiredError, SigningError, type SimpleWalletInput, type X402Client, type X402ClientConfig, X402ClientError, type X402RequestInit, type X402Transport, type X402TransportConfig, armoryDelete, armoryGet, armoryPatch, armoryPay, armoryPost, armoryPut, createArmory, createX402Client, createX402Payment, createX402Transport, detectX402Version, encodeX402Payment, getNetworks, getPaymentHeaderName, getTokens, getWalletAddress, normalizeWallet, parsePaymentRequired, recoverEIP3009Signer, signEIP3009, signEIP3009WithDomain, signPayment, validateNetwork, validateToken };
302
+ export { type ArmoryConfig, type ArmoryInstance, AuthorizationError, type ClientConfig, type HttpMethod, type NormalizedWallet, type ParsedPaymentRequirements, type PaymentOptions, ProviderRequiredError, SignerRequiredError, type SimpleWalletInput, type X402Client, type X402ClientConfig, type X402RequestInit, type X402Transport, type X402TransportConfig, armoryDelete, armoryGet, armoryPatch, armoryPay, armoryPost, armoryPut, createArmory, createX402Client, createX402Payment, createX402Transport, detectX402Version, getNetworks, getPaymentHeaderName, getTokens, getWalletAddress, normalizeWallet, parsePaymentRequired, recoverEIP3009Signer, signEIP3009, signEIP3009WithDomain, signPayment, validateNetwork, validateToken };
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
- import { createEIP712Domain, V2_HEADERS, isX402V2PaymentRequired, validatePaymentConfig, isValidationError, resolveNetwork, resolveToken, getNetworkConfig, normalizeNetworkName, decodeSettlementV2 } from '@armory-sh/base';
2
- export { EIP712_TYPES, ERC20_ABI, NETWORKS, V2_HEADERS, createEIP712Domain, createTransferWithAuthorization, decodePayment, decodePaymentV2, decodeSettlementV2, detectPaymentVersion, encodePaymentV2, encodeSettlementV2, getMainnets, getNetworkByChainId, getNetworkConfig, getTestnets, getTxHash, isCAIP2ChainId, isCAIPAssetId, isPaymentV2, isSettlementSuccessful, isSettlementV2, isX402V2PaymentRequired, safeBase64Decode, validateTransferWithAuthorization } from '@armory-sh/base';
1
+ import { X402ClientError, createEIP712Domain, V2_HEADERS, PaymentException, isX402V2PaymentRequired, validatePaymentConfig, isValidationError, resolveNetwork, resolveToken, normalizeBase64Url, decodeBase64ToUtf8, getNetworkConfig, normalizeNetworkName, encodePaymentV2, decodeSettlementV2 } from '@armory-sh/base';
2
+ export { PaymentException as PaymentError, SigningError, X402ClientError } from '@armory-sh/base';
3
+ import { runOnPaymentRequiredHooks, getRequirementAttemptOrderWithHooks, runBeforeSignPaymentHooks, runAfterPaymentResponseHooks } from '@armory-sh/base/client-hooks-runtime';
3
4
  import { ethers } from 'ethers';
4
5
 
5
6
  var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
@@ -8,28 +9,6 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
8
9
  if (typeof require !== "undefined") return require.apply(this, arguments);
9
10
  throw Error('Dynamic require of "' + x + '" is not supported');
10
11
  });
11
-
12
- // src/errors.ts
13
- var X402ClientError = class extends Error {
14
- cause;
15
- constructor(message, cause) {
16
- super(message);
17
- this.name = "X402ClientError";
18
- this.cause = cause;
19
- }
20
- };
21
- var SigningError = class extends X402ClientError {
22
- constructor(message, cause) {
23
- super(`Signing failed: ${message}`, cause);
24
- this.name = "SigningError";
25
- }
26
- };
27
- var PaymentError = class extends X402ClientError {
28
- constructor(message, cause) {
29
- super(`Payment failed: ${message}`, cause);
30
- this.name = "PaymentError";
31
- }
32
- };
33
12
  var SignerRequiredError = class extends X402ClientError {
34
13
  constructor(message = "Signer is required for this operation") {
35
14
  super(message);
@@ -48,8 +27,6 @@ var ProviderRequiredError = class extends X402ClientError {
48
27
  this.name = "ProviderRequiredError";
49
28
  }
50
29
  };
51
-
52
- // src/eip3009.ts
53
30
  var EIP712_TYPES_ETHERS = {
54
31
  TransferWithAuthorization: [
55
32
  { name: "from", type: "address" },
@@ -70,7 +47,11 @@ async function signEIP3009(signer, params, domain) {
70
47
  validBefore: BigInt(params.validBefore),
71
48
  nonce: params.nonce
72
49
  };
73
- const signature = await signer.signTypedData(domain, EIP712_TYPES_ETHERS, message);
50
+ const signature = await signer.signTypedData(
51
+ domain,
52
+ EIP712_TYPES_ETHERS,
53
+ message
54
+ );
74
55
  const { v, r, s } = ethers.Signature.from(signature);
75
56
  return { v: Number(v), r, s };
76
57
  } catch (error) {
@@ -82,7 +63,11 @@ async function signEIP3009(signer, params, domain) {
82
63
  }
83
64
  async function signEIP3009WithDomain(signer, params, chainId, verifyingContract, domainName, domainVersion) {
84
65
  const domain = createEIP712Domain(chainId, verifyingContract);
85
- const customDomain = domainName || domainVersion ? { ...domain, name: domainName ?? domain.name, version: domainVersion ?? domain.version } : domain;
66
+ const customDomain = domainName || domainVersion ? {
67
+ ...domain,
68
+ name: domainName ?? domain.name,
69
+ version: domainVersion ?? domain.version
70
+ } : domain;
86
71
  return signEIP3009(signer, params, customDomain);
87
72
  }
88
73
  async function signPayment(signer, from, to, value, chainId, verifyingContract, expirySeconds = 3600, domainName, domainVersion) {
@@ -90,7 +75,14 @@ async function signPayment(signer, from, to, value, chainId, verifyingContract,
90
75
  const nonce = `0x${(now * 1e3).toString(16).padStart(64, "0")}`;
91
76
  const signature = await signEIP3009WithDomain(
92
77
  signer,
93
- { from, to, value, validAfter: 0n, validBefore: BigInt(now + expirySeconds), nonce },
78
+ {
79
+ from,
80
+ to,
81
+ value,
82
+ validAfter: 0n,
83
+ validBefore: BigInt(now + expirySeconds),
84
+ nonce
85
+ },
94
86
  chainId,
95
87
  verifyingContract,
96
88
  domainName,
@@ -112,60 +104,15 @@ async function recoverEIP3009Signer(params, signature, domain) {
112
104
  r: signature.r,
113
105
  s: signature.s
114
106
  });
115
- const address = ethers.verifyTypedData(domain, EIP712_TYPES_ETHERS, message, sig);
107
+ const address = ethers.verifyTypedData(
108
+ domain,
109
+ EIP712_TYPES_ETHERS,
110
+ message,
111
+ sig
112
+ );
116
113
  return address;
117
114
  }
118
115
 
119
- // src/bytes.ts
120
- var textEncoder = new TextEncoder();
121
- var textDecoder = new TextDecoder();
122
- function getNodeBuffer() {
123
- if ("Buffer" in globalThis) {
124
- return globalThis.Buffer;
125
- }
126
- return void 0;
127
- }
128
- function toBase64(bytes) {
129
- if (typeof btoa === "function") {
130
- let binary = "";
131
- for (let index = 0; index < bytes.length; index += 1) {
132
- binary += String.fromCharCode(bytes[index]);
133
- }
134
- return btoa(binary);
135
- }
136
- const nodeBuffer = getNodeBuffer();
137
- if (nodeBuffer) {
138
- return nodeBuffer.from(bytes).toString("base64");
139
- }
140
- throw new Error("No base64 encoder available in this runtime");
141
- }
142
- function fromBase64(base64) {
143
- if (typeof atob === "function") {
144
- const binary = atob(base64);
145
- const bytes = new Uint8Array(binary.length);
146
- for (let index = 0; index < binary.length; index += 1) {
147
- bytes[index] = binary.charCodeAt(index);
148
- }
149
- return bytes;
150
- }
151
- const nodeBuffer = getNodeBuffer();
152
- if (nodeBuffer) {
153
- return Uint8Array.from(nodeBuffer.from(base64, "base64"));
154
- }
155
- throw new Error("No base64 decoder available in this runtime");
156
- }
157
- function encodeUtf8ToBase64(value) {
158
- const bytes = textEncoder.encode(value);
159
- return toBase64(bytes);
160
- }
161
- function decodeBase64ToUtf8(value) {
162
- const bytes = fromBase64(value);
163
- return textDecoder.decode(bytes);
164
- }
165
- function normalizeBase64Url(value) {
166
- return value.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(value.length / 4) * 4, "=");
167
- }
168
-
169
116
  // src/protocol.ts
170
117
  function detectX402Version(_response) {
171
118
  return 2;
@@ -184,28 +131,27 @@ function parseJsonOrBase64(value) {
184
131
  function parsePaymentRequired(response) {
185
132
  const v2Header = response.headers.get(V2_HEADERS.PAYMENT_REQUIRED);
186
133
  if (!v2Header) {
187
- throw new PaymentError("No PAYMENT-REQUIRED header found in V2 response");
134
+ throw new PaymentException("No PAYMENT-REQUIRED header found in V2 response");
188
135
  }
189
136
  try {
190
137
  const parsed = parseJsonOrBase64(v2Header);
191
138
  if (!isX402V2PaymentRequired(parsed)) {
192
- throw new PaymentError("Invalid x402 V2 payment required format");
139
+ throw new PaymentException("Invalid x402 V2 payment required format");
193
140
  }
194
141
  if (!parsed.accepts || parsed.accepts.length === 0) {
195
- throw new PaymentError("No payment requirements found in accepts array");
142
+ throw new PaymentException("No payment requirements found in accepts array");
196
143
  }
197
144
  return {
198
145
  version: 2,
199
- requirements: parsed.accepts[0]
146
+ accepts: parsed.accepts
200
147
  };
201
148
  } catch (error) {
202
- if (error instanceof PaymentError) throw error;
203
- throw new PaymentError(`Failed to parse V2 PAYMENT-REQUIRED header: ${error}`);
149
+ if (error instanceof PaymentException) throw error;
150
+ throw new PaymentException(
151
+ `Failed to parse V2 PAYMENT-REQUIRED header: ${error}`
152
+ );
204
153
  }
205
154
  }
206
- function toAtomicUnits(amount) {
207
- return Math.floor(parseFloat(amount) * 1e6).toString();
208
- }
209
155
  function extractChainId(network) {
210
156
  if (network.startsWith("eip155:")) {
211
157
  return parseInt(network.split(":")[1], 10);
@@ -214,7 +160,7 @@ function extractChainId(network) {
214
160
  if (net) {
215
161
  return net.chainId;
216
162
  }
217
- throw new PaymentError(`Unsupported network: ${network}`);
163
+ throw new PaymentException(`Unsupported network: ${network}`);
218
164
  }
219
165
  function createNonce() {
220
166
  const now = Math.floor(Date.now() / 1e3);
@@ -223,13 +169,18 @@ function createNonce() {
223
169
  async function createX402V2Payment(signer, requirements, fromAddress, nonce, validBefore, domainName, domainVersion) {
224
170
  const contractAddress = requirements.asset;
225
171
  const chainId = extractChainId(requirements.network);
172
+ const now = Math.floor(Date.now() / 1e3);
226
173
  const domain = createEIP712Domain(chainId, contractAddress);
227
- const customDomain = domainName || domainVersion ? { ...domain, name: domainName ?? domain.name, version: domainVersion ?? domain.version } : domain;
174
+ const customDomain = domainName || domainVersion ? {
175
+ ...domain,
176
+ name: domainName ?? domain.name,
177
+ version: domainVersion ?? domain.version
178
+ } : domain;
228
179
  const authorization = {
229
180
  from: fromAddress,
230
181
  to: requirements.payTo,
231
- value: toAtomicUnits(requirements.amount),
232
- validAfter: "0",
182
+ value: requirements.amount,
183
+ validAfter: (now - 600).toString(),
233
184
  validBefore: validBefore.toString(),
234
185
  nonce
235
186
  };
@@ -253,12 +204,12 @@ async function createX402V2Payment(signer, requirements, fromAddress, nonce, val
253
204
  payload
254
205
  };
255
206
  }
256
- async function createX402Payment(signer, parsed, fromAddress, nonce, validBefore, domainName, domainVersion) {
207
+ async function createX402Payment(signer, requirement, fromAddress, nonce, validBefore, domainName, domainVersion) {
257
208
  const effectiveNonce = nonce ?? createNonce();
258
209
  const effectiveValidBefore = validBefore ?? Math.floor(Date.now() / 1e3) + 3600;
259
210
  return createX402V2Payment(
260
211
  signer,
261
- parsed.requirements,
212
+ requirement,
262
213
  fromAddress,
263
214
  effectiveNonce,
264
215
  effectiveValidBefore,
@@ -266,9 +217,8 @@ async function createX402Payment(signer, parsed, fromAddress, nonce, validBefore
266
217
  domainVersion
267
218
  );
268
219
  }
269
- function encodeX402Payment(payload) {
270
- return encodeUtf8ToBase64(JSON.stringify(payload));
271
- }
220
+
221
+ // src/transport.ts
272
222
  var defaultConfig = {
273
223
  baseURL: "",
274
224
  headers: {},
@@ -280,7 +230,8 @@ var defaultConfig = {
280
230
  onPaymentSuccess: () => {
281
231
  },
282
232
  onPaymentError: () => {
283
- }
233
+ },
234
+ hooks: []
284
235
  };
285
236
  var createState = (config) => ({
286
237
  config: { ...defaultConfig, ...config }
@@ -301,48 +252,161 @@ var fetchWithTimeout = async (url, init, timeout) => {
301
252
  }
302
253
  };
303
254
  var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
255
+ var getRequirementDomainOverrides = (_parsed, requirement) => {
256
+ const extra = requirement.extra;
257
+ const extraRecord = extra && typeof extra === "object" ? extra : void 0;
258
+ const extraName = extraRecord && typeof extraRecord.name === "string" ? extraRecord.name : void 0;
259
+ const extraVersion = extraRecord && typeof extraRecord.version === "string" ? extraRecord.version : void 0;
260
+ return {
261
+ domainName: requirement.name ?? extraName,
262
+ domainVersion: requirement.version ?? extraVersion
263
+ };
264
+ };
265
+ var getPaymentFailureDetail = async (response) => {
266
+ const text = (await response.clone().text()).trim();
267
+ if (!text) {
268
+ return void 0;
269
+ }
270
+ try {
271
+ const parsed = JSON.parse(text);
272
+ if (typeof parsed.message === "string" && parsed.message.trim()) {
273
+ return parsed.message.trim();
274
+ }
275
+ if (typeof parsed.error === "string" && parsed.error.trim()) {
276
+ return parsed.error.trim();
277
+ }
278
+ } catch {
279
+ }
280
+ return text;
281
+ };
282
+ var createPaymentVerificationError = async (response) => {
283
+ const detail = await getPaymentFailureDetail(response);
284
+ return new Error(
285
+ detail ? `Payment verification failed: ${detail}` : "Payment verification failed"
286
+ );
287
+ };
304
288
  var handlePaymentRequired = async (state, response) => {
305
289
  if (!state.signer) {
306
- throw new SignerRequiredError("Cannot handle payment: no signer configured.");
290
+ throw new SignerRequiredError(
291
+ "Cannot handle payment: no signer configured."
292
+ );
307
293
  }
308
294
  try {
309
295
  const parsed = parsePaymentRequired(response);
296
+ const initialRequirement = parsed.accepts[0];
297
+ if (!initialRequirement) {
298
+ throw new Error("No payment requirements found in accepts array");
299
+ }
310
300
  const from = await state.signer.getAddress();
311
- const payload = await createX402Payment(state.signer, parsed, from);
312
- const encoded = encodeX402Payment(payload);
313
- const headerName = getPaymentHeaderName(parsed.version);
314
- const paymentResponse = await fetchWithTimeout(
315
- response.url,
316
- {
317
- method: "GET",
318
- headers: { ...state.config.headers, [headerName]: encoded }
319
- },
320
- state.config.timeout
301
+ const paymentRequiredContext = {
302
+ url: response.url,
303
+ requestInit: void 0,
304
+ accepts: parsed.accepts,
305
+ requirements: initialRequirement,
306
+ selectedRequirement: initialRequirement,
307
+ serverExtensions: void 0,
308
+ fromAddress: from,
309
+ nonce: `0x${Date.now().toString(16).padStart(64, "0")}`,
310
+ validBefore: Math.floor(Date.now() / 1e3) + initialRequirement.maxTimeoutSeconds
311
+ };
312
+ await runOnPaymentRequiredHooks(
313
+ state.config.hooks,
314
+ paymentRequiredContext
315
+ );
316
+ const attemptRequirements = await getRequirementAttemptOrderWithHooks(
317
+ state.config.hooks,
318
+ paymentRequiredContext
321
319
  );
322
- const settlement = decodeSettlementV2(paymentResponse.headers.get(V2_HEADERS.PAYMENT_RESPONSE) || "");
323
- return { success: true, settlement };
320
+ const headerName = getPaymentHeaderName(parsed.version);
321
+ let lastError;
322
+ for (const selectedRequirement of attemptRequirements) {
323
+ try {
324
+ const validBefore = Math.floor(Date.now() / 1e3) + selectedRequirement.maxTimeoutSeconds;
325
+ const attemptContext = {
326
+ ...paymentRequiredContext,
327
+ requirements: selectedRequirement,
328
+ selectedRequirement,
329
+ validBefore
330
+ };
331
+ const requirementDomain = getRequirementDomainOverrides(
332
+ parsed,
333
+ selectedRequirement
334
+ );
335
+ const payload = await createX402Payment(
336
+ state.signer,
337
+ selectedRequirement,
338
+ from,
339
+ attemptContext.nonce,
340
+ attemptContext.validBefore,
341
+ requirementDomain.domainName,
342
+ requirementDomain.domainVersion
343
+ );
344
+ await runBeforeSignPaymentHooks(state.config.hooks, {
345
+ payload,
346
+ requirements: selectedRequirement,
347
+ wallet: state.signer,
348
+ paymentContext: attemptContext
349
+ });
350
+ const encoded = encodePaymentV2(payload);
351
+ const paymentResponse = await fetchWithTimeout(
352
+ response.url,
353
+ {
354
+ method: "GET",
355
+ headers: { ...state.config.headers, [headerName]: encoded }
356
+ },
357
+ state.config.timeout
358
+ );
359
+ await runAfterPaymentResponseHooks(state.config.hooks, {
360
+ payload,
361
+ requirements: selectedRequirement,
362
+ wallet: state.signer,
363
+ paymentContext: attemptContext,
364
+ response: paymentResponse
365
+ });
366
+ if (paymentResponse.status === 402) {
367
+ lastError = await createPaymentVerificationError(paymentResponse);
368
+ continue;
369
+ }
370
+ const settlement = decodeSettlementV2(
371
+ paymentResponse.headers.get(V2_HEADERS.PAYMENT_RESPONSE) || ""
372
+ );
373
+ return { success: true, settlement };
374
+ } catch (error) {
375
+ lastError = error instanceof Error ? error : new Error(String(error));
376
+ }
377
+ }
378
+ throw lastError ?? new Error("Payment verification failed");
324
379
  } catch (error) {
325
- return { success: false, error: error instanceof Error ? error : new Error(String(error)) };
380
+ return {
381
+ success: false,
382
+ error: error instanceof Error ? error : new Error(String(error))
383
+ };
326
384
  }
327
385
  };
328
386
  var shouldRetryPayment = async (state, response) => {
329
387
  if (!state.config.autoPay) return false;
330
388
  const parsed = parsePaymentRequired(response);
331
- return await state.config.onPaymentRequired(parsed.requirements);
389
+ return await state.config.onPaymentRequired(parsed.accepts[0]);
332
390
  };
333
391
  var x402Fetch = async (state, url, init = {}) => {
334
392
  const fullUrl = state.config.baseURL ? new URL(url, state.config.baseURL).toString() : url;
335
393
  const headers = new Headers({ ...state.config.headers, ...init.headers });
336
394
  for (let attempt = 1; attempt <= state.config.maxRetries; attempt++) {
337
395
  try {
338
- const response = await fetchWithTimeout(fullUrl, { ...init, headers }, state.config.timeout);
396
+ const response = await fetchWithTimeout(
397
+ fullUrl,
398
+ { ...init, headers },
399
+ state.config.timeout
400
+ );
339
401
  if (response.status === 402 && !init.skipAutoPay && await shouldRetryPayment(state, response)) {
340
402
  const paymentResult = await handlePaymentRequired(state, response);
341
403
  if (paymentResult.success) {
342
404
  state.config.onPaymentSuccess(paymentResult.settlement);
343
405
  continue;
344
406
  }
345
- state.config.onPaymentError(paymentResult.error ?? new Error("Payment failed"));
407
+ state.config.onPaymentError(
408
+ paymentResult.error ?? new Error("Payment failed")
409
+ );
346
410
  }
347
411
  return response;
348
412
  } catch (error) {
@@ -365,7 +429,7 @@ var createMethod = (state, method) => async (url, bodyOrInit, init) => {
365
429
  });
366
430
  };
367
431
  var createX402Transport = (config) => {
368
- let state = createState(config);
432
+ const state = createState(config);
369
433
  return {
370
434
  fetch: (url, init) => x402Fetch(state, url, init),
371
435
  get: createMethod(state, "GET"),
@@ -380,37 +444,7 @@ var createX402Transport = (config) => {
380
444
  };
381
445
  };
382
446
 
383
- // src/client.ts
384
- function createX402Client(config) {
385
- const transport = createX402Transport(void 0);
386
- if ("signer" in config && config.signer) {
387
- transport.setSigner(config.signer);
388
- } else if ("provider" in config && config.provider) {
389
- const provider = config.provider;
390
- if (provider.getSigner) {
391
- try {
392
- const signer = provider.getSigner();
393
- if (signer) {
394
- transport.setSigner(signer);
395
- }
396
- } catch {
397
- }
398
- }
399
- } else {
400
- throw new SignerRequiredError(
401
- "Either 'signer' or 'provider' with getSigner() must be provided"
402
- );
403
- }
404
- return {
405
- get: (url, init) => transport.get(url, init),
406
- post: (url, body, init) => transport.post(url, body, init),
407
- put: (url, body, init) => transport.put(url, body, init),
408
- del: (url, init) => transport.del(url, init),
409
- patch: (url, body, init) => transport.patch(url, body, init),
410
- setSigner: (signer) => transport.setSigner(signer),
411
- getSigner: () => transport.getSigner()
412
- };
413
- }
447
+ // src/payment-api.ts
414
448
  var normalizeWallet = (wallet) => {
415
449
  if (typeof wallet === "object" && wallet !== null && "signer" in wallet) {
416
450
  return wallet.signer;
@@ -472,19 +506,37 @@ var armoryPay = async (wallet, url, network, token, options) => {
472
506
  }
473
507
  };
474
508
  var armoryGet = (wallet, url, network, token, options) => {
475
- return armoryPay(wallet, url, network, token, { ...options, method: "GET" });
509
+ return armoryPay(wallet, url, network, token, {
510
+ ...options,
511
+ method: "GET"
512
+ });
476
513
  };
477
514
  var armoryPost = (wallet, url, network, token, body, options) => {
478
- return armoryPay(wallet, url, network, token, { ...options, method: "POST", body });
515
+ return armoryPay(wallet, url, network, token, {
516
+ ...options,
517
+ method: "POST",
518
+ body
519
+ });
479
520
  };
480
521
  var armoryPut = (wallet, url, network, token, body, options) => {
481
- return armoryPay(wallet, url, network, token, { ...options, method: "PUT", body });
522
+ return armoryPay(wallet, url, network, token, {
523
+ ...options,
524
+ method: "PUT",
525
+ body
526
+ });
482
527
  };
483
528
  var armoryDelete = (wallet, url, network, token, options) => {
484
- return armoryPay(wallet, url, network, token, { ...options, method: "DELETE" });
529
+ return armoryPay(wallet, url, network, token, {
530
+ ...options,
531
+ method: "DELETE"
532
+ });
485
533
  };
486
534
  var armoryPatch = (wallet, url, network, token, body, options) => {
487
- return armoryPay(wallet, url, network, token, { ...options, method: "PATCH", body });
535
+ return armoryPay(wallet, url, network, token, {
536
+ ...options,
537
+ method: "PATCH",
538
+ body
539
+ });
488
540
  };
489
541
  var getWalletAddress = async (wallet) => {
490
542
  const signer = normalizeWallet(wallet);
@@ -498,7 +550,7 @@ var validateNetwork = (network) => {
498
550
  return { success: true, network: resolved.config.name };
499
551
  };
500
552
  var validateToken = (token, network) => {
501
- let resolvedNetwork = void 0;
553
+ let resolvedNetwork;
502
554
  if (network) {
503
555
  const networkResult = resolveNetwork(network);
504
556
  if (isValidationError(networkResult)) {
@@ -524,7 +576,15 @@ var getTokens = () => {
524
576
  const { getAvailableTokens } = __require("@armory-sh/base");
525
577
  return getAvailableTokens();
526
578
  };
527
- var ALL_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "DELETE", "PATCH"]);
579
+
580
+ // src/armory-api.ts
581
+ var ALL_METHODS = /* @__PURE__ */ new Set([
582
+ "GET",
583
+ "POST",
584
+ "PUT",
585
+ "DELETE",
586
+ "PATCH"
587
+ ]);
528
588
  var arrayify = (value) => {
529
589
  if (value === void 0) return void 0;
530
590
  return Array.isArray(value) ? value : [value];
@@ -592,4 +652,38 @@ var createArmory = (config) => {
592
652
  };
593
653
  };
594
654
 
595
- export { AuthorizationError, PaymentError, ProviderRequiredError, SignerRequiredError, SigningError, X402ClientError, armoryDelete, armoryGet, armoryPatch, armoryPay, armoryPost, armoryPut, createArmory, createX402Client, createX402Payment, createX402Transport, detectX402Version, encodeX402Payment, getNetworks, getPaymentHeaderName, getTokens, getWalletAddress, normalizeWallet, parsePaymentRequired, recoverEIP3009Signer, signEIP3009, signEIP3009WithDomain, signPayment, validateNetwork, validateToken };
655
+ // src/client.ts
656
+ function createX402Client(config) {
657
+ const transport = createX402Transport({
658
+ hooks: config.hooks
659
+ });
660
+ if ("signer" in config && config.signer) {
661
+ transport.setSigner(config.signer);
662
+ } else if ("provider" in config && config.provider) {
663
+ const provider = config.provider;
664
+ if (provider.getSigner) {
665
+ try {
666
+ const signer = provider.getSigner();
667
+ if (signer) {
668
+ transport.setSigner(signer);
669
+ }
670
+ } catch {
671
+ }
672
+ }
673
+ } else {
674
+ throw new SignerRequiredError(
675
+ "Either 'signer' or 'provider' with getSigner() must be provided"
676
+ );
677
+ }
678
+ return {
679
+ get: (url, init) => transport.get(url, init),
680
+ post: (url, body, init) => transport.post(url, body, init),
681
+ put: (url, body, init) => transport.put(url, body, init),
682
+ del: (url, init) => transport.del(url, init),
683
+ patch: (url, body, init) => transport.patch(url, body, init),
684
+ setSigner: (signer) => transport.setSigner(signer),
685
+ getSigner: () => transport.getSigner()
686
+ };
687
+ }
688
+
689
+ export { AuthorizationError, ProviderRequiredError, SignerRequiredError, armoryDelete, armoryGet, armoryPatch, armoryPay, armoryPost, armoryPut, createArmory, createX402Client, createX402Payment, createX402Transport, detectX402Version, getNetworks, getPaymentHeaderName, getTokens, getWalletAddress, normalizeWallet, parsePaymentRequired, recoverEIP3009Signer, signEIP3009, signEIP3009WithDomain, signPayment, validateNetwork, validateToken };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@armory-sh/client-ethers",
3
- "version": "0.2.27",
3
+ "version": "0.2.29",
4
4
  "license": "MIT",
5
5
  "author": "Sawyer Cutler <sawyer@dirtroad.dev>",
6
6
  "keywords": [
@@ -47,7 +47,7 @@
47
47
  "directory": "packages/client-ethers"
48
48
  },
49
49
  "dependencies": {
50
- "@armory-sh/base": "0.2.28",
50
+ "@armory-sh/base": "0.2.30",
51
51
  "ethers": "6.16.0"
52
52
  },
53
53
  "devDependencies": {
@@ -56,6 +56,8 @@
56
56
  },
57
57
  "scripts": {
58
58
  "build": "rm -rf dist && tsup",
59
+ "lint": "bun run build",
60
+ "format": "bun run lint",
59
61
  "test": "bun test",
60
62
  "example": "bun run examples/"
61
63
  }