@toon-protocol/client 0.14.4 → 0.14.6
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/dist/index.d.ts +74 -0
- package/dist/index.js +267 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,17 @@ import { NostrEvent, EventTemplate } from 'nostr-tools/pure';
|
|
|
5
5
|
import { PrivateKeyAccount } from 'viem/accounts';
|
|
6
6
|
export { A2uiDecision, ConsentDecision, ConsentRequest, DispatchGuardInfo, DispatchInput, GenerateContext, GeneratedRenderer, GenerativeDecision, GenerativeFallbackOptions, GenerativeFallbackRenderer, GenerativeFallbackResult, GuardedDispatchInput, IntentClassification, KindRegistry, MIME_A2UI, MIME_MCP_APP, McpUiDecision, NativeDecision, PublishBackOptions, RenderBranch, RenderDecision, RenderTrust, RendererGenerator, RendererPin, RendererPinStore, RendererPublisher, RendererSigner, ResolvedCoordinate, SwapApproval, SwapDecision, SwapRejection, SwapRejectionReason, UiResource, VerifyRendererInput, WidgetIntent, buildConsentRequest, buildRendererEventTemplate, classifyIntent, deterministicGenerator, extractUiResource, guardedRenderDispatch, isTrustDowngrade, publishBackCoordinate, renderDeterministicHtml, renderDispatch, resolveRendererMime, resolveUiCoordinate, resolveUiRenderer, verifyRendererTrust } from './render/index.js';
|
|
7
7
|
|
|
8
|
+
/** One on-chain wallet token balance. `amount` is base-unit integer, decimal. */
|
|
9
|
+
interface WalletBalance {
|
|
10
|
+
chain: 'evm' | 'solana' | 'mina';
|
|
11
|
+
address: string;
|
|
12
|
+
amount: string;
|
|
13
|
+
/** Token symbol, when resolved (e.g. `'USDC'`, `'MINA'`). */
|
|
14
|
+
asset?: string;
|
|
15
|
+
/** Token decimals, when resolved. */
|
|
16
|
+
assetScale?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
8
19
|
/**
|
|
9
20
|
* Solana payment-channel parameters supplied via `ToonClientConfig.solanaChannel`.
|
|
10
21
|
*
|
|
@@ -965,6 +976,8 @@ declare class ToonClient {
|
|
|
965
976
|
*/
|
|
966
977
|
private minaPrivateKey?;
|
|
967
978
|
private channelManager?;
|
|
979
|
+
/** Concrete on-chain client, kept so deposit/withdraw can reach chain methods. */
|
|
980
|
+
private onChainChannelClient?;
|
|
968
981
|
private readonly peerNegotiations;
|
|
969
982
|
/**
|
|
970
983
|
* Creates a new ToonClient instance.
|
|
@@ -1210,6 +1223,32 @@ declare class ToonClient {
|
|
|
1210
1223
|
* Gets the cumulative transferred amount for a tracked channel.
|
|
1211
1224
|
*/
|
|
1212
1225
|
getChannelCumulativeAmount(channelId: string): bigint;
|
|
1226
|
+
/**
|
|
1227
|
+
* Gets the on-chain deposit total (locked collateral) for a tracked channel.
|
|
1228
|
+
* The available (spendable) balance is this minus the cumulative spent amount.
|
|
1229
|
+
*/
|
|
1230
|
+
getChannelDepositTotal(channelId: string): bigint;
|
|
1231
|
+
/**
|
|
1232
|
+
* Deposit additional collateral into an open channel. `amount` is the delta to
|
|
1233
|
+
* add (base units, decimal string or bigint). The daemon signs its own tx; no
|
|
1234
|
+
* key material leaves the client. Reads the current tracked deposit, performs
|
|
1235
|
+
* the on-chain deposit, updates the tracked total, and returns the new total.
|
|
1236
|
+
* EVM is live; Solana/Mina deposit lands in a follow-up.
|
|
1237
|
+
*/
|
|
1238
|
+
depositToChannel(channelId: string, amount: string | bigint): Promise<{
|
|
1239
|
+
channelId: string;
|
|
1240
|
+
txHash?: string;
|
|
1241
|
+
depositTotal: string;
|
|
1242
|
+
}>;
|
|
1243
|
+
/**
|
|
1244
|
+
* Read the on-chain settlement-token balance of this client's OWN wallet on
|
|
1245
|
+
* each configured chain (EVM token, Solana SPL, native MINA). A free read — no
|
|
1246
|
+
* signing, no payment. Best-effort per chain: a chain whose config is absent or
|
|
1247
|
+
* whose RPC read fails is omitted rather than failing the whole result, so the
|
|
1248
|
+
* wallet view degrades gracefully. Available after `start()` (Solana/Mina keys
|
|
1249
|
+
* are derived there).
|
|
1250
|
+
*/
|
|
1251
|
+
getBalances(): Promise<WalletBalance[]>;
|
|
1213
1252
|
/**
|
|
1214
1253
|
* Resolves an ILP destination address to a peer ID.
|
|
1215
1254
|
* Convention: destination "g.toon.peer1" → peerId "peer1" (last segment).
|
|
@@ -2110,6 +2149,29 @@ declare class OnChainChannelClient implements ConnectorChannelClient {
|
|
|
2110
2149
|
* 4. Deposit initial funds if specified
|
|
2111
2150
|
*/
|
|
2112
2151
|
openChannel(params: OpenChannelParams): Promise<OpenChannelResult>;
|
|
2152
|
+
/**
|
|
2153
|
+
* Deposit additional collateral into an already-open channel. `amount` is the
|
|
2154
|
+
* DELTA to add (base units). Dispatches by the channel's cached chain context;
|
|
2155
|
+
* `currentDeposit` is the channel's current locked total (tracked off-chain by
|
|
2156
|
+
* the caller) — required for EVM, whose `setTotalDeposit` takes the new
|
|
2157
|
+
* cumulative total, not a delta. Returns the new on-chain deposit total.
|
|
2158
|
+
*
|
|
2159
|
+
* Non-custodial: the client deposits its OWN funds and signs its OWN tx.
|
|
2160
|
+
* EVM is live; Solana/Mina deposit extraction is a follow-up (PR B.1).
|
|
2161
|
+
*/
|
|
2162
|
+
depositToChannel(channelId: string, amount: bigint, opts: {
|
|
2163
|
+
currentDeposit: bigint;
|
|
2164
|
+
}): Promise<{
|
|
2165
|
+
txHash?: string;
|
|
2166
|
+
depositTotal: bigint;
|
|
2167
|
+
}>;
|
|
2168
|
+
/**
|
|
2169
|
+
* EVM deposit: approve the token-network for the delta if the allowance is
|
|
2170
|
+
* short, then `setTotalDeposit(channelId, participant, current + delta)` —
|
|
2171
|
+
* the contract takes the new cumulative total, so we add the delta to the
|
|
2172
|
+
* caller-supplied current locked amount.
|
|
2173
|
+
*/
|
|
2174
|
+
private depositEvm;
|
|
2113
2175
|
/**
|
|
2114
2176
|
* Opens a REAL on-chain Solana payment channel.
|
|
2115
2177
|
*
|
|
@@ -2274,6 +2336,18 @@ declare class ChannelManager {
|
|
|
2274
2336
|
* Gets the cumulative transferred amount for a tracked channel.
|
|
2275
2337
|
*/
|
|
2276
2338
|
getCumulativeAmount(channelId: string): bigint;
|
|
2339
|
+
/**
|
|
2340
|
+
* Gets the on-chain deposit total (collateral locked at open / via deposits)
|
|
2341
|
+
* for a tracked channel, or `0n` when none was captured. The available
|
|
2342
|
+
* (spendable) balance is `depositTotal - cumulativeAmount`.
|
|
2343
|
+
*/
|
|
2344
|
+
getDepositTotal(channelId: string): bigint;
|
|
2345
|
+
/**
|
|
2346
|
+
* Update the tracked on-chain deposit total after a successful deposit, so the
|
|
2347
|
+
* available balance (`depositTotal - cumulativeAmount`) reflects the new
|
|
2348
|
+
* collateral on the next read.
|
|
2349
|
+
*/
|
|
2350
|
+
setDepositTotal(channelId: string, total: bigint): void;
|
|
2277
2351
|
/**
|
|
2278
2352
|
* Gets all tracked channel IDs.
|
|
2279
2353
|
*/
|
package/dist/index.js
CHANGED
|
@@ -1640,17 +1640,17 @@ function readDiscoveredIlpPeer(peer) {
|
|
|
1640
1640
|
}
|
|
1641
1641
|
function selectIlpTransport(peer, options = {}) {
|
|
1642
1642
|
const needsDuplex = options.needsDuplex ?? false;
|
|
1643
|
-
const
|
|
1643
|
+
const http3 = peer.httpEndpoint?.trim() || void 0;
|
|
1644
1644
|
const btp = peer.btpEndpoint?.trim() || void 0;
|
|
1645
1645
|
const canUpgrade = peer.supportsUpgrade === true;
|
|
1646
1646
|
if (needsDuplex) {
|
|
1647
1647
|
if (btp) return { kind: "btp", btpEndpoint: btp };
|
|
1648
|
-
if (
|
|
1648
|
+
if (http3 && canUpgrade) return { kind: "http-upgradable", httpEndpoint: http3 };
|
|
1649
1649
|
throw new Error(
|
|
1650
1650
|
"Duplex transport required but peer exposes neither a btpEndpoint nor an upgradable httpEndpoint"
|
|
1651
1651
|
);
|
|
1652
1652
|
}
|
|
1653
|
-
if (
|
|
1653
|
+
if (http3) return { kind: "http", httpEndpoint: http3, canUpgrade };
|
|
1654
1654
|
if (btp) return { kind: "btp", btpEndpoint: btp };
|
|
1655
1655
|
throw new Error("Peer exposes neither an httpEndpoint nor a btpEndpoint");
|
|
1656
1656
|
}
|
|
@@ -2410,6 +2410,70 @@ var OnChainChannelClient = class {
|
|
|
2410
2410
|
if (chainPrefix === "mina") return this.openMinaChannel(params);
|
|
2411
2411
|
return this.openEvmChannel(params);
|
|
2412
2412
|
}
|
|
2413
|
+
/**
|
|
2414
|
+
* Deposit additional collateral into an already-open channel. `amount` is the
|
|
2415
|
+
* DELTA to add (base units). Dispatches by the channel's cached chain context;
|
|
2416
|
+
* `currentDeposit` is the channel's current locked total (tracked off-chain by
|
|
2417
|
+
* the caller) — required for EVM, whose `setTotalDeposit` takes the new
|
|
2418
|
+
* cumulative total, not a delta. Returns the new on-chain deposit total.
|
|
2419
|
+
*
|
|
2420
|
+
* Non-custodial: the client deposits its OWN funds and signs its OWN tx.
|
|
2421
|
+
* EVM is live; Solana/Mina deposit extraction is a follow-up (PR B.1).
|
|
2422
|
+
*/
|
|
2423
|
+
async depositToChannel(channelId, amount, opts) {
|
|
2424
|
+
if (amount <= 0n) throw new Error("Deposit amount must be positive.");
|
|
2425
|
+
const ctx = this.channelContext.get(channelId);
|
|
2426
|
+
if (!ctx) {
|
|
2427
|
+
throw new Error(
|
|
2428
|
+
`No on-chain context for channel "${channelId}" \u2014 it must be opened by this client first.`
|
|
2429
|
+
);
|
|
2430
|
+
}
|
|
2431
|
+
const chainPrefix = ctx.chain.split(":")[0];
|
|
2432
|
+
if (chainPrefix === "solana" || chainPrefix === "mina") {
|
|
2433
|
+
throw new Error(
|
|
2434
|
+
`Deposit on ${chainPrefix} is not yet supported (EVM only; follow-up PR adds it).`
|
|
2435
|
+
);
|
|
2436
|
+
}
|
|
2437
|
+
return this.depositEvm(channelId, amount, opts.currentDeposit, ctx);
|
|
2438
|
+
}
|
|
2439
|
+
/**
|
|
2440
|
+
* EVM deposit: approve the token-network for the delta if the allowance is
|
|
2441
|
+
* short, then `setTotalDeposit(channelId, participant, current + delta)` —
|
|
2442
|
+
* the contract takes the new cumulative total, so we add the delta to the
|
|
2443
|
+
* caller-supplied current locked amount.
|
|
2444
|
+
*/
|
|
2445
|
+
async depositEvm(channelId, amount, currentDeposit, ctx) {
|
|
2446
|
+
const { publicClient, walletClient } = this.createClients(ctx.chain);
|
|
2447
|
+
const tokenNetworkAddr = ctx.tokenNetworkAddress;
|
|
2448
|
+
const myAddress = this.evmSigner.address;
|
|
2449
|
+
const newTotal = currentDeposit + amount;
|
|
2450
|
+
if (ctx.tokenAddress) {
|
|
2451
|
+
const tokenAddr = ctx.tokenAddress;
|
|
2452
|
+
const allowance = await publicClient.readContract({
|
|
2453
|
+
address: tokenAddr,
|
|
2454
|
+
abi: ERC20_ABI,
|
|
2455
|
+
functionName: "allowance",
|
|
2456
|
+
args: [myAddress, tokenNetworkAddr]
|
|
2457
|
+
});
|
|
2458
|
+
if (allowance < amount) {
|
|
2459
|
+
const approveHash = await walletClient.writeContract({
|
|
2460
|
+
address: tokenAddr,
|
|
2461
|
+
abi: ERC20_ABI,
|
|
2462
|
+
functionName: "approve",
|
|
2463
|
+
args: [tokenNetworkAddr, maxUint256]
|
|
2464
|
+
});
|
|
2465
|
+
await publicClient.waitForTransactionReceipt({ hash: approveHash });
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
const depositHash = await walletClient.writeContract({
|
|
2469
|
+
address: tokenNetworkAddr,
|
|
2470
|
+
abi: TOKEN_NETWORK_ABI,
|
|
2471
|
+
functionName: "setTotalDeposit",
|
|
2472
|
+
args: [channelId, myAddress, newTotal]
|
|
2473
|
+
});
|
|
2474
|
+
await publicClient.waitForTransactionReceipt({ hash: depositHash });
|
|
2475
|
+
return { txHash: depositHash, depositTotal: newTotal };
|
|
2476
|
+
}
|
|
2413
2477
|
/**
|
|
2414
2478
|
* Opens a REAL on-chain Solana payment channel.
|
|
2415
2479
|
*
|
|
@@ -2611,7 +2675,8 @@ var OnChainChannelClient = class {
|
|
|
2611
2675
|
}
|
|
2612
2676
|
this.channelContext.set(channelId, {
|
|
2613
2677
|
chain,
|
|
2614
|
-
tokenNetworkAddress: tokenNetwork
|
|
2678
|
+
tokenNetworkAddress: tokenNetwork,
|
|
2679
|
+
...params.token ? { tokenAddress: params.token } : {}
|
|
2615
2680
|
});
|
|
2616
2681
|
if (deposit > 0n) {
|
|
2617
2682
|
const depositHash = await walletClient.writeContract({
|
|
@@ -3489,6 +3554,30 @@ var ChannelManager = class {
|
|
|
3489
3554
|
}
|
|
3490
3555
|
return tracking.cumulativeAmount;
|
|
3491
3556
|
}
|
|
3557
|
+
/**
|
|
3558
|
+
* Gets the on-chain deposit total (collateral locked at open / via deposits)
|
|
3559
|
+
* for a tracked channel, or `0n` when none was captured. The available
|
|
3560
|
+
* (spendable) balance is `depositTotal - cumulativeAmount`.
|
|
3561
|
+
*/
|
|
3562
|
+
getDepositTotal(channelId) {
|
|
3563
|
+
const tracking = this.channels.get(channelId);
|
|
3564
|
+
if (!tracking) {
|
|
3565
|
+
throw new Error(`Channel "${channelId}" is not being tracked.`);
|
|
3566
|
+
}
|
|
3567
|
+
return tracking.depositTotal ?? 0n;
|
|
3568
|
+
}
|
|
3569
|
+
/**
|
|
3570
|
+
* Update the tracked on-chain deposit total after a successful deposit, so the
|
|
3571
|
+
* available balance (`depositTotal - cumulativeAmount`) reflects the new
|
|
3572
|
+
* collateral on the next read.
|
|
3573
|
+
*/
|
|
3574
|
+
setDepositTotal(channelId, total) {
|
|
3575
|
+
const tracking = this.channels.get(channelId);
|
|
3576
|
+
if (!tracking) {
|
|
3577
|
+
throw new Error(`Channel "${channelId}" is not being tracked.`);
|
|
3578
|
+
}
|
|
3579
|
+
tracking.depositTotal = total;
|
|
3580
|
+
}
|
|
3492
3581
|
/**
|
|
3493
3582
|
* Gets all tracked channel IDs.
|
|
3494
3583
|
*/
|
|
@@ -3547,6 +3636,91 @@ var JsonFileChannelStore = class {
|
|
|
3547
3636
|
}
|
|
3548
3637
|
};
|
|
3549
3638
|
|
|
3639
|
+
// src/balance/WalletBalanceReader.ts
|
|
3640
|
+
import { createPublicClient as createPublicClient2, http as http2, defineChain as defineChain2 } from "viem";
|
|
3641
|
+
var ERC20_READ_ABI = [
|
|
3642
|
+
{ name: "balanceOf", type: "function", stateMutability: "view", inputs: [{ name: "account", type: "address" }], outputs: [{ type: "uint256" }] },
|
|
3643
|
+
{ name: "decimals", type: "function", stateMutability: "view", inputs: [], outputs: [{ type: "uint8" }] },
|
|
3644
|
+
{ name: "symbol", type: "function", stateMutability: "view", inputs: [], outputs: [{ type: "string" }] }
|
|
3645
|
+
];
|
|
3646
|
+
function parseEvmChainId(chainKey) {
|
|
3647
|
+
const parts = chainKey.split(":");
|
|
3648
|
+
const idStr = parts.length >= 3 ? parts[2] : parts[1];
|
|
3649
|
+
const id = Number.parseInt(idStr ?? "", 10);
|
|
3650
|
+
if (!Number.isFinite(id)) throw new Error(`Invalid EVM chain key "${chainKey}".`);
|
|
3651
|
+
return id;
|
|
3652
|
+
}
|
|
3653
|
+
async function readEvmTokenBalance(opts) {
|
|
3654
|
+
const chainId = parseEvmChainId(opts.chainKey);
|
|
3655
|
+
const client = createPublicClient2({
|
|
3656
|
+
transport: http2(opts.rpcUrl),
|
|
3657
|
+
chain: defineChain2({
|
|
3658
|
+
id: chainId,
|
|
3659
|
+
name: opts.chainKey,
|
|
3660
|
+
nativeCurrency: { name: "ETH", symbol: "ETH", decimals: 18 },
|
|
3661
|
+
rpcUrls: { default: { http: [opts.rpcUrl] } }
|
|
3662
|
+
})
|
|
3663
|
+
});
|
|
3664
|
+
const token = opts.tokenAddress;
|
|
3665
|
+
const owner = opts.owner;
|
|
3666
|
+
const [amount, decimals, symbol] = await Promise.all([
|
|
3667
|
+
client.readContract({ address: token, abi: ERC20_READ_ABI, functionName: "balanceOf", args: [owner] }),
|
|
3668
|
+
client.readContract({ address: token, abi: ERC20_READ_ABI, functionName: "decimals" }).catch(() => void 0),
|
|
3669
|
+
client.readContract({ address: token, abi: ERC20_READ_ABI, functionName: "symbol" }).catch(() => void 0)
|
|
3670
|
+
]);
|
|
3671
|
+
const out = { chain: "evm", address: opts.owner, amount: amount.toString() };
|
|
3672
|
+
if (typeof symbol === "string" && symbol) out.asset = symbol;
|
|
3673
|
+
if (decimals !== void 0) out.assetScale = Number(decimals);
|
|
3674
|
+
return out;
|
|
3675
|
+
}
|
|
3676
|
+
async function readSolanaTokenBalance(opts) {
|
|
3677
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
3678
|
+
const res = await fetchImpl(opts.rpcUrl, {
|
|
3679
|
+
method: "POST",
|
|
3680
|
+
headers: { "content-type": "application/json" },
|
|
3681
|
+
body: JSON.stringify({
|
|
3682
|
+
jsonrpc: "2.0",
|
|
3683
|
+
id: 1,
|
|
3684
|
+
method: "getTokenAccountsByOwner",
|
|
3685
|
+
params: [opts.owner, { mint: opts.mint }, { encoding: "jsonParsed", commitment: "confirmed" }]
|
|
3686
|
+
})
|
|
3687
|
+
});
|
|
3688
|
+
if (!res.ok) throw new Error(`Solana RPC request failed: HTTP ${res.status}`);
|
|
3689
|
+
const json = await res.json();
|
|
3690
|
+
if (json.error) throw new Error(`Solana RPC error: ${json.error.message ?? "unknown"}`);
|
|
3691
|
+
let amount = 0n;
|
|
3692
|
+
let decimals;
|
|
3693
|
+
for (const acc of json.result?.value ?? []) {
|
|
3694
|
+
const ta = acc.account?.data?.parsed?.info?.tokenAmount;
|
|
3695
|
+
if (ta?.amount) amount += BigInt(ta.amount);
|
|
3696
|
+
if (ta?.decimals !== void 0) decimals = ta.decimals;
|
|
3697
|
+
}
|
|
3698
|
+
const out = { chain: "solana", address: opts.owner, amount: amount.toString() };
|
|
3699
|
+
if (decimals !== void 0) out.assetScale = decimals;
|
|
3700
|
+
return out;
|
|
3701
|
+
}
|
|
3702
|
+
async function readMinaBalance(opts) {
|
|
3703
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
3704
|
+
const query = "query($pk:String!){account(publicKey:$pk){balance{total}}}";
|
|
3705
|
+
const res = await fetchImpl(opts.graphqlUrl, {
|
|
3706
|
+
method: "POST",
|
|
3707
|
+
headers: { "content-type": "application/json" },
|
|
3708
|
+
body: JSON.stringify({ query, variables: { pk: opts.owner } })
|
|
3709
|
+
});
|
|
3710
|
+
if (!res.ok) throw new Error(`Mina GraphQL request failed: HTTP ${res.status}`);
|
|
3711
|
+
const json = await res.json();
|
|
3712
|
+
if (json.errors && json.errors.length > 0) {
|
|
3713
|
+
throw new Error(`Mina GraphQL error: ${json.errors[0]?.message ?? "unknown"}`);
|
|
3714
|
+
}
|
|
3715
|
+
return {
|
|
3716
|
+
chain: "mina",
|
|
3717
|
+
address: opts.owner,
|
|
3718
|
+
amount: String(json.data?.account?.balance?.total ?? "0"),
|
|
3719
|
+
asset: "MINA",
|
|
3720
|
+
assetScale: 9
|
|
3721
|
+
};
|
|
3722
|
+
}
|
|
3723
|
+
|
|
3550
3724
|
// src/blob-storage.ts
|
|
3551
3725
|
import { buildBlobStorageRequest } from "@toon-protocol/core";
|
|
3552
3726
|
var ARWEAVE_TX_ID_REGEX = /^[A-Za-z0-9_-]{43}$/;
|
|
@@ -3617,8 +3791,8 @@ async function requestBlobStorage(client, secretKey, params) {
|
|
|
3617
3791
|
};
|
|
3618
3792
|
}
|
|
3619
3793
|
function extractArweaveTxId(base64Data) {
|
|
3620
|
-
const
|
|
3621
|
-
if (!
|
|
3794
|
+
const http3 = parseFulfillHttp(base64Data);
|
|
3795
|
+
if (!http3.isHttp) {
|
|
3622
3796
|
const legacy = decodeUtf8(fromBase64(base64Data));
|
|
3623
3797
|
if (!ARWEAVE_TX_ID_REGEX.test(legacy)) {
|
|
3624
3798
|
throw new Error(
|
|
@@ -3627,18 +3801,18 @@ function extractArweaveTxId(base64Data) {
|
|
|
3627
3801
|
}
|
|
3628
3802
|
return legacy;
|
|
3629
3803
|
}
|
|
3630
|
-
if (
|
|
3631
|
-
const detail =
|
|
3804
|
+
if (http3.status < 200 || http3.status >= 300) {
|
|
3805
|
+
const detail = http3.body ? ` - ${http3.body}` : "";
|
|
3632
3806
|
throw new Error(
|
|
3633
|
-
`Blob upload failed: DVM returned HTTP ${
|
|
3807
|
+
`Blob upload failed: DVM returned HTTP ${http3.status} ${http3.statusText}`.trimEnd() + detail
|
|
3634
3808
|
);
|
|
3635
3809
|
}
|
|
3636
3810
|
let parsed;
|
|
3637
3811
|
try {
|
|
3638
|
-
parsed = JSON.parse(
|
|
3812
|
+
parsed = JSON.parse(http3.body);
|
|
3639
3813
|
} catch {
|
|
3640
3814
|
throw new Error(
|
|
3641
|
-
`Blob upload response body was not valid JSON: "${
|
|
3815
|
+
`Blob upload response body was not valid JSON: "${http3.body}"`
|
|
3642
3816
|
);
|
|
3643
3817
|
}
|
|
3644
3818
|
const body = parsed;
|
|
@@ -3656,7 +3830,7 @@ function extractArweaveTxId(base64Data) {
|
|
|
3656
3830
|
}
|
|
3657
3831
|
}
|
|
3658
3832
|
throw new Error(
|
|
3659
|
-
`Blob upload response did not contain a valid Arweave tx ID: "${
|
|
3833
|
+
`Blob upload response did not contain a valid Arweave tx ID: "${http3.body}"`
|
|
3660
3834
|
);
|
|
3661
3835
|
}
|
|
3662
3836
|
|
|
@@ -3932,6 +4106,8 @@ var ToonClient = class {
|
|
|
3932
4106
|
*/
|
|
3933
4107
|
minaPrivateKey;
|
|
3934
4108
|
channelManager;
|
|
4109
|
+
/** Concrete on-chain client, kept so deposit/withdraw can reach chain methods. */
|
|
4110
|
+
onChainChannelClient;
|
|
3935
4111
|
peerNegotiations = /* @__PURE__ */ new Map();
|
|
3936
4112
|
/**
|
|
3937
4113
|
* Creates a new ToonClient instance.
|
|
@@ -4125,6 +4301,7 @@ var ToonClient = class {
|
|
|
4125
4301
|
}
|
|
4126
4302
|
}
|
|
4127
4303
|
if (this.channelManager && initialization.onChainChannelClient) {
|
|
4304
|
+
this.onChainChannelClient = initialization.onChainChannelClient;
|
|
4128
4305
|
this.channelManager.setChannelClient(
|
|
4129
4306
|
initialization.onChainChannelClient
|
|
4130
4307
|
);
|
|
@@ -4533,6 +4710,84 @@ var ToonClient = class {
|
|
|
4533
4710
|
if (!this.channelManager) throw new Error("ChannelManager not initialized");
|
|
4534
4711
|
return this.channelManager.getCumulativeAmount(channelId);
|
|
4535
4712
|
}
|
|
4713
|
+
/**
|
|
4714
|
+
* Gets the on-chain deposit total (locked collateral) for a tracked channel.
|
|
4715
|
+
* The available (spendable) balance is this minus the cumulative spent amount.
|
|
4716
|
+
*/
|
|
4717
|
+
getChannelDepositTotal(channelId) {
|
|
4718
|
+
if (!this.channelManager) throw new Error("ChannelManager not initialized");
|
|
4719
|
+
return this.channelManager.getDepositTotal(channelId);
|
|
4720
|
+
}
|
|
4721
|
+
/**
|
|
4722
|
+
* Deposit additional collateral into an open channel. `amount` is the delta to
|
|
4723
|
+
* add (base units, decimal string or bigint). The daemon signs its own tx; no
|
|
4724
|
+
* key material leaves the client. Reads the current tracked deposit, performs
|
|
4725
|
+
* the on-chain deposit, updates the tracked total, and returns the new total.
|
|
4726
|
+
* EVM is live; Solana/Mina deposit lands in a follow-up.
|
|
4727
|
+
*/
|
|
4728
|
+
async depositToChannel(channelId, amount) {
|
|
4729
|
+
if (!this.channelManager) throw new Error("ChannelManager not initialized");
|
|
4730
|
+
if (!this.onChainChannelClient) {
|
|
4731
|
+
throw new Error("On-chain channel client not configured (no chainRpcUrls).");
|
|
4732
|
+
}
|
|
4733
|
+
const delta = BigInt(amount);
|
|
4734
|
+
if (delta <= 0n) throw new Error("Deposit amount must be positive.");
|
|
4735
|
+
const currentDeposit = this.channelManager.getDepositTotal(channelId);
|
|
4736
|
+
const result = await this.onChainChannelClient.depositToChannel(channelId, delta, {
|
|
4737
|
+
currentDeposit
|
|
4738
|
+
});
|
|
4739
|
+
this.channelManager.setDepositTotal(channelId, result.depositTotal);
|
|
4740
|
+
return {
|
|
4741
|
+
channelId,
|
|
4742
|
+
...result.txHash ? { txHash: result.txHash } : {},
|
|
4743
|
+
depositTotal: result.depositTotal.toString()
|
|
4744
|
+
};
|
|
4745
|
+
}
|
|
4746
|
+
/**
|
|
4747
|
+
* Read the on-chain settlement-token balance of this client's OWN wallet on
|
|
4748
|
+
* each configured chain (EVM token, Solana SPL, native MINA). A free read — no
|
|
4749
|
+
* signing, no payment. Best-effort per chain: a chain whose config is absent or
|
|
4750
|
+
* whose RPC read fails is omitted rather than failing the whole result, so the
|
|
4751
|
+
* wallet view degrades gracefully. Available after `start()` (Solana/Mina keys
|
|
4752
|
+
* are derived there).
|
|
4753
|
+
*/
|
|
4754
|
+
async getBalances() {
|
|
4755
|
+
const out = [];
|
|
4756
|
+
const evmAddress = this.getEvmAddress();
|
|
4757
|
+
const rpcUrls = this.config.chainRpcUrls;
|
|
4758
|
+
const tokens = this.config.preferredTokens;
|
|
4759
|
+
if (evmAddress && rpcUrls && tokens) {
|
|
4760
|
+
const chainKeys = this.config.supportedChains ?? Object.keys(rpcUrls);
|
|
4761
|
+
const chainKey = chainKeys.find((c) => c.startsWith("evm") && rpcUrls[c] && tokens[c]);
|
|
4762
|
+
const rpcUrl = chainKey ? rpcUrls[chainKey] : void 0;
|
|
4763
|
+
const tokenAddress = chainKey ? tokens[chainKey] : void 0;
|
|
4764
|
+
if (chainKey && rpcUrl && tokenAddress) {
|
|
4765
|
+
try {
|
|
4766
|
+
out.push(await readEvmTokenBalance({ rpcUrl, chainKey, tokenAddress, owner: evmAddress }));
|
|
4767
|
+
} catch {
|
|
4768
|
+
}
|
|
4769
|
+
}
|
|
4770
|
+
}
|
|
4771
|
+
const solAddress = this.getSolanaAddress();
|
|
4772
|
+
const sol = this.config.solanaChannel;
|
|
4773
|
+
if (solAddress && sol?.rpcUrl && sol.tokenMint) {
|
|
4774
|
+
try {
|
|
4775
|
+
out.push(
|
|
4776
|
+
await readSolanaTokenBalance({ rpcUrl: sol.rpcUrl, mint: sol.tokenMint, owner: solAddress })
|
|
4777
|
+
);
|
|
4778
|
+
} catch {
|
|
4779
|
+
}
|
|
4780
|
+
}
|
|
4781
|
+
const minaAddress = this.getMinaAddress();
|
|
4782
|
+
const mina = this.config.minaChannel;
|
|
4783
|
+
if (minaAddress && mina?.graphqlUrl) {
|
|
4784
|
+
try {
|
|
4785
|
+
out.push(await readMinaBalance({ graphqlUrl: mina.graphqlUrl, owner: minaAddress }));
|
|
4786
|
+
} catch {
|
|
4787
|
+
}
|
|
4788
|
+
}
|
|
4789
|
+
return out;
|
|
4790
|
+
}
|
|
4536
4791
|
/**
|
|
4537
4792
|
* Resolves an ILP destination address to a peer ID.
|
|
4538
4793
|
* Convention: destination "g.toon.peer1" → peerId "peer1" (last segment).
|