@piprail/sdk 1.20.1 → 1.21.1

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.js CHANGED
@@ -533,6 +533,37 @@ var EXACT_NETWORK_SLUGS = {
533
533
  function chainIdForExactNetwork(slug) {
534
534
  return EXACT_NETWORK_SLUGS[slug] ?? null;
535
535
  }
536
+ async function resolveExactRailEvm(input) {
537
+ const { asset, method, readDomain, permit2Supported } = input;
538
+ if (asset === "native") return null;
539
+ const want = method === "eip3009" || method === "permit2" ? method : "auto";
540
+ let chosen;
541
+ let extra = {};
542
+ if (want === "permit2") {
543
+ chosen = "permit2";
544
+ } else {
545
+ const d = await readDomain(asset);
546
+ if (d) {
547
+ chosen = "eip3009";
548
+ extra = { name: d.name, version: d.version };
549
+ } else if (want === "eip3009") {
550
+ throw new Error(
551
+ `requirePayment: exact \`method: 'eip3009'\` requested for ${asset}, but it isn't an EIP-3009 token (no name()/version()/authorizationState). Use \`method: 'permit2'\` (any ERC-20, e.g. Binance-Peg USDC on BNB) or \`'auto'\`. (Or check your rpcUrl is reachable.)`
552
+ );
553
+ } else {
554
+ chosen = "permit2";
555
+ }
556
+ }
557
+ if (chosen === "permit2" && !permit2Supported()) {
558
+ if (method === "permit2") {
559
+ throw new Error(
560
+ `requirePayment: exact \`method: 'permit2'\` needs the x402 Permit2 proxy deployed on this chain, but it isn't there. Offer an EIP-3009 token (gasless, no proxy), or drop \`exact\` on this chain. (See PERMIT2_PROXY_CHAIN_IDS.)`
561
+ );
562
+ }
563
+ return null;
564
+ }
565
+ return { method: chosen, extra };
566
+ }
536
567
  var EIP3009_TYPES = {
537
568
  TransferWithAuthorization: [
538
569
  { name: "from", type: "address" },
@@ -635,10 +666,14 @@ async function payExactEvm(input) {
635
666
  `exact buyer rail requires an EOA signer; ${account.address} is a contract / EIP-1271 / EIP-7702-delegated account (no recoverable ECDSA signature). Pay via onchain-proof.`
636
667
  );
637
668
  }
638
- const domain = await readExactDomain(publicClient, accept.asset);
669
+ let domain = await readExactDomain(publicClient, accept.asset);
670
+ if (!domain) {
671
+ await new Promise((r) => setTimeout(r, 300));
672
+ domain = await readExactDomain(publicClient, accept.asset);
673
+ }
639
674
  if (!domain) {
640
675
  throw new UnsupportedSchemeError(
641
- `exact: ${accept.asset} on ${accept.network} isn't an EIP-3009 token (USDT needs Permit2; native coin and plain ERC-20s aren't exact-payable). Pay via onchain-proof.`
676
+ `exact: couldn't derive the EIP-712 domain for ${accept.asset} on ${accept.network}. Either it isn't an EIP-3009 token (USDT needs Permit2; native/plain ERC-20 aren't exact-payable) \u2014 pay via onchain-proof \u2014 OR your RPC couldn't read it (transient): if the gate advertised eip3009, pass a reliable rpcUrl and retry.`
642
677
  );
643
678
  }
644
679
  const g = globalThis.crypto;
@@ -1417,11 +1452,14 @@ function parseExactPaymentHeader(value) {
1417
1452
  if (typeof network !== "string") return null;
1418
1453
  const payload = v.payload;
1419
1454
  if (!payload || typeof payload !== "object") return null;
1420
- const signature = payload.signature;
1421
- if (typeof signature !== "string") return null;
1422
1455
  const x402Version = typeof v.x402Version === "number" ? v.x402Version : 2;
1423
1456
  const asset = accepted && typeof accepted.asset === "string" ? accepted.asset : void 0;
1424
1457
  const base2 = { x402Version, network, ...asset ? { asset } : {}, raw: v };
1458
+ if (typeof payload.transaction === "string") {
1459
+ return { ...base2, method: "svm", payload: { transaction: payload.transaction } };
1460
+ }
1461
+ const signature = payload.signature;
1462
+ if (typeof signature !== "string") return null;
1425
1463
  const authorization = payload.authorization;
1426
1464
  if (authorization && typeof authorization === "object") {
1427
1465
  for (const k of ["from", "to", "value", "validAfter", "validBefore", "nonce"]) {
@@ -1708,6 +1746,16 @@ function makeEvmNetwork(resolved) {
1708
1746
  exactPermit2Supported() {
1709
1747
  return isPermit2ProxyChain(resolved.chainId);
1710
1748
  },
1749
+ // The gate's rail-advertisement SPI — EIP-3009 vs Permit2 selection (the pure helper),
1750
+ // injecting the on-chain domain read + the Permit2 proxy-presence check.
1751
+ async resolveExactRail({ asset, method }) {
1752
+ return resolveExactRailEvm({
1753
+ asset,
1754
+ method,
1755
+ readDomain: (a) => readExactDomain(publicClient, a),
1756
+ permit2Supported: () => isPermit2ProxyChain(resolved.chainId)
1757
+ });
1758
+ },
1711
1759
  async settleExactSelf({ relayer, payload, accept }) {
1712
1760
  const a = relayer._native;
1713
1761
  if ("permit2Authorization" in payload) {
@@ -1720,6 +1768,9 @@ function makeEvmNetwork(resolved) {
1720
1768
  accept
1721
1769
  });
1722
1770
  }
1771
+ if ("transaction" in payload) {
1772
+ return { ok: false, error: "signature_invalid", detail: "An SVM (Solana) payload was submitted to an EVM exact rail." };
1773
+ }
1723
1774
  return verifyAndSettleExactEvm({
1724
1775
  publicClient,
1725
1776
  walletClient: a.walletClient,
@@ -1744,7 +1795,7 @@ var loaders = {
1744
1795
  solana: async () => {
1745
1796
  let mod;
1746
1797
  try {
1747
- mod = await import("./solana-WDKWWF33.js");
1798
+ mod = await import("./solana-IBVUZS54.js");
1748
1799
  } catch (cause) {
1749
1800
  throw new MissingDriverError(
1750
1801
  `Solana selected, but its packages aren't installed. Run: npm install @solana/web3.js @solana/spl-token bs58`,
@@ -2770,7 +2821,7 @@ var PipRailClient = class {
2770
2821
  * before publishing, so retry with a brief backoff if a fresh listing is missing.
2771
2822
  * - Results are cross-scheme (mostly the mainstream `exact` scheme); `fetch()` pays
2772
2823
  * `onchain-proof` rails by default, and standard `exact` rails too once you opt in
2773
- * with `schemes: ['onchain-proof', 'exact']` (EVM + EIP-3009 USDC/EURC).
2824
+ * with `schemes: ['onchain-proof', 'exact']` (EVM EIP-3009/Permit2 + Solana SVM).
2774
2825
  */
2775
2826
  async discover(opts = {}) {
2776
2827
  const found = await searchOpenIndexes({
@@ -2955,7 +3006,7 @@ var PipRailClient = class {
2955
3006
  );
2956
3007
  if (schemes.includes("exact") && exactOnNet && typeof net.payExact !== "function") {
2957
3008
  throw new UnsupportedSchemeError(
2958
- `This 402 offers a standard 'exact' rail on ${net.network}, but the ${net.family} family can't pay 'exact' (EVM + EIP-3009 only), and no 'onchain-proof' rail was offered.`
3009
+ `This 402 offers a standard 'exact' rail on ${net.network}, but the ${net.family} family can't pay 'exact' (supported on EVM + Solana today), and no 'onchain-proof' rail was offered.`
2959
3010
  );
2960
3011
  }
2961
3012
  if (!schemes.includes("exact") && exactOnNet && typeof net.payExact === "function") {
@@ -3335,7 +3386,7 @@ var PipRailClient = class {
3335
3386
  async payExactRail(net, wallet, accept, url, init, quote) {
3336
3387
  if (!net.payExact) {
3337
3388
  throw new UnsupportedSchemeError(
3338
- `the ${net.family} family can't pay a standard 'exact' rail (EVM + EIP-3009 only).`
3389
+ `the ${net.family} family can't pay a standard 'exact' rail (supported on EVM + Solana today).`
3339
3390
  );
3340
3391
  }
3341
3392
  throwIfAborted(init?.signal);
@@ -3600,6 +3651,18 @@ straight from your wallet to the server; PipRail custodies nothing. Follow this.
3600
3651
  and return the result.
3601
3652
  Always plan before you pay so you never commit to a payment you cannot finish.
3602
3653
 
3654
+ ## Gasless \u2014 the exact rail (zero gas for you)
3655
+ A 402 may offer up to two rails; you don't choose per payment \u2014 the client does, automatically:
3656
+ - onchain-proof (PipRail's default): you broadcast the payment yourself and pay the network gas
3657
+ (the native coin \u2014 ETH/SOL/\u2026). Works on every chain.
3658
+ - exact (the ratified x402 rail, opt-in): you only SIGN; the server \u2014 or a facilitator it chose
3659
+ (e.g. PayAI) \u2014 broadcasts it, so you pay ZERO gas (you need only the token, no native coin). It
3660
+ works on EVM + Solana, and the on-chain method (EIP-3009 / Permit2 / SVM) is picked automatically.
3661
+ When the exact scheme is enabled AND balance-aware routing is on, paying picks the cheapest
3662
+ settleable rail \u2014 i.e. the gasless exact one. Nothing changes in your loop: quote \u2192 plan \u2192 pay is
3663
+ identical. The exact scheme is OPT-IN by the operator (MCP: PIPRAIL_SCHEMES=onchain-proof,exact);
3664
+ you can't enable it yourself, but you can report when a 402 needs it (see UNSUPPORTED_SCHEME below).
3665
+
3603
3666
  ## Reading a refusal \u2014 never crash, never double-spend
3604
3667
  A failed pay returns a STRUCTURED object, never a thrown error you must catch:
3605
3668
  { ok:false, code, reason, explain, ref?, reasonCode?, declined? }
@@ -3617,9 +3680,13 @@ Branch on \`code\` (always reliable). Key cases:
3617
3680
  - code:'INSUFFICIENT_FUNDS' \u2014 top up the wallet (token and/or native gas), retry.
3618
3681
  - code:'PAYMENT_TIMEOUT' / 'MAX_RETRIES_EXCEEDED' / 'CONFIRMATION_TIMEOUT' \u2014 the
3619
3682
  payment may ALREADY be on-chain. Recover using the proof on \`.ref\` (re-verify
3620
- or re-submit it); never re-pay \u2014 a fresh payment would double-spend.
3683
+ or re-submit it); never re-pay \u2014 a fresh payment would double-spend. On a gasless
3684
+ exact rail \`.ref\` is the authorization NONCE, not a tx hash: re-present the SAME
3685
+ signed authorization, never sign a fresh one (that would risk a double-spend).
3621
3686
  - code:'NO_COMPATIBLE_ACCEPT' / 'UNSUPPORTED_SCHEME' \u2014 the 402 isn't payable on
3622
3687
  your chain/scheme; \`explain\` says whether it's the wrong chain or a scheme to enable.
3688
+ If it's a standard x402 server offering an exact rail, that's a config fix the operator makes
3689
+ once (enable the exact scheme); report it, don't retry the same call blindly.
3623
3690
 
3624
3691
  ## Knowing your leash \u2014 call piprail_budget
3625
3692
  piprail_budget tells you how much budget and time you have left, per
@@ -4012,6 +4079,24 @@ function buildX402DnsTxt(input) {
4012
4079
  }
4013
4080
 
4014
4081
  // src/facilitator.ts
4082
+ async function fetchFacilitatorFeePayer(url, network, timeoutMs = 8e3) {
4083
+ const base2 = url.replace(/\/+$/, "");
4084
+ const ctrl = new AbortController();
4085
+ const timer = setTimeout(() => ctrl.abort(), timeoutMs);
4086
+ try {
4087
+ const res = await fetch(`${base2}/supported`, { signal: ctrl.signal });
4088
+ if (!res.ok) return void 0;
4089
+ const body = await res.json();
4090
+ const kinds = Array.isArray(body?.kinds) ? body.kinds : [];
4091
+ const kind = kinds.find((k) => k?.scheme === "exact" && k?.network === network);
4092
+ const fp = kind?.extra?.feePayer;
4093
+ return typeof fp === "string" ? fp : void 0;
4094
+ } catch {
4095
+ return void 0;
4096
+ } finally {
4097
+ clearTimeout(timer);
4098
+ }
4099
+ }
4015
4100
  function safeStringify(value) {
4016
4101
  return JSON.stringify(value, (_k, v) => typeof v === "bigint" ? v.toString() : v);
4017
4102
  }
@@ -4134,6 +4219,7 @@ function createPaymentGate(options) {
4134
4219
  if (resolved) return resolved;
4135
4220
  const p = (async () => {
4136
4221
  const accepts = normaliseAccepts(options);
4222
+ const exactSkips = [];
4137
4223
  const specs = await Promise.all(
4138
4224
  accepts.map(async (a) => {
4139
4225
  const net = await resolveNetwork2({ chain: a.chain, rpcUrl: a.rpcUrl ?? options.rpcUrl });
@@ -4147,13 +4233,17 @@ function createPaymentGate(options) {
4147
4233
  const { asset, decimals, symbol } = net.resolveToken(a.token);
4148
4234
  const amountBase = parseUnits(a.amount, decimals);
4149
4235
  const spec = { net, asset, decimals, symbol, amountBase, amountFormatted: a.amount, payTo };
4150
- if (options.exact) spec.exact = await resolveExactRail(net, asset);
4236
+ if (options.exact) {
4237
+ const outcome = await resolveExactRail(net, asset);
4238
+ if (outcome.rail) spec.exact = outcome.rail;
4239
+ else if (outcome.skipReason) exactSkips.push(outcome.skipReason);
4240
+ }
4151
4241
  return spec;
4152
4242
  })
4153
4243
  );
4154
4244
  if (options.exact && !specs.some((s) => s.exact)) {
4155
4245
  throw new Error(
4156
- "requirePayment: `exact` was requested but none of the offered rails support it. The standard `exact` rail is EVM ERC-20 only \u2014 EIP-3009 (USDC / EURC) or Permit2 (any ERC-20, e.g. Binance-Peg USDC on BNB) \u2014 NOT native coins, NOT non-EVM chains. Offer an EVM ERC-20 token, or drop `exact`."
4246
+ "requirePayment: `exact` was requested but none of the offered rails support it. " + (exactSkips.length > 0 ? exactSkips.join(" ") : "The standard `exact` rail is EVM ERC-20 (EIP-3009 \u2014 USDC / EURC \u2014 or Permit2, e.g. Binance-Peg USDC on BNB) or a Solana SPL token (SVM) \u2014 NOT native coins, NOT families without a standard `exact` scheme. Offer an EVM ERC-20 / Solana SPL token, or drop `exact`.")
4157
4247
  );
4158
4248
  }
4159
4249
  return specs;
@@ -4166,53 +4256,48 @@ function createPaymentGate(options) {
4166
4256
  }
4167
4257
  async function resolveExactRail(net, asset) {
4168
4258
  const cfg = options.exact;
4169
- if (net.family !== "evm" || asset === "native" || !net.settleExactSelf) {
4170
- return void 0;
4171
- }
4172
- const want = cfg.method ?? "auto";
4173
- let method;
4174
- let domain;
4175
- if (want === "permit2") {
4176
- method = "permit2";
4177
- } else {
4178
- const d = net.exactDomain ? await net.exactDomain(asset) : null;
4179
- if (d) {
4180
- method = "eip3009";
4181
- domain = d;
4182
- } else if (want === "eip3009") {
4259
+ const settle = cfg.settle;
4260
+ if (!net.resolveExactRail) return {};
4261
+ let relayer;
4262
+ let feePayer;
4263
+ if (settle === "self") {
4264
+ if (cfg.relayer === void 0) {
4183
4265
  throw new Error(
4184
- `requirePayment: exact \`method: 'eip3009'\` requested for ${asset} on ${net.network}, but it isn't an EIP-3009 token (no name()/version()/authorizationState). Use \`method: 'permit2'\` (any ERC-20, e.g. Binance-Peg USDC on BNB) or \`'auto'\`. (Or check your rpcUrl is reachable.)`
4266
+ "requirePayment: exact `settle: 'self'` needs a `relayer` wallet (the gas-paying key that broadcasts the settle), e.g. exact: { settle: 'self', relayer: { privateKey } }."
4185
4267
  );
4186
- } else {
4187
- method = "permit2";
4188
4268
  }
4269
+ relayer = net.bindWallet(cfg.relayer);
4270
+ } else {
4271
+ feePayer = settle.feePayer;
4189
4272
  }
4190
- if (method === "permit2" && !(net.exactPermit2Supported?.() ?? false)) {
4191
- if (cfg.method === "permit2") {
4192
- throw new Error(
4193
- `requirePayment: exact \`method: 'permit2'\` needs the x402 Permit2 proxy deployed on ${net.network}, but it isn't there. Offer an EIP-3009 token (gasless, no proxy), or drop \`exact\` on this chain. (See PERMIT2_PROXY_CHAIN_IDS.)`
4194
- );
4273
+ const method = cfg.method ?? "auto";
4274
+ let info = await net.resolveExactRail({ asset, method, relayer, feePayer });
4275
+ if (!info && settle !== "self" && !feePayer && asset !== "native") {
4276
+ const discovered = await fetchFacilitatorFeePayer(settle.facilitator, net.network);
4277
+ if (!discovered) {
4278
+ return {
4279
+ skipReason: `${net.network}: couldn't read a fee payer from the facilitator (${settle.facilitator}/supported) \u2014 it may be down, or may not sponsor this network. Set \`exact.settle.feePayer\` explicitly to remove the runtime dependency, switch to \`settle: 'self'\` with your own relayer, or retry.`
4280
+ };
4195
4281
  }
4196
- return void 0;
4282
+ info = await net.resolveExactRail({ asset, method, relayer, feePayer: discovered });
4197
4283
  }
4198
- if (cfg.settle === "self") {
4199
- if (cfg.relayer === void 0) {
4284
+ if (!info) return {};
4285
+ if (settle !== "self" && info.method === "permit2") {
4286
+ if (cfg.method === "permit2") {
4200
4287
  throw new Error(
4201
- "requirePayment: exact `settle: 'self'` needs a `relayer` wallet (the gas-paying key that broadcasts the settle), e.g. exact: { settle: 'self', relayer: { privateKey } }."
4288
+ "requirePayment: exact `method: 'permit2'` can't be settled by a third-party facilitator \u2014 facilitators settle the standard EIP-3009 (EVM) / SVM (Solana) schemes, not PipRail\u2019s Permit2 proxy. Use an EIP-3009 token (USDC / EURC) with the facilitator, or `settle: 'self'` (your own relayer) to settle Permit2 yourself."
4202
4289
  );
4203
4290
  }
4204
- const relayer = net.bindWallet(cfg.relayer);
4205
- return { method, ...domain ? { domain } : {}, mode: { kind: "self", relayer } };
4291
+ return {
4292
+ skipReason: `${net.network}: this token isn't EIP-3009 (auto-selected Permit2), which a third-party facilitator can't settle \u2014 it would serve onchain-proof only here. Use an EIP-3009 token (USDC / EURC) with the facilitator, or \`settle: 'self'\` to settle Permit2 yourself.`
4293
+ };
4206
4294
  }
4207
- return {
4208
- method,
4209
- ...domain ? { domain } : {},
4210
- mode: {
4211
- kind: "facilitator",
4212
- url: cfg.settle.facilitator,
4213
- ...cfg.settle.authHeaders ? { authHeaders: cfg.settle.authHeaders } : {}
4214
- }
4295
+ const mode = settle === "self" ? { kind: "self", relayer } : {
4296
+ kind: "facilitator",
4297
+ url: settle.facilitator,
4298
+ ...settle.authHeaders ? { authHeaders: settle.authHeaders } : {}
4215
4299
  };
4300
+ return { rail: { method: info.method, ...info.extra ? { extra: info.extra } : {}, mode } };
4216
4301
  }
4217
4302
  const hasCustomStore = Boolean(options.isUsed || options.markUsed);
4218
4303
  const localUsed = /* @__PURE__ */ new Map();
@@ -4269,11 +4354,11 @@ function createPaymentGate(options) {
4269
4354
  maxTimeoutSeconds,
4270
4355
  extra: {
4271
4356
  assetTransferMethod: rail.method,
4272
- ...rail.domain ? { name: rail.domain.name, version: rail.domain.version } : {},
4273
4357
  minConfirmations,
4274
4358
  decimals: s.decimals,
4275
4359
  amountFormatted: s.amountFormatted,
4276
- ...s.symbol ? { symbol: s.symbol } : {}
4360
+ ...s.symbol ? { symbol: s.symbol } : {},
4361
+ ...rail.extra
4277
4362
  }
4278
4363
  };
4279
4364
  }
@@ -4426,10 +4511,23 @@ function createPaymentGate(options) {
4426
4511
  `No \`exact\` rail offered for ${exact.network}${exact.asset ? `/${exact.asset}` : ""} (offered: ${exactSpecs.map((s) => `${s.asset}@${s.net.network}`).join(", ")}).`
4427
4512
  );
4428
4513
  }
4429
- const auth = "permit2Authorization" in exact.payload ? exact.payload.permit2Authorization : exact.payload.authorization;
4430
- const nonce = auth.nonce;
4514
+ let nonce;
4515
+ let evmAuth = null;
4516
+ if ("transaction" in exact.payload) {
4517
+ try {
4518
+ nonce = Buffer.from(exact.payload.transaction, "base64").toString("base64");
4519
+ } catch {
4520
+ nonce = exact.payload.transaction;
4521
+ }
4522
+ } else if ("permit2Authorization" in exact.payload) {
4523
+ evmAuth = exact.payload.permit2Authorization;
4524
+ nonce = evmAuth.nonce;
4525
+ } else {
4526
+ evmAuth = exact.payload.authorization;
4527
+ nonce = evmAuth.nonce;
4528
+ }
4431
4529
  if (await claimTx(nonce)) {
4432
- return rejection("tx_already_used", `Authorization nonce ${nonce} was already redeemed.`);
4530
+ return rejection("tx_already_used", `Authorization ${evmAuth ? `nonce ${nonce}` : "transaction"} was already redeemed.`);
4433
4531
  }
4434
4532
  const accept = buildExactAccept(spec);
4435
4533
  const mode = spec.exact.mode;
@@ -4454,12 +4552,14 @@ function createPaymentGate(options) {
4454
4552
  amount: accept.amount,
4455
4553
  payTo: accept.payTo,
4456
4554
  maxTimeoutSeconds: accept.maxTimeoutSeconds,
4457
- // name/version are OPTIONAL on the wire type (a foreign rail may omit them), but the
4458
- // gate's OWN exact rail always read them on-chain at resolution — so they're present here.
4459
- extra: { name: accept.extra.name ?? "", version: accept.extra.version ?? "" }
4555
+ // The scheme's chain-specific `extra`, from the gate's OWN trusted rail: SVM forwards the
4556
+ // facilitator's `feePayer` (the gas sponsor); EVM forwards the token's EIP-712 domain.
4557
+ extra: accept.extra.assetTransferMethod === "svm" ? { feePayer: accept.extra.feePayer ?? "" } : { name: accept.extra.name ?? "", version: accept.extra.version ?? "" }
4460
4558
  },
4461
4559
  receipt: { network: accept.network, asset: accept.asset, payTo: accept.payTo, amount: accept.amount },
4462
- payerHint: auth.from
4560
+ // The buyer address, for the receipt's `payer` fallback. EVM carries it in the
4561
+ // authorization; SVM doesn't (the facilitator returns the settled payer) → omit it.
4562
+ ...evmAuth ? { payerHint: evmAuth.from } : {}
4463
4563
  });
4464
4564
  }
4465
4565
  } catch (err) {