@toon-protocol/client-mcp 0.10.1 → 0.10.3

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.
@@ -8677,6 +8677,20 @@ var TOKEN_NETWORK_ABI = [
8677
8677
  ],
8678
8678
  outputs: []
8679
8679
  },
8680
+ {
8681
+ name: "closeChannel",
8682
+ type: "function",
8683
+ stateMutability: "nonpayable",
8684
+ inputs: [{ name: "channelId", type: "bytes32" }],
8685
+ outputs: []
8686
+ },
8687
+ {
8688
+ name: "settleChannel",
8689
+ type: "function",
8690
+ stateMutability: "nonpayable",
8691
+ inputs: [{ name: "channelId", type: "bytes32" }],
8692
+ outputs: []
8693
+ },
8680
8694
  {
8681
8695
  name: "channels",
8682
8696
  type: "function",
@@ -8925,6 +8939,103 @@ var OnChainChannelClient = class {
8925
8939
  await publicClient.waitForTransactionReceipt({ hash: depositHash });
8926
8940
  return { txHash: depositHash, depositTotal: newTotal };
8927
8941
  }
8942
+ /**
8943
+ * Close a channel to begin the settlement grace period. Dispatches by the
8944
+ * channel's cached chain context. EVM `closeChannel` is unilateral (channelId
8945
+ * only); after it confirms we read the `channels()` view for the AUTHORITATIVE
8946
+ * `closedAt` + `settlementTimeout` (block-timestamp seconds) and compute
8947
+ * `settleableAt = closedAt + settlementTimeout`. Solana/Mina are follow-ups.
8948
+ */
8949
+ async closeChannel(channelId) {
8950
+ const ctx = this.channelContext.get(channelId);
8951
+ if (!ctx) {
8952
+ throw new Error(
8953
+ `No on-chain context for channel "${channelId}" \u2014 it must be opened by this client first.`
8954
+ );
8955
+ }
8956
+ const chainPrefix = ctx.chain.split(":")[0];
8957
+ if (chainPrefix === "solana" || chainPrefix === "mina") {
8958
+ throw new Error(
8959
+ `Close on ${chainPrefix} is not yet supported (EVM today; Solana/Mina follow-up).`
8960
+ );
8961
+ }
8962
+ const { publicClient, walletClient } = this.createClients(ctx.chain);
8963
+ const tokenNetworkAddr = ctx.tokenNetworkAddress;
8964
+ const closeHash = await walletClient.writeContract({
8965
+ address: tokenNetworkAddr,
8966
+ abi: TOKEN_NETWORK_ABI,
8967
+ functionName: "closeChannel",
8968
+ args: [channelId]
8969
+ });
8970
+ await publicClient.waitForTransactionReceipt({ hash: closeHash });
8971
+ const info = await this.readEvmChannel(publicClient, tokenNetworkAddr, channelId);
8972
+ return {
8973
+ txHash: closeHash,
8974
+ closedAt: info.closedAt,
8975
+ settlementTimeout: info.settlementTimeout,
8976
+ settleableAt: info.closedAt + info.settlementTimeout
8977
+ };
8978
+ }
8979
+ /**
8980
+ * Settle a closed channel after its grace period to release collateral. EVM
8981
+ * `settleChannel` is unilateral (channelId only); the contract itself reverts
8982
+ * before `closedAt + settlementTimeout`, so an early call surfaces as a tx
8983
+ * revert here — but the caller (ToonClient/daemon) enforces the time guard
8984
+ * BEFORE spending gas. Solana/Mina are follow-ups.
8985
+ */
8986
+ async settleChannel(channelId) {
8987
+ const ctx = this.channelContext.get(channelId);
8988
+ if (!ctx) {
8989
+ throw new Error(
8990
+ `No on-chain context for channel "${channelId}" \u2014 it must be opened by this client first.`
8991
+ );
8992
+ }
8993
+ const chainPrefix = ctx.chain.split(":")[0];
8994
+ if (chainPrefix === "solana" || chainPrefix === "mina") {
8995
+ throw new Error(
8996
+ `Settle on ${chainPrefix} is not yet supported (EVM today; Solana/Mina follow-up).`
8997
+ );
8998
+ }
8999
+ const { publicClient, walletClient } = this.createClients(ctx.chain);
9000
+ const settleHash = await walletClient.writeContract({
9001
+ address: ctx.tokenNetworkAddress,
9002
+ abi: TOKEN_NETWORK_ABI,
9003
+ functionName: "settleChannel",
9004
+ args: [channelId]
9005
+ });
9006
+ await publicClient.waitForTransactionReceipt({ hash: settleHash });
9007
+ return { txHash: settleHash };
9008
+ }
9009
+ /**
9010
+ * Read the EVM channel's close-relevant fields so a restarted daemon can
9011
+ * recompute the grace timer from chain (chain is authoritative). EVM-only.
9012
+ */
9013
+ async getChannelCloseInfo(channelId) {
9014
+ const ctx = this.channelContext.get(channelId);
9015
+ if (!ctx) throw new Error(`No on-chain context for channel "${channelId}".`);
9016
+ const chainPrefix = ctx.chain.split(":")[0];
9017
+ if (chainPrefix === "solana" || chainPrefix === "mina") {
9018
+ throw new Error(`getChannelCloseInfo on ${chainPrefix} is not supported.`);
9019
+ }
9020
+ const { publicClient } = this.createClients(ctx.chain);
9021
+ const info = await this.readEvmChannel(publicClient, ctx.tokenNetworkAddress, channelId);
9022
+ return {
9023
+ status: STATE_MAP2[info.state] ?? "open",
9024
+ closedAt: info.closedAt,
9025
+ settlementTimeout: info.settlementTimeout,
9026
+ settleableAt: info.closedAt + info.settlementTimeout
9027
+ };
9028
+ }
9029
+ /** Read + destructure the EVM `channels(bytes32)` view. */
9030
+ async readEvmChannel(publicClient, tokenNetworkAddr, channelId) {
9031
+ const res = await publicClient.readContract({
9032
+ address: tokenNetworkAddr,
9033
+ abi: TOKEN_NETWORK_ABI,
9034
+ functionName: "channels",
9035
+ args: [channelId]
9036
+ });
9037
+ return { settlementTimeout: res[0], state: Number(res[1]), closedAt: res[2] };
9038
+ }
8928
9039
  /**
8929
9040
  * Opens a REAL on-chain Solana payment channel.
8930
9041
  *
@@ -9866,7 +9977,12 @@ var ChannelManager = class {
9866
9977
  tokenNetworkAddress: tnAddr,
9867
9978
  tokenAddress: chainContext?.tokenAddress,
9868
9979
  recipient: chainContext?.recipient,
9869
- depositTotal: chainContext?.depositTotal
9980
+ depositTotal: chainContext?.depositTotal,
9981
+ // Resume the withdraw-flow timers so a daemon restart mid-grace
9982
+ // doesn't strand funds (the gate can't be evaluated without them).
9983
+ ...persisted.closedAt !== void 0 ? { closedAt: persisted.closedAt } : {},
9984
+ ...persisted.settleableAt !== void 0 ? { settleableAt: persisted.settleableAt } : {},
9985
+ ...persisted.settledAt !== void 0 ? { settledAt: persisted.settledAt } : {}
9870
9986
  });
9871
9987
  return;
9872
9988
  }
@@ -9901,12 +10017,7 @@ var ChannelManager = class {
9901
10017
  }
9902
10018
  tracking.nonce += 1;
9903
10019
  tracking.cumulativeAmount += additionalAmount;
9904
- if (this.store) {
9905
- this.store.save(channelId, {
9906
- nonce: tracking.nonce,
9907
- cumulativeAmount: tracking.cumulativeAmount
9908
- });
9909
- }
10020
+ this.persist(channelId);
9910
10021
  const signer = this.chainSigners.get(tracking.chainType);
9911
10022
  if (signer && tracking.chainType !== "evm") {
9912
10023
  if (!tracking.recipient) {
@@ -10006,6 +10117,57 @@ var ChannelManager = class {
10006
10117
  }
10007
10118
  tracking.depositTotal = total;
10008
10119
  }
10120
+ /** Persist a channel's full nonce/amount + withdraw-timer state to the store. */
10121
+ persist(channelId) {
10122
+ if (!this.store) return;
10123
+ const t = this.channels.get(channelId);
10124
+ if (!t) return;
10125
+ this.store.save(channelId, {
10126
+ nonce: t.nonce,
10127
+ cumulativeAmount: t.cumulativeAmount,
10128
+ ...t.closedAt !== void 0 ? { closedAt: t.closedAt } : {},
10129
+ ...t.settleableAt !== void 0 ? { settleableAt: t.settleableAt } : {},
10130
+ ...t.settledAt !== void 0 ? { settledAt: t.settledAt } : {}
10131
+ });
10132
+ }
10133
+ /**
10134
+ * Record that a channel was closed (withdraw flow): stores `closedAt` +
10135
+ * `settleableAt` (unix SECONDS) so the grace timer survives a daemon restart.
10136
+ */
10137
+ setChannelClosed(channelId, closedAt, settleableAt) {
10138
+ const tracking = this.channels.get(channelId);
10139
+ if (!tracking) {
10140
+ throw new Error(`Channel "${channelId}" is not being tracked.`);
10141
+ }
10142
+ tracking.closedAt = closedAt;
10143
+ tracking.settleableAt = settleableAt;
10144
+ this.persist(channelId);
10145
+ }
10146
+ /** Record that a channel was settled (collateral released). */
10147
+ setChannelSettled(channelId, settledAt) {
10148
+ const tracking = this.channels.get(channelId);
10149
+ if (!tracking) {
10150
+ throw new Error(`Channel "${channelId}" is not being tracked.`);
10151
+ }
10152
+ tracking.settledAt = settledAt;
10153
+ this.persist(channelId);
10154
+ }
10155
+ /** The `settleableAt` timestamp (unix seconds) for a closed channel, if set. */
10156
+ getSettleableAt(channelId) {
10157
+ return this.channels.get(channelId)?.settleableAt;
10158
+ }
10159
+ /**
10160
+ * Where a channel sits in the withdraw journey, from the tracked timers:
10161
+ * `open` (never closed) → `closing` (closed, grace not elapsed) →
10162
+ * `settleable` (grace elapsed) → `settled`. `nowSec` is injectable for tests.
10163
+ */
10164
+ getChannelCloseState(channelId, nowSec = BigInt(Math.floor(Date.now() / 1e3))) {
10165
+ const t = this.channels.get(channelId);
10166
+ if (!t || t.closedAt === void 0) return "open";
10167
+ if (t.settledAt !== void 0) return "settled";
10168
+ if (t.settleableAt !== void 0 && nowSec >= t.settleableAt) return "settleable";
10169
+ return "closing";
10170
+ }
10009
10171
  /**
10010
10172
  * Gets all tracked channel IDs.
10011
10173
  */
@@ -10028,7 +10190,10 @@ var JsonFileChannelStore = class {
10028
10190
  const data = this.readFile();
10029
10191
  data[channelId] = {
10030
10192
  nonce: tracking.nonce,
10031
- cumulativeAmount: tracking.cumulativeAmount.toString()
10193
+ cumulativeAmount: tracking.cumulativeAmount.toString(),
10194
+ ...tracking.closedAt !== void 0 ? { closedAt: tracking.closedAt.toString() } : {},
10195
+ ...tracking.settleableAt !== void 0 ? { settleableAt: tracking.settleableAt.toString() } : {},
10196
+ ...tracking.settledAt !== void 0 ? { settledAt: tracking.settledAt.toString() } : {}
10032
10197
  };
10033
10198
  this.writeFile(data);
10034
10199
  }
@@ -10038,7 +10203,10 @@ var JsonFileChannelStore = class {
10038
10203
  if (!entry) return void 0;
10039
10204
  return {
10040
10205
  nonce: entry.nonce,
10041
- cumulativeAmount: BigInt(entry.cumulativeAmount)
10206
+ cumulativeAmount: BigInt(entry.cumulativeAmount),
10207
+ ...entry.closedAt !== void 0 ? { closedAt: BigInt(entry.closedAt) } : {},
10208
+ ...entry.settleableAt !== void 0 ? { settleableAt: BigInt(entry.settleableAt) } : {},
10209
+ ...entry.settledAt !== void 0 ? { settledAt: BigInt(entry.settledAt) } : {}
10042
10210
  };
10043
10211
  }
10044
10212
  list() {
@@ -10348,8 +10516,7 @@ var Http402Client = class {
10348
10516
  claim
10349
10517
  );
10350
10518
  } finally {
10351
- await btp.disconnect().catch(() => {
10352
- });
10519
+ await btp.disconnect().catch(() => void 0);
10353
10520
  }
10354
10521
  }
10355
10522
  if (choice.kind === "btp") {
@@ -11158,6 +11325,60 @@ var ToonClient = class {
11158
11325
  depositTotal: result.depositTotal.toString()
11159
11326
  };
11160
11327
  }
11328
+ /**
11329
+ * Close a channel to begin the settlement grace period (first half of
11330
+ * withdraw). Records `closedAt`/`settleableAt` (unix seconds) on the tracked
11331
+ * channel — persisted, so the grace timer survives a daemon restart. Spends
11332
+ * on-chain. EVM today; Solana/Mina are follow-ups.
11333
+ */
11334
+ async closeChannel(channelId) {
11335
+ if (!this.channelManager) throw new Error("ChannelManager not initialized");
11336
+ if (!this.onChainChannelClient) {
11337
+ throw new Error("On-chain channel client not configured (no chainRpcUrls).");
11338
+ }
11339
+ const r = await this.onChainChannelClient.closeChannel(channelId);
11340
+ this.channelManager.setChannelClosed(channelId, r.closedAt, r.settleableAt);
11341
+ return {
11342
+ channelId,
11343
+ ...r.txHash ? { txHash: r.txHash } : {},
11344
+ closedAt: r.closedAt.toString(),
11345
+ settleableAt: r.settleableAt.toString()
11346
+ };
11347
+ }
11348
+ /**
11349
+ * Settle a closed channel to release collateral (second half of withdraw).
11350
+ * THE time guard: never settle before `settleableAt`. A too-early call throws
11351
+ * a retryable error (carrying the remaining seconds) BEFORE spending gas — the
11352
+ * contract would revert anyway. Spends on-chain. EVM today.
11353
+ */
11354
+ async settleChannel(channelId) {
11355
+ if (!this.channelManager) throw new Error("ChannelManager not initialized");
11356
+ if (!this.onChainChannelClient) {
11357
+ throw new Error("On-chain channel client not configured (no chainRpcUrls).");
11358
+ }
11359
+ const settleableAt = this.channelManager.getSettleableAt(channelId);
11360
+ if (settleableAt === void 0) {
11361
+ throw new Error(`Channel "${channelId}" is not closed; call closeChannel first.`);
11362
+ }
11363
+ const nowSec = BigInt(Math.floor(Date.now() / 1e3));
11364
+ if (nowSec < settleableAt) {
11365
+ const remaining = settleableAt - nowSec;
11366
+ throw Object.assign(
11367
+ new Error(
11368
+ `Channel "${channelId}" is not settleable yet \u2014 ${remaining}s remain (settleable at ${settleableAt}).`
11369
+ ),
11370
+ { name: "SettleTooEarlyError", retryable: true, settleableAt: settleableAt.toString() }
11371
+ );
11372
+ }
11373
+ const r = await this.onChainChannelClient.settleChannel(channelId);
11374
+ this.channelManager.setChannelSettled(channelId, nowSec);
11375
+ return { channelId, ...r.txHash ? { txHash: r.txHash } : {} };
11376
+ }
11377
+ /** Where a tracked channel sits in the withdraw journey. */
11378
+ getChannelCloseState(channelId) {
11379
+ if (!this.channelManager) throw new Error("ChannelManager not initialized");
11380
+ return this.channelManager.getChannelCloseState(channelId);
11381
+ }
11161
11382
  /**
11162
11383
  * Read the on-chain settlement-token balance of this client's OWN wallet on
11163
11384
  * each configured chain (EVM token, Solana SPL, native MINA). A free read — no
@@ -11525,6 +11746,18 @@ function writeKeystoreFile(path, keystore) {
11525
11746
  });
11526
11747
  }
11527
11748
 
11749
+ // ../arweave/dist/gateways.js
11750
+ var ARWEAVE_GATEWAYS = [
11751
+ "https://ar-io.dev",
11752
+ "https://arweave.net",
11753
+ "https://permagate.io"
11754
+ ];
11755
+ function arweaveUrls(txId, gateways = ARWEAVE_GATEWAYS) {
11756
+ const all = (gateways.length ? gateways : ARWEAVE_GATEWAYS).map((g) => `${g}/${txId}`);
11757
+ const [url, ...fallbacks] = all;
11758
+ return { url: url ?? `${ARWEAVE_GATEWAYS[0]}/${txId}`, fallbacks };
11759
+ }
11760
+
11528
11761
  // src/daemon/config.ts
11529
11762
  import { readFileSync as readFileSync3 } from "fs";
11530
11763
  import { homedir } from "os";
@@ -12080,18 +12313,6 @@ function buildIlpPrepare(params) {
12080
12313
  };
12081
12314
  }
12082
12315
 
12083
- // ../arweave/dist/gateways.js
12084
- var ARWEAVE_GATEWAYS = [
12085
- "https://ar-io.dev",
12086
- "https://arweave.net",
12087
- "https://permagate.io"
12088
- ];
12089
- function arweaveUrls(txId, gateways = ARWEAVE_GATEWAYS) {
12090
- const all = (gateways.length ? gateways : ARWEAVE_GATEWAYS).map((g) => `${g}/${txId}`);
12091
- const [url, ...fallbacks] = all;
12092
- return { url: url ?? `${ARWEAVE_GATEWAYS[0]}/${txId}`, fallbacks };
12093
- }
12094
-
12095
12316
  // src/daemon/config.ts
12096
12317
  var DEFAULT_KEYSTORE_PASSWORD = "toon-client-default";
12097
12318
  function configDir() {
@@ -12335,6 +12556,12 @@ var ControlClient = class {
12335
12556
  depositToChannel(body) {
12336
12557
  return this.request("POST", "/channels/deposit", body);
12337
12558
  }
12559
+ closeChannel(body) {
12560
+ return this.request("POST", "/channels/close", body);
12561
+ }
12562
+ settleChannel(body) {
12563
+ return this.request("POST", "/channels/settle", body);
12564
+ }
12338
12565
  swap(body) {
12339
12566
  return this.request("POST", "/swap", body);
12340
12567
  }
@@ -12503,6 +12730,7 @@ export {
12503
12730
  parseIlpPeerInfo2 as parseIlpPeerInfo,
12504
12731
  isEventExpired,
12505
12732
  buildIlpPrepare,
12733
+ ARWEAVE_GATEWAYS,
12506
12734
  arweaveUrls,
12507
12735
  DEFAULT_KEYSTORE_PASSWORD,
12508
12736
  configDir,
@@ -12540,4 +12768,4 @@ export {
12540
12768
  @scure/bip32/lib/esm/index.js:
12541
12769
  (*! scure-bip32 - MIT License (c) 2022 Patricio Palladino, Paul Miller (paulmillr.com) *)
12542
12770
  */
12543
- //# sourceMappingURL=chunk-5Z74D23J.js.map
12771
+ //# sourceMappingURL=chunk-XYR3ACYX.js.map