@armory-sh/client-viem 0.2.18 → 0.2.20

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/dist/index.d.ts CHANGED
@@ -1,6 +1,41 @@
1
- import { Address, Account, WalletClient, Transport } from 'viem';
2
- import { CustomToken, PaymentPayloadV2, PaymentRequirementsV2, NetworkId, TokenId, ArmoryPaymentResult, ValidationError } from '@armory-sh/base';
1
+ import { Account, Address, WalletClient, Transport } from 'viem';
2
+ import { PaymentRequirementsV2, HookConfig, HookRegistry, PaymentPayloadContext, CustomToken, PaymentPayloadV2, PaymentRequiredContext, Extensions, NetworkId, TokenId, ArmoryPaymentResult, ValidationError, Address as Address$1, PaymentRequiredV2 } from '@armory-sh/base';
3
3
  export { ArmoryPaymentResult, EIP3009Authorization, FacilitatorConfig, NetworkId, PaymentPayloadV2, PaymentRequiredV2, PaymentRequirementsV2, ResourceInfo, SchemePayloadV2, SettlementResponseV2, TokenId, V2_HEADERS } from '@armory-sh/base';
4
+ import { BazaarExtensionInfo, SIWxExtensionInfo } from '@armory-sh/extensions';
5
+
6
+ /**
7
+ * X402 Protocol Implementation for Viem Client (V2 Only)
8
+ *
9
+ * Handles parsing x402 V2 PAYMENT-REQUIRED headers
10
+ * and generating x402 V2 PAYMENT-SIGNATURE payloads
11
+ */
12
+
13
+ type X402Wallet$1 = {
14
+ type: "account";
15
+ account: Account;
16
+ } | {
17
+ type: "walletClient";
18
+ walletClient: {
19
+ account: Account;
20
+ };
21
+ };
22
+ type X402Version = 2;
23
+ declare function detectX402Version(_response: Response): X402Version;
24
+ type ParsedPaymentRequirements = PaymentRequirementsV2;
25
+ declare function parsePaymentRequired(response: Response): ParsedPaymentRequirements;
26
+
27
+ /**
28
+ * Viem-Specific Hook Types
29
+ *
30
+ * Extends the base hook types with viem-specific wallet context.
31
+ * Provides type-safe hook registration for client-viem.
32
+ */
33
+
34
+ interface ViemPaymentPayloadContext extends PaymentPayloadContext {
35
+ wallet: X402Wallet$1;
36
+ }
37
+ type ViemHookConfig = HookConfig<X402Wallet$1>;
38
+ type ViemHookRegistry = HookRegistry<X402Wallet$1>;
4
39
 
5
40
  /**
6
41
  * X402 Client Types - V2 Only
@@ -28,6 +63,8 @@ interface X402ClientConfig {
28
63
  domainName?: string;
29
64
  /** Override EIP-712 domain version for custom tokens */
30
65
  domainVersion?: string;
66
+ /** Extension hooks for handling protocol extensions */
67
+ hooks?: ViemHookRegistry;
31
68
  }
32
69
  interface X402Client {
33
70
  fetch: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
@@ -48,6 +85,8 @@ interface X402TransportConfig {
48
85
  domainName?: string;
49
86
  /** Override EIP-712 domain version */
50
87
  domainVersion?: string;
88
+ /** Extension hooks for handling protocol extensions */
89
+ hooks?: ViemHookRegistry;
51
90
  }
52
91
  interface PaymentResult {
53
92
  success: boolean;
@@ -73,6 +112,16 @@ interface UnsignedPaymentPayload {
73
112
  declare const createX402Client: (config: X402ClientConfig) => X402Client;
74
113
  declare const createX402Transport: (config: X402TransportConfig) => ((input: RequestInfo | URL, init?: RequestInit) => Promise<Response>);
75
114
 
115
+ /**
116
+ * Hook Execution Engine
117
+ *
118
+ * Executes hooks in priority order and handles errors gracefully.
119
+ * Hooks are sorted by priority (higher priority runs first).
120
+ */
121
+
122
+ declare function executeHooks(hooks: HookRegistry, context: PaymentRequiredContext | PaymentPayloadContext): Promise<void>;
123
+ declare function mergeExtensions(baseExtensions: Extensions | undefined, hookExtensions: Record<string, unknown>): Extensions;
124
+
76
125
  declare class X402ClientError extends Error {
77
126
  readonly cause?: unknown;
78
127
  constructor(message: string, cause?: unknown);
@@ -84,32 +133,33 @@ declare class PaymentError extends X402ClientError {
84
133
  constructor(message: string, cause?: unknown);
85
134
  }
86
135
 
87
- /**
88
- * X402 Protocol Implementation for Viem Client (V2 Only)
89
- *
90
- * Handles parsing x402 V2 PAYMENT-REQUIRED headers
91
- * and generating x402 V2 PAYMENT-SIGNATURE payloads
92
- */
93
-
94
- type X402Version = 2;
95
- declare function detectX402Version(_response: Response): X402Version;
96
- type ParsedPaymentRequirements = PaymentRequirementsV2;
97
- declare function parsePaymentRequired(response: Response): ParsedPaymentRequirements;
98
-
99
136
  /**
100
137
  * Simple one-line payment API for Armory
101
138
  * Focus on DX/UX - "everything just magically works"
102
139
  */
103
140
 
104
141
  /**
105
- * Simple wallet configuration
106
- * Pass a viem Account or WalletClient directly
142
+ * Simple wallet input - accepts wallet directly or wrapped for backward compatibility
143
+ */
144
+ type SimpleWalletInput = Account | WalletClient | {
145
+ account: Account;
146
+ } | {
147
+ walletClient: WalletClient;
148
+ };
149
+ /**
150
+ * Normalized wallet (internal use)
107
151
  */
108
- type SimpleWallet = {
152
+ type NormalizedWallet = {
153
+ type: "account";
109
154
  account: Account;
110
155
  } | {
156
+ type: "walletClient";
111
157
  walletClient: WalletClient;
112
158
  };
159
+ /**
160
+ * Normalize wallet input to internal format
161
+ */
162
+ declare const normalizeWallet: (wallet: SimpleWalletInput) => NormalizedWallet;
113
163
  /**
114
164
  * Make a payment-protected API request with one line of code
115
165
  *
@@ -129,7 +179,7 @@ type SimpleWallet = {
129
179
  * }
130
180
  * ```
131
181
  */
132
- declare const armoryPay: <T = unknown>(wallet: SimpleWallet, url: string, network: NetworkId, token: TokenId, options?: {
182
+ declare const armoryPay: <T = unknown>(wallet: SimpleWalletInput, url: string, network: NetworkId, token: TokenId, options?: {
133
183
  /** Request method (default: GET) */
134
184
  method?: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
135
185
  /** Request body (for POST/PUT/PATCH) */
@@ -146,15 +196,27 @@ declare const armoryPay: <T = unknown>(wallet: SimpleWallet, url: string, networ
146
196
  /**
147
197
  * Make a GET request with payment
148
198
  */
149
- declare const armoryGet: <T = unknown>(wallet: SimpleWallet, url: string, network: NetworkId, token: TokenId, options?: Omit<Parameters<typeof armoryPay>[4], "method">) => Promise<ArmoryPaymentResult<T>>;
199
+ declare const armoryGet: <T = unknown>(wallet: SimpleWalletInput, url: string, network: NetworkId, token: TokenId, options?: Omit<Parameters<typeof armoryPay>[4], "method">) => Promise<ArmoryPaymentResult<T>>;
150
200
  /**
151
201
  * Make a POST request with payment
152
202
  */
153
- declare const armoryPost: <T = unknown>(wallet: SimpleWallet, url: string, network: NetworkId, token: TokenId, body?: unknown, options?: Omit<Parameters<typeof armoryPay>[4], "method" | "body">) => Promise<ArmoryPaymentResult<T>>;
203
+ 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>>;
204
+ /**
205
+ * Make a PUT request with payment
206
+ */
207
+ 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>>;
154
208
  /**
155
- * Get the wallet address from a SimpleWallet
209
+ * Make a DELETE request with payment
156
210
  */
157
- declare const getWalletAddress: (wallet: SimpleWallet) => Address;
211
+ declare const armoryDelete: <T = unknown>(wallet: SimpleWalletInput, url: string, network: NetworkId, token: TokenId, options?: Omit<Parameters<typeof armoryPay>[4], "method">) => Promise<ArmoryPaymentResult<T>>;
212
+ /**
213
+ * Make a PATCH request with payment
214
+ */
215
+ 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>>;
216
+ /**
217
+ * Get the wallet address from a SimpleWalletInput
218
+ */
219
+ declare const getWalletAddress: (wallet: SimpleWalletInput) => Address;
158
220
  /**
159
221
  * Validate a network identifier without making a request
160
222
  */
@@ -179,4 +241,49 @@ declare const getNetworks: () => string[];
179
241
  */
180
242
  declare const getTokens: () => string[];
181
243
 
182
- export { type ParsedPaymentRequirements, PaymentError, type PaymentResult, SigningError, type SimpleWallet, type Token, type X402Client, type X402ClientConfig, X402ClientError, type X402ProtocolVersion, type X402TransportConfig, type X402Wallet, armoryGet, armoryPay, armoryPost, createX402Client, createX402Transport, detectX402Version, getNetworks, getTokens, getWalletAddress, parsePaymentRequired, validateNetwork, validateToken };
244
+ /**
245
+ * Armory API - Simplified payment interface
246
+ * Provides a configurable object with method-based payment functions
247
+ */
248
+
249
+ type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
250
+ interface ArmoryConfig {
251
+ wallet: SimpleWalletInput;
252
+ methods?: HttpMethod[] | HttpMethod;
253
+ tokens?: TokenId[] | TokenId;
254
+ chains?: NetworkId[] | NetworkId;
255
+ debug?: boolean;
256
+ }
257
+ interface PaymentOptions {
258
+ method?: HttpMethod;
259
+ body?: unknown;
260
+ }
261
+ interface ArmoryInstance {
262
+ get<T>(url: string, body?: unknown): Promise<ArmoryPaymentResult<T>>;
263
+ post<T>(url: string, body?: unknown): Promise<ArmoryPaymentResult<T>>;
264
+ put<T>(url: string, body?: unknown): Promise<ArmoryPaymentResult<T>>;
265
+ delete<T>(url: string): Promise<ArmoryPaymentResult<T>>;
266
+ patch<T>(url: string, body?: unknown): Promise<ArmoryPaymentResult<T>>;
267
+ pay<T>(url: string, options?: PaymentOptions): Promise<ArmoryPaymentResult<T>>;
268
+ call<T>(url: string): Promise<ArmoryPaymentResult<T>>;
269
+ }
270
+ declare const createArmory: (config: ArmoryConfig) => ArmoryInstance;
271
+
272
+ /**
273
+ * Client-side extension handling for x402
274
+ * Integrates with @armory-sh/extensions package
275
+ */
276
+
277
+ interface ClientExtensionContext {
278
+ bazaar?: BazaarExtensionInfo;
279
+ signInWithX?: SIWxExtensionInfo;
280
+ }
281
+ declare function parseExtensions(paymentRequired: PaymentRequiredV2): ClientExtensionContext;
282
+ declare function extractExtension<T>(extensions: Extensions | undefined, key: string): T | null;
283
+ declare function createSIWxProof(challenge: SIWxExtensionInfo, wallet: X402Wallet$1, nonce: string, expirationSeconds?: number): Promise<{
284
+ header: string;
285
+ address: Address$1;
286
+ }>;
287
+ declare function addExtensionsToPayload(payload: PaymentPayloadV2, extensions?: Extensions): PaymentPayloadV2;
288
+
289
+ export { type ArmoryConfig, type ArmoryInstance, type ClientExtensionContext, type HttpMethod, type NormalizedWallet, type ParsedPaymentRequirements, PaymentError, type PaymentOptions, type PaymentResult, SigningError, type SimpleWalletInput, type Token, type ViemHookConfig, type ViemHookRegistry, type ViemPaymentPayloadContext, type X402Client, type X402ClientConfig, X402ClientError, type X402ProtocolVersion, type X402TransportConfig, type X402Wallet, addExtensionsToPayload, armoryDelete, armoryGet, armoryPatch, armoryPay, armoryPost, armoryPut, createArmory, createSIWxProof, createX402Client, createX402Transport, detectX402Version, executeHooks, extractExtension, getNetworks, getTokens, getWalletAddress, mergeExtensions, normalizeWallet, parseExtensions, parsePaymentRequired, validateNetwork, validateToken };
package/dist/index.js CHANGED
@@ -82,6 +82,28 @@ async function createX402Payment(wallet, requirements, from, nonce = `0x${Date.n
82
82
  };
83
83
  }
84
84
 
85
+ // src/hooks-engine.ts
86
+ async function executeHooks(hooks, context) {
87
+ const sortedHooks = Object.entries(hooks).sort(([, a], [, b]) => {
88
+ const priorityA = a.priority ?? 0;
89
+ const priorityB = b.priority ?? 0;
90
+ return priorityB - priorityA;
91
+ });
92
+ for (const [key, config] of sortedHooks) {
93
+ try {
94
+ await config.hook(context);
95
+ } catch (error) {
96
+ console.warn(`[X402] Hook "${key}" failed:`, error);
97
+ }
98
+ }
99
+ }
100
+ function mergeExtensions(baseExtensions, hookExtensions) {
101
+ return {
102
+ ...baseExtensions ?? {},
103
+ ...hookExtensions
104
+ };
105
+ }
106
+
85
107
  // src/client.ts
86
108
  var DEFAULT_EXPIRY = 3600;
87
109
  var toCaip2Id = (chainId) => `eip155:${chainId}`;
@@ -119,7 +141,7 @@ var checkSettlement = (response) => {
119
141
  var createFetch = (wallet, config) => {
120
142
  const protocolWallet = toProtocolWallet(wallet);
121
143
  const getAddress = () => getWalletAddress(protocolWallet);
122
- const { defaultExpiry, nonceGenerator, debug, domainName, domainVersion } = config;
144
+ const { defaultExpiry, nonceGenerator, debug, domainName, domainVersion, hooks } = config;
123
145
  return async (input, init) => {
124
146
  let response = await fetch(input, init);
125
147
  if (response.status === 402) {
@@ -133,6 +155,18 @@ var createFetch = (wallet, config) => {
133
155
  const fromAddress = getAddress();
134
156
  const nonce = generateNonce(nonceGenerator);
135
157
  const validBefore = Math.floor(Date.now() / 1e3) + defaultExpiry;
158
+ const paymentRequiredContext = {
159
+ url: input,
160
+ requestInit: init,
161
+ requirements: parsed,
162
+ serverExtensions: parsed.extra,
163
+ fromAddress,
164
+ nonce,
165
+ validBefore
166
+ };
167
+ if (hooks) {
168
+ await executeHooks(hooks, paymentRequiredContext);
169
+ }
136
170
  const payment = await createX402Payment(
137
171
  protocolWallet,
138
172
  parsed,
@@ -145,6 +179,15 @@ var createFetch = (wallet, config) => {
145
179
  if (debug) {
146
180
  console.log("[X402] Created payment payload");
147
181
  }
182
+ if (hooks) {
183
+ const paymentPayloadContext = {
184
+ payload: payment,
185
+ requirements: parsed,
186
+ wallet: protocolWallet,
187
+ paymentContext: paymentRequiredContext
188
+ };
189
+ await executeHooks(hooks, paymentPayloadContext);
190
+ }
148
191
  const headers = new Headers(init?.headers);
149
192
  addPaymentHeader(headers, payment);
150
193
  response = await fetch(input, { ...init, headers });
@@ -157,7 +200,7 @@ var createFetch = (wallet, config) => {
157
200
  };
158
201
  };
159
202
  var createX402Client = (config) => {
160
- const { wallet, version = 2, defaultExpiry = DEFAULT_EXPIRY, nonceGenerator } = config;
203
+ const { wallet, version = 2, defaultExpiry = DEFAULT_EXPIRY, nonceGenerator, hooks } = config;
161
204
  const { domainName, domainVersion } = extractDomainConfig(config);
162
205
  const protocolWallet = toProtocolWallet(wallet);
163
206
  const getAddress = () => getWalletAddress(protocolWallet);
@@ -167,7 +210,8 @@ var createX402Client = (config) => {
167
210
  nonceGenerator,
168
211
  debug: config.debug,
169
212
  domainName,
170
- domainVersion
213
+ domainVersion,
214
+ hooks
171
215
  });
172
216
  return {
173
217
  fetch: fetchFn,
@@ -215,7 +259,8 @@ var createX402Transport = (config) => {
215
259
  debug: config.debug,
216
260
  token: config.token,
217
261
  domainName: config.domainName,
218
- domainVersion: config.domainVersion
262
+ domainVersion: config.domainVersion,
263
+ hooks: config.hooks
219
264
  });
220
265
  return client.fetch;
221
266
  };
@@ -229,9 +274,30 @@ import {
229
274
  getAvailableNetworks,
230
275
  getAvailableTokens
231
276
  } from "@armory-sh/base";
277
+ var normalizeWallet = (wallet) => {
278
+ if (typeof wallet === "object" && wallet !== null) {
279
+ if ("account" in wallet && "type" in wallet) {
280
+ return wallet;
281
+ }
282
+ if ("account" in wallet) {
283
+ return { type: "account", account: wallet.account };
284
+ }
285
+ if ("walletClient" in wallet) {
286
+ return { type: "walletClient", walletClient: wallet.walletClient };
287
+ }
288
+ }
289
+ if ("address" in wallet && "type" in wallet) {
290
+ return { type: "account", account: wallet };
291
+ }
292
+ if ("account" in wallet) {
293
+ return { type: "walletClient", walletClient: wallet };
294
+ }
295
+ throw new Error("Invalid wallet input");
296
+ };
232
297
  var armoryPay = async (wallet, url, network, token, options) => {
233
298
  try {
234
- const x402Wallet = "account" in wallet ? { type: "account", account: wallet.account } : { type: "walletClient", walletClient: wallet.walletClient };
299
+ const normalized = normalizeWallet(wallet);
300
+ const x402Wallet = normalized.type === "account" ? { type: "account", account: normalized.account } : { type: "walletClient", walletClient: normalized.walletClient };
235
301
  const config = validatePaymentConfig(network, token);
236
302
  if (isValidationError(config)) {
237
303
  return {
@@ -296,8 +362,18 @@ var armoryGet = (wallet, url, network, token, options) => {
296
362
  var armoryPost = (wallet, url, network, token, body, options) => {
297
363
  return armoryPay(wallet, url, network, token, { ...options, method: "POST", body });
298
364
  };
365
+ var armoryPut = (wallet, url, network, token, body, options) => {
366
+ return armoryPay(wallet, url, network, token, { ...options, method: "PUT", body });
367
+ };
368
+ var armoryDelete = (wallet, url, network, token, options) => {
369
+ return armoryPay(wallet, url, network, token, { ...options, method: "DELETE" });
370
+ };
371
+ var armoryPatch = (wallet, url, network, token, body, options) => {
372
+ return armoryPay(wallet, url, network, token, { ...options, method: "PATCH", body });
373
+ };
299
374
  var getWalletAddress2 = (wallet) => {
300
- return "account" in wallet ? wallet.account.address : wallet.walletClient.account.address;
375
+ const normalized = normalizeWallet(wallet);
376
+ return normalized.type === "account" ? normalized.account.address : normalized.walletClient.account.address;
301
377
  };
302
378
  var validateNetwork = (network) => {
303
379
  const resolved = resolveNetwork(network);
@@ -332,22 +408,338 @@ var getTokens = () => {
332
408
  return getAvailableTokens();
333
409
  };
334
410
 
411
+ // src/armory-api.ts
412
+ import {
413
+ resolveNetwork as resolveNetwork2,
414
+ resolveToken as resolveToken2,
415
+ getNetworkByChainId as getNetworkByChainId2
416
+ } from "@armory-sh/base";
417
+ var ALL_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "DELETE", "PATCH"]);
418
+ var arrayify = (value) => {
419
+ if (value === void 0) return void 0;
420
+ return Array.isArray(value) ? value : [value];
421
+ };
422
+ var normalizeArmoryConfig = (config) => ({
423
+ wallet: normalizeWallet(config.wallet),
424
+ allowedMethods: config.methods ? new Set(arrayify(config.methods)) : ALL_METHODS,
425
+ allowedTokens: arrayify(config.tokens) ?? [],
426
+ allowedChains: arrayify(config.chains) ?? [],
427
+ debug: config.debug ?? false
428
+ });
429
+ var scorePaymentOption = (option, allowedTokens, allowedChains) => {
430
+ let score = 0;
431
+ const chainId = parseInt(option.network.split(":")[1], 10);
432
+ const tokenAddress = option.asset.toLowerCase();
433
+ for (const chain of allowedChains) {
434
+ const resolved = resolveNetwork2(chain);
435
+ if (!("code" in resolved) && resolved.config.chainId === chainId) {
436
+ score += 10;
437
+ break;
438
+ }
439
+ }
440
+ for (const token of allowedTokens) {
441
+ const resolved = resolveToken2(token);
442
+ if (!("code" in resolved) && resolved.config.contractAddress.toLowerCase() === tokenAddress) {
443
+ score += 5;
444
+ break;
445
+ }
446
+ }
447
+ if (chainId === 8453) score += 2;
448
+ if (tokenAddress === "0x833589fcd6edb6e08f4c7c32d4f71b54bdA02913") {
449
+ score += 1;
450
+ }
451
+ const amount = parseInt(option.amount, 10);
452
+ if (amount < 1e6) score += 1;
453
+ return score;
454
+ };
455
+ var selectPaymentOption = (accepts, allowedTokens, allowedChains, allowedMethods) => {
456
+ const validOptions = accepts.filter((option) => {
457
+ const chainId = parseInt(option.network.split(":")[1], 10);
458
+ for (const chain of allowedChains) {
459
+ const resolved = resolveNetwork2(chain);
460
+ if (!("code" in resolved) && resolved.config.chainId === chainId) {
461
+ for (const token of allowedTokens) {
462
+ const resolvedToken = resolveToken2(token, resolved);
463
+ if (!("code" in resolvedToken) && resolvedToken.config.contractAddress.toLowerCase() === option.asset.toLowerCase()) {
464
+ return true;
465
+ }
466
+ }
467
+ }
468
+ }
469
+ if (allowedChains.length === 0 && allowedTokens.length === 0) {
470
+ return true;
471
+ }
472
+ return false;
473
+ });
474
+ if (validOptions.length === 0) return null;
475
+ if (validOptions.length === 1) return validOptions[0];
476
+ return validOptions.sort(
477
+ (a, b) => scorePaymentOption(b, allowedTokens, allowedChains) - scorePaymentOption(a, allowedTokens, allowedChains)
478
+ )[0];
479
+ };
480
+ var createArmory = (config) => {
481
+ const internal = normalizeArmoryConfig(config);
482
+ const x402Wallet = internal.wallet.type === "account" ? { type: "account", account: internal.wallet.account } : { type: "walletClient", walletClient: internal.wallet.walletClient };
483
+ const makeRequest = async (url, method, body) => {
484
+ try {
485
+ const headers = new Headers();
486
+ let response = await fetch(url, { method, headers });
487
+ if (response.status === 402) {
488
+ const paymentRequiredHeader = response.headers.get("PAYMENT-REQUIRED");
489
+ if (!paymentRequiredHeader) {
490
+ return {
491
+ success: false,
492
+ code: "PAYMENT_REQUIRED",
493
+ message: "Payment required but no PAYMENT-REQUIRED header found"
494
+ };
495
+ }
496
+ const decoded = Buffer.from(paymentRequiredHeader, "base64").toString("utf-8");
497
+ const paymentRequired = JSON.parse(decoded);
498
+ if (paymentRequired.x402Version !== 2 || !paymentRequired.accepts) {
499
+ return {
500
+ success: false,
501
+ code: "PAYMENT_REQUIRED",
502
+ message: "Invalid payment required format"
503
+ };
504
+ }
505
+ const selectedOption = selectPaymentOption(
506
+ paymentRequired.accepts,
507
+ internal.allowedTokens,
508
+ internal.allowedChains,
509
+ internal.allowedMethods
510
+ );
511
+ if (!selectedOption) {
512
+ return {
513
+ success: false,
514
+ code: "PAYMENT_REQUIRED",
515
+ message: "No compatible payment option found"
516
+ };
517
+ }
518
+ const chainId = parseInt(selectedOption.network.split(":")[1], 10);
519
+ const networkConfig = getNetworkByChainId2(chainId) ?? {
520
+ chainId,
521
+ name: "Unknown",
522
+ rpcUrl: "",
523
+ usdcAddress: selectedOption.asset,
524
+ caip2Id: selectedOption.network,
525
+ caipAssetId: `${selectedOption.network}/erc20:${selectedOption.asset}`
526
+ };
527
+ const client = createX402Client({
528
+ wallet: x402Wallet,
529
+ version: 2,
530
+ token: {
531
+ symbol: "CUSTOM",
532
+ name: "Payment Token",
533
+ version: "1",
534
+ chainId,
535
+ contractAddress: selectedOption.asset,
536
+ decimals: 6
537
+ },
538
+ debug: internal.debug
539
+ });
540
+ const fromAddress = internal.wallet.type === "account" ? internal.wallet.account.address : internal.wallet.walletClient.account.address;
541
+ const nonce = `0x${Date.now().toString(16).padStart(64, "0")}`;
542
+ const validBefore = Math.floor(Date.now() / 1e3) + 3600;
543
+ const payment = await client.createPayment(
544
+ selectedOption.amount,
545
+ selectedOption.payTo,
546
+ selectedOption.asset,
547
+ chainId,
548
+ validBefore
549
+ );
550
+ const signatureHeader = Buffer.from(JSON.stringify(payment)).toString("base64");
551
+ headers.set("PAYMENT-SIGNATURE", signatureHeader);
552
+ response = await fetch(url, {
553
+ method,
554
+ headers,
555
+ body: body !== void 0 ? JSON.stringify(body) : void 0
556
+ });
557
+ if (internal.debug) {
558
+ console.log("[Armory] Payment completed, status:", response.status);
559
+ }
560
+ } else {
561
+ response = await fetch(url, {
562
+ method,
563
+ headers,
564
+ body: body !== void 0 ? JSON.stringify(body) : void 0
565
+ });
566
+ }
567
+ if (!response.ok) {
568
+ const error = await response.text();
569
+ return {
570
+ success: false,
571
+ code: "PAYMENT_DECLINED",
572
+ message: `Request failed: ${response.status} ${error}`
573
+ };
574
+ }
575
+ const data = await response.json();
576
+ const txHash = response.headers.get("X-PAYMENT-RESPONSE") || response.headers.get("PAYMENT-RESPONSE");
577
+ return {
578
+ success: true,
579
+ data,
580
+ ...txHash && { txHash }
581
+ };
582
+ } catch (error) {
583
+ return {
584
+ success: false,
585
+ code: "NETWORK_ERROR",
586
+ message: error instanceof Error ? error.message : "Unknown error occurred",
587
+ details: error
588
+ };
589
+ }
590
+ };
591
+ return {
592
+ get: (url, body) => makeRequest(url, "GET", body),
593
+ post: (url, body) => makeRequest(url, "POST", body),
594
+ put: (url, body) => makeRequest(url, "PUT", body),
595
+ delete: (url) => makeRequest(url, "DELETE"),
596
+ patch: (url, body) => makeRequest(url, "PATCH", body),
597
+ pay: (url, options) => makeRequest(url, options?.method ?? "GET", options?.body),
598
+ call: (url) => makeRequest(url, "GET")
599
+ };
600
+ };
601
+
335
602
  // src/index.ts
336
603
  import { V2_HEADERS as V2_HEADERS3 } from "@armory-sh/base";
604
+
605
+ // src/extensions.ts
606
+ function parseExtensions(paymentRequired) {
607
+ const extensions = paymentRequired.extensions || {};
608
+ const result = {};
609
+ if (extensions.bazaar) {
610
+ const bazaarExt = extensions.bazaar;
611
+ if (bazaarExt && typeof bazaarExt === "object" && "info" in bazaarExt) {
612
+ result.bazaar = bazaarExt.info;
613
+ }
614
+ }
615
+ if (extensions["sign-in-with-x"]) {
616
+ const siwxExt = extensions["sign-in-with-x"];
617
+ if (siwxExt && typeof siwxExt === "object" && "info" in siwxExt) {
618
+ result.signInWithX = siwxExt.info;
619
+ }
620
+ }
621
+ return result;
622
+ }
623
+ function extractExtension(extensions, key) {
624
+ if (!extensions || typeof extensions !== "object") {
625
+ return null;
626
+ }
627
+ const extension = extensions[key];
628
+ if (!extension || typeof extension !== "object") {
629
+ return null;
630
+ }
631
+ const ext = extension;
632
+ return ext.info || null;
633
+ }
634
+ async function createSIWxProof(challenge, wallet, nonce, expirationSeconds = 3600) {
635
+ const getDefaultDomain = () => {
636
+ if (typeof window !== "undefined" && window.location) {
637
+ return window.location.hostname;
638
+ }
639
+ return "localhost";
640
+ };
641
+ const address = getWalletAddress(wallet);
642
+ const now = /* @__PURE__ */ new Date();
643
+ const expiration = new Date(now.getTime() + expirationSeconds * 1e3);
644
+ const payload = {
645
+ domain: challenge.domain || getDefaultDomain(),
646
+ resourceUri: challenge.resourceUri,
647
+ address,
648
+ statement: challenge.statement,
649
+ version: challenge.version || "1",
650
+ chainId: challenge.network,
651
+ nonce,
652
+ issuedAt: now.toISOString(),
653
+ expirationTime: expiration.toISOString()
654
+ };
655
+ const message = createSIWxMessage(payload);
656
+ let signature;
657
+ if (wallet.type === "account") {
658
+ if (!wallet.account.signTypedData) {
659
+ throw new Error("Wallet does not support signing");
660
+ }
661
+ signature = await wallet.account.signMessage({ message });
662
+ } else {
663
+ if (!wallet.walletClient.account.signTypedData) {
664
+ throw new Error("Wallet does not support signing");
665
+ }
666
+ signature = await wallet.walletClient.account.signMessage({ message });
667
+ }
668
+ payload.signature = signature;
669
+ const header = encodeSIWxHeader(payload);
670
+ return { header, address };
671
+ }
672
+ function base64UrlEncode(str) {
673
+ return btoa(str).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
674
+ }
675
+ function createSIWxMessage(payload) {
676
+ const lines = [];
677
+ lines.push(`${payload.domain} wants you to sign in`);
678
+ if (payload.statement) {
679
+ lines.push("");
680
+ lines.push(payload.statement);
681
+ }
682
+ lines.push("");
683
+ if (payload.resourceUri) {
684
+ lines.push(`URI: ${payload.resourceUri}`);
685
+ }
686
+ if (payload.version) {
687
+ lines.push(`Version: ${payload.version}`);
688
+ }
689
+ if (payload.nonce) {
690
+ lines.push(`Nonce: ${payload.nonce}`);
691
+ }
692
+ if (payload.issuedAt) {
693
+ lines.push(`Issued At: ${payload.issuedAt}`);
694
+ }
695
+ if (payload.expirationTime) {
696
+ lines.push(`Expiration Time: ${payload.expirationTime}`);
697
+ }
698
+ if (payload.chainId) {
699
+ const chains = Array.isArray(payload.chainId) ? payload.chainId.join(", ") : payload.chainId;
700
+ lines.push(`Chain ID(s): ${chains}`);
701
+ }
702
+ return lines.join("\n");
703
+ }
704
+ function encodeSIWxHeader(payload) {
705
+ const payloadStr = JSON.stringify(payload);
706
+ const encoded = base64UrlEncode(payloadStr);
707
+ return `siwx-v1-${encoded}`;
708
+ }
709
+ function addExtensionsToPayload(payload, extensions) {
710
+ if (!extensions || Object.keys(extensions).length === 0) {
711
+ return payload;
712
+ }
713
+ return {
714
+ ...payload,
715
+ extensions
716
+ };
717
+ }
337
718
  export {
338
719
  PaymentError,
339
720
  SigningError,
340
721
  V2_HEADERS3 as V2_HEADERS,
341
722
  X402ClientError,
723
+ addExtensionsToPayload,
724
+ armoryDelete,
342
725
  armoryGet,
726
+ armoryPatch,
343
727
  armoryPay,
344
728
  armoryPost,
729
+ armoryPut,
730
+ createArmory,
731
+ createSIWxProof,
345
732
  createX402Client,
346
733
  createX402Transport,
347
734
  detectX402Version,
735
+ executeHooks,
736
+ extractExtension,
348
737
  getNetworks,
349
738
  getTokens,
350
739
  getWalletAddress2 as getWalletAddress,
740
+ mergeExtensions,
741
+ normalizeWallet,
742
+ parseExtensions,
351
743
  parsePaymentRequired,
352
744
  validateNetwork,
353
745
  validateToken
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@armory-sh/client-viem",
3
- "version": "0.2.18",
3
+ "version": "0.2.20",
4
4
  "license": "MIT",
5
5
  "author": "Sawyer Cutler <sawyer@dirtroad.dev>",
6
6
  "type": "module",
@@ -12,6 +12,11 @@
12
12
  "bun": "./src/index.ts",
13
13
  "default": "./dist/index.js"
14
14
  },
15
+ "./extensions": {
16
+ "types": "./dist/extensions.d.ts",
17
+ "bun": "./src/extensions.ts",
18
+ "default": "./dist/extensions.js"
19
+ },
15
20
  "./dist/*": "./dist/*.js"
16
21
  },
17
22
  "files": [
@@ -27,7 +32,9 @@
27
32
  "directory": "packages/client-viem"
28
33
  },
29
34
  "dependencies": {
30
- "@armory-sh/base": "^0.2.19",
35
+ "@armory-sh/base": "^0.2.21",
36
+ "@armory-sh/extensions": "0.1.2",
37
+ "@types/node": "^25.2.3",
31
38
  "viem": "2.45.0"
32
39
  },
33
40
  "devDependencies": {