@zeroxyz/sdk 0.3.0 → 0.5.0

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.
@@ -77,6 +77,16 @@ var ZeroPaymentError = class extends ZeroError {
77
77
  this.name = "ZeroPaymentError";
78
78
  }
79
79
  };
80
+ var ZeroInsufficientFundsError = class extends ZeroError {
81
+ have;
82
+ need;
83
+ constructor(message, init) {
84
+ super("insufficient_funds", message, { cause: init.cause });
85
+ this.name = "ZeroInsufficientFundsError";
86
+ this.have = init.have;
87
+ this.need = init.need;
88
+ }
89
+ };
80
90
  var ZeroSessionCloseFailedError = class extends ZeroError {
81
91
  session;
82
92
  response;
@@ -423,7 +433,12 @@ var FETCH_SKIP_REASONS = Object.freeze({
423
433
  bodyReadFailed: "body_read_failed",
424
434
  mppSessionCloseFailedNotRecorded: "mpp_session_close_failed_not_recorded",
425
435
  paidButStill402NotRecorded: FETCH_WARNINGS.paidButStill402NotRecorded,
426
- paymentSettlementUnconfirmed: FETCH_WARNINGS.paymentSettlementUnconfirmed
436
+ paymentSettlementUnconfirmed: FETCH_WARNINGS.paymentSettlementUnconfirmed,
437
+ // Pre-sign abort: deliberately NOT recorded as a run — the wallet was
438
+ // empty before we reached the capability, so charging it as a 402 would
439
+ // corrupt cap reliability. The wallet funnel is tracked via the
440
+ // fetch_executed.outcome analytics event instead.
441
+ insufficientFunds: "insufficient_funds_not_recorded"
427
442
  });
428
443
  var MAX_RECEIPT_HEADER_BYTES = 8 * 1024;
429
444
  var decodeSessionReceiptHeader = (header) => {
@@ -565,6 +580,7 @@ var Payments = class {
565
580
  );
566
581
  let capturedAmount = null;
567
582
  let capturedNetwork;
583
+ let preflightError = null;
568
584
  const x402 = registerExactEvmScheme(new x402Client(), { signer: account });
569
585
  x402.onBeforePaymentCreation(async (context) => {
570
586
  const selected = context.selectedRequirements;
@@ -598,6 +614,29 @@ var Payments = class {
598
614
  reason: `Payment of ${capturedAmount} USDC exceeds maxPay ${input.maxPay}`
599
615
  };
600
616
  }
617
+ const detected = networkToChain(capturedNetwork);
618
+ const balanceChain = detected === "base-sepolia" ? "base-sepolia" : "base";
619
+ let balance = null;
620
+ try {
621
+ balance = await this.readUsdcBalance(account.address, balanceChain);
622
+ } catch (readErr) {
623
+ this.client.logger?.({
624
+ level: "warn",
625
+ message: "Pre-flight balance read failed; proceeding to attempt payment",
626
+ meta: {
627
+ chain: balanceChain,
628
+ error: readErr instanceof Error ? readErr.message : String(readErr)
629
+ }
630
+ });
631
+ }
632
+ if (balance !== null && balance < rawAmountUnits) {
633
+ const have = formatUnits(balance, 6);
634
+ preflightError = new ZeroInsufficientFundsError(
635
+ `wallet has ${have} USDC on Base, needs ${capturedAmount}.`,
636
+ { have, need: capturedAmount }
637
+ );
638
+ return { abort: true, reason: preflightError.message };
639
+ }
601
640
  });
602
641
  const configuredFetch = buildConfiguredFetch(this.client, {
603
642
  timeoutMs: input.timeoutMs
@@ -618,6 +657,7 @@ var Payments = class {
618
657
  signal: input.signal
619
658
  });
620
659
  } catch (err) {
660
+ if (preflightError) throw preflightError;
621
661
  if (err instanceof ZeroError) throw err;
622
662
  throw new ZeroPaymentError(
623
663
  `x402 payment failed: ${err instanceof Error ? err.message : String(err)}`,
@@ -862,23 +902,29 @@ var Payments = class {
862
902
  );
863
903
  if (tempoBalance < requiredRaw) {
864
904
  if (coerced.chain === "tempo-testnet") {
865
- throw new ZeroPaymentError(
866
- `Insufficient pathUSD on Tempo testnet: have ${formatUnits(tempoBalance, 6)}, need ${capturedAmount}. Fund via the Tempo testnet faucet: https://docs.tempo.xyz/quickstart/faucet`
905
+ const have = formatUnits(tempoBalance, 6);
906
+ throw new ZeroInsufficientFundsError(
907
+ `Insufficient pathUSD on Tempo testnet: have ${have}, need ${capturedAmount}. Fund via the Tempo testnet faucet: https://docs.tempo.xyz/quickstart/faucet`,
908
+ { have, need: capturedAmount }
867
909
  );
868
910
  }
869
- await this.bridgeBaseToTempo(account, requiredRaw - tempoBalance);
911
+ await this.bridgeBaseToTempo(account, requiredRaw - tempoBalance, {
912
+ isSessionDeposit: challenge.intent === "session",
913
+ depositTotal: capturedAmount
914
+ });
870
915
  }
871
916
  return capturedAmount;
872
917
  };
873
- bridgeBaseToTempo = async (account, requiredAmount) => {
918
+ bridgeBaseToTempo = async (account, requiredAmount, depositContext) => {
874
919
  this.ensureRelayClient();
875
920
  const baseBalance = await this.readUsdcBalance(account.address, "base");
876
921
  const buffer = calculateBuffer(baseBalance);
877
922
  const bridgeAmount = requiredAmount + buffer;
878
923
  if (baseBalance < bridgeAmount) {
879
- throw new ZeroPaymentError(
880
- `Insufficient Base USDC to bridge: have ${formatUnits(baseBalance, 6)}, need ${formatUnits(bridgeAmount, 6)} (${formatUnits(requiredAmount, 6)} + ${formatUnits(buffer, 6)} buffer). This capability uses an MPP payment channel, which is pre-funded up front. Either fund your wallet on Base, or pass \`maxPay\` to size a smaller channel (e.g. \`maxPay: '0.05'\` for a ~5-cent channel sized for a single cheap call).`
881
- );
924
+ const have = formatUnits(baseBalance, 6);
925
+ const need = formatUnits(bridgeAmount, 6);
926
+ const message = depositContext?.isSessionDeposit ? `wallet has ${have} USDC on Base, but this capability pre-funds a ${depositContext.depositTotal} USDC MPP payment channel up front (the channel is deposited before the call, so it asks for more than a single charge). Needs ${need} on Base to open it \u2014 fund the wallet, or pass a smaller \`maxPay\` (e.g. '0.05') to size the channel down to one cheap call.` : `wallet has ${have} USDC on Base, needs ${need} to fund this capability's MPP payment channel (${formatUnits(requiredAmount, 6)} + ${formatUnits(buffer, 6)} buffer). Tip: pass a smaller \`maxPay\` (e.g. '0.05') to size a cheaper channel.`;
927
+ throw new ZeroInsufficientFundsError(message, { have, need });
882
928
  }
883
929
  this.client.logger?.({
884
930
  level: "info",
@@ -1098,7 +1144,7 @@ var Payments = class {
1098
1144
 
1099
1145
  // package.json
1100
1146
  var package_default = {
1101
- version: "0.3.0"};
1147
+ version: "0.5.0"};
1102
1148
 
1103
1149
  // src/version.ts
1104
1150
  var SDK_VERSION = package_default.version;
@@ -1755,6 +1801,11 @@ var capabilityResponseSchema = z.object({
1755
1801
  lastUsedAt: z.string().nullable().optional(),
1756
1802
  lastSuccessfullyRanAt: z.string().nullable().optional()
1757
1803
  });
1804
+ var resolveCapabilityResponseSchema = z.object({
1805
+ capabilityId: z.string(),
1806
+ capabilitySlug: z.string(),
1807
+ matchedVia: z.enum(["exact", "template"])
1808
+ });
1758
1809
 
1759
1810
  // src/namespaces/capabilities.ts
1760
1811
  var Capabilities = class {
@@ -1771,6 +1822,31 @@ var Capabilities = class {
1771
1822
  },
1772
1823
  capabilityResponseSchema
1773
1824
  );
1825
+ // Resolve a raw endpoint URL + method back to the capability that owns it,
1826
+ // for the `zero fetch <url>` case where the client has no local search
1827
+ // context to match against (a memoized/cross-session URL). Returns null on
1828
+ // 404 — i.e. no match or an ambiguous multi-match, both of which the server
1829
+ // reports as 404 (resolveByUrl fails closed rather than mis-attribute).
1830
+ // The URL travels in the POST body so it stays out of access logs and is
1831
+ // never stored; the caller uses the returned capabilityId to record an
1832
+ // attributed run without the URL ever touching the run row (ZERO-335).
1833
+ resolveByUrl = async (url, method, opts = {}) => {
1834
+ try {
1835
+ return await request(
1836
+ this.client,
1837
+ {
1838
+ method: "POST",
1839
+ path: "/v1/capabilities/resolve",
1840
+ body: { url, method },
1841
+ signal: opts.signal
1842
+ },
1843
+ resolveCapabilityResponseSchema
1844
+ );
1845
+ } catch (err) {
1846
+ if (err instanceof ZeroApiError && err.status === 404) return null;
1847
+ throw err;
1848
+ }
1849
+ };
1774
1850
  };
1775
1851
  var createRunResponseSchema = z.object({
1776
1852
  runId: z.string()
@@ -2351,14 +2427,17 @@ var resolveRecordingClient = (client, opts) => {
2351
2427
  const canRecord = opts.account !== void 0 || credentials.kind === "account" || credentials.kind === "session";
2352
2428
  return { recordingClient, canRecord };
2353
2429
  };
2354
- var recordErrorFetchRun = async (client, opts, result) => {
2355
- if (opts.capabilityId === void 0) return;
2430
+ var recordErrorFetchRun = async (client, opts, result, capabilityIdOverride) => {
2431
+ const effectiveCapId = opts.capabilityId;
2432
+ if (effectiveCapId === void 0) return;
2356
2433
  const { recordingClient, canRecord } = resolveRecordingClient(client, opts);
2357
2434
  if (!canRecord) return;
2435
+ const fetchOriginForRecord = opts.capabilityId === void 0 ? "direct_url" : opts.fetchOrigin;
2358
2436
  try {
2359
2437
  const created = await recordingClient.runs.create({
2360
- capabilityId: opts.capabilityId,
2438
+ capabilityId: effectiveCapId,
2361
2439
  ...opts.searchId && { searchId: opts.searchId },
2440
+ ...fetchOriginForRecord && { fetchOrigin: fetchOriginForRecord },
2362
2441
  ...result.status !== null && { status: result.status },
2363
2442
  latencyMs: result.latencyMs,
2364
2443
  ...opts.requestSchema && { requestSchema: opts.requestSchema }
@@ -2368,7 +2447,7 @@ var recordErrorFetchRun = async (client, opts, result) => {
2368
2447
  } catch {
2369
2448
  }
2370
2449
  };
2371
- var recordTimeoutRun = async (client, opts, err, startedAt, capabilityIdRequested) => {
2450
+ var recordTimeoutRun = async (client, opts, err, startedAt, capabilityIdRequested, capabilityIdOverride) => {
2372
2451
  const result = errorFetchResult(
2373
2452
  startedAt,
2374
2453
  err.message,
@@ -2405,6 +2484,11 @@ var clientFetch = async (client, url, opts = {}) => {
2405
2484
  const startedAt = Date.now();
2406
2485
  const capabilityIdRequested = opts.capabilityId !== void 0;
2407
2486
  const method = opts.method ?? (opts.body !== void 0 ? "POST" : "GET");
2487
+ const { canRecord: canRecordForResolve } = resolveRecordingClient(
2488
+ client,
2489
+ opts
2490
+ );
2491
+ const resolvePromise = !capabilityIdRequested && canRecordForResolve ? client.capabilities.resolveByUrl(redactUrl(url), method).catch(() => null) : Promise.resolve(null);
2408
2492
  const probeHeaders = withDefaultContentType(opts.headers, opts.body);
2409
2493
  const requestInit = {
2410
2494
  method,
@@ -2480,6 +2564,15 @@ var clientFetch = async (client, url, opts = {}) => {
2480
2564
  session: err.session
2481
2565
  };
2482
2566
  warnings.push(FETCH_WARNINGS.mppSessionCloseFailed);
2567
+ } else if (err instanceof ZeroInsufficientFundsError) {
2568
+ return errorFetchResult(
2569
+ startedAt,
2570
+ err.message,
2571
+ FETCH_SKIP_REASONS.insufficientFunds,
2572
+ capabilityIdRequested,
2573
+ "insufficient_funds",
2574
+ null
2575
+ );
2483
2576
  } else if (err instanceof ZeroPaymentError || err instanceof ZeroAuthError) {
2484
2577
  const result2 = errorFetchResult(
2485
2578
  startedAt,
@@ -2587,7 +2680,15 @@ var clientFetch = async (client, url, opts = {}) => {
2587
2680
  else if (closeFailed)
2588
2681
  skipReasons.push(FETCH_SKIP_REASONS.mppSessionCloseFailedNotRecorded);
2589
2682
  }
2590
- if (opts.capabilityId !== void 0 && !paidButStill402 && !closeFailed) {
2683
+ const resolvedCap = await resolvePromise;
2684
+ const effectiveCapabilityId = opts.capabilityId ?? resolvedCap?.capabilityId;
2685
+ const capabilityResolution = !capabilityIdRequested && canRecordForResolve ? resolvedCap !== null ? {
2686
+ resolved: true,
2687
+ capabilityId: resolvedCap.capabilityId,
2688
+ capabilitySlug: resolvedCap.capabilitySlug,
2689
+ matchedVia: resolvedCap.matchedVia
2690
+ } : { resolved: false } : void 0;
2691
+ if (effectiveCapabilityId !== void 0 && !paidButStill402 && !closeFailed) {
2591
2692
  const { recordingClient, canRecord } = resolveRecordingClient(client, opts);
2592
2693
  if (!canRecord) {
2593
2694
  skipReasons.push("no_credentials");
@@ -2607,8 +2708,11 @@ var clientFetch = async (client, url, opts = {}) => {
2607
2708
  }
2608
2709
  }
2609
2710
  const result2 = await recordingClient.runs.create({
2610
- capabilityId: opts.capabilityId,
2711
+ capabilityId: effectiveCapabilityId,
2611
2712
  searchId: opts.searchId,
2713
+ // Auto-resolved runs are always direct_url — they only fire when
2714
+ // no capabilityId was supplied (the direct_url path by definition).
2715
+ ...opts.capabilityId === void 0 ? { fetchOrigin: "direct_url" } : opts.fetchOrigin && { fetchOrigin: opts.fetchOrigin },
2612
2716
  status: response.status,
2613
2717
  latencyMs,
2614
2718
  ...opts.requestSchema && { requestSchema: opts.requestSchema },
@@ -2641,7 +2745,7 @@ var clientFetch = async (client, url, opts = {}) => {
2641
2745
  message: "Failed to record run for fetch",
2642
2746
  meta: {
2643
2747
  url: redactUrl(url),
2644
- capabilityId: opts.capabilityId,
2748
+ capabilityId: effectiveCapabilityId,
2645
2749
  error: errorMessage2,
2646
2750
  errorCode: code
2647
2751
  }
@@ -2669,6 +2773,8 @@ var clientFetch = async (client, url, opts = {}) => {
2669
2773
  result.runTrackingSkipped = skipReasons;
2670
2774
  }
2671
2775
  if (warnings.length > 0) result.warnings = warnings;
2776
+ if (capabilityResolution !== void 0)
2777
+ result.capabilityResolution = capabilityResolution;
2672
2778
  return result;
2673
2779
  };
2674
2780
  var searchResultSchema = z.object({
@@ -3083,5 +3189,5 @@ var ZeroClient = class _ZeroClient {
3083
3189
  };
3084
3190
 
3085
3191
  export { Auth, AuthDevice, BUG_REPORT_CATEGORIES, BugReports, Capabilities, DEFAULT_BASE_URL, DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT_MS, FETCH_SKIP_REASONS, FETCH_WARNINGS, Payments, Runs, SDK_VERSION, TEMPO_CHAIN_ID, TEMPO_TESTNET_CHAIN_ID, Wallet, ZeroApiError, ZeroAuthError, ZeroClient, ZeroConfigurationError, ZeroError, ZeroPaymentError, ZeroSessionCloseFailedError, ZeroTimeoutError, ZeroValidationError, ZeroWalletError, coerceTempoChainId, createManagedAccount, paymentHasAnchor, tempoChainLabelFromId };
3086
- //# sourceMappingURL=chunk-FH6C6TXR.js.map
3087
- //# sourceMappingURL=chunk-FH6C6TXR.js.map
3192
+ //# sourceMappingURL=chunk-RY3VFSIE.js.map
3193
+ //# sourceMappingURL=chunk-RY3VFSIE.js.map