@piprail/sdk 2.15.1 → 2.16.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.
package/CHANGELOG.md CHANGED
@@ -4,7 +4,193 @@ 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
- ## [Unreleased]
7
+ ## [2.16.0] — 2026-09-06 — the XRP Ledger `exact` buyer, and paying the 91% of the x402 web we could not
8
+
9
+ ### Added — the XRP Ledger `exact` buyer (stage 04, first family)
10
+
11
+ - **A PipRail agent can now pay a standard x402 `exact` rail on the XRP Ledger** — 1,732 live rails,
12
+ and as far as we can tell the only open-source buyer on that chain. `drivers/xrpl/exact.ts`
13
+ implements `scheme_exact_xrpl.md` in both directions: the buyer signs a complete `Payment` and
14
+ broadcasts nothing; the gate decodes the blob, re-derives every checked field from its own trusted
15
+ accept, submits, and waits for a validated `tesSUCCESS`.
16
+
17
+ XRPL is the family where **the payer pays the fee** (it is a field inside the signed transaction),
18
+ so there is no sponsor, no co-signature, and no fee-drain guard. The scheme replaces those with a
19
+ list of MUST-NOTs the settler enforces — no `Memos`, no `Paths`, no `tfPartialPayment`, no
20
+ `Delegate`, never `Amount` and `DeliverMax` together — each of which is a way a hostile blob could
21
+ underdeliver, and each of which has a test.
22
+
23
+ The challenge binds through `InvoiceID = SHA-256(extra.invoiceId)`, **not** a memo: the scheme has
24
+ settlers reject `Memos`, which is exactly what PipRail's `onchain-proof` XRPL path uses for its
25
+ nonce. The two paths deliberately share no code.
26
+
27
+ **`extra.areFeesSponsored` is required by the scheme and absent from 100% of live rails**, so it is
28
+ read as *false unless present*. Requiring it would have rejected the entire deployed XRPL x402 web
29
+ — the third instance of that bug class this release cycle, and the reason it now has a test of its
30
+ own.
31
+
32
+ **Native XRP only, deliberately.** The two XRPL asset forms use different wire amount conventions
33
+ (native is an integer *drops* string, an issued currency is a *decimal* `value` — both verified
34
+ against live merchant challenges). The SDK prices and spend-caps in base units, so reading an
35
+ IOU's `"12"` as base units would understate a 12-RLUSD payment by 10^15 and slip it under any
36
+ policy cap while the buyer signed the real amount. Issued currencies are therefore dropped at
37
+ gather rather than mispriced; the RLUSD half waits until that decimal path is threaded through
38
+ quoting and the policy.
39
+
40
+ - **`ResolvedNetwork.exactPayableAsset?(asset)`** — a new optional driver hook. The buyer's gather
41
+ used to hard-code "any recognised token is exact-payable, the native coin never is", which was
42
+ true until XRPL, where it is wrong in *both* directions. A driver that declares the hook is
43
+ authoritative for all of its assets; every other family omits it and behaves exactly as before.
44
+
45
+ - **Three conformance fixes found by pointing the buyer at real merchants**, each of which made a
46
+ live XRPL rail unpayable on its own:
47
+ - `describeAsset` and `recipientReady` only knew the native coin as `'native'`. The XRPL x402 web
48
+ writes it `'XRP'` — all 863 native rails do. The first miss dropped every rail at gather ("no
49
+ compatible accept" on a chain we fully support); the second then demanded a **trustline** for a
50
+ coin that cannot have one. Both now go through one `isXrpNative()` predicate.
51
+ - `LastLedgerSequence` was a fixed 20-ledger window. The settler checks it is "within policy
52
+ window", and the merchant's policy is what `maxTimeoutSeconds` announces — so a rail offering
53
+ 60s was getting an ~80s blob, one that outlives the merchant's own quote. Now derived, clamped.
54
+ - `Flags` was hard-set to `0`. `tfPartialPayment` is a different bit, so zero was never needed to
55
+ exclude it — but it made our transactions the only ones on the ledger without
56
+ `tfFullyCanonicalSig`. Now set, matching what every other XRPL client sends.
57
+ - `extra.sourceTag` is mirrored into the transaction's `SourceTag`. Not in the scheme's `extra`
58
+ table, but present on **1,728 of the 1,732** live rails — it is how deployed vendors correlate a
59
+ payment back to their own quote.
60
+ - Live-proven on mainnet: settle tx
61
+ `F673064D2E0D982481844A769E829FD8B486EB9FB7B014F866C92F1C49770505` — 0.01 XRP through our own
62
+ gate on the `exact` rail, payer debited 0.010012 XRP (the amount **plus its own 12-drop fee**),
63
+ merchant credited 0.010000, and a genuine replay of the same signed blob refused with a 402.
64
+ - Buyer-side errors are mapped per `ERRORS.md` §9: an unactivated payer account (XRPL reports it as
65
+ `actNotFound`, but on that ledger "does not exist" means "unfunded") becomes
66
+ `InsufficientFundsError` with the base-reserve fix in the message; an `xrpl.js` validation failure
67
+ at signing becomes `UnsupportedSchemeError` carrying the library's reason; any other RPC failure is
68
+ rethrown unchanged rather than disguised as an affordability problem. All three are tested.
69
+ - **Not proven, and it is NOT our bug: a paid round-trip against a THIRD-PARTY XRPL merchant.**
70
+ Two live vendors refuse our payload with `invalid_payload` — from **their own pre-check**, before
71
+ their facilitator ever sees it (proven by routing a v1-shaped body past that pre-check, which then
72
+ returned the facilitator's own schema error: it wants exactly the `{x402Version: 2, accepted,
73
+ payload}` envelope we already send). The decisive test: **the same client, envelope and header pay
74
+ one of those same gateways successfully on its Base rail** (0.001 USDC, HTTP 200). So our envelope
75
+ is correct and the fault is XRPL-specific on their side. Our transaction also matches, field for
76
+ field, a payment that merchant has accepted from another client. Working conclusion: the deployed
77
+ XRPL dialect has drifted from `scheme_exact_xrpl.md` — worth raising upstream.
78
+ **No funds were lost across ~10 refused attempts** — every vendor rejects before settling, and the
79
+ ledger confirms no payment ever left the wallet.
80
+
81
+
82
+ ### Fixed — the buyer required a field the spec makes optional, and so could not pay 91% of the x402 web
83
+
84
+ - **`schemes: ['exact']` clients now pay the standard x402 web: 26.3% → 98.8% of the resources
85
+ listed on the CDP Bazaar** (15,686 resources / 40,388 `exact` rails, measured 2026-09-06 by
86
+ `npm run x402:coverage`; the figures live in `scripts/x402-corpus/coverage.json`, which owns
87
+ them). Rail-level coverage went 9.2% → 88.9%.
88
+
89
+ The buyer's gather predicate required `extra.assetTransferMethod` on every `exact` rail. The
90
+ ratified scheme says the opposite — `scheme_exact_evm.md`: *"If no `assetTransferMethod` is
91
+ specified in `PaymentRequired.extra`, clients should default to `"eip3009"`"* — and the SVM,
92
+ Algorand, Aptos, NEAR and Hedera schemes never define the key at all (their required extra is
93
+ `feePayer`). Only **9%** of live rails carry it; **79%** carry just the EIP-712 domain. So a
94
+ PipRail agent silently refused every Solana rail, every Arbitrum rail and 85% of Base rails —
95
+ including textbook canonical-USDC EIP-3009 rails it was perfectly able to sign.
96
+
97
+ This was never a pay-path bug: the EVM driver already defaulted to EIP-3009 and re-derives the
98
+ token's EIP-712 domain on-chain. Only the gather refused. A rail that NAMES a method we don't
99
+ implement (the spec's `erc7710`) is still skipped rather than signed blind.
100
+
101
+ Found by probing seven real third-party 402s and paying none of them, while 1,636 unit tests were
102
+ green. The tests agreed with their own hand-built fixtures; nothing checked the fixtures against
103
+ the world. That gap is now closed by `sdk/test/x402-corpus.test.ts` — 62 REAL challenges captured
104
+ from the live index, replayed through the real client.
105
+
106
+ ### Fixed — the Solana buyer required `extra.decimals`, which the SVM scheme never defines
107
+
108
+ - **99.3% of live Solana rails planned as payable and then threw at signing time.**
109
+ `payExactSolana` required `extra.decimals` for the TransferChecked and raised
110
+ `UnsupportedSchemeError` without it — but `scheme_exact_svm.md` doesn't define `decimals` at all
111
+ (its one required extra is `feePayer`), and only **41 of the 5,760** live Solana rails carry it.
112
+ The same "we require a field the spec makes optional" mistake as `assetTransferMethod`, one layer
113
+ deeper, and worse because it broke the plan-vs-pay contract: the rail gathered, `planPayment`
114
+ said `payable`, and the payment then failed.
115
+
116
+ The decimals now come **from the mint on-chain**, which is also the safer source — a server that
117
+ understated them could otherwise have the buyer sign a transfer 10^n larger than the quote, and
118
+ TransferChecked would accept it. A rail that states decimals disagreeing with the mint is refused;
119
+ `extra.decimals` survives only as a fallback if the mint read fails.
120
+
121
+ Found by paying a real Solana merchant. No unit test could have caught it — every fixture in the
122
+ suite carried `extra.decimals`, because our own gate always emits it.
123
+
124
+ ### Live-proven on mainnet (2026-09-06)
125
+
126
+ Real payments to real third-party merchants, one per wire shape this release unlocks:
127
+
128
+ | shape | merchant | proof |
129
+ |---|---|---|
130
+ | v2 · Base · domain-only (no marker) | `api.onesource.io/api/chain/block-number` | 200, 0.001 USDC, `0x44e9fed18bf9…` |
131
+ | v2 · Base · slug network | `coil.trade/api/crypto/gate` | 200, 0.001 USDC, `0xc38e6da9cbc2…` |
132
+ | **v1** · Base · `X-PAYMENT` wire | `x402factory.ai/base/coinprice` | 200, 0.001 USDC, `eip3009-nonce:0xb6c321fa…` |
133
+ | v2 · Solana · `feePayer`, no marker | `hypernatt.com/api/m2m/liq-radar` | 200, 0.001 USDC, `solana-nonce:3g3Wj5G6G7D…` |
134
+
135
+ Every row is a real body from a real stranger's endpoint, re-run against the FINAL build of this
136
+ release (an earlier run on the same day proved the same four shapes).
137
+
138
+ Own-gate regression, also on mainnet and also re-run on the final build: Base USDC + EURC
139
+ round-tripped gasless via EIP-3009 — payer ETH delta **0**, merchant received, **replay rejected**
140
+ (settle `0x85dfcc2b480f…` / `0x41fa1bdf5ea1…`); Algorand settled live with the enriched receipt and
141
+ a rejected replay (`TBRSSLNUJGCUS6…`).
142
+
143
+ ### Added — x402 **v1** challenges on the buyer path
144
+
145
+ - **A v1 server can now be quoted and paid.** PipRail's stated posture has always been "emit strict
146
+ v2, accept liberal v1 + v2", and the gate honoured it — but the buyer's `isValidChallenge`
147
+ hard-required `x402Version === 2`, so every v1 server was an `InvalidEnvelopeError`, and no v1
148
+ header emitter existed to answer one with. 251 of the 15,686 catalogued resources are v1.
149
+ `normalizeV1Challenge` lifts a v1 body (`maxAmountRequired`, slug network, per-accept `resource`
150
+ string) into the standard shape, and `buildV1PaymentHeader` frames the answer on the v1 wire.
151
+ Probed live: a v1 server ignores `PAYMENT-SIGNATURE` and reads only `X-PAYMENT`, so the network
152
+ slug is echoed back exactly as the server sent it (v1 verifiers string-compare it).
153
+ The slug is remembered on a synthetic `wireNetwork` field, which is **stripped from every
154
+ outgoing `accepted` echo** — that block is documented as the merchant's rail returned verbatim,
155
+ and facilitators compare (some hash) it, so a key the merchant never published must not appear
156
+ in it.
157
+
158
+ ### Added — the Algorand CAIP-2 id the spec actually uses
159
+
160
+ - `scheme_exact_algo.md` gives Algorand mainnet as the base64 genesis hash **truncated to 32 chars**
161
+ (`algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73k`), and the deployed web follows it — 874 live rails on
162
+ the spec form vs 589 on the padded 44-char id PipRail binds. Both forms are now accepted on parse.
163
+ What PipRail *emits* is unchanged (switching that would break deployed 2.x Algorand buyers; it is
164
+ queued for the next major).
165
+
166
+ - **The GATE now matches an echoed network id through the same canonicalisation.** It compared
167
+ `accepted.network` to its own rail with `===`, so a conformant buyer that canonicalised the id
168
+ before echoing it — the Algorand spec form, or TON's superseded `ton:-239` — had a perfectly
169
+ valid payment refused. All three selector sites (`onchain-proof`, `exact`, `upto`) now compare
170
+ through `normalizeNetwork`. This can only widen which of the gate's OWN rails is selected: payTo,
171
+ amount and asset are re-derived from the gate's spec either way, so a wrong network still cannot
172
+ redirect a payment. The Algorand driver's `supports()` accepts both spellings directly too,
173
+ mirroring TON.
174
+
175
+ ### Added — coverage is now measured, not asserted
176
+
177
+ - **`npm run x402:coverage`** (`scripts/x402-corpus/audit.mjs`) pages the live CDP Bazaar, classifies
178
+ every rail against the SDK's own exported predicates, and writes `scripts/x402-corpus/coverage.json`.
179
+ Every coverage figure in this repo cites that file. `--live` runs a real read-only `quote()` against
180
+ live third-party 402s (8/9 quotable, the 9th a merchant whose `upto` rail omits the spec-mandated
181
+ `facilitatorAddress`); `--fixture` regenerates the regression corpus.
182
+ - `PayOption.method` reports the transfer method the buyer would actually sign, appending
183
+ `' (default)'` when the rail named none — so the inference is visible instead of implicit.
184
+ - New exports: `exactTransferMethod`, `isSettleableExactMethod`, `KNOWN_EXACT_TRANSFER_METHODS`,
185
+ `DEFAULT_EXACT_TRANSFER_METHOD`, `normalizeV1Challenge`, `buildV1PaymentHeader`.
186
+
187
+ ### Changed — types now admit what the wire allows
188
+
189
+ - `X402ExactAcceptEntry.extra` and `extra.assetTransferMethod` are **optional**. A conformant rail may
190
+ ship `extra: {}` or omit the block entirely, and the type said otherwise, which is how a
191
+ bare-`extra` deref came to look like a real hazard. Every read on the buyer path optional-chains.
192
+ Defaults are unchanged: `schemes` still defaults to `['onchain-proof']`, so a zero-config client
193
+ behaves byte-identically.
8
194
 
9
195
  ## [2.15.1] — 2026-08-28 — a dead Sui RPC, an RPC failure blamed on the token, and fuzz hardening
10
196
 
@@ -1924,6 +2110,7 @@ straight into your wallet. The API is small and self-contained.
1924
2110
  to your wallet; PipRail never holds funds.
1925
2111
  - `viem ^2.21` is a peer dependency. Node 20+ or a modern browser.
1926
2112
 
2113
+ [2.16.0]: https://www.npmjs.com/package/@piprail/sdk
1927
2114
  [2.15.1]: https://www.npmjs.com/package/@piprail/sdk
1928
2115
  [2.15.0]: https://www.npmjs.com/package/@piprail/sdk
1929
2116
  [2.14.0]: https://www.npmjs.com/package/@piprail/sdk
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  **Accept and make [x402](https://x402.org) crypto payments — on any EVM chain plus Solana, TON, Tron, NEAR, Sui, Aptos, Algorand, Stellar & the XRP Ledger — in a couple of lines.**
4
4
 
5
- No middleman, no database, no fee, no account. Payments settle **straight into your wallet**, verified locally against your own RPC. Gate a route to make it paid-only; point an agent at a paid URL and it pays itself.
5
+ No middleman, no database, no fee, no account, no API key. Payments settle **straight into your wallet**, verified locally against your own RPC. Gate a route to make it paid-only; point an agent at a paid URL and it pays itself.
6
6
 
7
7
  ```bash
8
8
  npm install @piprail/sdk viem
@@ -1,3 +1,6 @@
1
+ import {
2
+ ALGORAND_SPEC_CAIP2
3
+ } from "./chunk-2CX7XRZK.js";
1
4
  import {
2
5
  ConfirmationTimeoutError,
3
6
  InsufficientFundsError,
@@ -110,7 +113,7 @@ async function payExactAlgorand(input) {
110
113
  "Algorand exact is ASA-only (an asset transfer); native ALGO is not exact-payable. Pay via onchain-proof."
111
114
  );
112
115
  }
113
- const feePayer = accept.extra.feePayer;
116
+ const feePayer = accept.extra?.feePayer;
114
117
  if (!feePayer) {
115
118
  throw new UnsupportedSchemeError("Algorand exact rail must advertise extra.feePayer (the fee sponsor address).");
116
119
  }
@@ -171,7 +174,7 @@ function decodeElement(bytes) {
171
174
  }
172
175
  async function verifyAndSettleExactAlgorand(input) {
173
176
  const { client, feePayerSk, feePayerAddr, payload, accept } = input;
174
- const railFeePayer = accept.extra.feePayer;
177
+ const railFeePayer = accept.extra?.feePayer;
175
178
  if (!railFeePayer) throw new SettlementError("Algorand exact: rail is missing extra.feePayer.");
176
179
  if (railFeePayer !== feePayerAddr) {
177
180
  throw new SettlementError("Algorand exact: the gate relayer address does not match the rail extra.feePayer \u2014 misconfigured rail.");
@@ -463,7 +466,13 @@ function makeAlgorandNetwork(preset, algodUrl) {
463
466
  return {
464
467
  family: "algorand",
465
468
  network,
466
- supports: (n) => n === network,
469
+ // Accept our padded 44-char genesis-hash id AND the spec's 32-char truncated form
470
+ // (`scheme_exact_algo.md`'s Network Identifiers table — the form 874 of the 1,463 live
471
+ // Algorand rails use), so a caller that bypasses `normalizeNetwork` and hands the raw
472
+ // spec id to the driver still matches. Mirrors TON's legacy-id tolerance; the
473
+ // normalizeNetwork alias remains the canonical seam. We still EMIT the padded form —
474
+ // changing that would break every deployed PipRail 2.x buyer, so it waits for a major.
475
+ supports: (n) => n === network || n === ALGORAND_SPEC_CAIP2,
467
476
  resolveToken(token) {
468
477
  if (token === "native") {
469
478
  return { asset: "native", decimals: ALGO_DECIMALS, symbol: ALGO_SYMBOL };
@@ -1,5 +1,8 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
2
 
3
+ var _chunkV2IJ5HUWcjs = require('./chunk-V2IJ5HUW.cjs');
4
+
5
+
3
6
 
4
7
 
5
8
 
@@ -110,7 +113,7 @@ async function payExactAlgorand(input) {
110
113
  "Algorand exact is ASA-only (an asset transfer); native ALGO is not exact-payable. Pay via onchain-proof."
111
114
  );
112
115
  }
113
- const feePayer = accept.extra.feePayer;
116
+ const feePayer = _optionalChain([accept, 'access', _2 => _2.extra, 'optionalAccess', _3 => _3.feePayer]);
114
117
  if (!feePayer) {
115
118
  throw new (0, _chunkQONQSZHJcjs.UnsupportedSchemeError)("Algorand exact rail must advertise extra.feePayer (the fee sponsor address).");
116
119
  }
@@ -171,7 +174,7 @@ function decodeElement(bytes) {
171
174
  }
172
175
  async function verifyAndSettleExactAlgorand(input) {
173
176
  const { client, feePayerSk, feePayerAddr, payload, accept } = input;
174
- const railFeePayer = accept.extra.feePayer;
177
+ const railFeePayer = _optionalChain([accept, 'access', _4 => _4.extra, 'optionalAccess', _5 => _5.feePayer]);
175
178
  if (!railFeePayer) throw new (0, _chunkQONQSZHJcjs.SettlementError)("Algorand exact: rail is missing extra.feePayer.");
176
179
  if (railFeePayer !== feePayerAddr) {
177
180
  throw new (0, _chunkQONQSZHJcjs.SettlementError)("Algorand exact: the gate relayer address does not match the rail extra.feePayer \u2014 misconfigured rail.");
@@ -206,7 +209,7 @@ async function verifyAndSettleExactAlgorand(input) {
206
209
  return fail("transfer_not_found", `Transfer asset id ${at.assetIndex} \u2260 rail asset ${assetId}.`);
207
210
  }
208
211
  if (!addrEq(at.receiver, accept.payTo)) {
209
- return fail("wrong_recipient", `Transfer pays ${_optionalChain([at, 'access', _2 => _2.receiver, 'optionalAccess', _3 => _3.toString, 'call', _4 => _4()])}, not payTo ${accept.payTo}.`);
212
+ return fail("wrong_recipient", `Transfer pays ${_optionalChain([at, 'access', _6 => _6.receiver, 'optionalAccess', _7 => _7.toString, 'call', _8 => _8()])}, not payTo ${accept.payTo}.`);
210
213
  }
211
214
  if (BigInt(at.amount) < BigInt(accept.amount)) {
212
215
  return fail("amount_too_low", `Transfer pays ${at.amount} of the ASA, required ${accept.amount}.`);
@@ -221,7 +224,7 @@ async function verifyAndSettleExactAlgorand(input) {
221
224
  return fail("signature_invalid", "The fee transaction must be a payment (pay) transaction.");
222
225
  }
223
226
  if (!addrEq(fee.sender, railFeePayer)) {
224
- return fail("signature_invalid", `The fee transaction sender ${_optionalChain([fee, 'access', _5 => _5.sender, 'optionalAccess', _6 => _6.toString, 'call', _7 => _7()])} is not the rail fee payer ${railFeePayer}.`);
227
+ return fail("signature_invalid", `The fee transaction sender ${_optionalChain([fee, 'access', _9 => _9.sender, 'optionalAccess', _10 => _10.toString, 'call', _11 => _11()])} is not the rail fee payer ${railFeePayer}.`);
225
228
  }
226
229
  if (BigInt(fee.payment.amount) !== 0n) {
227
230
  return fail("signature_invalid", "The fee transaction must move 0 ALGO (it only pools the group fee).");
@@ -447,12 +450,12 @@ function makeAlgorandNetwork(preset, algodUrl) {
447
450
  const settleClient = {
448
451
  async simulate(signedGroup) {
449
452
  const resp = await algod.simulateRawTransactions(signedGroup).do();
450
- const fm = _optionalChain([resp, 'optionalAccess', _8 => _8.txnGroups, 'optionalAccess', _9 => _9[0], 'optionalAccess', _10 => _10.failureMessage]);
453
+ const fm = _optionalChain([resp, 'optionalAccess', _12 => _12.txnGroups, 'optionalAccess', _13 => _13[0], 'optionalAccess', _14 => _14.failureMessage]);
451
454
  return { failureMessage: fm && fm.length > 0 ? fm : null };
452
455
  },
453
456
  async submit(signedGroup) {
454
457
  const res = await algod.sendRawTransaction(signedGroup).do();
455
- return _nullishCoalesce(_optionalChain([res, 'optionalAccess', _11 => _11.txid]), () => ( ""));
458
+ return _nullishCoalesce(_optionalChain([res, 'optionalAccess', _15 => _15.txid]), () => ( ""));
456
459
  },
457
460
  async waitForConfirmation(txid) {
458
461
  const info = await _algosdk2.default.waitForConfirmation(algod, txid, 10);
@@ -463,7 +466,13 @@ function makeAlgorandNetwork(preset, algodUrl) {
463
466
  return {
464
467
  family: "algorand",
465
468
  network,
466
- supports: (n) => n === network,
469
+ // Accept our padded 44-char genesis-hash id AND the spec's 32-char truncated form
470
+ // (`scheme_exact_algo.md`'s Network Identifiers table — the form 874 of the 1,463 live
471
+ // Algorand rails use), so a caller that bypasses `normalizeNetwork` and hands the raw
472
+ // spec id to the driver still matches. Mirrors TON's legacy-id tolerance; the
473
+ // normalizeNetwork alias remains the canonical seam. We still EMIT the padded form —
474
+ // changing that would break every deployed PipRail 2.x buyer, so it waits for a major.
475
+ supports: (n) => n === network || n === _chunkV2IJ5HUWcjs.ALGORAND_SPEC_CAIP2,
467
476
  resolveToken(token) {
468
477
  if (token === "native") {
469
478
  return { asset: "native", decimals: ALGO_DECIMALS, symbol: ALGO_SYMBOL };
@@ -575,7 +584,7 @@ function makeAlgorandNetwork(preset, algodUrl) {
575
584
  const optedIn = (_nullishCoalesce(info.assets, () => ( []))).some((a) => Number(a.assetId) === assetId);
576
585
  return optedIn ? { ready: true } : { ready: false, reason: "NOT_OPTED_IN" };
577
586
  } catch (e) {
578
- if (/does not exist|no accounts found|404|account not found/i.test(String(_nullishCoalesce(_optionalChain([e, 'optionalAccess', _12 => _12.message]), () => ( e))))) {
587
+ if (/does not exist|no accounts found|404|account not found/i.test(String(_nullishCoalesce(_optionalChain([e, 'optionalAccess', _16 => _16.message]), () => ( e))))) {
579
588
  return { ready: false, reason: "NOT_OPTED_IN" };
580
589
  }
581
590
  return { ready: "unknown" };
@@ -107,7 +107,7 @@ async function payExactAptos(input) {
107
107
  "Aptos exact is Fungible-Asset only (a primary_fungible_store transfer); native APT is not exact-payable. Pay via onchain-proof."
108
108
  );
109
109
  }
110
- const feePayer = accept.extra.feePayer;
110
+ const feePayer = accept.extra?.feePayer;
111
111
  if (!feePayer) {
112
112
  throw new UnsupportedSchemeError("Aptos exact rail must advertise extra.feePayer (the gas sponsor address).");
113
113
  }
@@ -157,7 +157,7 @@ function argBytes(arg) {
157
157
  }
158
158
  async function verifyAndSettleExactAptos(input) {
159
159
  const { client, feePayerAddr, payload, accept } = input;
160
- const railFeePayer = accept.extra.feePayer;
160
+ const railFeePayer = accept.extra?.feePayer;
161
161
  if (!railFeePayer) throw new SettlementError("Aptos exact: rail is missing extra.feePayer.");
162
162
  if (railFeePayer !== feePayerAddr) {
163
163
  throw new SettlementError("Aptos exact: the gate relayer address does not match the rail extra.feePayer \u2014 misconfigured rail.");
@@ -107,7 +107,7 @@ async function payExactAptos(input) {
107
107
  "Aptos exact is Fungible-Asset only (a primary_fungible_store transfer); native APT is not exact-payable. Pay via onchain-proof."
108
108
  );
109
109
  }
110
- const feePayer = accept.extra.feePayer;
110
+ const feePayer = _optionalChain([accept, 'access', _ => _.extra, 'optionalAccess', _2 => _2.feePayer]);
111
111
  if (!feePayer) {
112
112
  throw new (0, _chunkQONQSZHJcjs.UnsupportedSchemeError)("Aptos exact rail must advertise extra.feePayer (the gas sponsor address).");
113
113
  }
@@ -151,13 +151,13 @@ function fail(error, detail) {
151
151
  return { ok: false, error, detail };
152
152
  }
153
153
  function argBytes(arg) {
154
- const v = _optionalChain([arg, 'access', _ => _.value, 'optionalAccess', _2 => _2.value]);
154
+ const v = _optionalChain([arg, 'access', _3 => _3.value, 'optionalAccess', _4 => _4.value]);
155
155
  if (v instanceof Uint8Array) return v;
156
156
  throw new Error("unexpected entry-function argument shape");
157
157
  }
158
158
  async function verifyAndSettleExactAptos(input) {
159
159
  const { client, feePayerAddr, payload, accept } = input;
160
- const railFeePayer = accept.extra.feePayer;
160
+ const railFeePayer = _optionalChain([accept, 'access', _5 => _5.extra, 'optionalAccess', _6 => _6.feePayer]);
161
161
  if (!railFeePayer) throw new (0, _chunkQONQSZHJcjs.SettlementError)("Aptos exact: rail is missing extra.feePayer.");
162
162
  if (railFeePayer !== feePayerAddr) {
163
163
  throw new (0, _chunkQONQSZHJcjs.SettlementError)("Aptos exact: the gate relayer address does not match the rail extra.feePayer \u2014 misconfigured rail.");
@@ -179,7 +179,7 @@ async function verifyAndSettleExactAptos(input) {
179
179
  }
180
180
  const raw = tx.rawTransaction;
181
181
  if (!tx.feePayerAddress || !tx.feePayerAddress.equals(railFeePayerAddr)) {
182
- return fail("signature_invalid", `The transaction fee payer ${_nullishCoalesce(_optionalChain([tx, 'access', _3 => _3.feePayerAddress, 'optionalAccess', _4 => _4.toString, 'call', _5 => _5()]), () => ( "(none)"))} is not the rail fee payer ${railFeePayer}.`);
182
+ return fail("signature_invalid", `The transaction fee payer ${_nullishCoalesce(_optionalChain([tx, 'access', _7 => _7.feePayerAddress, 'optionalAccess', _8 => _8.toString, 'call', _9 => _9()]), () => ( "(none)"))} is not the rail fee payer ${railFeePayer}.`);
183
183
  }
184
184
  const payloadKind = raw.payload;
185
185
  if (!(payloadKind instanceof _tssdk.TransactionPayloadEntryFunction)) {
@@ -620,7 +620,7 @@ function makeAptosNetwork(preset, rpcUrl) {
620
620
  feePayerPublicKey: feePayerAccount.publicKey,
621
621
  transaction
622
622
  });
623
- return { success: _optionalChain([sim, 'optionalAccess', _6 => _6.success]) === true, vmStatus: String(_nullishCoalesce(_optionalChain([sim, 'optionalAccess', _7 => _7.vm_status]), () => ( ""))) };
623
+ return { success: _optionalChain([sim, 'optionalAccess', _10 => _10.success]) === true, vmStatus: String(_nullishCoalesce(_optionalChain([sim, 'optionalAccess', _11 => _11.vm_status]), () => ( ""))) };
624
624
  },
625
625
  async submit({ transaction, senderAuthenticator, feePayerAuthenticator }) {
626
626
  const res = await aptos.transaction.submit.simple({ transaction, senderAuthenticator, feePayerAuthenticator });