@piprail/sdk 2.15.1 → 2.16.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/CHANGELOG.md CHANGED
@@ -4,7 +4,213 @@ 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.1] — 2026-09-06 — GoPlausible settles Solana, live-proven
8
+
9
+ ### Added
10
+
11
+ - **GoPlausible is now seeded as a keyless Solana facilitator.** They told us directly that they
12
+ settle Solana as well as Algorand and Base, and they were right: their `/supported` advertises
13
+ `solana:5eykt4Us…` `exact` with a sponsor fee payer, and PipRail's own `facilitatorCoverage()`
14
+ reads the same.
15
+
16
+ **Live-proven on Solana mainnet 2026-09-06** rather than taken on trust. A real
17
+ `402 → pay → verify → 200` round trip on the `exact` rail: the merchant received exactly
18
+ 0.01 USDC, the **buyer paid 0 SOL**, GoPlausible's sponsor `8a8fFNfk…RAYi` paid the
19
+ 10,001-lamport fee (confirmed by reading `accountKeys[0]` on the settled transaction), and a
20
+ replay of the same proof was rejected. Tx
21
+ `3DEGg6Lue8471meBH8hbLxV1bLmZGKvmvmSY3tjg7tQFnxzE9GQXUXokjQnZgTqaEU4SaXuY9F7v3m7JsX5xDgrZ`.
22
+
23
+ Solana previously seeded only PayAI and OpenFacilitator, so this is a third keyless option on
24
+ that chain. No default changes: the seed list is a convenience, and the SDK still depends on no
25
+ facilitator.
26
+
27
+ ## [2.16.0] — 2026-09-06 — the XRP Ledger `exact` buyer, and paying the 91% of the x402 web we could not
28
+
29
+ ### Added — the XRP Ledger `exact` buyer (stage 04, first family)
30
+
31
+ - **A PipRail agent can now pay a standard x402 `exact` rail on the XRP Ledger** — 1,732 live rails,
32
+ and as far as we can tell the only open-source buyer on that chain. `drivers/xrpl/exact.ts`
33
+ implements `scheme_exact_xrpl.md` in both directions: the buyer signs a complete `Payment` and
34
+ broadcasts nothing; the gate decodes the blob, re-derives every checked field from its own trusted
35
+ accept, submits, and waits for a validated `tesSUCCESS`.
36
+
37
+ XRPL is the family where **the payer pays the fee** (it is a field inside the signed transaction),
38
+ so there is no sponsor, no co-signature, and no fee-drain guard. The scheme replaces those with a
39
+ list of MUST-NOTs the settler enforces — no `Memos`, no `Paths`, no `tfPartialPayment`, no
40
+ `Delegate`, never `Amount` and `DeliverMax` together — each of which is a way a hostile blob could
41
+ underdeliver, and each of which has a test.
42
+
43
+ The challenge binds through `InvoiceID = SHA-256(extra.invoiceId)`, **not** a memo: the scheme has
44
+ settlers reject `Memos`, which is exactly what PipRail's `onchain-proof` XRPL path uses for its
45
+ nonce. The two paths deliberately share no code.
46
+
47
+ **`extra.areFeesSponsored` is required by the scheme and absent from 100% of live rails**, so it is
48
+ read as *false unless present*. Requiring it would have rejected the entire deployed XRPL x402 web
49
+ — the third instance of that bug class this release cycle, and the reason it now has a test of its
50
+ own.
51
+
52
+ **Native XRP only, deliberately.** The two XRPL asset forms use different wire amount conventions
53
+ (native is an integer *drops* string, an issued currency is a *decimal* `value` — both verified
54
+ against live merchant challenges). The SDK prices and spend-caps in base units, so reading an
55
+ IOU's `"12"` as base units would understate a 12-RLUSD payment by 10^15 and slip it under any
56
+ policy cap while the buyer signed the real amount. Issued currencies are therefore dropped at
57
+ gather rather than mispriced; the RLUSD half waits until that decimal path is threaded through
58
+ quoting and the policy.
59
+
60
+ - **`ResolvedNetwork.exactPayableAsset?(asset)`** — a new optional driver hook. The buyer's gather
61
+ used to hard-code "any recognised token is exact-payable, the native coin never is", which was
62
+ true until XRPL, where it is wrong in *both* directions. A driver that declares the hook is
63
+ authoritative for all of its assets; every other family omits it and behaves exactly as before.
64
+
65
+ - **Three conformance fixes found by pointing the buyer at real merchants**, each of which made a
66
+ live XRPL rail unpayable on its own:
67
+ - `describeAsset` and `recipientReady` only knew the native coin as `'native'`. The XRPL x402 web
68
+ writes it `'XRP'` — all 863 native rails do. The first miss dropped every rail at gather ("no
69
+ compatible accept" on a chain we fully support); the second then demanded a **trustline** for a
70
+ coin that cannot have one. Both now go through one `isXrpNative()` predicate.
71
+ - `LastLedgerSequence` was a fixed 20-ledger window. The settler checks it is "within policy
72
+ window", and the merchant's policy is what `maxTimeoutSeconds` announces — so a rail offering
73
+ 60s was getting an ~80s blob, one that outlives the merchant's own quote. Now derived, clamped.
74
+ - `Flags` was hard-set to `0`. `tfPartialPayment` is a different bit, so zero was never needed to
75
+ exclude it — but it made our transactions the only ones on the ledger without
76
+ `tfFullyCanonicalSig`. Now set, matching what every other XRPL client sends.
77
+ - `extra.sourceTag` is mirrored into the transaction's `SourceTag`. Not in the scheme's `extra`
78
+ table, but present on **1,728 of the 1,732** live rails — it is how deployed vendors correlate a
79
+ payment back to their own quote.
80
+ - Live-proven on mainnet: settle tx
81
+ `F673064D2E0D982481844A769E829FD8B486EB9FB7B014F866C92F1C49770505` — 0.01 XRP through our own
82
+ gate on the `exact` rail, payer debited 0.010012 XRP (the amount **plus its own 12-drop fee**),
83
+ merchant credited 0.010000, and a genuine replay of the same signed blob refused with a 402.
84
+ - Buyer-side errors are mapped per `ERRORS.md` §9: an unactivated payer account (XRPL reports it as
85
+ `actNotFound`, but on that ledger "does not exist" means "unfunded") becomes
86
+ `InsufficientFundsError` with the base-reserve fix in the message; an `xrpl.js` validation failure
87
+ at signing becomes `UnsupportedSchemeError` carrying the library's reason; any other RPC failure is
88
+ rethrown unchanged rather than disguised as an affordability problem. All three are tested.
89
+ - **Not proven, and it is NOT our bug: a paid round-trip against a THIRD-PARTY XRPL merchant.**
90
+ Two live vendors refuse our payload with `invalid_payload` — from **their own pre-check**, before
91
+ their facilitator ever sees it (proven by routing a v1-shaped body past that pre-check, which then
92
+ returned the facilitator's own schema error: it wants exactly the `{x402Version: 2, accepted,
93
+ payload}` envelope we already send). The decisive test: **the same client, envelope and header pay
94
+ one of those same gateways successfully on its Base rail** (0.001 USDC, HTTP 200). So our envelope
95
+ is correct and the fault is XRPL-specific on their side. Our transaction also matches, field for
96
+ field, a payment that merchant has accepted from another client. Working conclusion: the deployed
97
+ XRPL dialect has drifted from `scheme_exact_xrpl.md` — worth raising upstream.
98
+ **No funds were lost across ~10 refused attempts** — every vendor rejects before settling, and the
99
+ ledger confirms no payment ever left the wallet.
100
+
101
+
102
+ ### Fixed — the buyer required a field the spec makes optional, and so could not pay 91% of the x402 web
103
+
104
+ - **`schemes: ['exact']` clients now pay the standard x402 web: 26.3% → 98.8% of the resources
105
+ listed on the CDP Bazaar** (15,686 resources / 40,388 `exact` rails, measured 2026-09-06 by
106
+ `npm run x402:coverage`; the figures live in `scripts/x402-corpus/coverage.json`, which owns
107
+ them). Rail-level coverage went 9.2% → 88.9%.
108
+
109
+ The buyer's gather predicate required `extra.assetTransferMethod` on every `exact` rail. The
110
+ ratified scheme says the opposite — `scheme_exact_evm.md`: *"If no `assetTransferMethod` is
111
+ specified in `PaymentRequired.extra`, clients should default to `"eip3009"`"* — and the SVM,
112
+ Algorand, Aptos, NEAR and Hedera schemes never define the key at all (their required extra is
113
+ `feePayer`). Only **9%** of live rails carry it; **79%** carry just the EIP-712 domain. So a
114
+ PipRail agent silently refused every Solana rail, every Arbitrum rail and 85% of Base rails —
115
+ including textbook canonical-USDC EIP-3009 rails it was perfectly able to sign.
116
+
117
+ This was never a pay-path bug: the EVM driver already defaulted to EIP-3009 and re-derives the
118
+ token's EIP-712 domain on-chain. Only the gather refused. A rail that NAMES a method we don't
119
+ implement (the spec's `erc7710`) is still skipped rather than signed blind.
120
+
121
+ Found by probing seven real third-party 402s and paying none of them, while 1,636 unit tests were
122
+ green. The tests agreed with their own hand-built fixtures; nothing checked the fixtures against
123
+ the world. That gap is now closed by `sdk/test/x402-corpus.test.ts` — 62 REAL challenges captured
124
+ from the live index, replayed through the real client.
125
+
126
+ ### Fixed — the Solana buyer required `extra.decimals`, which the SVM scheme never defines
127
+
128
+ - **99.3% of live Solana rails planned as payable and then threw at signing time.**
129
+ `payExactSolana` required `extra.decimals` for the TransferChecked and raised
130
+ `UnsupportedSchemeError` without it — but `scheme_exact_svm.md` doesn't define `decimals` at all
131
+ (its one required extra is `feePayer`), and only **41 of the 5,760** live Solana rails carry it.
132
+ The same "we require a field the spec makes optional" mistake as `assetTransferMethod`, one layer
133
+ deeper, and worse because it broke the plan-vs-pay contract: the rail gathered, `planPayment`
134
+ said `payable`, and the payment then failed.
135
+
136
+ The decimals now come **from the mint on-chain**, which is also the safer source — a server that
137
+ understated them could otherwise have the buyer sign a transfer 10^n larger than the quote, and
138
+ TransferChecked would accept it. A rail that states decimals disagreeing with the mint is refused;
139
+ `extra.decimals` survives only as a fallback if the mint read fails.
140
+
141
+ Found by paying a real Solana merchant. No unit test could have caught it — every fixture in the
142
+ suite carried `extra.decimals`, because our own gate always emits it.
143
+
144
+ ### Live-proven on mainnet (2026-09-06)
145
+
146
+ Real payments to real third-party merchants, one per wire shape this release unlocks:
147
+
148
+ | shape | merchant | proof |
149
+ |---|---|---|
150
+ | v2 · Base · domain-only (no marker) | `api.onesource.io/api/chain/block-number` | 200, 0.001 USDC, `0x44e9fed18bf9…` |
151
+ | v2 · Base · slug network | `coil.trade/api/crypto/gate` | 200, 0.001 USDC, `0xc38e6da9cbc2…` |
152
+ | **v1** · Base · `X-PAYMENT` wire | `x402factory.ai/base/coinprice` | 200, 0.001 USDC, `eip3009-nonce:0xb6c321fa…` |
153
+ | v2 · Solana · `feePayer`, no marker | `hypernatt.com/api/m2m/liq-radar` | 200, 0.001 USDC, `solana-nonce:3g3Wj5G6G7D…` |
154
+
155
+ Every row is a real body from a real stranger's endpoint, re-run against the FINAL build of this
156
+ release (an earlier run on the same day proved the same four shapes).
157
+
158
+ Own-gate regression, also on mainnet and also re-run on the final build: Base USDC + EURC
159
+ round-tripped gasless via EIP-3009 — payer ETH delta **0**, merchant received, **replay rejected**
160
+ (settle `0x85dfcc2b480f…` / `0x41fa1bdf5ea1…`); Algorand settled live with the enriched receipt and
161
+ a rejected replay (`TBRSSLNUJGCUS6…`).
162
+
163
+ ### Added — x402 **v1** challenges on the buyer path
164
+
165
+ - **A v1 server can now be quoted and paid.** PipRail's stated posture has always been "emit strict
166
+ v2, accept liberal v1 + v2", and the gate honoured it — but the buyer's `isValidChallenge`
167
+ hard-required `x402Version === 2`, so every v1 server was an `InvalidEnvelopeError`, and no v1
168
+ header emitter existed to answer one with. 251 of the 15,686 catalogued resources are v1.
169
+ `normalizeV1Challenge` lifts a v1 body (`maxAmountRequired`, slug network, per-accept `resource`
170
+ string) into the standard shape, and `buildV1PaymentHeader` frames the answer on the v1 wire.
171
+ Probed live: a v1 server ignores `PAYMENT-SIGNATURE` and reads only `X-PAYMENT`, so the network
172
+ slug is echoed back exactly as the server sent it (v1 verifiers string-compare it).
173
+ The slug is remembered on a synthetic `wireNetwork` field, which is **stripped from every
174
+ outgoing `accepted` echo** — that block is documented as the merchant's rail returned verbatim,
175
+ and facilitators compare (some hash) it, so a key the merchant never published must not appear
176
+ in it.
177
+
178
+ ### Added — the Algorand CAIP-2 id the spec actually uses
179
+
180
+ - `scheme_exact_algo.md` gives Algorand mainnet as the base64 genesis hash **truncated to 32 chars**
181
+ (`algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73k`), and the deployed web follows it — 874 live rails on
182
+ the spec form vs 589 on the padded 44-char id PipRail binds. Both forms are now accepted on parse.
183
+ What PipRail *emits* is unchanged (switching that would break deployed 2.x Algorand buyers; it is
184
+ queued for the next major).
185
+
186
+ - **The GATE now matches an echoed network id through the same canonicalisation.** It compared
187
+ `accepted.network` to its own rail with `===`, so a conformant buyer that canonicalised the id
188
+ before echoing it — the Algorand spec form, or TON's superseded `ton:-239` — had a perfectly
189
+ valid payment refused. All three selector sites (`onchain-proof`, `exact`, `upto`) now compare
190
+ through `normalizeNetwork`. This can only widen which of the gate's OWN rails is selected: payTo,
191
+ amount and asset are re-derived from the gate's spec either way, so a wrong network still cannot
192
+ redirect a payment. The Algorand driver's `supports()` accepts both spellings directly too,
193
+ mirroring TON.
194
+
195
+ ### Added — coverage is now measured, not asserted
196
+
197
+ - **`npm run x402:coverage`** (`scripts/x402-corpus/audit.mjs`) pages the live CDP Bazaar, classifies
198
+ every rail against the SDK's own exported predicates, and writes `scripts/x402-corpus/coverage.json`.
199
+ Every coverage figure in this repo cites that file. `--live` runs a real read-only `quote()` against
200
+ live third-party 402s (8/9 quotable, the 9th a merchant whose `upto` rail omits the spec-mandated
201
+ `facilitatorAddress`); `--fixture` regenerates the regression corpus.
202
+ - `PayOption.method` reports the transfer method the buyer would actually sign, appending
203
+ `' (default)'` when the rail named none — so the inference is visible instead of implicit.
204
+ - New exports: `exactTransferMethod`, `isSettleableExactMethod`, `KNOWN_EXACT_TRANSFER_METHODS`,
205
+ `DEFAULT_EXACT_TRANSFER_METHOD`, `normalizeV1Challenge`, `buildV1PaymentHeader`.
206
+
207
+ ### Changed — types now admit what the wire allows
208
+
209
+ - `X402ExactAcceptEntry.extra` and `extra.assetTransferMethod` are **optional**. A conformant rail may
210
+ ship `extra: {}` or omit the block entirely, and the type said otherwise, which is how a
211
+ bare-`extra` deref came to look like a real hazard. Every read on the buyer path optional-chains.
212
+ Defaults are unchanged: `schemes` still defaults to `['onchain-proof']`, so a zero-config client
213
+ behaves byte-identically.
8
214
 
9
215
  ## [2.15.1] — 2026-08-28 — a dead Sui RPC, an RPC failure blamed on the token, and fuzz hardening
10
216
 
@@ -1924,6 +2130,8 @@ straight into your wallet. The API is small and self-contained.
1924
2130
  to your wallet; PipRail never holds funds.
1925
2131
  - `viem ^2.21` is a peer dependency. Node 20+ or a modern browser.
1926
2132
 
2133
+ [2.16.1]: https://www.npmjs.com/package/@piprail/sdk
2134
+ [2.16.0]: https://www.npmjs.com/package/@piprail/sdk
1927
2135
  [2.15.1]: https://www.npmjs.com/package/@piprail/sdk
1928
2136
  [2.15.0]: https://www.npmjs.com/package/@piprail/sdk
1929
2137
  [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 });