@piprail/sdk 1.4.0 → 1.5.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 +36 -0
- package/ERRORS.md +11 -0
- package/README.md +31 -3
- package/dist/{algorand-XJ5OVWQB.js → algorand-B67G4335.js} +34 -0
- package/dist/{algorand-IDFUG5CI.cjs → algorand-IJJKE35X.cjs} +38 -4
- package/dist/{aptos-TPSOQ2VL.cjs → aptos-X3G2UBYW.cjs} +28 -0
- package/dist/{aptos-AWWSCPDH.js → aptos-YQWTGFRZ.js} +28 -0
- package/dist/index.cjs +307 -29
- package/dist/index.d.cts +152 -1
- package/dist/index.d.ts +152 -1
- package/dist/index.js +295 -17
- package/dist/{near-NOJTO4GX.js → near-7MBBCDUE.js} +50 -0
- package/dist/{near-KDA5DPTX.cjs → near-GGUHLXAF.cjs} +57 -7
- package/dist/{solana-HNRTS4KM.js → solana-7WJVZGDW.js} +22 -0
- package/dist/{solana-DVA6I55L.cjs → solana-W24TCJV4.cjs} +25 -3
- package/dist/{stellar-4TDVVJYO.js → stellar-HV6VGZX3.js} +50 -0
- package/dist/{stellar-4D5EWT3V.cjs → stellar-YMY3K2YB.cjs} +50 -0
- package/dist/{sui-ALUTM5GX.js → sui-2WFWVFJX.js} +23 -0
- package/dist/{sui-5HMIHOZK.cjs → sui-32KVESR5.cjs} +23 -0
- package/dist/{ton-3XMIM2FU.js → ton-DGZB7W4U.js} +23 -0
- package/dist/{ton-TVK4TEDX.cjs → ton-FIQGV2LC.cjs} +23 -0
- package/dist/{tron-6D65YJEU.js → tron-RLIL2FDI.js} +28 -0
- package/dist/{tron-Y5RZJZRT.cjs → tron-ZSXAPZ2C.cjs} +28 -0
- package/dist/{xrpl-ICO6G7UK.cjs → xrpl-2PKP7HOI.cjs} +61 -1
- package/dist/{xrpl-ISFG3SSN.js → xrpl-UEC2GYVV.js} +60 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,42 @@ 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.5.0] — 2026-06-04
|
|
8
|
+
|
|
9
|
+
**The killer agent feature — `client.planPayment(url)`.** A read-only call that surveys a 402
|
|
10
|
+
across every rail it offers *on your chain* against your wallet's OWN holdings — **token balance +
|
|
11
|
+
native gas + recipient-readiness** (trustline / ATA / storage_deposit / ASA opt-in / activation) —
|
|
12
|
+
and tells you, crystal-clear, whether it's settleable, on which rail, and if not, exactly what to
|
|
13
|
+
top up. It completes the trio the SDK already ships: **`quote()` (what it costs) → `estimateCost()`
|
|
14
|
+
(the gas) → `planPayment()` (can I actually settle, and where).** Fully backward-compatible and
|
|
15
|
+
opt-in; defaults are unchanged. The official x402 client picks `accepts[0]` blind; PipRail is the
|
|
16
|
+
only backendless SDK that can answer "can I actually pay this?" across 28 chains with pure RPC
|
|
17
|
+
reads, no oracle/facilitator/bridge. Live-proven on Algorand mainnet (ready / recipient-not-ready /
|
|
18
|
+
insufficient / multi-rail-rank, 4/4).
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
- **`client.planPayment(url, init?)` → `PaymentPlan | null`.** Never throws for a read problem (a
|
|
22
|
+
transient/RPC failure surfaces as a rail in `state: 'unknown'` + a warning, never a false
|
|
23
|
+
"unaffordable"); returns `null` when the URL isn't 402-gated; and when the 402 offers no rail on
|
|
24
|
+
your chain it EXPLAINS that (status `blocked` + a hint) instead of throwing. The plan carries:
|
|
25
|
+
`payable` + `best` (the cheapest settleable rail), `options[]` (every rail with typed `blockers`
|
|
26
|
+
— `INSUFFICIENT_TOKEN`/`INSUFFICIENT_GAS`/`RECIPIENT_NOT_READY`/`OUTSIDE_POLICY` — plus soft
|
|
27
|
+
`warnings`, a `shortfall`, live `balance`, and `recipient.fix`), and a one-sentence `fundingHint`.
|
|
28
|
+
- **`client.canAfford(url)` → `boolean`** — convenience over the above.
|
|
29
|
+
- **`fetch(url, { autoRoute: true })` / `new PipRailClient({ autoRoute: true })`** — opt-in:
|
|
30
|
+
`fetch` pays the cheapest rail the wallet can ACTUALLY settle (not the first policy-passing one),
|
|
31
|
+
or throws `PaymentDeclinedError` carrying the funding hint before any send. **Default off** —
|
|
32
|
+
the zero-config path is byte-identical.
|
|
33
|
+
- **`planAcross(clients, url)`** — the cross-chain brain: give it one client per chain you fund and
|
|
34
|
+
it merges their plans, payable-first (no oracle, so the cross-coin tiebreak is your client order).
|
|
35
|
+
- **`piprail_plan_payment`** agent tool (budget-bound; `paymentTools(client)` now returns 3 tools).
|
|
36
|
+
- **Driver contract:** `balanceOf(wallet, asset)` + `recipientReady(payTo, asset)` on every family
|
|
37
|
+
(10/10), RPC-read-only and NEVER-throw (transient ⇒ `null`/`'unknown'`, per ERRORS.md §5). Real
|
|
38
|
+
receive-prerequisite probes on NEAR (`storage_balance_of`), Stellar/XRPL (trustline presence),
|
|
39
|
+
Algorand (ASA opt-in); truthful `'n/a'` on EVM/Solana/TON/Tron/Sui/Aptos (no prerequisite).
|
|
40
|
+
- New exported types: `PaymentPlan`, `PayOption`, `PayBlocker`, `PayWarning`, `RecipientReason`,
|
|
41
|
+
`WalletBalance` (and the previously-missing `AptosToken`/`AlgorandToken`).
|
|
42
|
+
|
|
7
43
|
## [1.4.0] — 2026-06-04
|
|
8
44
|
|
|
9
45
|
A new chain **family** — **Algorand** — the **10th driver family**, bringing the built-in count to
|
package/ERRORS.md
CHANGED
|
@@ -149,6 +149,17 @@ Every `PaymentDriver` / `ResolvedNetwork` method has a fixed error behaviour:
|
|
|
149
149
|
| `send(wallet, accept)` | wrap the broadcast; map **sender** affordability → `InsufficientFundsError` (§6) and **recipient** setup → `RecipientNotReadyError` (§6.1); **rethrow everything else unchanged** (never swallow). Every mapped throw carries `{ cause }` = the raw chain error. |
|
|
150
150
|
| `verify(ref, accept)` | **return** a `VerifyResult` with a canonical `VerifyErrorCode`. **Guard every RPC read** so a transient failure returns `tx_not_found` — `verify()` must not throw for an RPC hiccup. Re-derive the watched account from the trusted `accept`, never the client ref. |
|
|
151
151
|
| `confirm(ref, n)` | broadcast-but-not-confirmed / timeout → `ConfirmationTimeoutError`. |
|
|
152
|
+
| `estimateCost(accept, opts?)` | **never throw** — guard the RPC read and fall back to a `'heuristic'` constant; always return a valid `CostEstimate`. |
|
|
153
|
+
| `balanceOf(wallet, asset)` | **never throw** — RPC-read-only. A field whose read was unavailable (transient/rate-limit) returns `null`, NOT `0` (a false 0 reads as "broke"). For `asset==='native'`, `token === native`. |
|
|
154
|
+
| `recipientReady(payTo, asset)` | **never throw** — report the receive prerequisite: `{ ready:'n/a' }` (no prerequisite on this family/native), `{ ready:true }`, `{ ready:false, reason }` (a `RecipientReason`), or `{ ready:'unknown' }` on a transient read. `'n/a'` must be TRUTHFUL — never a stand-in for "didn't check". |
|
|
155
|
+
|
|
156
|
+
> **`planPayment` is a RETURN-channel feature.** The client's `planPayment`/`canAfford` compose
|
|
157
|
+
> `balanceOf` + `recipientReady` + `estimateCost` + the policy verdict into a `PaymentPlan` — and,
|
|
158
|
+
> like `verify()`, they **return** the outcome rather than throwing: a transient read becomes a rail
|
|
159
|
+
> in `state:'unknown'` (+ a warning), an unsettleable rail carries typed `blockers`, and a 402 with
|
|
160
|
+
> no rail on the client's chain is *explained* in the plan. The only throw is `InvalidEnvelopeError`
|
|
161
|
+
> on an unparseable challenge. (`fetch({ autoRoute:true })` is the one place a plan turns into a
|
|
162
|
+
> THROWN `PaymentDeclinedError` — refusing before any send when nothing is settleable.)
|
|
152
163
|
|
|
153
164
|
### 6. Affordability converges on one error, by two mechanisms
|
|
154
165
|
|
package/README.md
CHANGED
|
@@ -77,13 +77,41 @@ client.spent() // → { count, byAsset: [{ symbol:'USDC', totalFormatted:'0.05',
|
|
|
77
77
|
|
|
78
78
|
**Know the gas before you pay.** `client.estimateCost(url)` returns the quote **and** a `CostEstimate` — the network fee in the chain's **native coin** (you pay in USDC but burn ETH / SOL / TON / XLM / XRP / TRX on gas, a separate balance the agent must keep topped up). It's best-effort and labelled (`cost.basis`): a live-RPC read where cheap (`'estimated'` — EVM gas price, XRPL fee), a typical-cost constant otherwise (`'heuristic'`), and it never throws. Most valuable on **Tron**, where a USD₮ transfer can cost real TRX. So an agent can budget the *total* — payment **+** gas — before any funds move. Every driver implements it; the math is extracted per-chain and shaped uniformly by one shared `nativeCost()` helper.
|
|
79
79
|
|
|
80
|
+
### Plan before you pay — `planPayment()` (never fumble a payment)
|
|
81
|
+
|
|
82
|
+
`quote()` tells you the price and `estimateCost()` the gas — **`planPayment(url)`** closes the loop: **one read-only call** that checks, against your wallet's *own* holdings, whether a 402 will actually go through — and if not, exactly what to fix. No funds move.
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
const plan = await client.planPayment(url)
|
|
86
|
+
|
|
87
|
+
if (plan?.payable) {
|
|
88
|
+
await client.fetch(url, { autoRoute: true }) // pays plan.best — the cheapest rail you can settle
|
|
89
|
+
} else {
|
|
90
|
+
console.log(plan?.fundingHint)
|
|
91
|
+
// "Have the USDC, but need ~0.000021 ETH for gas on base (have 0)."
|
|
92
|
+
// "Recipient 2OT6…GC5E4 can't receive on algorand yet — must opt into the USDC ASA."
|
|
93
|
+
// "Top up 0.04 USDC on base (have 0.01)."
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
For **every rail the 402 offers on your chain**, the plan reads **token balance + native-coin gas + recipient-readiness** (trustline / ATA / `storage_deposit` / ASA opt-in / activation) and returns:
|
|
98
|
+
- **`payable`** + **`best`** — the cheapest rail you can actually settle (recipient confirmed able to receive);
|
|
99
|
+
- **`options[]`** — each rail with typed **`blockers`** (`INSUFFICIENT_TOKEN` · `INSUFFICIENT_GAS` · `RECIPIENT_NOT_READY` · `OUTSIDE_POLICY`), soft **`warnings`** (`SYMBOL_MISMATCH`, `THIN_GAS_MARGIN`, `BALANCE_UNREADABLE`, …), a **`shortfall`**, the live **`balance`**, and **`recipient.fix`**;
|
|
100
|
+
- **`fundingHint`** — one human sentence on exactly what to top up.
|
|
101
|
+
|
|
102
|
+
**Why it's the agent unlock.** The official x402 client picks `accepts[0]` blind and learns it can't pay only when the broadcast reverts (no token, no gas) or the transfer silently strands (recipient not set up to receive). `planPayment` turns those runtime failures into a pre-checked decision — and on the no-facilitator path *you* pay your own gas, so "I hold USDC but no ETH" is a first-class answer, not a crash. It **never throws for a read hiccup** (a throttled RPC surfaces as `state: 'unknown'` + a warning, never a false "broke"), returns `null` when the URL isn't gated, and *explains* "this is offered on solana, base — you're on xrpl" instead of erroring. `client.canAfford(url)` is the one-boolean convenience.
|
|
103
|
+
|
|
104
|
+
**Auto-route (opt-in).** `new PipRailClient({ autoRoute: true })` (or `fetch(url, { autoRoute: true })`) makes `fetch` pay the cheapest *settleable* rail instead of the first policy-passing one — refusing with `PaymentDeclinedError` + the funding hint before any send. **Default off; the zero-config path is unchanged.**
|
|
105
|
+
|
|
106
|
+
**Across chains.** A client is bound to one chain; **`planAcross([baseClient, solanaClient, …], url)`** runs each plan in parallel and merges them payable-first, so an agent holding funds on several chains learns which to use. (No price oracle — cross-coin ties break on the order you list the clients.)
|
|
107
|
+
|
|
80
108
|
### Hand an LLM a budget-bound wallet
|
|
81
109
|
|
|
82
110
|
`paymentTools(client)` returns framework-agnostic tool descriptors (name + description + JSON Schema + `invoke`) — drop them into MCP, the Vercel AI SDK, OpenAI/Anthropic function-calling, or LangChain in a couple of lines. The budget rides on the client, so the model can't overspend.
|
|
83
111
|
|
|
84
112
|
```ts
|
|
85
113
|
import { paymentTools } from '@piprail/sdk'
|
|
86
|
-
const tools = paymentTools(client) // → [piprail_quote_payment, piprail_pay_request]
|
|
114
|
+
const tools = paymentTools(client) // → [piprail_quote_payment, piprail_plan_payment, piprail_pay_request]
|
|
87
115
|
```
|
|
88
116
|
|
|
89
117
|
See [`examples/agent-tools.mjs`](../examples/agent-tools.mjs) for MCP / AI-SDK wiring.
|
|
@@ -477,7 +505,7 @@ The SDK is browser-clean (no Node-only globals in the protocol layer), so a plai
|
|
|
477
505
|
Two layers, one contract. Worth knowing if you're extending the SDK or auditing it.
|
|
478
506
|
|
|
479
507
|
- **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.
|
|
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.
|
|
508
|
+
- **The `PaymentDriver` contract.** `resolve(chain)` → a bound `ResolvedNetwork` exposing `resolveToken` · `describeAsset` · `assertValidPayTo` · `bindWallet` · `send` · `confirm` · `estimateCost` · `balanceOf` · `recipientReady` · `verify`. That's the entire boundary every family implements and the protocol layer ever sees.
|
|
481
509
|
- **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
510
|
- **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
511
|
- **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).
|
|
@@ -556,7 +584,7 @@ Provide **either** `chain` + `token` + `amount` (single) **or** a non-empty `acc
|
|
|
556
584
|
| `retryTimeoutMs` | `30000` | Timeout for the retry leg after broadcast |
|
|
557
585
|
| `onEvent` | — | `(event) => void` observability: `payment-required` · `payment-broadcast` · `payment-confirmed` · `payment-unconfirmed` (broadcast OK, local confirm timed out → deferring to server) · `payment-settled` · `payment-failed` |
|
|
558
586
|
|
|
559
|
-
Methods: `fetch` · `get` · `post` (return the gated `Response` after settlement) · **`quote(url)`** (price without paying → `PipRailQuote \| null`) · **`estimateCost(url)`** (price **+** native-coin gas estimate → `PipRailCostQuote \| null`) · **`spent()`** (per-asset ledger snapshot).
|
|
587
|
+
Methods: `fetch` · `get` · `post` (return the gated `Response` after settlement) · **`quote(url)`** (price without paying → `PipRailQuote \| null`) · **`estimateCost(url)`** (price **+** native-coin gas estimate → `PipRailCostQuote \| null`) · **`planPayment(url)`** (affordability + recipient-readiness across the offered rails → `PaymentPlan \| null`) · **`canAfford(url)`** (→ `boolean`) · **`spent()`** (per-asset ledger snapshot). Pass `{ autoRoute: true }` to `fetch` (or set it on the client) to pay the cheapest *settleable* rail. Module-level **`planAcross(clients, url)`** plans across chains.
|
|
560
588
|
|
|
561
589
|
**Wallets by family** — the `chain` selector routes; each driver validates its own key format (a mismatch throws `WrongFamilyError`):
|
|
562
590
|
|
|
@@ -314,6 +314,40 @@ function makeAlgorandNetwork(preset, algodUrl) {
|
|
|
314
314
|
detail: "min fee 1000 \xB5Algos (1 transaction)"
|
|
315
315
|
});
|
|
316
316
|
},
|
|
317
|
+
async balanceOf(wallet, asset) {
|
|
318
|
+
let owner;
|
|
319
|
+
try {
|
|
320
|
+
owner = resolveAlgorandWallet(wallet._native).addr;
|
|
321
|
+
} catch {
|
|
322
|
+
return { token: null, native: null };
|
|
323
|
+
}
|
|
324
|
+
let info;
|
|
325
|
+
try {
|
|
326
|
+
info = await algod.accountInformation(owner).do();
|
|
327
|
+
} catch {
|
|
328
|
+
return { token: null, native: null };
|
|
329
|
+
}
|
|
330
|
+
const native = info.amount != null ? BigInt(info.amount) : null;
|
|
331
|
+
if (asset === "native") return { token: native, native };
|
|
332
|
+
const assetId = parseAlgorandAssetId(asset);
|
|
333
|
+
const holding = (info.assets ?? []).find((a) => Number(a.assetId) === assetId);
|
|
334
|
+
return { token: holding ? BigInt(holding.amount) : 0n, native };
|
|
335
|
+
},
|
|
336
|
+
async recipientReady(payTo, asset) {
|
|
337
|
+
if (asset === "native") return { ready: "n/a" };
|
|
338
|
+
const assetId = parseAlgorandAssetId(asset);
|
|
339
|
+
if (assetId == null) return { ready: "unknown" };
|
|
340
|
+
try {
|
|
341
|
+
const info = await algod.accountInformation(payTo).do();
|
|
342
|
+
const optedIn = (info.assets ?? []).some((a) => Number(a.assetId) === assetId);
|
|
343
|
+
return optedIn ? { ready: true } : { ready: false, reason: "NOT_OPTED_IN" };
|
|
344
|
+
} catch (e) {
|
|
345
|
+
if (/does not exist|no accounts found|404|account not found/i.test(String(e?.message ?? e))) {
|
|
346
|
+
return { ready: false, reason: "NOT_OPTED_IN" };
|
|
347
|
+
}
|
|
348
|
+
return { ready: "unknown" };
|
|
349
|
+
}
|
|
350
|
+
},
|
|
317
351
|
async verify(_ref, accept) {
|
|
318
352
|
return verifyAlgorand({ reader, accept });
|
|
319
353
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
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(); } }
|
|
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
3
|
|
|
4
4
|
|
|
@@ -89,7 +89,7 @@ async function verifyAlgorand(params) {
|
|
|
89
89
|
let txs;
|
|
90
90
|
try {
|
|
91
91
|
txs = await reader.transactionsForAccount(accept.payTo, 50);
|
|
92
|
-
} catch (
|
|
92
|
+
} catch (e2) {
|
|
93
93
|
return rpcFailed(nonce);
|
|
94
94
|
}
|
|
95
95
|
const tx = txs.find((t) => typeof t.note === "string" && t.note === nonce);
|
|
@@ -117,7 +117,7 @@ async function verifyAlgorand(params) {
|
|
|
117
117
|
let paid = 0n;
|
|
118
118
|
try {
|
|
119
119
|
paid = tx.amount ? BigInt(tx.amount) : 0n;
|
|
120
|
-
} catch (
|
|
120
|
+
} catch (e3) {
|
|
121
121
|
paid = 0n;
|
|
122
122
|
}
|
|
123
123
|
if (paid < required) {
|
|
@@ -314,6 +314,40 @@ function makeAlgorandNetwork(preset, algodUrl) {
|
|
|
314
314
|
detail: "min fee 1000 \xB5Algos (1 transaction)"
|
|
315
315
|
});
|
|
316
316
|
},
|
|
317
|
+
async balanceOf(wallet, asset) {
|
|
318
|
+
let owner;
|
|
319
|
+
try {
|
|
320
|
+
owner = resolveAlgorandWallet(wallet._native).addr;
|
|
321
|
+
} catch (e4) {
|
|
322
|
+
return { token: null, native: null };
|
|
323
|
+
}
|
|
324
|
+
let info;
|
|
325
|
+
try {
|
|
326
|
+
info = await algod.accountInformation(owner).do();
|
|
327
|
+
} catch (e5) {
|
|
328
|
+
return { token: null, native: null };
|
|
329
|
+
}
|
|
330
|
+
const native = info.amount != null ? BigInt(info.amount) : null;
|
|
331
|
+
if (asset === "native") return { token: native, native };
|
|
332
|
+
const assetId = parseAlgorandAssetId(asset);
|
|
333
|
+
const holding = (_nullishCoalesce(info.assets, () => ( []))).find((a) => Number(a.assetId) === assetId);
|
|
334
|
+
return { token: holding ? BigInt(holding.amount) : 0n, native };
|
|
335
|
+
},
|
|
336
|
+
async recipientReady(payTo, asset) {
|
|
337
|
+
if (asset === "native") return { ready: "n/a" };
|
|
338
|
+
const assetId = parseAlgorandAssetId(asset);
|
|
339
|
+
if (assetId == null) return { ready: "unknown" };
|
|
340
|
+
try {
|
|
341
|
+
const info = await algod.accountInformation(payTo).do();
|
|
342
|
+
const optedIn = (_nullishCoalesce(info.assets, () => ( []))).some((a) => Number(a.assetId) === assetId);
|
|
343
|
+
return optedIn ? { ready: true } : { ready: false, reason: "NOT_OPTED_IN" };
|
|
344
|
+
} catch (e) {
|
|
345
|
+
if (/does not exist|no accounts found|404|account not found/i.test(String(_nullishCoalesce(_optionalChain([e, 'optionalAccess', _ => _.message]), () => ( e))))) {
|
|
346
|
+
return { ready: false, reason: "NOT_OPTED_IN" };
|
|
347
|
+
}
|
|
348
|
+
return { ready: "unknown" };
|
|
349
|
+
}
|
|
350
|
+
},
|
|
317
351
|
async verify(_ref, accept) {
|
|
318
352
|
return verifyAlgorand({ reader, accept });
|
|
319
353
|
}
|
|
@@ -354,7 +388,7 @@ function adaptTxn(raw) {
|
|
|
354
388
|
function decodeNote(bytes) {
|
|
355
389
|
try {
|
|
356
390
|
return new TextDecoder().decode(bytes);
|
|
357
|
-
} catch (
|
|
391
|
+
} catch (e6) {
|
|
358
392
|
return void 0;
|
|
359
393
|
}
|
|
360
394
|
}
|
|
@@ -322,6 +322,34 @@ function makeAptosNetwork(preset, rpcUrl) {
|
|
|
322
322
|
detail: "\u22480.001 APT (a simple Fungible-Asset transfer; Aptos gas is sub-cent)"
|
|
323
323
|
});
|
|
324
324
|
},
|
|
325
|
+
async balanceOf(wallet, asset) {
|
|
326
|
+
let owner;
|
|
327
|
+
try {
|
|
328
|
+
owner = resolveAptosAccount(wallet._native).accountAddress.toString();
|
|
329
|
+
} catch (e7) {
|
|
330
|
+
return { token: null, native: null };
|
|
331
|
+
}
|
|
332
|
+
const native = await aptos.getAccountAPTAmount({ accountAddress: owner }).then((n) => BigInt(n)).catch(() => null);
|
|
333
|
+
if (asset === "native") return { token: native, native };
|
|
334
|
+
let token = null;
|
|
335
|
+
try {
|
|
336
|
+
const [bal] = await aptos.view({
|
|
337
|
+
payload: {
|
|
338
|
+
function: "0x1::primary_fungible_store::balance",
|
|
339
|
+
typeArguments: ["0x1::fungible_asset::Metadata"],
|
|
340
|
+
functionArguments: [owner, asset]
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
token = BigInt(String(bal));
|
|
344
|
+
} catch (e8) {
|
|
345
|
+
token = null;
|
|
346
|
+
}
|
|
347
|
+
return { token, native };
|
|
348
|
+
},
|
|
349
|
+
// No receive prerequisite — the recipient's primary FA store auto-creates on receipt.
|
|
350
|
+
async recipientReady() {
|
|
351
|
+
return { ready: "n/a" };
|
|
352
|
+
},
|
|
325
353
|
async verify(ref, accept) {
|
|
326
354
|
return verifyAptos({ reader, hash: ref, accept });
|
|
327
355
|
}
|
|
@@ -322,6 +322,34 @@ function makeAptosNetwork(preset, rpcUrl) {
|
|
|
322
322
|
detail: "\u22480.001 APT (a simple Fungible-Asset transfer; Aptos gas is sub-cent)"
|
|
323
323
|
});
|
|
324
324
|
},
|
|
325
|
+
async balanceOf(wallet, asset) {
|
|
326
|
+
let owner;
|
|
327
|
+
try {
|
|
328
|
+
owner = resolveAptosAccount(wallet._native).accountAddress.toString();
|
|
329
|
+
} catch {
|
|
330
|
+
return { token: null, native: null };
|
|
331
|
+
}
|
|
332
|
+
const native = await aptos.getAccountAPTAmount({ accountAddress: owner }).then((n) => BigInt(n)).catch(() => null);
|
|
333
|
+
if (asset === "native") return { token: native, native };
|
|
334
|
+
let token = null;
|
|
335
|
+
try {
|
|
336
|
+
const [bal] = await aptos.view({
|
|
337
|
+
payload: {
|
|
338
|
+
function: "0x1::primary_fungible_store::balance",
|
|
339
|
+
typeArguments: ["0x1::fungible_asset::Metadata"],
|
|
340
|
+
functionArguments: [owner, asset]
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
token = BigInt(String(bal));
|
|
344
|
+
} catch {
|
|
345
|
+
token = null;
|
|
346
|
+
}
|
|
347
|
+
return { token, native };
|
|
348
|
+
},
|
|
349
|
+
// No receive prerequisite — the recipient's primary FA store auto-creates on receipt.
|
|
350
|
+
async recipientReady() {
|
|
351
|
+
return { ready: "n/a" };
|
|
352
|
+
},
|
|
325
353
|
async verify(ref, accept) {
|
|
326
354
|
return verifyAptos({ reader, hash: ref, accept });
|
|
327
355
|
}
|