@toon-protocol/client-mcp 0.10.1 → 0.10.2

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() {
@@ -11158,6 +11326,60 @@ var ToonClient = class {
11158
11326
  depositTotal: result.depositTotal.toString()
11159
11327
  };
11160
11328
  }
11329
+ /**
11330
+ * Close a channel to begin the settlement grace period (first half of
11331
+ * withdraw). Records `closedAt`/`settleableAt` (unix seconds) on the tracked
11332
+ * channel — persisted, so the grace timer survives a daemon restart. Spends
11333
+ * on-chain. EVM today; Solana/Mina are follow-ups.
11334
+ */
11335
+ async closeChannel(channelId) {
11336
+ if (!this.channelManager) throw new Error("ChannelManager not initialized");
11337
+ if (!this.onChainChannelClient) {
11338
+ throw new Error("On-chain channel client not configured (no chainRpcUrls).");
11339
+ }
11340
+ const r = await this.onChainChannelClient.closeChannel(channelId);
11341
+ this.channelManager.setChannelClosed(channelId, r.closedAt, r.settleableAt);
11342
+ return {
11343
+ channelId,
11344
+ ...r.txHash ? { txHash: r.txHash } : {},
11345
+ closedAt: r.closedAt.toString(),
11346
+ settleableAt: r.settleableAt.toString()
11347
+ };
11348
+ }
11349
+ /**
11350
+ * Settle a closed channel to release collateral (second half of withdraw).
11351
+ * THE time guard: never settle before `settleableAt`. A too-early call throws
11352
+ * a retryable error (carrying the remaining seconds) BEFORE spending gas — the
11353
+ * contract would revert anyway. Spends on-chain. EVM today.
11354
+ */
11355
+ async settleChannel(channelId) {
11356
+ if (!this.channelManager) throw new Error("ChannelManager not initialized");
11357
+ if (!this.onChainChannelClient) {
11358
+ throw new Error("On-chain channel client not configured (no chainRpcUrls).");
11359
+ }
11360
+ const settleableAt = this.channelManager.getSettleableAt(channelId);
11361
+ if (settleableAt === void 0) {
11362
+ throw new Error(`Channel "${channelId}" is not closed; call closeChannel first.`);
11363
+ }
11364
+ const nowSec = BigInt(Math.floor(Date.now() / 1e3));
11365
+ if (nowSec < settleableAt) {
11366
+ const remaining = settleableAt - nowSec;
11367
+ throw Object.assign(
11368
+ new Error(
11369
+ `Channel "${channelId}" is not settleable yet \u2014 ${remaining}s remain (settleable at ${settleableAt}).`
11370
+ ),
11371
+ { name: "SettleTooEarlyError", retryable: true, settleableAt: settleableAt.toString() }
11372
+ );
11373
+ }
11374
+ const r = await this.onChainChannelClient.settleChannel(channelId);
11375
+ this.channelManager.setChannelSettled(channelId, nowSec);
11376
+ return { channelId, ...r.txHash ? { txHash: r.txHash } : {} };
11377
+ }
11378
+ /** Where a tracked channel sits in the withdraw journey. */
11379
+ getChannelCloseState(channelId) {
11380
+ if (!this.channelManager) throw new Error("ChannelManager not initialized");
11381
+ return this.channelManager.getChannelCloseState(channelId);
11382
+ }
11161
11383
  /**
11162
11384
  * Read the on-chain settlement-token balance of this client's OWN wallet on
11163
11385
  * each configured chain (EVM token, Solana SPL, native MINA). A free read — no
@@ -12335,6 +12557,12 @@ var ControlClient = class {
12335
12557
  depositToChannel(body) {
12336
12558
  return this.request("POST", "/channels/deposit", body);
12337
12559
  }
12560
+ closeChannel(body) {
12561
+ return this.request("POST", "/channels/close", body);
12562
+ }
12563
+ settleChannel(body) {
12564
+ return this.request("POST", "/channels/settle", body);
12565
+ }
12338
12566
  swap(body) {
12339
12567
  return this.request("POST", "/swap", body);
12340
12568
  }
@@ -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-BO5F3VT5.js.map