@armory-sh/client-ethers 0.2.26-alpha.23.82 → 0.2.27-alpha.23.83

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/README.md +2 -0
  2. package/dist/index.js +86 -41
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -79,10 +79,12 @@ const client = createX402Client({
79
79
  ```
80
80
 
81
81
  `parsePaymentRequired` returns `accepts[]` (x402 v2 challenge options). Clients select from this list.
82
+ `hooks` are lifecycle callbacks. `extensions` are protocol payload fields. Hooks can drive selection and payload behavior, but they are not extensions.
82
83
 
83
84
  ## Features
84
85
 
85
86
  - **Auto 402 Handling**: Automatically intercepts and pays for 402 responses
87
+ - **Detailed Verification Errors**: 402 retry failures include server details (for example `insufficient_funds`)
86
88
  - **EIP-3009 Signing**: Full support for EIP-3009 TransferWithAuthorization
87
89
  - **Multi-Network**: Ethereum, Base, SKALE support
88
90
  - **Multi-Token**: USDC, EURC, USDT, WBTC, WETH, SKL
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { X402ClientError, createEIP712Domain, V2_HEADERS, PaymentException, isX402V2PaymentRequired, validatePaymentConfig, isValidationError, resolveNetwork, resolveToken, normalizeBase64Url, decodeBase64ToUtf8, getNetworkConfig, normalizeNetworkName, encodePaymentV2, decodeSettlementV2 } from '@armory-sh/base';
2
2
  export { PaymentException as PaymentError, SigningError, X402ClientError } from '@armory-sh/base';
3
- import { runOnPaymentRequiredHooks, selectRequirementWithHooks, runBeforeSignPaymentHooks, runAfterPaymentResponseHooks } from '@armory-sh/base/client-hooks-runtime';
3
+ import { runOnPaymentRequiredHooks, getRequirementAttemptOrderWithHooks, runBeforeSignPaymentHooks, runAfterPaymentResponseHooks } from '@armory-sh/base/client-hooks-runtime';
4
4
  import { ethers } from 'ethers';
5
5
 
6
6
  var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
@@ -262,6 +262,29 @@ var getRequirementDomainOverrides = (_parsed, requirement) => {
262
262
  domainVersion: requirement.version ?? extraVersion
263
263
  };
264
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
+ };
265
288
  var handlePaymentRequired = async (state, response) => {
266
289
  if (!state.signer) {
267
290
  throw new SignerRequiredError(
@@ -286,51 +309,73 @@ var handlePaymentRequired = async (state, response) => {
286
309
  nonce: `0x${Date.now().toString(16).padStart(64, "0")}`,
287
310
  validBefore: Math.floor(Date.now() / 1e3) + initialRequirement.maxTimeoutSeconds
288
311
  };
289
- await runOnPaymentRequiredHooks(state.config.hooks, paymentRequiredContext);
290
- const selectedRequirement = await selectRequirementWithHooks(
312
+ await runOnPaymentRequiredHooks(
291
313
  state.config.hooks,
292
314
  paymentRequiredContext
293
315
  );
294
- const requirementDomain = getRequirementDomainOverrides(
295
- parsed,
296
- selectedRequirement
297
- );
298
- const payload = await createX402Payment(
299
- state.signer,
300
- selectedRequirement,
301
- from,
302
- paymentRequiredContext.nonce,
303
- paymentRequiredContext.validBefore,
304
- requirementDomain.domainName,
305
- requirementDomain.domainVersion
316
+ const attemptRequirements = await getRequirementAttemptOrderWithHooks(
317
+ state.config.hooks,
318
+ paymentRequiredContext
306
319
  );
307
- await runBeforeSignPaymentHooks(state.config.hooks, {
308
- payload,
309
- requirements: selectedRequirement,
310
- wallet: state.signer,
311
- paymentContext: paymentRequiredContext
312
- });
313
- const encoded = encodePaymentV2(payload);
314
320
  const headerName = getPaymentHeaderName(parsed.version);
315
- const paymentResponse = await fetchWithTimeout(
316
- response.url,
317
- {
318
- method: "GET",
319
- headers: { ...state.config.headers, [headerName]: encoded }
320
- },
321
- state.config.timeout
322
- );
323
- const settlement = decodeSettlementV2(
324
- paymentResponse.headers.get(V2_HEADERS.PAYMENT_RESPONSE) || ""
325
- );
326
- await runAfterPaymentResponseHooks(state.config.hooks, {
327
- payload,
328
- requirements: selectedRequirement,
329
- wallet: state.signer,
330
- paymentContext: paymentRequiredContext,
331
- response: paymentResponse
332
- });
333
- return { success: true, settlement };
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");
334
379
  } catch (error) {
335
380
  return {
336
381
  success: false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@armory-sh/client-ethers",
3
- "version": "0.2.26-alpha.23.82",
3
+ "version": "0.2.27-alpha.23.83",
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.27-alpha.23.82",
50
+ "@armory-sh/base": "0.2.28-alpha.23.83",
51
51
  "ethers": "6.16.0"
52
52
  },
53
53
  "devDependencies": {