@piprail/sdk 2.15.0 → 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 +260 -0
- package/README.md +2 -2
- package/dist/{algorand-CSEUTWNW.js → algorand-25FMBCT3.js} +13 -4
- package/dist/{algorand-I2O24RSM.cjs → algorand-Q3TUQLPK.cjs} +49 -40
- package/dist/{aptos-J7QGCWNW.js → aptos-SBV6SGDP.js} +3 -3
- package/dist/{aptos-GA5AEYYL.cjs → aptos-VBJONBFY.cjs} +36 -36
- package/dist/chunk-2CX7XRZK.js +649 -0
- package/dist/chunk-C52H5TYB.js +474 -0
- package/dist/chunk-OXEFPLZA.cjs +474 -0
- package/dist/{chunk-3FR22M3O.cjs → chunk-QONQSZHJ.cjs} +2 -2
- package/dist/{chunk-3ZWM3DKM.js → chunk-QU25LSVS.js} +2 -2
- package/dist/chunk-V2IJ5HUW.cjs +649 -0
- package/dist/index.cjs +507 -1347
- package/dist/index.d.cts +39 -6
- package/dist/index.d.ts +39 -6
- package/dist/index.js +192 -1032
- package/dist/{ledger-uFtXlIHY.d.cts → ledger-Crc1bZox.d.cts} +164 -10
- package/dist/{ledger-uFtXlIHY.d.ts → ledger-Crc1bZox.d.ts} +164 -10
- package/dist/{near-NDQLGI4U.cjs → near-5LTTDU6G.cjs} +27 -27
- package/dist/{near-YA4Y4IGC.js → near-OLKCMTBI.js} +1 -1
- package/dist/node.d.cts +2 -2
- package/dist/node.d.ts +2 -2
- package/dist/{solana-OU6IUMQM.cjs → solana-AI2G7V33.cjs} +73 -52
- package/dist/{solana-MB6G3OA5.js → solana-HTKDRTD3.js} +32 -11
- package/dist/{stellar-RSNVOIKL.cjs → stellar-E2KWEV2E.cjs} +20 -20
- package/dist/{stellar-WDWSYOED.js → stellar-EUFZLX6J.js} +1 -1
- package/dist/{sui-GVSFNNJX.js → sui-KCIITCYH.js} +6 -2
- package/dist/{sui-MVC46BFJ.cjs → sui-Q3NJOJZS.cjs} +21 -17
- package/dist/{ton-MYOI3U3H.cjs → ton-J7TQWRN4.cjs} +16 -16
- package/dist/{ton-EJMGM3YI.js → ton-WH2JVQOO.js} +1 -1
- package/dist/{tron-VAPVMGJK.js → tron-FXBXDNEY.js} +1 -1
- package/dist/{tron-42X5N4GQ.cjs → tron-HIPMOX7S.cjs} +25 -25
- package/dist/{xrpl-Q2UWPCTJ.js → xrpl-RUOB37QH.js} +283 -5
- package/dist/{xrpl-MGUBUNSZ.cjs → xrpl-SVVS445B.cjs} +312 -34
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,264 @@ 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.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.
|
|
194
|
+
|
|
195
|
+
## [2.15.1] — 2026-08-28 — a dead Sui RPC, an RPC failure blamed on the token, and fuzz hardening
|
|
196
|
+
|
|
197
|
+
### Fixed — Sui's shipped default RPC was dead (whole-chain outage)
|
|
198
|
+
|
|
199
|
+
- **`chain: 'sui'` with no `rpcUrl` was 100% non-functional.** The preset shipped
|
|
200
|
+
`https://fullnode.mainnet.sui.io:443`, and Mysten has since **turned JSON-RPC off** on the
|
|
201
|
+
official public fullnodes — *every* method now answers `-32601 Method not found. JSON-RPC on
|
|
202
|
+
public fullnodes has been deprecated`, pointing at gRPC/GraphQL. Since `pay`, `verify`,
|
|
203
|
+
`balanceOf` and `recipientReady` all read through `SuiJsonRpcClient`, nothing on Sui worked
|
|
204
|
+
unless the caller passed their own endpoint. The default is now
|
|
205
|
+
`https://sui-rpc.publicnode.com`, verified for `suix_getBalance`, `suix_getCoinMetadata`,
|
|
206
|
+
`suix_getOwnedObjects` and `sui_getTransactionBlock`.
|
|
207
|
+
No unit test could catch this — they all mock the reader. Caught by the live wallet sweep,
|
|
208
|
+
which reads each driver's `defaultRpc` and does a real balance read; that sweep is now a
|
|
209
|
+
stage of the SDK audit so a dead default surfaces immediately.
|
|
210
|
+
|
|
211
|
+
### Fixed — the gate blamed the token when the RPC was at fault
|
|
212
|
+
|
|
213
|
+
- **A transient RPC failure was reported as "this token isn't EIP-3009".** `readExactDomain`
|
|
214
|
+
fails closed to `null` on two very different causes — the token genuinely isn't EIP-3009, or
|
|
215
|
+
the on-chain read failed (unreachable, rate-limited, or an HTML error page from a proxy) —
|
|
216
|
+
and the gate asserted the first. On Base USDC, which *is* EIP-3009, an unreachable RPC
|
|
217
|
+
produced `this token isn't EIP-3009 (auto-selected Permit2)`: the merchant's gasless rail
|
|
218
|
+
silently downgraded to `onchain-proof` (buyers start paying gas) and they were told to change
|
|
219
|
+
the one thing that was correct. The buyer side already retried once and named both causes;
|
|
220
|
+
the gate now does the same — a single 300 ms retry on the domain read, and a skip reason that
|
|
221
|
+
states both possibilities and how to tell them apart. Behaviour on a healthy RPC is unchanged.
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
### Fixed — hardening found by a new adversarial fuzz sweep
|
|
225
|
+
|
|
226
|
+
`test/fuzz-payment-paths.test.ts` generates thousands of hostile inputs per run against the
|
|
227
|
+
paths that decide "was I actually paid?", seeded so a failure reproduces exactly
|
|
228
|
+
(`FUZZ_SEED=<n>`). It found three things, all fixed here. No API changed; every existing
|
|
229
|
+
call keeps its behaviour.
|
|
230
|
+
|
|
231
|
+
- **A spent proof could be re-redeemed by padding its reference with whitespace.** The
|
|
232
|
+
gate's single-use key was case-folded (EVM hashes are case-insensitive hex) but not
|
|
233
|
+
trimmed, so ` 0xabc…`, `0xabc… ` and `\t0xabc…\n` each hashed to a *different* key than
|
|
234
|
+
`0xabc…` and unlocked the resource a second time for one payment. The ref is now
|
|
235
|
+
canonicalised at the boundary, before the claim and before the driver sees it, so the
|
|
236
|
+
built-in store **and** a caller's custom `isUsed`/`markUsed` both receive one identical
|
|
237
|
+
value. Reaching this on a live EVM gate needed an RPC that accepts a padded hash — a real
|
|
238
|
+
one rejects it — so this was defence-in-depth rather than a known live exploit; the point
|
|
239
|
+
is that at-most-once no longer depends on how strict somebody else's RPC happens to be.
|
|
240
|
+
Six regression cases in `test/server-replay.test.ts`.
|
|
241
|
+
- **`parseUnits` threw a bare `Error`** on the single most likely amount mistake there is —
|
|
242
|
+
pricing at more decimal places than the token has — while its two other rejection branches
|
|
243
|
+
threw `InvalidConfigError`. A caller branching on `err instanceof PipRailError`
|
|
244
|
+
(ERRORS.md §1) missed exactly that case. Now typed, as is the malformed-amount branch of
|
|
245
|
+
`floorUnits`.
|
|
246
|
+
- **The header builders threw a raw `TypeError`** when handed a value `JSON.stringify`
|
|
247
|
+
cannot serialise — a `BigInt` most obviously, and amounts are `bigint` everywhere *inside*
|
|
248
|
+
the SDK, so passing one out is a natural slip. `buildSignatureHeader`,
|
|
249
|
+
`buildExactSignatureHeader` and their siblings now throw `InvalidConfigError` with a
|
|
250
|
+
message that names the fix.
|
|
251
|
+
|
|
252
|
+
### Changed — facilitator registry hygiene
|
|
253
|
+
|
|
254
|
+
- **Two dead facilitators removed** (see 2.14.2): the docs, the website data and a live
|
|
255
|
+
example probe were still advertising them after the registry was corrected. A new
|
|
256
|
+
cross-surface guard (`test/facilitators-surface.test.ts`) now holds all of them to
|
|
257
|
+
`KNOWN_FACILITATORS` — a dead host may appear only inside an explanation of its removal,
|
|
258
|
+
the docs' copy-paste URL list is set-compared against the registry, and the website's
|
|
259
|
+
generated data must match the registry's networks **and URL order** (that order is what
|
|
260
|
+
`firstKeylessFacilitator` returns).
|
|
261
|
+
- All **25** settlement hashes recorded in the registry's notes were re-verified on-chain on
|
|
262
|
+
2026-08-28 — 25/25 present and successful, 0 refuted — by
|
|
263
|
+
`.claude/skills/facilitator-probe/scripts/verify-tx.mjs`.
|
|
264
|
+
|
|
7
265
|
## [2.15.0] — 2026-06-24 — merchant on-ramp: presets, adapters, `gate.selfTest()`
|
|
8
266
|
|
|
9
267
|
### Added — merchant on-ramp (presets, adapters, self-test)
|
|
@@ -1852,6 +2110,8 @@ straight into your wallet. The API is small and self-contained.
|
|
|
1852
2110
|
to your wallet; PipRail never holds funds.
|
|
1853
2111
|
- `viem ^2.21` is a peer dependency. Node 20+ or a modern browser.
|
|
1854
2112
|
|
|
2113
|
+
[2.16.0]: https://www.npmjs.com/package/@piprail/sdk
|
|
2114
|
+
[2.15.1]: https://www.npmjs.com/package/@piprail/sdk
|
|
1855
2115
|
[2.15.0]: https://www.npmjs.com/package/@piprail/sdk
|
|
1856
2116
|
[2.14.0]: https://www.npmjs.com/package/@piprail/sdk
|
|
1857
2117
|
[2.13.1]: 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
|
|
@@ -26,7 +26,7 @@ app.get('/report',
|
|
|
26
26
|
|
|
27
27
|
That route now costs **0.05 USDC on Base**, paid straight to your wallet. One parameter picks the chain. Add `onPaid` / `onFailed` to be notified the moment a payment settles or is rejected — both carry the same reason, and the buyer's client is notified too. → [Accepting payments](https://docs.piprail.com/accepting-payments/require-payment-and-gate/)
|
|
28
28
|
|
|
29
|
-
> **Don't want to wire it by hand?** `npm create piprail
|
|
29
|
+
> **Don't want to wire it by hand?** `npm create @piprail` scaffolds a complete, mainnet-ready, deployable merchant (node / Cloudflare / Vercel) — paste a public address, no key, no account. → [Scaffold a merchant](https://docs.piprail.com/getting-started/scaffolder/)
|
|
30
30
|
|
|
31
31
|
## Let an agent pay for it
|
|
32
32
|
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ALGORAND_SPEC_CAIP2
|
|
3
|
+
} from "./chunk-2CX7XRZK.js";
|
|
1
4
|
import {
|
|
2
5
|
ConfirmationTimeoutError,
|
|
3
6
|
InsufficientFundsError,
|
|
@@ -10,7 +13,7 @@ import {
|
|
|
10
13
|
nativeCost,
|
|
11
14
|
rejectForeignToken,
|
|
12
15
|
toInsufficientFundsError
|
|
13
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-QU25LSVS.js";
|
|
14
17
|
|
|
15
18
|
// src/drivers/algorand/index.ts
|
|
16
19
|
import algosdk3 from "algosdk";
|
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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,6 @@
|
|
|
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');
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
|
|
@@ -10,7 +11,9 @@
|
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
var _chunkQONQSZHJcjs = require('./chunk-QONQSZHJ.cjs');
|
|
14
17
|
|
|
15
18
|
// src/drivers/algorand/index.ts
|
|
16
19
|
var _algosdk = require('algosdk'); var _algosdk2 = _interopRequireDefault(_algosdk);
|
|
@@ -58,13 +61,13 @@ async function payAlgorand(params) {
|
|
|
58
61
|
} catch (err) {
|
|
59
62
|
const mapped = mapAlgorandError(err, accept.payTo);
|
|
60
63
|
if (mapped) throw mapped;
|
|
61
|
-
throw _nullishCoalesce(
|
|
64
|
+
throw _nullishCoalesce(_chunkQONQSZHJcjs.toInsufficientFundsError.call(void 0, err), () => ( err));
|
|
62
65
|
}
|
|
63
66
|
}
|
|
64
67
|
function mapAlgorandError(err, payTo) {
|
|
65
68
|
const m = err instanceof Error ? err.message : String(err);
|
|
66
69
|
if (/must optin/i.test(m) || /missing from/i.test(m) && m.includes(payTo)) {
|
|
67
|
-
return new (0,
|
|
70
|
+
return new (0, _chunkQONQSZHJcjs.RecipientNotReadyError)(
|
|
68
71
|
`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
72
|
{ cause: err }
|
|
70
73
|
);
|
|
@@ -72,7 +75,7 @@ function mapAlgorandError(err, payTo) {
|
|
|
72
75
|
if (/overspend|below min|min(imum)? balance|tried to spend|balance \d+ below|asset \d+ missing from|insufficient|underflow/i.test(
|
|
73
76
|
m
|
|
74
77
|
)) {
|
|
75
|
-
return new (0,
|
|
78
|
+
return new (0, _chunkQONQSZHJcjs.InsufficientFundsError)(
|
|
76
79
|
`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
80
|
{ cause: err }
|
|
78
81
|
);
|
|
@@ -99,33 +102,33 @@ function isSet(addr) {
|
|
|
99
102
|
function randomNote() {
|
|
100
103
|
const g = globalThis.crypto;
|
|
101
104
|
if (!_optionalChain([g, 'optionalAccess', _ => _.getRandomValues])) {
|
|
102
|
-
throw new (0,
|
|
105
|
+
throw new (0, _chunkQONQSZHJcjs.UnsupportedSchemeError)("Algorand exact: no Web Crypto CSPRNG available to generate a unique note.");
|
|
103
106
|
}
|
|
104
107
|
return g.getRandomValues(new Uint8Array(32));
|
|
105
108
|
}
|
|
106
109
|
async function payExactAlgorand(input) {
|
|
107
110
|
const { suggestedParams, sk, sender, accept } = input;
|
|
108
111
|
if (accept.asset === "native") {
|
|
109
|
-
throw new (0,
|
|
112
|
+
throw new (0, _chunkQONQSZHJcjs.UnsupportedSchemeError)(
|
|
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
|
-
throw new (0,
|
|
118
|
+
throw new (0, _chunkQONQSZHJcjs.UnsupportedSchemeError)("Algorand exact rail must advertise extra.feePayer (the fee sponsor address).");
|
|
116
119
|
}
|
|
117
120
|
const assetId = parseAlgorandAssetId(accept.asset);
|
|
118
121
|
if (assetId === null) {
|
|
119
|
-
throw new (0,
|
|
122
|
+
throw new (0, _chunkQONQSZHJcjs.UnsupportedSchemeError)(`Algorand exact rail asset "${accept.asset}" must be a numeric ASA id.`);
|
|
120
123
|
}
|
|
121
124
|
if (!_algosdk2.default.isValidAddress(feePayer)) {
|
|
122
|
-
throw new (0,
|
|
125
|
+
throw new (0, _chunkQONQSZHJcjs.UnsupportedSchemeError)(`Algorand exact: extra.feePayer "${feePayer}" is not a valid Algorand address.`);
|
|
123
126
|
}
|
|
124
127
|
if (!_algosdk2.default.isValidAddress(accept.payTo)) {
|
|
125
|
-
throw new (0,
|
|
128
|
+
throw new (0, _chunkQONQSZHJcjs.UnsupportedSchemeError)(`Algorand exact: payTo "${accept.payTo}" is not a valid Algorand address.`);
|
|
126
129
|
}
|
|
127
130
|
if (feePayer === sender) {
|
|
128
|
-
throw new (0,
|
|
131
|
+
throw new (0, _chunkQONQSZHJcjs.UnsupportedSchemeError)(
|
|
129
132
|
"Algorand exact: the fee payer must differ from the payer \u2014 the buyer must pay ZERO ALGO (the sponsor pays the pooled fee)."
|
|
130
133
|
);
|
|
131
134
|
}
|
|
@@ -171,14 +174,14 @@ 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;
|
|
175
|
-
if (!railFeePayer) throw new (0,
|
|
177
|
+
const railFeePayer = _optionalChain([accept, 'access', _4 => _4.extra, 'optionalAccess', _5 => _5.feePayer]);
|
|
178
|
+
if (!railFeePayer) throw new (0, _chunkQONQSZHJcjs.SettlementError)("Algorand exact: rail is missing extra.feePayer.");
|
|
176
179
|
if (railFeePayer !== feePayerAddr) {
|
|
177
|
-
throw new (0,
|
|
180
|
+
throw new (0, _chunkQONQSZHJcjs.SettlementError)("Algorand exact: the gate relayer address does not match the rail extra.feePayer \u2014 misconfigured rail.");
|
|
178
181
|
}
|
|
179
182
|
const assetId = parseAlgorandAssetId(accept.asset);
|
|
180
|
-
if (assetId === null) throw new (0,
|
|
181
|
-
if (!_algosdk2.default.isValidAddress(accept.payTo)) throw new (0,
|
|
183
|
+
if (assetId === null) throw new (0, _chunkQONQSZHJcjs.SettlementError)(`Algorand exact: rail asset "${accept.asset}" is not a numeric ASA id.`);
|
|
184
|
+
if (!_algosdk2.default.isValidAddress(accept.payTo)) throw new (0, _chunkQONQSZHJcjs.SettlementError)(`Algorand exact: rail payTo "${accept.payTo}" is invalid.`);
|
|
182
185
|
const { paymentIndex, paymentGroup } = payload;
|
|
183
186
|
if (paymentGroup.length !== GROUP_SIZE) {
|
|
184
187
|
return fail("signature_invalid", `Algorand exact expects a ${GROUP_SIZE}-transaction group (payment + fee), got ${paymentGroup.length}.`);
|
|
@@ -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',
|
|
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',
|
|
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).");
|
|
@@ -244,7 +247,7 @@ async function verifyAndSettleExactAlgorand(input) {
|
|
|
244
247
|
try {
|
|
245
248
|
feeSigned = fee.signTxn(feePayerSk);
|
|
246
249
|
} catch (err) {
|
|
247
|
-
throw new (0,
|
|
250
|
+
throw new (0, _chunkQONQSZHJcjs.SettlementError)(`Algorand exact settle: the gate could not sign the fee transaction (${shorten(err instanceof Error ? err.message : String(err))}).`, { cause: err });
|
|
248
251
|
}
|
|
249
252
|
const signedGroup = elements.map((el, i) => i === paymentIndex ? payElement.raw : feeSigned);
|
|
250
253
|
try {
|
|
@@ -266,7 +269,7 @@ async function verifyAndSettleExactAlgorand(input) {
|
|
|
266
269
|
if (/signature|sig|auth|malformed/i.test(m)) return fail("signature_invalid", `Settle rejected \u2014 bad signature: ${shorten(m)}.`);
|
|
267
270
|
if (/already in ledger|already committed|duplicate/i.test(m)) return fail("tx_already_used", `This payment was already settled: ${shorten(m)}.`);
|
|
268
271
|
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,
|
|
272
|
+
throw new (0, _chunkQONQSZHJcjs.SettlementError)(
|
|
270
273
|
`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
274
|
{ cause: err }
|
|
272
275
|
);
|
|
@@ -275,10 +278,10 @@ async function verifyAndSettleExactAlgorand(input) {
|
|
|
275
278
|
try {
|
|
276
279
|
round = await client.waitForConfirmation(buyerTxid);
|
|
277
280
|
} catch (err) {
|
|
278
|
-
throw new (0,
|
|
281
|
+
throw new (0, _chunkQONQSZHJcjs.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
282
|
}
|
|
280
283
|
if (round === null) {
|
|
281
|
-
throw new (0,
|
|
284
|
+
throw new (0, _chunkQONQSZHJcjs.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
285
|
}
|
|
283
286
|
return {
|
|
284
287
|
ok: true,
|
|
@@ -374,13 +377,13 @@ function rpcFailed(nonce) {
|
|
|
374
377
|
|
|
375
378
|
function assertAlgorandWallet(wallet, network) {
|
|
376
379
|
if (typeof wallet !== "object" || wallet === null) {
|
|
377
|
-
throw new (0,
|
|
380
|
+
throw new (0, _chunkQONQSZHJcjs.WrongFamilyError)(
|
|
378
381
|
`chain ${network} is Algorand; wallet must be { key } (25-word mnemonic) or { account }.`
|
|
379
382
|
);
|
|
380
383
|
}
|
|
381
|
-
|
|
384
|
+
_chunkQONQSZHJcjs.assertNoLegacyWalletKey.call(void 0, wallet, "Algorand");
|
|
382
385
|
if (!("key" in wallet) && !("account" in wallet)) {
|
|
383
|
-
throw new (0,
|
|
386
|
+
throw new (0, _chunkQONQSZHJcjs.WrongFamilyError)(
|
|
384
387
|
`chain ${network} is Algorand; wallet must be { key } (25-word mnemonic) or { account }.`
|
|
385
388
|
);
|
|
386
389
|
}
|
|
@@ -395,13 +398,13 @@ function resolveAlgorandWallet(config) {
|
|
|
395
398
|
const { addr, sk } = _algosdk2.default.mnemonicToSecretKey(config.key);
|
|
396
399
|
return { addr: addr.toString(), sk };
|
|
397
400
|
} catch (cause) {
|
|
398
|
-
throw new (0,
|
|
401
|
+
throw new (0, _chunkQONQSZHJcjs.WrongFamilyError)(
|
|
399
402
|
"Algorand wallet { key } is not a valid 25-word Algorand mnemonic.",
|
|
400
403
|
{ cause }
|
|
401
404
|
);
|
|
402
405
|
}
|
|
403
406
|
}
|
|
404
|
-
throw new (0,
|
|
407
|
+
throw new (0, _chunkQONQSZHJcjs.WrongFamilyError)("Algorand wallet needs { key } (25-word mnemonic) or { account }.");
|
|
405
408
|
}
|
|
406
409
|
|
|
407
410
|
// src/drivers/algorand/index.ts
|
|
@@ -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',
|
|
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',
|
|
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
|
-
|
|
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 };
|
|
@@ -472,16 +481,16 @@ function makeAlgorandNetwork(preset, algodUrl) {
|
|
|
472
481
|
const info = preset.tokens[token.toUpperCase()];
|
|
473
482
|
if (!info) {
|
|
474
483
|
const known = Object.keys(preset.tokens).join(", ") || "(none built in)";
|
|
475
|
-
throw new (0,
|
|
484
|
+
throw new (0, _chunkQONQSZHJcjs.UnknownTokenError)(
|
|
476
485
|
`token "${token}" isn't built in for Algorand (known: ${known}). Pass { assetId, decimals } for a custom ASA, or use 'native'.`
|
|
477
486
|
);
|
|
478
487
|
}
|
|
479
488
|
return { asset: algorandAssetId(info.assetId), decimals: info.decimals, symbol: info.symbol };
|
|
480
489
|
}
|
|
481
|
-
|
|
490
|
+
_chunkQONQSZHJcjs.rejectForeignToken.call(void 0, token, "algorand", network);
|
|
482
491
|
const t = token;
|
|
483
492
|
if (typeof t.assetId !== "number" || typeof t.decimals !== "number") {
|
|
484
|
-
throw new (0,
|
|
493
|
+
throw new (0, _chunkQONQSZHJcjs.WrongFamilyError)(
|
|
485
494
|
`chain ${network} is Algorand; a custom token must be { assetId, decimals }.`
|
|
486
495
|
);
|
|
487
496
|
}
|
|
@@ -502,12 +511,12 @@ function makeAlgorandNetwork(preset, algodUrl) {
|
|
|
502
511
|
},
|
|
503
512
|
assertValidPayTo(payTo) {
|
|
504
513
|
if (payTo.startsWith("0x")) {
|
|
505
|
-
throw new (0,
|
|
514
|
+
throw new (0, _chunkQONQSZHJcjs.WrongFamilyError)(
|
|
506
515
|
`chain ${network} is Algorand, but payTo "${payTo}" looks like an EVM address.`
|
|
507
516
|
);
|
|
508
517
|
}
|
|
509
518
|
if (!_algosdk2.default.isValidAddress(payTo)) {
|
|
510
|
-
throw new (0,
|
|
519
|
+
throw new (0, _chunkQONQSZHJcjs.WrongFamilyError)(
|
|
511
520
|
`chain ${network} is Algorand, but payTo "${payTo}" is not a valid Algorand address.`
|
|
512
521
|
);
|
|
513
522
|
}
|
|
@@ -524,14 +533,14 @@ function makeAlgorandNetwork(preset, algodUrl) {
|
|
|
524
533
|
const info = await _algosdk2.default.waitForConfirmation(algod, ref, 10);
|
|
525
534
|
return { height: String(_nullishCoalesce(info.confirmedRound, () => ( 0))) };
|
|
526
535
|
} catch (err) {
|
|
527
|
-
throw new (0,
|
|
536
|
+
throw new (0, _chunkQONQSZHJcjs.ConfirmationTimeoutError)(`Algorand tx ${ref} did not confirm in time.`, {
|
|
528
537
|
cause: err
|
|
529
538
|
});
|
|
530
539
|
}
|
|
531
540
|
},
|
|
532
541
|
async estimateCost(accept) {
|
|
533
542
|
if (accept.scheme === "exact") {
|
|
534
|
-
return
|
|
543
|
+
return _chunkQONQSZHJcjs.nativeCost.call(void 0, {
|
|
535
544
|
symbol: ALGO_SYMBOL,
|
|
536
545
|
decimals: ALGO_DECIMALS,
|
|
537
546
|
fee: 0n,
|
|
@@ -539,7 +548,7 @@ function makeAlgorandNetwork(preset, algodUrl) {
|
|
|
539
548
|
detail: "gasless \u2014 the fee payer (facilitator/relayer) pools the group fee; the buyer pays 0 ALGO"
|
|
540
549
|
});
|
|
541
550
|
}
|
|
542
|
-
return
|
|
551
|
+
return _chunkQONQSZHJcjs.nativeCost.call(void 0, {
|
|
543
552
|
symbol: ALGO_SYMBOL,
|
|
544
553
|
decimals: ALGO_DECIMALS,
|
|
545
554
|
fee: 1000n,
|
|
@@ -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',
|
|
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" };
|