@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 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 http2 = peer.httpEndpoint?.trim() || void 0;
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 (http2 && canUpgrade) return { kind: "http-upgradable", httpEndpoint: http2 };
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 (http2) return { kind: "http", httpEndpoint: http2, canUpgrade };
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 http2 = parseFulfillHttp(base64Data);
3621
- if (!http2.isHttp) {
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 (http2.status < 200 || http2.status >= 300) {
3631
- const detail = http2.body ? ` - ${http2.body}` : "";
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 ${http2.status} ${http2.statusText}`.trimEnd() + detail
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(http2.body);
3812
+ parsed = JSON.parse(http3.body);
3639
3813
  } catch {
3640
3814
  throw new Error(
3641
- `Blob upload response body was not valid JSON: "${http2.body}"`
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: "${http2.body}"`
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).