@piprail/sdk 1.3.0 → 1.4.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/CHAINS.md +31 -7
- package/CHANGELOG.md +51 -0
- package/ERRORS.md +2 -2
- package/README.md +15 -9
- package/dist/algorand-IDFUG5CI.cjs +363 -0
- package/dist/algorand-XJ5OVWQB.js +363 -0
- package/dist/{aptos-MQY7KOOJ.js → aptos-AWWSCPDH.js} +6 -3
- package/dist/{aptos-T3MNKUPB.cjs → aptos-TPSOQ2VL.cjs} +21 -18
- package/dist/{chunk-YJPWIK5L.cjs → chunk-IQGT65WS.cjs} +4 -2
- package/dist/{chunk-AGKC3C7Y.js → chunk-QDS6FBZP.js} +4 -2
- package/dist/index.cjs +61 -48
- package/dist/index.d.cts +10 -3
- package/dist/index.d.ts +10 -3
- package/dist/index.js +22 -9
- package/dist/{near-DISWUB7Y.cjs → near-KDA5DPTX.cjs} +19 -19
- package/dist/{near-YX3XOASO.js → near-NOJTO4GX.js} +1 -1
- package/dist/{solana-RJPNEFSN.cjs → solana-DVA6I55L.cjs} +14 -14
- package/dist/{solana-37F2PR5H.js → solana-HNRTS4KM.js} +1 -1
- package/dist/{stellar-SUGNX52Z.cjs → stellar-4D5EWT3V.cjs} +20 -20
- package/dist/{stellar-ALOVOMFD.js → stellar-4TDVVJYO.js} +1 -1
- package/dist/{sui-HZWPHVU4.cjs → sui-5HMIHOZK.cjs} +17 -17
- package/dist/{sui-OLC5ID4X.js → sui-ALUTM5GX.js} +1 -1
- package/dist/{ton-NIDWF77T.js → ton-3XMIM2FU.js} +1 -1
- package/dist/{ton-C4KTFXDL.cjs → ton-TVK4TEDX.cjs} +14 -14
- package/dist/{tron-LPMK57H7.js → tron-6D65YJEU.js} +1 -1
- package/dist/{tron-DTU7NPEM.cjs → tron-Y5RZJZRT.cjs} +24 -24
- package/dist/{xrpl-N6ZAJRGC.cjs → xrpl-ICO6G7UK.cjs} +20 -20
- package/dist/{xrpl-6ODQS7JR.js → xrpl-ISFG3SSN.js} +1 -1
- package/package.json +9 -2
package/CHAINS.md
CHANGED
|
@@ -6,7 +6,8 @@ themselves differ, and a few have **setup steps you must do before a wallet can
|
|
|
6
6
|
receive**. This page is the exact list.
|
|
7
7
|
|
|
8
8
|
**Most chains need nothing special.** The ones with caveats are **NEAR**, **TON**,
|
|
9
|
-
**Stellar**, **XRPL**, and **
|
|
9
|
+
**Stellar**, **XRPL**, **Tron**, and **Algorand** (USDC needs a one-time ASA opt-in) —
|
|
10
|
+
read those sections before you ship them.
|
|
10
11
|
|
|
11
12
|
## At a glance
|
|
12
13
|
|
|
@@ -15,6 +16,8 @@ receive**. This page is the exact list.
|
|
|
15
16
|
| **EVM** (Ethereum, Base, Arbitrum, Optimism, Polygon, BNB, Avalanche, Mantle, Sonic, Linea, Scroll, Celo, zkSync, Unichain, World Chain, Sei, Injective, + any EVM chain) | ✅ ETH/BNB/POL/… | USDC (all) · USDT (all **except Base, World Chain, Sei**) | No | `{ privateKey }` |
|
|
16
17
|
| **Solana** | ✅ SOL | USDC · USDT | No (payer creates the recipient's token account) | `{ secretKey }` |
|
|
17
18
|
| **Sui** | ✅ SUI | USDC (no USDT) | No | `{ privateKey }` (`suiprivkey1…`) |
|
|
19
|
+
| **Aptos** | ✅ APT | USDC · USDT | No (primary FA store auto-creates) | `{ privateKey }` (`ed25519-priv-0x…`) |
|
|
20
|
+
| **Algorand** | ✅ ALGO | **USDC only** (Tether deprecated USDT) | USDC: ⚠️ **ASA opt-in** · **native ALGO: none** | `{ mnemonic }` (25 words) |
|
|
18
21
|
| **Stellar** | ✅ XLM | USDC · EURC | ⚠️ **Yes — trustline + funded account** | `{ secret }` (`S…`) |
|
|
19
22
|
| **XRP Ledger** | ✅ XRP | USDC · RLUSD (no USDT) | ⚠️ **Yes — trustline + activated account** | `{ seed }` (`s…`) |
|
|
20
23
|
| **TON** | ✅ TON | **USD₮ only** (no USDC) | No (payer's gas auto-deploys the jetton wallet) | `{ mnemonic }` (24 words) |
|
|
@@ -22,12 +25,14 @@ receive**. This page is the exact list.
|
|
|
22
25
|
| **NEAR** | ✅ NEAR | USDC · USDT | tokens: ⚠️ `storage_deposit` · **native NEAR: none** | `{ accountId, privateKey }` |
|
|
23
26
|
|
|
24
27
|
> **`token: 'native'`** (paying in the chain's own coin) is accepted on **every family** —
|
|
25
|
-
> EVM, Solana, Sui, Stellar, XRPL, TON, NEAR, **and Tron** (native TRX,
|
|
26
|
-
> exceptions. On NEAR, native is the **zero-setup** path: no `storage_deposit`,
|
|
27
|
-
> even creates a fresh recipient (the NEP-141 token path still needs
|
|
28
|
+
> EVM, Solana, Sui, Aptos, Algorand, Stellar, XRPL, TON, NEAR, **and Tron** (native TRX,
|
|
29
|
+
> digest-bound). No exceptions. On NEAR, native is the **zero-setup** path: no `storage_deposit`,
|
|
30
|
+
> and a transfer even creates a fresh recipient (the NEP-141 token path still needs
|
|
31
|
+
> `storage_deposit`).
|
|
28
32
|
>
|
|
29
33
|
> **Custom tokens** work everywhere with no allowlist: EVM `{ address, decimals }` ·
|
|
30
|
-
> Solana `{ mint, decimals }` · Sui `{ coinType, decimals }` ·
|
|
34
|
+
> Solana `{ mint, decimals }` · Sui `{ coinType, decimals }` · Aptos `{ metadata, decimals }` ·
|
|
35
|
+
> Algorand `{ assetId, decimals }` · TON `{ master, decimals }` ·
|
|
31
36
|
> Tron `{ address, decimals }` · NEAR `{ contractId, decimals }` · Stellar
|
|
32
37
|
> `{ issuer, code, decimals }` · XRPL `{ issuer, currencyHex, decimals }`.
|
|
33
38
|
|
|
@@ -93,6 +98,23 @@ receive**. This page is the exact list.
|
|
|
93
98
|
- **Finality is slow-ish:** verification waits for the tx to solidify (~19 blocks, ~57s); until then it reads as `tx_not_found` and is retried.
|
|
94
99
|
- **Wallet:** `{ privateKey }` (32-byte hex, same format as EVM); addresses are Base58 `T…`.
|
|
95
100
|
|
|
101
|
+
### Algorand — USDC needs a one-time ASA opt-in (native ALGO doesn't)
|
|
102
|
+
- **Pay in:** `'native'` (ALGO, the zero-setup path), `'USDC'`, or a custom ASA `{ assetId, decimals }`. **USDC only for the stablecoin** — Tether deprecated/froze USDT on Algorand (2025-09-01), so it's not built in (pass it as a custom ASA if you must).
|
|
103
|
+
- **Receiving USDC needs an ASA opt-in.** Before an account can *receive* USDC (ASA `31566704`) — or any ASA — it must **opt into that asset** once: a 0-amount asset-transfer to itself, which raises its minimum balance by 0.1 ALGO (locked, recoverable). No opt-in → the payment fails and PipRail returns `RECIPIENT_NOT_READY`. **Native ALGO needs no opt-in.** The payer is implicitly opted-in if it already holds USDC. The one-time opt-in is plain `algosdk` (PipRail stays a payments SDK, not a wallet manager):
|
|
104
|
+
```ts
|
|
105
|
+
import algosdk from 'algosdk'
|
|
106
|
+
const algod = new algosdk.Algodv2('', 'https://mainnet-api.algonode.cloud', '')
|
|
107
|
+
const sp = await algod.getTransactionParams().do()
|
|
108
|
+
const optIn = algosdk.makeAssetTransferTxnWithSuggestedParamsFromObject({
|
|
109
|
+
sender: account.addr, receiver: account.addr, amount: 0, assetIndex: 31566704, suggestedParams: sp,
|
|
110
|
+
})
|
|
111
|
+
await algod.sendRawTransaction(optIn.signTxn(account.sk)).do() // one-time, per account per ASA
|
|
112
|
+
```
|
|
113
|
+
- **Fast + cheap:** ~3s single-step finality, flat 0.001 ALGO min fee. The challenge nonce rides in the transaction's **note field** (Template A), so the proof is bound to its challenge; verify reads the merchant account's inbound transfers via the indexer.
|
|
114
|
+
- **x402:** Algorand's `exact` scheme is part of the official x402 standard, but the incumbent on-chain path uses a hosted **facilitator** — PipRail is the **backendless, no-facilitator** option (payer broadcasts, merchant verifies locally).
|
|
115
|
+
- **Wallet:** `{ mnemonic }` (a 25-word Algorand recovery phrase) or `{ account }` (an algosdk `{ addr, sk }`).
|
|
116
|
+
- **Endpoints:** `rpcUrl` overrides the **algod** endpoint (submit/params); the verify-side **indexer** uses the public AlgoNode default (override needs are rare; the public indexer is production-grade for the inbound-transfer read).
|
|
117
|
+
|
|
96
118
|
### Stellar — the receiver needs a trustline + a funded account
|
|
97
119
|
- **Pay in:** `'native'` (XLM), `'USDC'`, `'EURC'`, or a custom `{ issuer, code, decimals }`.
|
|
98
120
|
- **Receiving an issued asset needs a one-time TRUSTLINE.** The merchant (`payTo`) must (1) **exist** on-chain (funded above the ~1 XLM base reserve) and (2) hold a **trustline** (`changeTrust`) for that exact `code+issuer` *before* it can receive. No trustline → the payment fails. Each trustline locks **+0.5 XLM** of reserve. The **payer** likewise needs its own trustline to hold/send the asset.
|
|
@@ -130,6 +152,7 @@ Fix the *recipient*, not the payer:
|
|
|
130
152
|
| `op_no_destination` | Stellar | the `payTo` account doesn't exist | create it with ≥1 XLM (base reserve) |
|
|
131
153
|
| `op_no_trust` | Stellar | recipient has no trustline for the asset | add the trustline (+0.5 XLM reserve) |
|
|
132
154
|
| `… is not registered` | NEAR | recipient isn't `storage_deposit`-registered on the token | call `storage_deposit` once (~0.00125 NEAR) |
|
|
155
|
+
| `must optin` / `asset … missing from <payTo>` | Algorand | recipient hasn't opted into the USDC ASA | opt the recipient into the ASA once (0-amount self-transfer, +0.1 ALGO min balance) |
|
|
133
156
|
|
|
134
157
|
Everything else (EVM, Solana, Sui, Tron, native TON/NEAR) needs no recipient setup, so you'll
|
|
135
158
|
only ever see `INSUFFICIENT_FUNDS` there if the payer is short. Full taxonomy: **[ERRORS.md](./ERRORS.md)**.
|
|
@@ -143,9 +166,10 @@ right asset to `payTo`), but binds the proof to your challenge differently:
|
|
|
143
166
|
|
|
144
167
|
- **Memo-bound** (the challenge nonce is written on-chain): **NEAR tokens** (ft_transfer
|
|
145
168
|
memo), **TON** (transfer comment), **Stellar** (`MEMO_HASH = sha256(nonce)`), **XRPL**
|
|
146
|
-
(Memo + a derived DestinationTag)
|
|
169
|
+
(Memo + a derived DestinationTag), **Algorand** (the transaction's note field — native ALGO
|
|
170
|
+
and USDC alike).
|
|
147
171
|
- **Digest-bound** (no on-chain nonce; the proof is the tx id, made single-use by the gate
|
|
148
|
-
+ a recency window): **EVM**, **Solana**, **Sui**, **Tron**, and **native NEAR**. For
|
|
172
|
+
+ a recency window): **EVM**, **Solana**, **Sui**, **Aptos**, **Tron**, and **native NEAR**. For
|
|
149
173
|
these, a persistent `isUsed`/`markUsed` store + a tight `maxTimeoutSeconds` are
|
|
150
174
|
load-bearing in multi-instance deployments (the default used-set is single-process).
|
|
151
175
|
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,57 @@ 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
|
+
## [1.4.0] — 2026-06-04
|
|
8
|
+
|
|
9
|
+
A new chain **family** — **Algorand** — the **10th driver family**, bringing the built-in count to
|
|
10
|
+
**28 chains across 10 families (19 EVM)**. Algorand is genuinely part of the **official x402
|
|
11
|
+
standard** (its `exact` scheme is merged into the canonical x402 repo and the `@x402/avm` package),
|
|
12
|
+
and one of the loudest agentic-commerce chains of 2026 — but the incumbent x402 path there is
|
|
13
|
+
**facilitator-mediated**, so PipRail is the **first facilitator-free, backendless, verify-locally
|
|
14
|
+
x402 SDK on Algorand**. Fully backward-compatible; `algosdk` is a lazy-loaded optional peer, so
|
|
15
|
+
pure-EVM (and other) installs never download it.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- **Algorand (`chain: 'algorand'`, CAIP-2 `algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73k`)** — native
|
|
19
|
+
Circle **USDC** (ASA `31566704`, 6 dp) + native **ALGO** (6 dp). The USDC ASA was verified live on
|
|
20
|
+
mainnet (algod `/v2/assets/31566704` → unit-name `USDC`, decimals 6, creator = Circle's `2UEQ…`
|
|
21
|
+
account, url `centre.io/usdc`) before shipping. **USDC-only:** Tether deprecated USDT on Algorand
|
|
22
|
+
(frozen 2025-09-01), so it's intentionally omitted — pass it as a custom `{ assetId, decimals }`.
|
|
23
|
+
- **Template A (memo-bound, like Stellar/XRPL/NEAR):** every Algorand transaction carries an
|
|
24
|
+
arbitrary **note field (≤1KB)**, so the challenge nonce rides in it verbatim (no hashing needed —
|
|
25
|
+
a UUID dwarfs nothing of the 1KB cap). `verify()` re-derives the watched account from the
|
|
26
|
+
**trusted `accept.payTo`** (never the client ref), reads its recent inbound transfers via the
|
|
27
|
+
indexer, and matches `note === nonce` + recipient + asset + amount + recency — a proof is
|
|
28
|
+
cryptographically bound to its challenge. Native ALGO is a `pay` txn; USDC/ASAs are `axfer`; both
|
|
29
|
+
carry the note. Amounts are integer base units (like EVM). `algosdk` is an **optional peer
|
|
30
|
+
(`>=3 <4`)**, lazy-loaded on first use; the built EVM bundle stays free of any static `algosdk`
|
|
31
|
+
import (its own chunk).
|
|
32
|
+
- **Receive prerequisite:** to receive a USDC/ASA, the recipient must **opt into the ASA** (a
|
|
33
|
+
one-time 0-amount self-transfer) — conceptually identical to an XRPL/Stellar trustline. A submit
|
|
34
|
+
failure for a not-opted-in recipient maps to the typed `RecipientNotReadyError`; native ALGO needs
|
|
35
|
+
no opt-in.
|
|
36
|
+
|
|
37
|
+
**Live-proven on Algorand mainnet — both assets, 12/12.** Real 402 → pay → confirm → verify → 200
|
|
38
|
+
round-trips, each with balance moved + replay rejected (`tx_already_used`) + all agent surfaces
|
|
39
|
+
green: **native ALGO** 6/6 (tx `AXXJVYAP7BLK6C76AWCJ3XA5HTECIRSCNRQ2WLFRNSZ6CD5GH32Q`) and
|
|
40
|
+
**USDC** 6/6 (tx `INWCUUBAMIBYOPPUOBWXEHZQAQL6KSV7DPEEVGKAI64Z46TRQKOA`, merchant +0.05 USDC).
|
|
41
|
+
Also verified against the test contract (typecheck + 441 tests + build + the lazy-chunk invariant).
|
|
42
|
+
Funding follow-up: file an Algorand **xGov retroactive** grant for the shipped open-source SDK
|
|
43
|
+
(SDKs/libraries are a named eligible category).
|
|
44
|
+
|
|
45
|
+
## [1.3.1] — 2026-06-04
|
|
46
|
+
|
|
47
|
+
Aptos pay-path fix surfaced by the live mainnet test — no API change, fully compatible with 1.3.0.
|
|
48
|
+
|
|
49
|
+
### Fixed
|
|
50
|
+
- **Aptos: cap `maxGasAmount` (50k) on the Fungible-Asset transfer.** Aptos validates
|
|
51
|
+
`max_gas_amount × gas_unit_price` against the sender's balance *before* execution, so the SDK
|
|
52
|
+
default (200k units) made a tiny transfer demand ~0.5 APT held just to be admitted — a wallet
|
|
53
|
+
with a modest APT balance was rejected with `INSUFFICIENT_BALANCE_FOR_TRANSACTION_FEE` even
|
|
54
|
+
though the transfer itself uses a fraction of that. A `primary_fungible_store::transfer` (even
|
|
55
|
+
one that creates the recipient's primary store) stays well under 50k gas units, so the cap keeps
|
|
56
|
+
ample gas headroom while the upfront fee requirement stays small. Live-validated on Aptos mainnet.
|
|
57
|
+
|
|
7
58
|
## [1.3.0] — 2026-06-04
|
|
8
59
|
|
|
9
60
|
A new chain **family** — **Aptos** — the **9th driver family** and the only Move L1 with BOTH
|
package/ERRORS.md
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
This is the **single source of truth** for how `@piprail/sdk` reports errors. It is
|
|
4
4
|
deliberately small and uniform: every module — the client, the server gate, the registry,
|
|
5
|
-
and every chain driver (all
|
|
6
|
-
and any future one) — follows it
|
|
5
|
+
and every chain driver (all ten families: EVM, Solana, TON, Tron, NEAR, Sui, Stellar, XRPL, Aptos,
|
|
6
|
+
Algorand, and any future one) — follows it
|
|
7
7
|
*exactly*, so a human developer, a merchant server, or an AI agent always gets a **typed,
|
|
8
8
|
understandable** reason, never an opaque chain-library blob.
|
|
9
9
|
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @piprail/sdk
|
|
2
2
|
|
|
3
|
-
**Accept crypto payments from any HTTP request — on any EVM chain, Solana, TON, Tron, NEAR, Sui, Stellar, and the XRP Ledger — in a couple of lines.**
|
|
3
|
+
**Accept crypto payments from any HTTP request — on any EVM chain, Solana, TON, Tron, NEAR, Sui, Aptos, Algorand, Stellar, and the XRP Ledger — in a couple of lines.**
|
|
4
4
|
|
|
5
5
|
No middleman. No database. No fee. No account. Payments settle **straight into your wallet**, verified locally against your own RPC. Drop one middleware in front of a route and it's paid-only; point an agent at a paid URL and it pays itself.
|
|
6
6
|
|
|
@@ -90,7 +90,7 @@ See [`examples/agent-tools.mjs`](../examples/agent-tools.mjs) for MCP / AI-SDK w
|
|
|
90
90
|
|
|
91
91
|
### Accept several chains at once
|
|
92
92
|
|
|
93
|
-
`requirePayment` (and `createPaymentGate`) take an **`accept: [...]`** array — one challenge that's payable on **any** of several chains/tokens, across **all
|
|
93
|
+
`requirePayment` (and `createPaymentGate`) take an **`accept: [...]`** array — one challenge that's payable on **any** of several chains/tokens, across **all ten families** (EVM, Solana, TON, Tron, Stellar, XRPL, NEAR, Sui, Aptos, Algorand). The agent pays with whatever it holds:
|
|
94
94
|
|
|
95
95
|
```ts
|
|
96
96
|
requirePayment({
|
|
@@ -133,7 +133,7 @@ requirePayment({ chain: 'ton', token: 'native', amount: '1', payTo }) /
|
|
|
133
133
|
requirePayment({ chain: 'xrpl', token: 'native', amount: '1', payTo }) // XRP
|
|
134
134
|
```
|
|
135
135
|
|
|
136
|
-
**Native or stablecoin — your choice, on every chain.** Every gate accepts the chain's native coin (ETH, BNB, POL, AVAX, SOL, TON, XLM, XRP, SUI, NEAR, **TRX**, …) just as readily as a stablecoin — set `token: 'native'` and the SDK fills in the right decimals (18 on EVM, 9 on Solana/TON/Sui, 7 on Stellar, 6 on XRPL/Tron, 24 on NEAR). Verification, replay protection, and self-custody are identical to the stablecoin path — across **all
|
|
136
|
+
**Native or stablecoin — your choice, on every chain.** Every gate accepts the chain's native coin (ETH, BNB, POL, AVAX, SOL, TON, XLM, XRP, SUI, NEAR, **TRX**, …) just as readily as a stablecoin — set `token: 'native'` and the SDK fills in the right decimals (18 on EVM, 9 on Solana/TON/Sui, 8 on Aptos, 7 on Stellar, 6 on XRPL/Tron/Algorand, 24 on NEAR). Verification, replay protection, and self-custody are identical to the stablecoin path — across **all ten families, no exceptions**. (On **NEAR**, native is the zero-setup path — no `storage_deposit` — while the NEP-141 token path needs registration; see the NEAR note. On **Tron**, USD₮ is the default since TRX is volatile gas, but native TRX works too.)
|
|
137
137
|
|
|
138
138
|
`token` is **required** — every gate states exactly what it accepts, so there's never any doubt whether a route takes USDC, USDT, or the native coin. Name a built-in symbol (`'USDC'`, `'USDT'`), use `'native'` for the chain's own coin (ETH, BNB, SOL, TON, XLM, …), or pass a custom token by address. The symbol is all you write — the SDK fills in the contract + decimals.
|
|
139
139
|
|
|
@@ -168,6 +168,7 @@ Every token address below was verified on-chain (symbol + decimals) before shipp
|
|
|
168
168
|
| `'near'` | NEAR | USDC, USDT |
|
|
169
169
|
| `'sui'` | Sui | USDC |
|
|
170
170
|
| `'aptos'` | Aptos | USDC, USDT |
|
|
171
|
+
| `'algorand'` | Algorand | USDC |
|
|
171
172
|
| `'stellar'` | Stellar | USDC, EURC |
|
|
172
173
|
| `'xrpl'` | XRP Ledger | USDC, RLUSD |
|
|
173
174
|
|
|
@@ -179,6 +180,8 @@ Every token address below was verified on-chain (symbol + decimals) before shipp
|
|
|
179
180
|
|
|
180
181
|
**Sui note:** **USDC only** — no native USDT on Sui (Wormhole-bridged only). Native SUI works with `token: 'native'`.
|
|
181
182
|
|
|
183
|
+
**Algorand note:** **USDC only** — Tether deprecated USDT on Algorand (frozen 2025-09-01), so it's intentionally absent (pass it as a custom `{ assetId, decimals }`). Native ALGO works with `token: 'native'` (the zero-setup path). To **receive** USDC the recipient must **opt into the ASA** once (a 0-amount self-transfer — like a trustline); a not-opted-in recipient surfaces `RECIPIENT_NOT_READY`. The challenge nonce binds inside the transaction's note field (Template A). Algorand's `exact` scheme is part of the official x402 standard; the incumbent on-chain path there uses a hosted facilitator, so PipRail is the backendless, no-facilitator option.
|
|
184
|
+
|
|
182
185
|
**Stellar / XRPL note:** to **receive** an issued asset (USDC/EURC on Stellar; USDC/RLUSD on XRPL) the recipient needs a one-time **trustline** for that asset, and the account must already exist / be activated (a small native reserve — **locked, not spent**). Native XLM/XRP need no trustline. The payer needs its own trustline too.
|
|
183
186
|
|
|
184
187
|
### Using TON? Grab one free API key (≈30 seconds)
|
|
@@ -475,9 +478,9 @@ Two layers, one contract. Worth knowing if you're extending the SDK or auditing
|
|
|
475
478
|
|
|
476
479
|
- **The protocol layer is chain-agnostic.** `server.ts` (`requirePayment`/`createPaymentGate`), `client.ts` (`PipRailClient`), `x402.ts` (wire envelopes), `policy.ts`, `ledger.ts`, and `agent.ts` depend **only** on the `PaymentDriver` contract in `drivers/types.ts` — zero `viem`, zero `@solana/web3.js`, zero chain SDK. The chain is data the caller passes, not an allowlist the SDK ships.
|
|
477
480
|
- **The `PaymentDriver` contract.** `resolve(chain)` → a bound `ResolvedNetwork` exposing `resolveToken` · `describeAsset` · `assertValidPayTo` · `bindWallet` · `send` · `confirm` · `estimateCost` · `verify`. That's the entire boundary every family implements and the protocol layer ever sees.
|
|
478
|
-
- **Families mirror each other file-for-file.** Each lives in `drivers/<family>/` as `chains` · `wallet` · `pay` · `verify` · `index`, with family-suffixed functions (`payEvm`/`paySui`/…, `verifyEvm`/`verifyNear`/…).
|
|
479
|
-
- **Routing + lazy auto-mount.** `registry.ts` maps a `chain` value to its family synchronously (`familyForChain`). EVM is always present (viem is a hard peer); every non-EVM family **loads itself on first use** via one dynamic `import()`, so a pure-EVM install never downloads `@solana`/`@ton`/`@stellar`/`xrpl`/`tronweb`/`near-api-js`/`@mysten/sui`. A build-time invariant asserts the main bundle has **zero** static imports of those libs — only per-family lazy chunks.
|
|
480
|
-
- **Two verification templates.** *Template A (memo-bound)* — Stellar, XRPL, TON, NEAR — carries the challenge nonce inside the transfer (memo / tag / comment), so the proof is cryptographically bound to its challenge. *Template B (digest-bound)* — EVM, Solana, Tron, Sui — binds via a single-use proof set + recipient + amount + a tight recency window (use a persistent `isUsed`/`markUsed` store in production).
|
|
481
|
+
- **Families mirror each other file-for-file.** Each lives in `drivers/<family>/` as `chains` · `wallet` · `pay` · `verify` · `index`, with family-suffixed functions (`payEvm`/`paySui`/…, `verifyEvm`/`verifyNear`/…). Ten today: `evm`, `solana`, `ton`, `stellar`, `xrpl`, `tron`, `near`, `sui`, `aptos`, `algorand`. Adding one = copy the five files, implement the contract, `registerDriver` — the protocol layer never changes.
|
|
482
|
+
- **Routing + lazy auto-mount.** `registry.ts` maps a `chain` value to its family synchronously (`familyForChain`). EVM is always present (viem is a hard peer); every non-EVM family **loads itself on first use** via one dynamic `import()`, so a pure-EVM install never downloads `@solana`/`@ton`/`@stellar`/`xrpl`/`tronweb`/`near-api-js`/`@mysten/sui`/`@aptos-labs/ts-sdk`/`algosdk`. A build-time invariant asserts the main bundle has **zero** static imports of those libs — only per-family lazy chunks.
|
|
483
|
+
- **Two verification templates.** *Template A (memo-bound)* — Stellar, XRPL, TON, NEAR, Algorand — carries the challenge nonce inside the transfer (memo / tag / comment / note), so the proof is cryptographically bound to its challenge. *Template B (digest-bound)* — EVM, Solana, Tron, Sui, Aptos — binds via a single-use proof set + recipient + amount + a tight recency window (use a persistent `isUsed`/`markUsed` store in production).
|
|
481
484
|
- **Gas estimation.** Every driver's `estimateCost` extracts its own per-chain fee math, shaped into one uniform `CostEstimate` by the shared `nativeCost()` helper (`util/cost.ts`).
|
|
482
485
|
- **The tests are the contract** (`test/`, Vitest), and two living standards govern any change: **[ERRORS.md](./ERRORS.md)** (how every module reports errors) and **STANDARDS.md** (how anything in the SDK is built + the verification gate). Runnable examples — including a local Anvil end-to-end — live in [`examples/`](../examples).
|
|
483
486
|
|
|
@@ -499,11 +502,12 @@ A failed payment is almost always one of two things, and PipRail tells them apar
|
|
|
499
502
|
|
|
500
503
|
| Chain | The recipient must… | Sender also needs |
|
|
501
504
|
|---|---|---|
|
|
502
|
-
| **EVM · Solana · Sui · Tron** | nothing (just be a valid address; Solana's token account is auto-created by the SDK) | native gas |
|
|
505
|
+
| **EVM · Solana · Sui · Aptos · Tron** | nothing (just be a valid address; Solana's token account is auto-created by the SDK; Aptos's primary FA store auto-creates) | native gas |
|
|
503
506
|
| **TON** | nothing for native; a jetton wallet auto-deploys on first receipt (sender pays the gas) | TON for gas |
|
|
504
507
|
| **NEAR** | nothing for native; for a token, be `storage_deposit`-registered on it (NEP-145, ~0.00125 NEAR, one-time) | NEAR for gas |
|
|
505
508
|
| **Stellar** | exist (created with ≥1 XLM base reserve); for USDC/EURC, hold a **trustline** (+0.5 XLM each) | base + trustline reserves |
|
|
506
509
|
| **XRP Ledger** | be **activated** — hold ≥1 XRP base reserve to exist; for USDC/RLUSD, a **trustline** | keep its own 1 XRP reserve |
|
|
510
|
+
| **Algorand** | nothing for native ALGO; for USDC, **opt into the ASA** once (a 0-amount self-transfer, ~0.1 ALGO min-balance bump) | ALGO for fees + its own opt-in |
|
|
507
511
|
|
|
508
512
|
> These are anti-spam "state rent" rules built into each ledger — e.g. an XRPL account can't receive a sub-1-XRP first payment because that payment must create the account at its ≥1 XRP base reserve. PipRail surfaces them as `RECIPIENT_NOT_READY` with the fix, so a payment that "can't go through" is self-explanatory. Per-chain specifics live in **[CHAINS.md](./CHAINS.md)**.
|
|
509
513
|
|
|
@@ -526,7 +530,7 @@ The full standard every module follows is **[ERRORS.md](./ERRORS.md)**.
|
|
|
526
530
|
|---|---|---|
|
|
527
531
|
| `chain` | — | `'base'` / `'bnb'` / `'solana'` / `'ton'` / …, a viem `Chain`, or `{ id, rpcUrl }` (single-chain form) |
|
|
528
532
|
| `amount` | — | Human-readable, e.g. `'0.05'` (single-chain form) |
|
|
529
|
-
| `token` | — | `'USDC'` / `'USDT'`, `'native'`, or a custom `{ address, decimals }` (EVM/Tron) / `{ mint, decimals }` (Solana) / `{ master, decimals }` (TON) / `{ issuer, code, decimals }` (Stellar) / `{ issuer, currencyHex, decimals }` (XRPL) / `{ contractId, decimals }` (NEAR) / `{ coinType, decimals }` (Sui) — required for the single form |
|
|
533
|
+
| `token` | — | `'USDC'` / `'USDT'`, `'native'`, or a custom `{ address, decimals }` (EVM/Tron) / `{ mint, decimals }` (Solana) / `{ master, decimals }` (TON) / `{ issuer, code, decimals }` (Stellar) / `{ issuer, currencyHex, decimals }` (XRPL) / `{ contractId, decimals }` (NEAR) / `{ coinType, decimals }` (Sui) / `{ metadata, decimals }` (Aptos) / `{ assetId, decimals }` (Algorand) — required for the single form |
|
|
530
534
|
| `accept` | — | Multi-chain form: `[{ chain, token, amount, payTo?, rpcUrl? }, …]` — offer several chains in one challenge |
|
|
531
535
|
| `payTo` | — | Wallet that receives the payment (per-option fallback in the multi form) |
|
|
532
536
|
| `description` | — | Optional text shown to the agent in the challenge (what the payment is for) |
|
|
@@ -566,6 +570,8 @@ Methods: `fetch` · `get` · `post` (return the gated `Response` after settlemen
|
|
|
566
570
|
| Tron | `{ privateKey }` (32-byte hex — secp256k1) |
|
|
567
571
|
| NEAR | `{ accountId, privateKey }` (privateKey = ed25519:… secret) |
|
|
568
572
|
| Sui | `{ privateKey }` (suiprivkey1… bech32) or `{ keypair }` |
|
|
573
|
+
| Aptos | `{ privateKey }` (ed25519-priv-0x… AIP-80) or `{ account }` |
|
|
574
|
+
| Algorand | `{ mnemonic }` (25 words) or `{ account }` (algosdk `{ addr, sk }`) |
|
|
569
575
|
|
|
570
576
|
**Hand an LLM a wallet:** `paymentTools(client)` → framework-agnostic tool descriptors (MCP / AI SDK / function-calling), budget enforced by the client.
|
|
571
577
|
|
|
@@ -576,7 +582,7 @@ Methods: `fetch` · `get` · `post` (return the gated `Response` after settlemen
|
|
|
576
582
|
## Requirements
|
|
577
583
|
|
|
578
584
|
- Node 20+ or a modern browser.
|
|
579
|
-
- `viem ^2.21` (peer dep). Solana: `@solana/web3.js`, `@solana/spl-token`, `bs58` (optional peers). TON: `@ton/ton`, `@ton/core`, `@ton/crypto` (optional peers). Stellar: `@stellar/stellar-sdk` (optional peer). XRPL: `xrpl` (optional peer). Tron: `tronweb` (optional peer). NEAR: `near-api-js` (optional peer). Sui: `@mysten/sui` (optional peer).
|
|
585
|
+
- `viem ^2.21` (peer dep). Solana: `@solana/web3.js`, `@solana/spl-token`, `bs58` (optional peers). TON: `@ton/ton`, `@ton/core`, `@ton/crypto` (optional peers). Stellar: `@stellar/stellar-sdk` (optional peer). XRPL: `xrpl` (optional peer). Tron: `tronweb` (optional peer). NEAR: `near-api-js` (optional peer). Sui: `@mysten/sui` (optional peer). Aptos: `@aptos-labs/ts-sdk` (optional peer). Algorand: `algosdk` (optional peer).
|
|
580
586
|
|
|
581
587
|
## License & trademark
|
|
582
588
|
|
|
@@ -0,0 +1,363 @@
|
|
|
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(); } }
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
var _chunkIQGT65WScjs = require('./chunk-IQGT65WS.cjs');
|
|
11
|
+
|
|
12
|
+
// src/drivers/algorand/index.ts
|
|
13
|
+
var _algosdk = require('algosdk'); var _algosdk2 = _interopRequireDefault(_algosdk);
|
|
14
|
+
|
|
15
|
+
// src/drivers/algorand/chains.ts
|
|
16
|
+
var ALGO_DECIMALS = 6;
|
|
17
|
+
var ALGO_SYMBOL = "ALGO";
|
|
18
|
+
var ALGORAND_MAINNET = {
|
|
19
|
+
caip2: "algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73k",
|
|
20
|
+
defaultAlgod: "https://mainnet-api.algonode.cloud",
|
|
21
|
+
defaultIndexer: "https://mainnet-idx.algonode.cloud",
|
|
22
|
+
tokens: {
|
|
23
|
+
// Circle USDC — ASA id + 6 decimals verified live on mainnet algod
|
|
24
|
+
// (/v2/assets/31566704 → unit-name "USDC", decimals 6, creator = Circle) before shipping.
|
|
25
|
+
// USDC-only: Tether deprecated USDT on Algorand, so it's intentionally omitted.
|
|
26
|
+
USDC: { assetId: 31566704, decimals: 6, symbol: "USDC" }
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
function algorandAssetId(assetId) {
|
|
30
|
+
return String(assetId);
|
|
31
|
+
}
|
|
32
|
+
function parseAlgorandAssetId(asset) {
|
|
33
|
+
if (asset === "native") return null;
|
|
34
|
+
if (!/^\d+$/.test(asset)) return null;
|
|
35
|
+
const n = Number(asset);
|
|
36
|
+
return Number.isSafeInteger(n) && n > 0 ? n : null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/drivers/algorand/pay.ts
|
|
40
|
+
async function payAlgorand(params) {
|
|
41
|
+
const { client, sk, sender, accept } = params;
|
|
42
|
+
const note = new TextEncoder().encode(accept.extra.nonce);
|
|
43
|
+
const amount = BigInt(accept.amount);
|
|
44
|
+
const assetId = parseAlgorandAssetId(accept.asset);
|
|
45
|
+
try {
|
|
46
|
+
const { txn, txId } = await client.build({
|
|
47
|
+
sender,
|
|
48
|
+
receiver: accept.payTo,
|
|
49
|
+
amount,
|
|
50
|
+
note,
|
|
51
|
+
...assetId === null ? {} : { assetId }
|
|
52
|
+
});
|
|
53
|
+
await client.signSend({ txn, sk });
|
|
54
|
+
return txId;
|
|
55
|
+
} catch (err) {
|
|
56
|
+
const mapped = mapAlgorandError(err, accept.payTo);
|
|
57
|
+
if (mapped) throw mapped;
|
|
58
|
+
throw _nullishCoalesce(_chunkIQGT65WScjs.toInsufficientFundsError.call(void 0, err), () => ( err));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function mapAlgorandError(err, payTo) {
|
|
62
|
+
const m = err instanceof Error ? err.message : String(err);
|
|
63
|
+
if (/must optin/i.test(m) || /missing from/i.test(m) && m.includes(payTo)) {
|
|
64
|
+
return new (0, _chunkIQGT65WScjs.RecipientNotReadyError)(
|
|
65
|
+
`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)})`,
|
|
66
|
+
{ cause: err }
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
if (/overspend|below min|min(imum)? balance|tried to spend|balance \d+ below|asset \d+ missing from|insufficient|underflow/i.test(
|
|
70
|
+
m
|
|
71
|
+
)) {
|
|
72
|
+
return new (0, _chunkIQGT65WScjs.InsufficientFundsError)(
|
|
73
|
+
`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)})`,
|
|
74
|
+
{ cause: err }
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
function firstLine(message) {
|
|
80
|
+
return message.split("\n")[0].slice(0, 160);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// src/drivers/algorand/verify.ts
|
|
84
|
+
async function verifyAlgorand(params) {
|
|
85
|
+
const { reader, accept } = params;
|
|
86
|
+
const nonce = accept.extra.nonce;
|
|
87
|
+
const required = BigInt(accept.amount);
|
|
88
|
+
const wantAssetId = parseAlgorandAssetId(accept.asset);
|
|
89
|
+
let txs;
|
|
90
|
+
try {
|
|
91
|
+
txs = await reader.transactionsForAccount(accept.payTo, 50);
|
|
92
|
+
} catch (e) {
|
|
93
|
+
return rpcFailed(nonce);
|
|
94
|
+
}
|
|
95
|
+
const tx = txs.find((t) => typeof t.note === "string" && t.note === nonce);
|
|
96
|
+
if (!tx) return notFound(nonce);
|
|
97
|
+
if (typeof tx.roundTime === "number") {
|
|
98
|
+
const ageSeconds = Math.floor(Date.now() / 1e3) - tx.roundTime;
|
|
99
|
+
if (Number.isFinite(ageSeconds) && ageSeconds > accept.maxTimeoutSeconds) {
|
|
100
|
+
return {
|
|
101
|
+
ok: false,
|
|
102
|
+
error: "payment_expired",
|
|
103
|
+
detail: `Payment is ${ageSeconds}s old; max allowed is ${accept.maxTimeoutSeconds}s.`
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const isNative = wantAssetId === null;
|
|
108
|
+
const typeOk = isNative ? tx.txType === "pay" : tx.txType === "axfer";
|
|
109
|
+
const assetOk = isNative ? tx.assetId == null : tx.assetId === wantAssetId;
|
|
110
|
+
if (!typeOk || tx.receiver !== accept.payTo || !assetOk) {
|
|
111
|
+
return {
|
|
112
|
+
ok: false,
|
|
113
|
+
error: "transfer_not_found",
|
|
114
|
+
detail: `Algorand tx ${tx.id} carries our nonce but has no matching ${isNative ? "ALGO" : `ASA ${wantAssetId}`} transfer to ${accept.payTo}.`
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
let paid = 0n;
|
|
118
|
+
try {
|
|
119
|
+
paid = tx.amount ? BigInt(tx.amount) : 0n;
|
|
120
|
+
} catch (e2) {
|
|
121
|
+
paid = 0n;
|
|
122
|
+
}
|
|
123
|
+
if (paid < required) {
|
|
124
|
+
return { ok: false, error: "amount_too_low", detail: `Paid ${paid}, required ${required}.` };
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
ok: true,
|
|
128
|
+
receipt: {
|
|
129
|
+
scheme: "onchain-proof",
|
|
130
|
+
success: true,
|
|
131
|
+
network: accept.network,
|
|
132
|
+
transaction: tx.id,
|
|
133
|
+
asset: accept.asset,
|
|
134
|
+
amount: accept.amount,
|
|
135
|
+
payer: _nullishCoalesce(tx.sender, () => ( "")),
|
|
136
|
+
payTo: accept.payTo,
|
|
137
|
+
verifiedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function notFound(nonce) {
|
|
142
|
+
return {
|
|
143
|
+
ok: false,
|
|
144
|
+
error: "transfer_not_found",
|
|
145
|
+
detail: `No matching Algorand payment found for nonce ${nonce} (not yet settled, or wrong recipient/amount/asset/note).`
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function rpcFailed(nonce) {
|
|
149
|
+
return {
|
|
150
|
+
ok: false,
|
|
151
|
+
error: "tx_not_found",
|
|
152
|
+
detail: `Could not read the Algorand indexer for nonce ${nonce} (transient RPC failure) \u2014 retry.`
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// src/drivers/algorand/wallet.ts
|
|
157
|
+
|
|
158
|
+
function assertAlgorandWallet(wallet, network) {
|
|
159
|
+
if (typeof wallet !== "object" || wallet === null) {
|
|
160
|
+
throw new (0, _chunkIQGT65WScjs.WrongFamilyError)(
|
|
161
|
+
`chain ${network} is Algorand; wallet must be { mnemonic } (25 words) or { account }.`
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
if ("privateKey" in wallet || "walletClient" in wallet) {
|
|
165
|
+
throw new (0, _chunkIQGT65WScjs.WrongFamilyError)(
|
|
166
|
+
`chain ${network} is Algorand; an EVM/Aptos wallet can't be used \u2014 pass { mnemonic } (25 words) or { account }.`
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
if ("secretKey" in wallet || "signer" in wallet || "secret" in wallet || "keypair" in wallet || "keyPair" in wallet || "seed" in wallet || "accountId" in wallet) {
|
|
170
|
+
throw new (0, _chunkIQGT65WScjs.WrongFamilyError)(
|
|
171
|
+
`chain ${network} is Algorand; that looks like another family's wallet \u2014 pass { mnemonic } (25 words) or { account }.`
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
if (!("mnemonic" in wallet) && !("account" in wallet)) {
|
|
175
|
+
throw new (0, _chunkIQGT65WScjs.WrongFamilyError)(
|
|
176
|
+
`chain ${network} is Algorand; wallet must be { mnemonic } (25 words) or { account }.`
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
return wallet;
|
|
180
|
+
}
|
|
181
|
+
function resolveAlgorandWallet(config) {
|
|
182
|
+
if (config.account) {
|
|
183
|
+
return { addr: String(config.account.addr), sk: config.account.sk };
|
|
184
|
+
}
|
|
185
|
+
if (config.mnemonic != null) {
|
|
186
|
+
try {
|
|
187
|
+
const { addr, sk } = _algosdk2.default.mnemonicToSecretKey(config.mnemonic);
|
|
188
|
+
return { addr: addr.toString(), sk };
|
|
189
|
+
} catch (cause) {
|
|
190
|
+
throw new (0, _chunkIQGT65WScjs.WrongFamilyError)(
|
|
191
|
+
"Algorand wallet { mnemonic } is not a valid 25-word Algorand mnemonic.",
|
|
192
|
+
{ cause }
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
throw new (0, _chunkIQGT65WScjs.WrongFamilyError)("Algorand wallet needs { mnemonic } (25 words) or { account }.");
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// src/drivers/algorand/index.ts
|
|
200
|
+
var algorandDriver = {
|
|
201
|
+
family: "algorand",
|
|
202
|
+
resolve(opts) {
|
|
203
|
+
if (opts.chain !== "algorand") return null;
|
|
204
|
+
const algodUrl = _nullishCoalesce(opts.rpcUrl, () => ( ALGORAND_MAINNET.defaultAlgod));
|
|
205
|
+
return makeAlgorandNetwork(ALGORAND_MAINNET, algodUrl);
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
function makeAlgorandNetwork(preset, algodUrl) {
|
|
209
|
+
const algod = new _algosdk2.default.Algodv2("", algodUrl, "");
|
|
210
|
+
const indexer = new _algosdk2.default.Indexer("", preset.defaultIndexer, "");
|
|
211
|
+
const network = preset.caip2;
|
|
212
|
+
const reader = {
|
|
213
|
+
async transactionsForAccount(account, limit) {
|
|
214
|
+
const res = await indexer.lookupAccountTransactions(account).limit(limit).do();
|
|
215
|
+
return (_nullishCoalesce(res.transactions, () => ( []))).map((t) => adaptTxn(t)).filter((r) => r !== null);
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
const payClient = {
|
|
219
|
+
async build(transfer) {
|
|
220
|
+
const suggestedParams = await algod.getTransactionParams().do();
|
|
221
|
+
const common = {
|
|
222
|
+
sender: transfer.sender,
|
|
223
|
+
receiver: transfer.receiver,
|
|
224
|
+
amount: transfer.amount,
|
|
225
|
+
note: transfer.note,
|
|
226
|
+
suggestedParams
|
|
227
|
+
};
|
|
228
|
+
const txn = transfer.assetId === void 0 ? _algosdk2.default.makePaymentTxnWithSuggestedParamsFromObject(common) : _algosdk2.default.makeAssetTransferTxnWithSuggestedParamsFromObject({
|
|
229
|
+
...common,
|
|
230
|
+
assetIndex: transfer.assetId
|
|
231
|
+
});
|
|
232
|
+
return { txn, txId: txn.txID() };
|
|
233
|
+
},
|
|
234
|
+
async signSend({ txn, sk }) {
|
|
235
|
+
const signed = txn.signTxn(sk);
|
|
236
|
+
await algod.sendRawTransaction(signed).do();
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
return {
|
|
240
|
+
family: "algorand",
|
|
241
|
+
network,
|
|
242
|
+
supports: (n) => n === network,
|
|
243
|
+
resolveToken(token) {
|
|
244
|
+
if (token === "native") {
|
|
245
|
+
return { asset: "native", decimals: ALGO_DECIMALS, symbol: ALGO_SYMBOL };
|
|
246
|
+
}
|
|
247
|
+
if (typeof token === "string") {
|
|
248
|
+
const info = preset.tokens[token.toUpperCase()];
|
|
249
|
+
if (!info) {
|
|
250
|
+
const known = Object.keys(preset.tokens).join(", ") || "(none built in)";
|
|
251
|
+
throw new (0, _chunkIQGT65WScjs.UnknownTokenError)(
|
|
252
|
+
`token "${token}" isn't built in for Algorand (known: ${known}). Pass { assetId, decimals } for a custom ASA, or use 'native'.`
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
return { asset: algorandAssetId(info.assetId), decimals: info.decimals, symbol: info.symbol };
|
|
256
|
+
}
|
|
257
|
+
_chunkIQGT65WScjs.rejectForeignToken.call(void 0, token, "algorand", network);
|
|
258
|
+
const t = token;
|
|
259
|
+
if (typeof t.assetId !== "number" || typeof t.decimals !== "number") {
|
|
260
|
+
throw new (0, _chunkIQGT65WScjs.WrongFamilyError)(
|
|
261
|
+
`chain ${network} is Algorand; a custom token must be { assetId, decimals }.`
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
return {
|
|
265
|
+
asset: algorandAssetId(t.assetId),
|
|
266
|
+
decimals: t.decimals,
|
|
267
|
+
...t.symbol ? { symbol: t.symbol } : {}
|
|
268
|
+
};
|
|
269
|
+
},
|
|
270
|
+
describeAsset(asset) {
|
|
271
|
+
if (asset === "native") return { symbol: ALGO_SYMBOL, decimals: ALGO_DECIMALS };
|
|
272
|
+
for (const info of Object.values(preset.tokens)) {
|
|
273
|
+
if (algorandAssetId(info.assetId) === asset) {
|
|
274
|
+
return { symbol: info.symbol, decimals: info.decimals };
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return null;
|
|
278
|
+
},
|
|
279
|
+
assertValidPayTo(payTo) {
|
|
280
|
+
if (payTo.startsWith("0x")) {
|
|
281
|
+
throw new (0, _chunkIQGT65WScjs.WrongFamilyError)(
|
|
282
|
+
`chain ${network} is Algorand, but payTo "${payTo}" looks like an EVM address.`
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
if (!_algosdk2.default.isValidAddress(payTo)) {
|
|
286
|
+
throw new (0, _chunkIQGT65WScjs.WrongFamilyError)(
|
|
287
|
+
`chain ${network} is Algorand, but payTo "${payTo}" is not a valid Algorand address.`
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
},
|
|
291
|
+
bindWallet(wallet) {
|
|
292
|
+
return { _native: assertAlgorandWallet(wallet, network) };
|
|
293
|
+
},
|
|
294
|
+
async send(wallet, accept) {
|
|
295
|
+
const signer = resolveAlgorandWallet(wallet._native);
|
|
296
|
+
return payAlgorand({ client: payClient, sk: signer.sk, sender: signer.addr, accept });
|
|
297
|
+
},
|
|
298
|
+
async confirm(ref) {
|
|
299
|
+
try {
|
|
300
|
+
const info = await _algosdk2.default.waitForConfirmation(algod, ref, 10);
|
|
301
|
+
return { height: String(_nullishCoalesce(info.confirmedRound, () => ( 0))) };
|
|
302
|
+
} catch (err) {
|
|
303
|
+
throw new (0, _chunkIQGT65WScjs.ConfirmationTimeoutError)(`Algorand tx ${ref} did not confirm in time.`, {
|
|
304
|
+
cause: err
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
},
|
|
308
|
+
async estimateCost() {
|
|
309
|
+
return _chunkIQGT65WScjs.nativeCost.call(void 0, {
|
|
310
|
+
symbol: ALGO_SYMBOL,
|
|
311
|
+
decimals: ALGO_DECIMALS,
|
|
312
|
+
fee: 1000n,
|
|
313
|
+
basis: "heuristic",
|
|
314
|
+
detail: "min fee 1000 \xB5Algos (1 transaction)"
|
|
315
|
+
});
|
|
316
|
+
},
|
|
317
|
+
async verify(_ref, accept) {
|
|
318
|
+
return verifyAlgorand({ reader, accept });
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
function adaptTxn(raw) {
|
|
323
|
+
const t = raw;
|
|
324
|
+
if (!t || typeof t.id !== "string") return null;
|
|
325
|
+
const note = t.note && t.note.length ? decodeNote(t.note) : void 0;
|
|
326
|
+
const base = {
|
|
327
|
+
id: t.id,
|
|
328
|
+
txType: String(_nullishCoalesce(t.txType, () => ( ""))),
|
|
329
|
+
...note !== void 0 ? { note } : {},
|
|
330
|
+
...t.sender != null ? { sender: String(t.sender) } : {},
|
|
331
|
+
...typeof t.roundTime === "number" ? { roundTime: t.roundTime } : {}
|
|
332
|
+
};
|
|
333
|
+
if (t.paymentTransaction) {
|
|
334
|
+
const pt = t.paymentTransaction;
|
|
335
|
+
return {
|
|
336
|
+
...base,
|
|
337
|
+
txType: "pay",
|
|
338
|
+
...pt.receiver != null ? { receiver: String(pt.receiver) } : {},
|
|
339
|
+
...pt.amount != null ? { amount: String(pt.amount) } : {}
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
if (t.assetTransferTransaction) {
|
|
343
|
+
const att = t.assetTransferTransaction;
|
|
344
|
+
return {
|
|
345
|
+
...base,
|
|
346
|
+
txType: "axfer",
|
|
347
|
+
...att.receiver != null ? { receiver: String(att.receiver) } : {},
|
|
348
|
+
...att.amount != null ? { amount: String(att.amount) } : {},
|
|
349
|
+
...att.assetId != null ? { assetId: Number(att.assetId) } : {}
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
return base;
|
|
353
|
+
}
|
|
354
|
+
function decodeNote(bytes) {
|
|
355
|
+
try {
|
|
356
|
+
return new TextDecoder().decode(bytes);
|
|
357
|
+
} catch (e3) {
|
|
358
|
+
return void 0;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
exports.algorandDriver = algorandDriver;
|