@piprail/sdk 2.13.1 → 2.14.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.
Files changed (30) hide show
  1. package/CHANGELOG.md +109 -0
  2. package/dist/{algorand-AQK5EAUF.cjs → algorand-ICJNXO5V.cjs} +43 -42
  3. package/dist/{algorand-TQCYK2XT.js → algorand-RM7MP4PK.js} +12 -11
  4. package/dist/{aptos-RYQOSFBQ.js → aptos-2FIRTMQX.js} +11 -10
  5. package/dist/{aptos-65UX7GKA.cjs → aptos-MVEG5ZPS.cjs} +41 -40
  6. package/dist/{chunk-DCOUJZPL.cjs → chunk-57V5DNJT.cjs} +45 -19
  7. package/dist/{chunk-O32N6MFN.js → chunk-NUPCFNC5.js} +27 -1
  8. package/dist/index.cjs +503 -239
  9. package/dist/index.d.cts +279 -7
  10. package/dist/index.d.ts +279 -7
  11. package/dist/index.js +299 -35
  12. package/dist/{ledger-mF_SoiDB.d.ts → ledger-uFtXlIHY.d.cts} +31 -1
  13. package/dist/{ledger-mF_SoiDB.d.cts → ledger-uFtXlIHY.d.ts} +31 -1
  14. package/dist/{near-UDPFA6SV.cjs → near-C2IS4RCH.cjs} +39 -30
  15. package/dist/{near-VDATC5AV.js → near-L2OWD7M6.js} +14 -5
  16. package/dist/node.d.cts +2 -2
  17. package/dist/node.d.ts +2 -2
  18. package/dist/{solana-RQNXFCZO.cjs → solana-B424IQ5I.cjs} +54 -39
  19. package/dist/{solana-VYLWFNAH.js → solana-V2TGLHJ4.js} +22 -7
  20. package/dist/{stellar-OK2HW5PZ.cjs → stellar-4YR4UZQX.cjs} +23 -23
  21. package/dist/{stellar-KBR4V77T.js → stellar-J7VE7QP7.js} +4 -4
  22. package/dist/{sui-SVFBJFW5.cjs → sui-F7UW6JXJ.cjs} +35 -27
  23. package/dist/{sui-BXHB6D3W.js → sui-GMRQAAJH.js} +20 -12
  24. package/dist/{ton-D7YN6BGR.cjs → ton-3SHFVDLP.cjs} +33 -22
  25. package/dist/{ton-NNKQFQ6U.js → ton-PEBYBNWJ.js} +18 -7
  26. package/dist/{tron-WZFU57NX.cjs → tron-BS5EID2U.cjs} +39 -39
  27. package/dist/{tron-W2D5PQXV.js → tron-ZYJHHROT.js} +10 -10
  28. package/dist/{xrpl-37E23W53.js → xrpl-P2D6TS6I.js} +13 -13
  29. package/dist/{xrpl-5NHY3NEL.cjs → xrpl-SUEFOOKW.cjs} +31 -31
  30. package/package.json +3 -2
package/CHANGELOG.md CHANGED
@@ -4,6 +4,113 @@ All notable changes to `@piprail/sdk` are documented here. The format
4
4
  follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the
5
5
  versions follow [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## [2.14.1] — 2026-06-24 — robustness & typed-error polish from a live-artifact break-it pass
8
+
9
+ A small, fully-additive patch. After 2.14.0 shipped, the published npm artifact was put through an
10
+ adversarial **break-it** pass from the examples (10 new suites, ~1,000+ assertions, run against the
11
+ real `npm i @piprail/sdk` — not the workspace build; see
12
+ `examples/basics/x402-parity-sandbox/FINDINGS.md`). Every security-critical and happy-path property
13
+ held; this release tightens the robustness/contract edges it surfaced. **No default changes, no
14
+ settled-payment outcome changes** — safe to bump.
15
+
16
+ ### Fixed
17
+
18
+ - **`classifyChallenge` / `describeChallenge` now honour their documented "never throws" contract.**
19
+ A nullish or shapeless challenge (a *foreign* 402 reaching the self-describe / landing-page path)
20
+ previously threw a raw `TypeError` instead of degrading to a verdict / generic pointer. Both now
21
+ guard `accepts` and degrade gracefully. (F-A1/F-A2)
22
+ - **A scientific-notation `amount` is rejected, not silently 1000×'d.** `createPaymentGate({ amount:
23
+ '1e3' })` used to be read as **1000 tokens**; `parseUnits` no longer expands scientific notation
24
+ for merchant amounts (`floorUnits` still does, for RPC dust reads) and throws on it. (F-B1)
25
+ - **Typed config errors (ERRORS.md §5).** A new **`InvalidConfigError`** (`code: 'INVALID_CONFIG'`)
26
+ replaces the raw `viem`/`TypeError`s thrown for an invalid `amount` (non-string or non-decimal), a
27
+ missing `payTo`, or a non-array `resources` in `buildWellKnownX402Manifest`. (F-C1/F-C2)
28
+ - **`buildWellKnownX402Manifest` no longer passes a non-finite `lastUpdated` through** to serialize
29
+ as JSON `null` — a `NaN`/`Infinity` falls back to now. (F-D4)
30
+ - **`DIRECTORY_INFO` is now `Object.freeze`'d** at runtime, matching its `Readonly<>` type. (F-D2)
31
+ - **The `exports` map now exposes `./package.json`**, so `require('@piprail/sdk/package.json')` (used
32
+ by version-introspection tooling) no longer throws `ERR_PACKAGE_PATH_NOT_EXPORTED`. (F-D3)
33
+
34
+ ### Added
35
+
36
+ - **`InvalidConfigError`** — exported typed error for invalid merchant gate/manifest config.
37
+
38
+ ### Notes (verified by-design, not changed)
39
+
40
+ - `client.estimateCost(url)` still **throws `NoCompatibleAcceptError`** when the client can't pay any
41
+ offered rail — a deliberate "this 402 isn't for you" signal (it returns `null` only for a non-402
42
+ or a malformed envelope). Docs clarified. (F-A3)
43
+ - TON's native gas symbol is **`GRAM`** (the ticker after TON's 2026-06-15 governance rebrand), not a
44
+ bug. (F-D1)
45
+
46
+ ## [2.14.0] — 2026-06-23 — x402-over-MCP transport, payment-identifier idempotency, the `.well-known/x402.json` manifest — and a deep three-pass security hardening of every chain
47
+
48
+ A feature **and** hardening release. It adds the third official x402 transport, an opt-in
49
+ idempotency extension, and a forward-compatible discovery manifest — then puts the whole SDK through
50
+ **three independent adversarial audit passes** (90+ agents) plus **live mainnet settlements of every
51
+ transaction type across 10+ chains**, fixing ~28 issues the audits surfaced. Every default is
52
+ unchanged; everything new is additive/opt-in. Proven on real mainnet payments on Base, BNB, Solana,
53
+ Sui, Aptos, Algorand, Stellar, TON, Tron, and the XRP Ledger (onchain-proof, EIP-3009 + Permit2
54
+ `exact`, non-EVM self-settle/facilitator `exact`, `upto`, A2A **and** MCP transports, verifiable
55
+ receipts, discovery/registration).
56
+
57
+ ### Added
58
+
59
+ - **x402-over-MCP transport (the third official transport).** `createMcpPaymentTool` wraps a
60
+ `PaymentGate` as a paid **MCP tool** — the MCP analogue of `requirePayment` / `createA2APaymentHandler`.
61
+ A 402 is an `isError` tool result (the `PaymentRequired` in `structuredContent` + a byte-equal
62
+ `content[0].text`); the payment rides under `params._meta["x402/payment"]`, the settlement under
63
+ `_meta["x402/payment-response"]`. All verify/settle/replay runs through the gate's `verifyObject`
64
+ (zero new crypto, zero driver changes) so every family rides MCP for free. Ships the full pure codec
65
+ + buyer read/frame helpers (`toMcpPaymentRequired`/`toMcpPaymentResponse`/`fromMcpPayment*`/
66
+ `isMcpPaymentRequired`/`buildMcpPaymentMeta`); a fully-automatic `McpPayer` is a documented fast-follow.
67
+ - **Opt-in `payment-identifier` idempotency extension.** `paymentIdentifier: true` advertises the
68
+ extension; a buyer supplies a stable `id` (16–128 chars, `[A-Za-z0-9_-]`) it can retry under,
69
+ deduped on the gate's existing used-proof set (`pid:<id>`, case-sensitive). Default OFF; the three
70
+ verify branches are untouched. Codecs `buildPaymentIdentifierAdvertisement` / `readPaymentIdentifier`.
71
+ - **Forward-compatible `.well-known/x402.json` manifest.** `buildWellKnownX402Manifest` emits the
72
+ richer, V2-shaped manifest (`{ x402Version: 2, lastUpdated, items[] }`) beside the legacy flat
73
+ `buildWellKnownX402` breadcrumb — a self-describing item per resource (its rails + call method), so a
74
+ crawler enumerates an origin without hitting each 402.
75
+ - `estimateCost` now reports the buyer-gasless `exact`/`upto` rails as fee `0` across every family
76
+ (NEAR included), and ships a multi-chain cost matrix in the tests.
77
+
78
+ ### Fixed & hardened (three adversarial audit passes + live mainnet)
79
+
80
+ - **SECURITY — memo-bound double-redeem (server).** The gate keyed its replay set on the
81
+ client-controlled `txHash`; memo-bound families (XRPL/Stellar/TON/Algorand) + native NEAR locate the
82
+ tx by memo/decoded-hash and ignore the ref, so one real payment could be re-redeemed N times within
83
+ the recency window. The gate now binds the replay key to the **verified** `receipt.transaction`
84
+ (a no-op for digest-bound EVM/Solana).
85
+ - **SECURITY — TON jetton forgery.** `verify()` now requires a successful `vm` compute for a jetton
86
+ credit, rejecting a forged `internal_transfer` to an undeployed merchant jetton wallet (native exempt).
87
+ - **Recency fails CLOSED uniformly.** Every digest/memo driver (EVM/Solana/Sui/NEAR/Stellar/Algorand/
88
+ XRPL/Tron/**Aptos**) now returns `payment_expired` on a missing/non-finite on-chain timestamp instead
89
+ of skipping the age check — closing a fail-open replay-backstop bypass.
90
+ - **MCP transport B7 (at-most-once).** A `fulfill()` throw *after* settlement still returns a success
91
+ `_meta` payment-response (never a re-challenge), so a serve-time hiccup never re-charges the buyer.
92
+ - **Sponsor fee-drain guard hardened.** The Solana `exact` ComputeBudget guard is now an allowlist
93
+ (rejects any discriminator ≠ set-unit-limit/price), closing the deprecated `RequestUnits`
94
+ `additional_fee` drain.
95
+ - **Tron `tron:`-prefix double-redeem** closed (verify reads the ref verbatim). **Sui** recipient match
96
+ is case/padding-insensitive (a mixed-case `payTo` is no longer paid-but-unverifiable).
97
+ - **Never-throw reads honored.** TON `verify()` maps a transient jetton-wallet RPC failure to a
98
+ retryable `tx_not_found` (and no longer poison-caches the rejected promise); EVM `estimateCost`
99
+ optional-chains a missing `extra`; the client drops a native or `extra`-less rail from the
100
+ exact/upto gather so plan/autoRoute/pay stay consistent.
101
+ - **Typed errors at the boundary.** An unknown chain name → `UnsupportedNetworkError`; an invalid EVM
102
+ custom-token address → `WrongFamilyError` (at config time, not a raw viem throw later).
103
+ - `floorUnits`/`parseUnits` accept scientific notation (XRPL funded-trustline false-`null` fix);
104
+ Solana `balanceOf` detects the Token-2022 program (no false 0); the memo-bound verifiers scan a full
105
+ 200-tx page; the `payment-identifier` id stays case-sensitive in the default store; legacy v1
106
+ `txHash`-only receipts map onto `transaction`.
107
+
108
+ ### Docs
109
+
110
+ - New **MCP transport (seller)** page; documented the manifest, the payment-identifier extension, the
111
+ uniform recency fail-closed, the sponsor-drain guard, the TON jetton compute-success rule, and the
112
+ 5-family `exact` surface — a 17-fix single-source-of-truth pass over the docs.
113
+
7
114
  ## [2.13.1] — 2026-06-21 — Family-correct `exact`-rail recovery hints (no EVM-only assumptions on non-EVM rails)
8
115
 
9
116
  A correctness pass on the family-agnostic `exact` buyer path: its failure/timeout messages and one
@@ -1695,6 +1802,8 @@ straight into your wallet. The API is small and self-contained.
1695
1802
  to your wallet; PipRail never holds funds.
1696
1803
  - `viem ^2.21` is a peer dependency. Node 20+ or a modern browser.
1697
1804
 
1805
+ [2.14.0]: https://www.npmjs.com/package/@piprail/sdk
1806
+ [2.13.1]: https://www.npmjs.com/package/@piprail/sdk
1698
1807
  [2.12.0]: https://www.npmjs.com/package/@piprail/sdk
1699
1808
  [2.11.0]: https://www.npmjs.com/package/@piprail/sdk
1700
1809
  [2.10.0]: https://www.npmjs.com/package/@piprail/sdk
@@ -10,7 +10,7 @@
10
10
 
11
11
 
12
12
 
13
- var _chunkDCOUJZPLcjs = require('./chunk-DCOUJZPL.cjs');
13
+ var _chunk57V5DNJTcjs = require('./chunk-57V5DNJT.cjs');
14
14
 
15
15
  // src/drivers/algorand/index.ts
16
16
  var _algosdk = require('algosdk'); var _algosdk2 = _interopRequireDefault(_algosdk);
@@ -58,13 +58,13 @@ async function payAlgorand(params) {
58
58
  } catch (err) {
59
59
  const mapped = mapAlgorandError(err, accept.payTo);
60
60
  if (mapped) throw mapped;
61
- throw _nullishCoalesce(_chunkDCOUJZPLcjs.toInsufficientFundsError.call(void 0, err), () => ( err));
61
+ throw _nullishCoalesce(_chunk57V5DNJTcjs.toInsufficientFundsError.call(void 0, err), () => ( err));
62
62
  }
63
63
  }
64
64
  function mapAlgorandError(err, payTo) {
65
65
  const m = err instanceof Error ? err.message : String(err);
66
66
  if (/must optin/i.test(m) || /missing from/i.test(m) && m.includes(payTo)) {
67
- return new (0, _chunkDCOUJZPLcjs.RecipientNotReadyError)(
67
+ return new (0, _chunk57V5DNJTcjs.RecipientNotReadyError)(
68
68
  `Algorand recipient ${payTo} hasn't opted into this asset \u2014 it must opt in (a 0-amount asset transfer to itself) before it can receive. (Algorand: ${firstLine(m)})`,
69
69
  { cause: err }
70
70
  );
@@ -72,7 +72,7 @@ function mapAlgorandError(err, payTo) {
72
72
  if (/overspend|below min|min(imum)? balance|tried to spend|balance \d+ below|asset \d+ missing from|insufficient|underflow/i.test(
73
73
  m
74
74
  )) {
75
- return new (0, _chunkDCOUJZPLcjs.InsufficientFundsError)(
75
+ return new (0, _chunk57V5DNJTcjs.InsufficientFundsError)(
76
76
  `Algorand payment failed: the sender can't cover it \u2014 token balance, ALGO for fees, the 0.1-ALGO minimum balance, or a missing asset opt-in on the sender. (Algorand: ${firstLine(m)})`,
77
77
  { cause: err }
78
78
  );
@@ -99,33 +99,33 @@ function isSet(addr) {
99
99
  function randomNote() {
100
100
  const g = globalThis.crypto;
101
101
  if (!_optionalChain([g, 'optionalAccess', _ => _.getRandomValues])) {
102
- throw new (0, _chunkDCOUJZPLcjs.UnsupportedSchemeError)("Algorand exact: no Web Crypto CSPRNG available to generate a unique note.");
102
+ throw new (0, _chunk57V5DNJTcjs.UnsupportedSchemeError)("Algorand exact: no Web Crypto CSPRNG available to generate a unique note.");
103
103
  }
104
104
  return g.getRandomValues(new Uint8Array(32));
105
105
  }
106
106
  async function payExactAlgorand(input) {
107
107
  const { suggestedParams, sk, sender, accept } = input;
108
108
  if (accept.asset === "native") {
109
- throw new (0, _chunkDCOUJZPLcjs.UnsupportedSchemeError)(
109
+ throw new (0, _chunk57V5DNJTcjs.UnsupportedSchemeError)(
110
110
  "Algorand exact is ASA-only (an asset transfer); native ALGO is not exact-payable. Pay via onchain-proof."
111
111
  );
112
112
  }
113
113
  const feePayer = accept.extra.feePayer;
114
114
  if (!feePayer) {
115
- throw new (0, _chunkDCOUJZPLcjs.UnsupportedSchemeError)("Algorand exact rail must advertise extra.feePayer (the fee sponsor address).");
115
+ throw new (0, _chunk57V5DNJTcjs.UnsupportedSchemeError)("Algorand exact rail must advertise extra.feePayer (the fee sponsor address).");
116
116
  }
117
117
  const assetId = parseAlgorandAssetId(accept.asset);
118
118
  if (assetId === null) {
119
- throw new (0, _chunkDCOUJZPLcjs.UnsupportedSchemeError)(`Algorand exact rail asset "${accept.asset}" must be a numeric ASA id.`);
119
+ throw new (0, _chunk57V5DNJTcjs.UnsupportedSchemeError)(`Algorand exact rail asset "${accept.asset}" must be a numeric ASA id.`);
120
120
  }
121
121
  if (!_algosdk2.default.isValidAddress(feePayer)) {
122
- throw new (0, _chunkDCOUJZPLcjs.UnsupportedSchemeError)(`Algorand exact: extra.feePayer "${feePayer}" is not a valid Algorand address.`);
122
+ throw new (0, _chunk57V5DNJTcjs.UnsupportedSchemeError)(`Algorand exact: extra.feePayer "${feePayer}" is not a valid Algorand address.`);
123
123
  }
124
124
  if (!_algosdk2.default.isValidAddress(accept.payTo)) {
125
- throw new (0, _chunkDCOUJZPLcjs.UnsupportedSchemeError)(`Algorand exact: payTo "${accept.payTo}" is not a valid Algorand address.`);
125
+ throw new (0, _chunk57V5DNJTcjs.UnsupportedSchemeError)(`Algorand exact: payTo "${accept.payTo}" is not a valid Algorand address.`);
126
126
  }
127
127
  if (feePayer === sender) {
128
- throw new (0, _chunkDCOUJZPLcjs.UnsupportedSchemeError)(
128
+ throw new (0, _chunk57V5DNJTcjs.UnsupportedSchemeError)(
129
129
  "Algorand exact: the fee payer must differ from the payer \u2014 the buyer must pay ZERO ALGO (the sponsor pays the pooled fee)."
130
130
  );
131
131
  }
@@ -172,13 +172,13 @@ function decodeElement(bytes) {
172
172
  async function verifyAndSettleExactAlgorand(input) {
173
173
  const { client, feePayerSk, feePayerAddr, payload, accept } = input;
174
174
  const railFeePayer = accept.extra.feePayer;
175
- if (!railFeePayer) throw new (0, _chunkDCOUJZPLcjs.SettlementError)("Algorand exact: rail is missing extra.feePayer.");
175
+ if (!railFeePayer) throw new (0, _chunk57V5DNJTcjs.SettlementError)("Algorand exact: rail is missing extra.feePayer.");
176
176
  if (railFeePayer !== feePayerAddr) {
177
- throw new (0, _chunkDCOUJZPLcjs.SettlementError)("Algorand exact: the gate relayer address does not match the rail extra.feePayer \u2014 misconfigured rail.");
177
+ throw new (0, _chunk57V5DNJTcjs.SettlementError)("Algorand exact: the gate relayer address does not match the rail extra.feePayer \u2014 misconfigured rail.");
178
178
  }
179
179
  const assetId = parseAlgorandAssetId(accept.asset);
180
- if (assetId === null) throw new (0, _chunkDCOUJZPLcjs.SettlementError)(`Algorand exact: rail asset "${accept.asset}" is not a numeric ASA id.`);
181
- if (!_algosdk2.default.isValidAddress(accept.payTo)) throw new (0, _chunkDCOUJZPLcjs.SettlementError)(`Algorand exact: rail payTo "${accept.payTo}" is invalid.`);
180
+ if (assetId === null) throw new (0, _chunk57V5DNJTcjs.SettlementError)(`Algorand exact: rail asset "${accept.asset}" is not a numeric ASA id.`);
181
+ if (!_algosdk2.default.isValidAddress(accept.payTo)) throw new (0, _chunk57V5DNJTcjs.SettlementError)(`Algorand exact: rail payTo "${accept.payTo}" is invalid.`);
182
182
  const { paymentIndex, paymentGroup } = payload;
183
183
  if (paymentGroup.length !== GROUP_SIZE) {
184
184
  return fail("signature_invalid", `Algorand exact expects a ${GROUP_SIZE}-transaction group (payment + fee), got ${paymentGroup.length}.`);
@@ -244,7 +244,7 @@ async function verifyAndSettleExactAlgorand(input) {
244
244
  try {
245
245
  feeSigned = fee.signTxn(feePayerSk);
246
246
  } catch (err) {
247
- throw new (0, _chunkDCOUJZPLcjs.SettlementError)(`Algorand exact settle: the gate could not sign the fee transaction (${shorten(err instanceof Error ? err.message : String(err))}).`, { cause: err });
247
+ throw new (0, _chunk57V5DNJTcjs.SettlementError)(`Algorand exact settle: the gate could not sign the fee transaction (${shorten(err instanceof Error ? err.message : String(err))}).`, { cause: err });
248
248
  }
249
249
  const signedGroup = elements.map((el, i) => i === paymentIndex ? payElement.raw : feeSigned);
250
250
  try {
@@ -266,7 +266,7 @@ async function verifyAndSettleExactAlgorand(input) {
266
266
  if (/signature|sig|auth|malformed/i.test(m)) return fail("signature_invalid", `Settle rejected \u2014 bad signature: ${shorten(m)}.`);
267
267
  if (/already in ledger|already committed|duplicate/i.test(m)) return fail("tx_already_used", `This payment was already settled: ${shorten(m)}.`);
268
268
  if (/\bround\b|expired|lifetime|stale|validity/i.test(m)) return fail("payment_expired", `Settle rejected \u2014 validity window expired: ${shorten(m)}.`);
269
- throw new (0, _chunkDCOUJZPLcjs.SettlementError)(
269
+ throw new (0, _chunk57V5DNJTcjs.SettlementError)(
270
270
  `Algorand exact settle: the group failed to submit (${shorten(m)}). The buyer's signed transaction is still valid \u2014 fund/fix the fee payer (${railFeePayer}) and the buyer can re-present it.`,
271
271
  { cause: err }
272
272
  );
@@ -275,10 +275,10 @@ async function verifyAndSettleExactAlgorand(input) {
275
275
  try {
276
276
  round = await client.waitForConfirmation(buyerTxid);
277
277
  } catch (err) {
278
- throw new (0, _chunkDCOUJZPLcjs.SettlementError)(`Algorand exact settle: submitted ${buyerTxid} but confirmation read failed (${shorten(err instanceof Error ? err.message : String(err))}). It likely landed \u2014 re-verify before re-presenting; do NOT re-pay.`, { cause: err });
278
+ throw new (0, _chunk57V5DNJTcjs.SettlementError)(`Algorand exact settle: submitted ${buyerTxid} but confirmation read failed (${shorten(err instanceof Error ? err.message : String(err))}). It likely landed \u2014 re-verify before re-presenting; do NOT re-pay.`, { cause: err });
279
279
  }
280
280
  if (round === null) {
281
- throw new (0, _chunkDCOUJZPLcjs.SettlementError)(`Algorand exact settle: submitted ${buyerTxid} but it did not confirm in time. It likely landed \u2014 re-verify before re-presenting; do NOT re-pay.`);
281
+ throw new (0, _chunk57V5DNJTcjs.SettlementError)(`Algorand exact settle: submitted ${buyerTxid} but it did not confirm in time. It likely landed \u2014 re-verify before re-presenting; do NOT re-pay.`);
282
282
  }
283
283
  return {
284
284
  ok: true,
@@ -304,21 +304,22 @@ async function verifyAlgorand(params) {
304
304
  const wantAssetId = parseAlgorandAssetId(accept.asset);
305
305
  let txs;
306
306
  try {
307
- txs = await reader.transactionsForAccount(accept.payTo, 50);
307
+ txs = await reader.transactionsForAccount(accept.payTo, 200);
308
308
  } catch (e4) {
309
309
  return rpcFailed(nonce);
310
310
  }
311
311
  const tx = txs.find((t) => typeof t.note === "string" && t.note === nonce);
312
312
  if (!tx) return notFound(nonce);
313
- if (typeof tx.roundTime === "number") {
314
- const ageSeconds = Math.floor(Date.now() / 1e3) - tx.roundTime;
315
- if (Number.isFinite(ageSeconds) && ageSeconds > accept.maxTimeoutSeconds) {
316
- return {
317
- ok: false,
318
- error: "payment_expired",
319
- detail: `Payment is ${ageSeconds}s old; max allowed is ${accept.maxTimeoutSeconds}s.`
320
- };
321
- }
313
+ if (!Number.isFinite(tx.roundTime)) {
314
+ return { ok: false, error: "payment_expired", detail: `Cannot bound the age of Algorand tx ${tx.id} (no/invalid round-time) \u2014 failing closed.` };
315
+ }
316
+ const ageSeconds = Math.floor(Date.now() / 1e3) - tx.roundTime;
317
+ if (!Number.isFinite(ageSeconds) || ageSeconds > accept.maxTimeoutSeconds) {
318
+ return {
319
+ ok: false,
320
+ error: "payment_expired",
321
+ detail: `Payment is ${ageSeconds}s old; max allowed is ${accept.maxTimeoutSeconds}s.`
322
+ };
322
323
  }
323
324
  const isNative = wantAssetId === null;
324
325
  const typeOk = isNative ? tx.txType === "pay" : tx.txType === "axfer";
@@ -373,13 +374,13 @@ function rpcFailed(nonce) {
373
374
 
374
375
  function assertAlgorandWallet(wallet, network) {
375
376
  if (typeof wallet !== "object" || wallet === null) {
376
- throw new (0, _chunkDCOUJZPLcjs.WrongFamilyError)(
377
+ throw new (0, _chunk57V5DNJTcjs.WrongFamilyError)(
377
378
  `chain ${network} is Algorand; wallet must be { key } (25-word mnemonic) or { account }.`
378
379
  );
379
380
  }
380
- _chunkDCOUJZPLcjs.assertNoLegacyWalletKey.call(void 0, wallet, "Algorand");
381
+ _chunk57V5DNJTcjs.assertNoLegacyWalletKey.call(void 0, wallet, "Algorand");
381
382
  if (!("key" in wallet) && !("account" in wallet)) {
382
- throw new (0, _chunkDCOUJZPLcjs.WrongFamilyError)(
383
+ throw new (0, _chunk57V5DNJTcjs.WrongFamilyError)(
383
384
  `chain ${network} is Algorand; wallet must be { key } (25-word mnemonic) or { account }.`
384
385
  );
385
386
  }
@@ -394,13 +395,13 @@ function resolveAlgorandWallet(config) {
394
395
  const { addr, sk } = _algosdk2.default.mnemonicToSecretKey(config.key);
395
396
  return { addr: addr.toString(), sk };
396
397
  } catch (cause) {
397
- throw new (0, _chunkDCOUJZPLcjs.WrongFamilyError)(
398
+ throw new (0, _chunk57V5DNJTcjs.WrongFamilyError)(
398
399
  "Algorand wallet { key } is not a valid 25-word Algorand mnemonic.",
399
400
  { cause }
400
401
  );
401
402
  }
402
403
  }
403
- throw new (0, _chunkDCOUJZPLcjs.WrongFamilyError)("Algorand wallet needs { key } (25-word mnemonic) or { account }.");
404
+ throw new (0, _chunk57V5DNJTcjs.WrongFamilyError)("Algorand wallet needs { key } (25-word mnemonic) or { account }.");
404
405
  }
405
406
 
406
407
  // src/drivers/algorand/index.ts
@@ -471,16 +472,16 @@ function makeAlgorandNetwork(preset, algodUrl) {
471
472
  const info = preset.tokens[token.toUpperCase()];
472
473
  if (!info) {
473
474
  const known = Object.keys(preset.tokens).join(", ") || "(none built in)";
474
- throw new (0, _chunkDCOUJZPLcjs.UnknownTokenError)(
475
+ throw new (0, _chunk57V5DNJTcjs.UnknownTokenError)(
475
476
  `token "${token}" isn't built in for Algorand (known: ${known}). Pass { assetId, decimals } for a custom ASA, or use 'native'.`
476
477
  );
477
478
  }
478
479
  return { asset: algorandAssetId(info.assetId), decimals: info.decimals, symbol: info.symbol };
479
480
  }
480
- _chunkDCOUJZPLcjs.rejectForeignToken.call(void 0, token, "algorand", network);
481
+ _chunk57V5DNJTcjs.rejectForeignToken.call(void 0, token, "algorand", network);
481
482
  const t = token;
482
483
  if (typeof t.assetId !== "number" || typeof t.decimals !== "number") {
483
- throw new (0, _chunkDCOUJZPLcjs.WrongFamilyError)(
484
+ throw new (0, _chunk57V5DNJTcjs.WrongFamilyError)(
484
485
  `chain ${network} is Algorand; a custom token must be { assetId, decimals }.`
485
486
  );
486
487
  }
@@ -501,12 +502,12 @@ function makeAlgorandNetwork(preset, algodUrl) {
501
502
  },
502
503
  assertValidPayTo(payTo) {
503
504
  if (payTo.startsWith("0x")) {
504
- throw new (0, _chunkDCOUJZPLcjs.WrongFamilyError)(
505
+ throw new (0, _chunk57V5DNJTcjs.WrongFamilyError)(
505
506
  `chain ${network} is Algorand, but payTo "${payTo}" looks like an EVM address.`
506
507
  );
507
508
  }
508
509
  if (!_algosdk2.default.isValidAddress(payTo)) {
509
- throw new (0, _chunkDCOUJZPLcjs.WrongFamilyError)(
510
+ throw new (0, _chunk57V5DNJTcjs.WrongFamilyError)(
510
511
  `chain ${network} is Algorand, but payTo "${payTo}" is not a valid Algorand address.`
511
512
  );
512
513
  }
@@ -523,14 +524,14 @@ function makeAlgorandNetwork(preset, algodUrl) {
523
524
  const info = await _algosdk2.default.waitForConfirmation(algod, ref, 10);
524
525
  return { height: String(_nullishCoalesce(info.confirmedRound, () => ( 0))) };
525
526
  } catch (err) {
526
- throw new (0, _chunkDCOUJZPLcjs.ConfirmationTimeoutError)(`Algorand tx ${ref} did not confirm in time.`, {
527
+ throw new (0, _chunk57V5DNJTcjs.ConfirmationTimeoutError)(`Algorand tx ${ref} did not confirm in time.`, {
527
528
  cause: err
528
529
  });
529
530
  }
530
531
  },
531
532
  async estimateCost(accept) {
532
533
  if (accept.scheme === "exact") {
533
- return _chunkDCOUJZPLcjs.nativeCost.call(void 0, {
534
+ return _chunk57V5DNJTcjs.nativeCost.call(void 0, {
534
535
  symbol: ALGO_SYMBOL,
535
536
  decimals: ALGO_DECIMALS,
536
537
  fee: 0n,
@@ -538,7 +539,7 @@ function makeAlgorandNetwork(preset, algodUrl) {
538
539
  detail: "gasless \u2014 the fee payer (facilitator/relayer) pools the group fee; the buyer pays 0 ALGO"
539
540
  });
540
541
  }
541
- return _chunkDCOUJZPLcjs.nativeCost.call(void 0, {
542
+ return _chunk57V5DNJTcjs.nativeCost.call(void 0, {
542
543
  symbol: ALGO_SYMBOL,
543
544
  decimals: ALGO_DECIMALS,
544
545
  fee: 1000n,
@@ -10,7 +10,7 @@ import {
10
10
  nativeCost,
11
11
  rejectForeignToken,
12
12
  toInsufficientFundsError
13
- } from "./chunk-O32N6MFN.js";
13
+ } from "./chunk-NUPCFNC5.js";
14
14
 
15
15
  // src/drivers/algorand/index.ts
16
16
  import algosdk3 from "algosdk";
@@ -304,21 +304,22 @@ async function verifyAlgorand(params) {
304
304
  const wantAssetId = parseAlgorandAssetId(accept.asset);
305
305
  let txs;
306
306
  try {
307
- txs = await reader.transactionsForAccount(accept.payTo, 50);
307
+ txs = await reader.transactionsForAccount(accept.payTo, 200);
308
308
  } catch {
309
309
  return rpcFailed(nonce);
310
310
  }
311
311
  const tx = txs.find((t) => typeof t.note === "string" && t.note === nonce);
312
312
  if (!tx) return notFound(nonce);
313
- if (typeof tx.roundTime === "number") {
314
- const ageSeconds = Math.floor(Date.now() / 1e3) - tx.roundTime;
315
- if (Number.isFinite(ageSeconds) && ageSeconds > accept.maxTimeoutSeconds) {
316
- return {
317
- ok: false,
318
- error: "payment_expired",
319
- detail: `Payment is ${ageSeconds}s old; max allowed is ${accept.maxTimeoutSeconds}s.`
320
- };
321
- }
313
+ if (!Number.isFinite(tx.roundTime)) {
314
+ return { ok: false, error: "payment_expired", detail: `Cannot bound the age of Algorand tx ${tx.id} (no/invalid round-time) \u2014 failing closed.` };
315
+ }
316
+ const ageSeconds = Math.floor(Date.now() / 1e3) - tx.roundTime;
317
+ if (!Number.isFinite(ageSeconds) || ageSeconds > accept.maxTimeoutSeconds) {
318
+ return {
319
+ ok: false,
320
+ error: "payment_expired",
321
+ detail: `Payment is ${ageSeconds}s old; max allowed is ${accept.maxTimeoutSeconds}s.`
322
+ };
322
323
  }
323
324
  const isNative = wantAssetId === null;
324
325
  const typeOk = isNative ? tx.txType === "pay" : tx.txType === "axfer";
@@ -9,7 +9,7 @@ import {
9
9
  nativeCost,
10
10
  rejectForeignToken,
11
11
  toInsufficientFundsError
12
- } from "./chunk-O32N6MFN.js";
12
+ } from "./chunk-NUPCFNC5.js";
13
13
 
14
14
  // src/drivers/aptos/index.ts
15
15
  import { Aptos, AptosConfig, Network, AccountAddress as AccountAddress2 } from "@aptos-labs/ts-sdk";
@@ -312,15 +312,16 @@ async function verifyAptos(params) {
312
312
  if (!tx.success) {
313
313
  return { ok: false, error: "tx_reverted", detail: `Aptos tx ${hash} did not succeed.` };
314
314
  }
315
- if (typeof tx.timestampSeconds === "number") {
316
- const ageSeconds = Math.floor(Date.now() / 1e3) - tx.timestampSeconds;
317
- if (ageSeconds > accept.maxTimeoutSeconds) {
318
- return {
319
- ok: false,
320
- error: "payment_expired",
321
- detail: `Payment is ${ageSeconds}s old; max allowed is ${accept.maxTimeoutSeconds}s.`
322
- };
323
- }
315
+ if (!Number.isFinite(tx.timestampSeconds)) {
316
+ return { ok: false, error: "payment_expired", detail: `Cannot bound the age of Aptos tx ${hash} (no/invalid timestamp) \u2014 failing closed.` };
317
+ }
318
+ const ageSeconds = Math.floor(Date.now() / 1e3) - tx.timestampSeconds;
319
+ if (!Number.isFinite(ageSeconds) || ageSeconds > accept.maxTimeoutSeconds) {
320
+ return {
321
+ ok: false,
322
+ error: "payment_expired",
323
+ detail: `Payment is ${ageSeconds}s old; max allowed is ${accept.maxTimeoutSeconds}s.`
324
+ };
324
325
  }
325
326
  let wantStore;
326
327
  try {