@toon-protocol/client 0.14.7 → 0.14.8
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 +82 -0
- package/dist/index.js +231 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1240,6 +1240,30 @@ declare class ToonClient {
|
|
|
1240
1240
|
txHash?: string;
|
|
1241
1241
|
depositTotal: string;
|
|
1242
1242
|
}>;
|
|
1243
|
+
/**
|
|
1244
|
+
* Close a channel to begin the settlement grace period (first half of
|
|
1245
|
+
* withdraw). Records `closedAt`/`settleableAt` (unix seconds) on the tracked
|
|
1246
|
+
* channel — persisted, so the grace timer survives a daemon restart. Spends
|
|
1247
|
+
* on-chain. EVM today; Solana/Mina are follow-ups.
|
|
1248
|
+
*/
|
|
1249
|
+
closeChannel(channelId: string): Promise<{
|
|
1250
|
+
channelId: string;
|
|
1251
|
+
txHash?: string;
|
|
1252
|
+
closedAt: string;
|
|
1253
|
+
settleableAt: string;
|
|
1254
|
+
}>;
|
|
1255
|
+
/**
|
|
1256
|
+
* Settle a closed channel to release collateral (second half of withdraw).
|
|
1257
|
+
* THE time guard: never settle before `settleableAt`. A too-early call throws
|
|
1258
|
+
* a retryable error (carrying the remaining seconds) BEFORE spending gas — the
|
|
1259
|
+
* contract would revert anyway. Spends on-chain. EVM today.
|
|
1260
|
+
*/
|
|
1261
|
+
settleChannel(channelId: string): Promise<{
|
|
1262
|
+
channelId: string;
|
|
1263
|
+
txHash?: string;
|
|
1264
|
+
}>;
|
|
1265
|
+
/** Where a tracked channel sits in the withdraw journey. */
|
|
1266
|
+
getChannelCloseState(channelId: string): 'open' | 'closing' | 'settleable' | 'settled';
|
|
1243
1267
|
/**
|
|
1244
1268
|
* Read the on-chain settlement-token balance of this client's OWN wallet on
|
|
1245
1269
|
* each configured chain (EVM token, Solana SPL, native MINA). A free read — no
|
|
@@ -2179,6 +2203,41 @@ declare class OnChainChannelClient implements ConnectorChannelClient {
|
|
|
2179
2203
|
* caller-supplied current locked amount.
|
|
2180
2204
|
*/
|
|
2181
2205
|
private depositEvm;
|
|
2206
|
+
/**
|
|
2207
|
+
* Close a channel to begin the settlement grace period. Dispatches by the
|
|
2208
|
+
* channel's cached chain context. EVM `closeChannel` is unilateral (channelId
|
|
2209
|
+
* only); after it confirms we read the `channels()` view for the AUTHORITATIVE
|
|
2210
|
+
* `closedAt` + `settlementTimeout` (block-timestamp seconds) and compute
|
|
2211
|
+
* `settleableAt = closedAt + settlementTimeout`. Solana/Mina are follow-ups.
|
|
2212
|
+
*/
|
|
2213
|
+
closeChannel(channelId: string): Promise<{
|
|
2214
|
+
txHash?: string;
|
|
2215
|
+
closedAt: bigint;
|
|
2216
|
+
settlementTimeout: bigint;
|
|
2217
|
+
settleableAt: bigint;
|
|
2218
|
+
}>;
|
|
2219
|
+
/**
|
|
2220
|
+
* Settle a closed channel after its grace period to release collateral. EVM
|
|
2221
|
+
* `settleChannel` is unilateral (channelId only); the contract itself reverts
|
|
2222
|
+
* before `closedAt + settlementTimeout`, so an early call surfaces as a tx
|
|
2223
|
+
* revert here — but the caller (ToonClient/daemon) enforces the time guard
|
|
2224
|
+
* BEFORE spending gas. Solana/Mina are follow-ups.
|
|
2225
|
+
*/
|
|
2226
|
+
settleChannel(channelId: string): Promise<{
|
|
2227
|
+
txHash?: string;
|
|
2228
|
+
}>;
|
|
2229
|
+
/**
|
|
2230
|
+
* Read the EVM channel's close-relevant fields so a restarted daemon can
|
|
2231
|
+
* recompute the grace timer from chain (chain is authoritative). EVM-only.
|
|
2232
|
+
*/
|
|
2233
|
+
getChannelCloseInfo(channelId: string): Promise<{
|
|
2234
|
+
status: ChannelState['status'];
|
|
2235
|
+
closedAt: bigint;
|
|
2236
|
+
settlementTimeout: bigint;
|
|
2237
|
+
settleableAt: bigint;
|
|
2238
|
+
}>;
|
|
2239
|
+
/** Read + destructure the EVM `channels(bytes32)` view. */
|
|
2240
|
+
private readEvmChannel;
|
|
2182
2241
|
/**
|
|
2183
2242
|
* Opens a REAL on-chain Solana payment channel.
|
|
2184
2243
|
*
|
|
@@ -2242,6 +2301,12 @@ declare class OnChainChannelClient implements ConnectorChannelClient {
|
|
|
2242
2301
|
interface ChannelStoreEntry {
|
|
2243
2302
|
nonce: number;
|
|
2244
2303
|
cumulativeAmount: bigint;
|
|
2304
|
+
/** Unix SECONDS when close was initiated (withdraw flow). */
|
|
2305
|
+
closedAt?: bigint;
|
|
2306
|
+
/** Unix SECONDS the channel becomes settleable (= closedAt + settlementTimeout). */
|
|
2307
|
+
settleableAt?: bigint;
|
|
2308
|
+
/** Unix SECONDS the channel was settled (collateral released). */
|
|
2309
|
+
settledAt?: bigint;
|
|
2245
2310
|
}
|
|
2246
2311
|
/**
|
|
2247
2312
|
* Persistence interface for payment channel nonce/amount state.
|
|
@@ -2355,6 +2420,23 @@ declare class ChannelManager {
|
|
|
2355
2420
|
* collateral on the next read.
|
|
2356
2421
|
*/
|
|
2357
2422
|
setDepositTotal(channelId: string, total: bigint): void;
|
|
2423
|
+
/** Persist a channel's full nonce/amount + withdraw-timer state to the store. */
|
|
2424
|
+
private persist;
|
|
2425
|
+
/**
|
|
2426
|
+
* Record that a channel was closed (withdraw flow): stores `closedAt` +
|
|
2427
|
+
* `settleableAt` (unix SECONDS) so the grace timer survives a daemon restart.
|
|
2428
|
+
*/
|
|
2429
|
+
setChannelClosed(channelId: string, closedAt: bigint, settleableAt: bigint): void;
|
|
2430
|
+
/** Record that a channel was settled (collateral released). */
|
|
2431
|
+
setChannelSettled(channelId: string, settledAt: bigint): void;
|
|
2432
|
+
/** The `settleableAt` timestamp (unix seconds) for a closed channel, if set. */
|
|
2433
|
+
getSettleableAt(channelId: string): bigint | undefined;
|
|
2434
|
+
/**
|
|
2435
|
+
* Where a channel sits in the withdraw journey, from the tracked timers:
|
|
2436
|
+
* `open` (never closed) → `closing` (closed, grace not elapsed) →
|
|
2437
|
+
* `settleable` (grace elapsed) → `settled`. `nowSec` is injectable for tests.
|
|
2438
|
+
*/
|
|
2439
|
+
getChannelCloseState(channelId: string, nowSec?: bigint): 'open' | 'closing' | 'settleable' | 'settled';
|
|
2358
2440
|
/**
|
|
2359
2441
|
* Gets all tracked channel IDs.
|
|
2360
2442
|
*/
|
package/dist/index.js
CHANGED
|
@@ -2272,6 +2272,20 @@ var TOKEN_NETWORK_ABI = [
|
|
|
2272
2272
|
],
|
|
2273
2273
|
outputs: []
|
|
2274
2274
|
},
|
|
2275
|
+
{
|
|
2276
|
+
name: "closeChannel",
|
|
2277
|
+
type: "function",
|
|
2278
|
+
stateMutability: "nonpayable",
|
|
2279
|
+
inputs: [{ name: "channelId", type: "bytes32" }],
|
|
2280
|
+
outputs: []
|
|
2281
|
+
},
|
|
2282
|
+
{
|
|
2283
|
+
name: "settleChannel",
|
|
2284
|
+
type: "function",
|
|
2285
|
+
stateMutability: "nonpayable",
|
|
2286
|
+
inputs: [{ name: "channelId", type: "bytes32" }],
|
|
2287
|
+
outputs: []
|
|
2288
|
+
},
|
|
2275
2289
|
{
|
|
2276
2290
|
name: "channels",
|
|
2277
2291
|
type: "function",
|
|
@@ -2520,6 +2534,103 @@ var OnChainChannelClient = class {
|
|
|
2520
2534
|
await publicClient.waitForTransactionReceipt({ hash: depositHash });
|
|
2521
2535
|
return { txHash: depositHash, depositTotal: newTotal };
|
|
2522
2536
|
}
|
|
2537
|
+
/**
|
|
2538
|
+
* Close a channel to begin the settlement grace period. Dispatches by the
|
|
2539
|
+
* channel's cached chain context. EVM `closeChannel` is unilateral (channelId
|
|
2540
|
+
* only); after it confirms we read the `channels()` view for the AUTHORITATIVE
|
|
2541
|
+
* `closedAt` + `settlementTimeout` (block-timestamp seconds) and compute
|
|
2542
|
+
* `settleableAt = closedAt + settlementTimeout`. Solana/Mina are follow-ups.
|
|
2543
|
+
*/
|
|
2544
|
+
async closeChannel(channelId) {
|
|
2545
|
+
const ctx = this.channelContext.get(channelId);
|
|
2546
|
+
if (!ctx) {
|
|
2547
|
+
throw new Error(
|
|
2548
|
+
`No on-chain context for channel "${channelId}" \u2014 it must be opened by this client first.`
|
|
2549
|
+
);
|
|
2550
|
+
}
|
|
2551
|
+
const chainPrefix = ctx.chain.split(":")[0];
|
|
2552
|
+
if (chainPrefix === "solana" || chainPrefix === "mina") {
|
|
2553
|
+
throw new Error(
|
|
2554
|
+
`Close on ${chainPrefix} is not yet supported (EVM today; Solana/Mina follow-up).`
|
|
2555
|
+
);
|
|
2556
|
+
}
|
|
2557
|
+
const { publicClient, walletClient } = this.createClients(ctx.chain);
|
|
2558
|
+
const tokenNetworkAddr = ctx.tokenNetworkAddress;
|
|
2559
|
+
const closeHash = await walletClient.writeContract({
|
|
2560
|
+
address: tokenNetworkAddr,
|
|
2561
|
+
abi: TOKEN_NETWORK_ABI,
|
|
2562
|
+
functionName: "closeChannel",
|
|
2563
|
+
args: [channelId]
|
|
2564
|
+
});
|
|
2565
|
+
await publicClient.waitForTransactionReceipt({ hash: closeHash });
|
|
2566
|
+
const info = await this.readEvmChannel(publicClient, tokenNetworkAddr, channelId);
|
|
2567
|
+
return {
|
|
2568
|
+
txHash: closeHash,
|
|
2569
|
+
closedAt: info.closedAt,
|
|
2570
|
+
settlementTimeout: info.settlementTimeout,
|
|
2571
|
+
settleableAt: info.closedAt + info.settlementTimeout
|
|
2572
|
+
};
|
|
2573
|
+
}
|
|
2574
|
+
/**
|
|
2575
|
+
* Settle a closed channel after its grace period to release collateral. EVM
|
|
2576
|
+
* `settleChannel` is unilateral (channelId only); the contract itself reverts
|
|
2577
|
+
* before `closedAt + settlementTimeout`, so an early call surfaces as a tx
|
|
2578
|
+
* revert here — but the caller (ToonClient/daemon) enforces the time guard
|
|
2579
|
+
* BEFORE spending gas. Solana/Mina are follow-ups.
|
|
2580
|
+
*/
|
|
2581
|
+
async settleChannel(channelId) {
|
|
2582
|
+
const ctx = this.channelContext.get(channelId);
|
|
2583
|
+
if (!ctx) {
|
|
2584
|
+
throw new Error(
|
|
2585
|
+
`No on-chain context for channel "${channelId}" \u2014 it must be opened by this client first.`
|
|
2586
|
+
);
|
|
2587
|
+
}
|
|
2588
|
+
const chainPrefix = ctx.chain.split(":")[0];
|
|
2589
|
+
if (chainPrefix === "solana" || chainPrefix === "mina") {
|
|
2590
|
+
throw new Error(
|
|
2591
|
+
`Settle on ${chainPrefix} is not yet supported (EVM today; Solana/Mina follow-up).`
|
|
2592
|
+
);
|
|
2593
|
+
}
|
|
2594
|
+
const { publicClient, walletClient } = this.createClients(ctx.chain);
|
|
2595
|
+
const settleHash = await walletClient.writeContract({
|
|
2596
|
+
address: ctx.tokenNetworkAddress,
|
|
2597
|
+
abi: TOKEN_NETWORK_ABI,
|
|
2598
|
+
functionName: "settleChannel",
|
|
2599
|
+
args: [channelId]
|
|
2600
|
+
});
|
|
2601
|
+
await publicClient.waitForTransactionReceipt({ hash: settleHash });
|
|
2602
|
+
return { txHash: settleHash };
|
|
2603
|
+
}
|
|
2604
|
+
/**
|
|
2605
|
+
* Read the EVM channel's close-relevant fields so a restarted daemon can
|
|
2606
|
+
* recompute the grace timer from chain (chain is authoritative). EVM-only.
|
|
2607
|
+
*/
|
|
2608
|
+
async getChannelCloseInfo(channelId) {
|
|
2609
|
+
const ctx = this.channelContext.get(channelId);
|
|
2610
|
+
if (!ctx) throw new Error(`No on-chain context for channel "${channelId}".`);
|
|
2611
|
+
const chainPrefix = ctx.chain.split(":")[0];
|
|
2612
|
+
if (chainPrefix === "solana" || chainPrefix === "mina") {
|
|
2613
|
+
throw new Error(`getChannelCloseInfo on ${chainPrefix} is not supported.`);
|
|
2614
|
+
}
|
|
2615
|
+
const { publicClient } = this.createClients(ctx.chain);
|
|
2616
|
+
const info = await this.readEvmChannel(publicClient, ctx.tokenNetworkAddress, channelId);
|
|
2617
|
+
return {
|
|
2618
|
+
status: STATE_MAP2[info.state] ?? "open",
|
|
2619
|
+
closedAt: info.closedAt,
|
|
2620
|
+
settlementTimeout: info.settlementTimeout,
|
|
2621
|
+
settleableAt: info.closedAt + info.settlementTimeout
|
|
2622
|
+
};
|
|
2623
|
+
}
|
|
2624
|
+
/** Read + destructure the EVM `channels(bytes32)` view. */
|
|
2625
|
+
async readEvmChannel(publicClient, tokenNetworkAddr, channelId) {
|
|
2626
|
+
const res = await publicClient.readContract({
|
|
2627
|
+
address: tokenNetworkAddr,
|
|
2628
|
+
abi: TOKEN_NETWORK_ABI,
|
|
2629
|
+
functionName: "channels",
|
|
2630
|
+
args: [channelId]
|
|
2631
|
+
});
|
|
2632
|
+
return { settlementTimeout: res[0], state: Number(res[1]), closedAt: res[2] };
|
|
2633
|
+
}
|
|
2523
2634
|
/**
|
|
2524
2635
|
* Opens a REAL on-chain Solana payment channel.
|
|
2525
2636
|
*
|
|
@@ -3484,7 +3595,12 @@ var ChannelManager = class {
|
|
|
3484
3595
|
tokenNetworkAddress: tnAddr,
|
|
3485
3596
|
tokenAddress: chainContext?.tokenAddress,
|
|
3486
3597
|
recipient: chainContext?.recipient,
|
|
3487
|
-
depositTotal: chainContext?.depositTotal
|
|
3598
|
+
depositTotal: chainContext?.depositTotal,
|
|
3599
|
+
// Resume the withdraw-flow timers so a daemon restart mid-grace
|
|
3600
|
+
// doesn't strand funds (the gate can't be evaluated without them).
|
|
3601
|
+
...persisted.closedAt !== void 0 ? { closedAt: persisted.closedAt } : {},
|
|
3602
|
+
...persisted.settleableAt !== void 0 ? { settleableAt: persisted.settleableAt } : {},
|
|
3603
|
+
...persisted.settledAt !== void 0 ? { settledAt: persisted.settledAt } : {}
|
|
3488
3604
|
});
|
|
3489
3605
|
return;
|
|
3490
3606
|
}
|
|
@@ -3519,12 +3635,7 @@ var ChannelManager = class {
|
|
|
3519
3635
|
}
|
|
3520
3636
|
tracking.nonce += 1;
|
|
3521
3637
|
tracking.cumulativeAmount += additionalAmount;
|
|
3522
|
-
|
|
3523
|
-
this.store.save(channelId, {
|
|
3524
|
-
nonce: tracking.nonce,
|
|
3525
|
-
cumulativeAmount: tracking.cumulativeAmount
|
|
3526
|
-
});
|
|
3527
|
-
}
|
|
3638
|
+
this.persist(channelId);
|
|
3528
3639
|
const signer = this.chainSigners.get(tracking.chainType);
|
|
3529
3640
|
if (signer && tracking.chainType !== "evm") {
|
|
3530
3641
|
if (!tracking.recipient) {
|
|
@@ -3624,6 +3735,57 @@ var ChannelManager = class {
|
|
|
3624
3735
|
}
|
|
3625
3736
|
tracking.depositTotal = total;
|
|
3626
3737
|
}
|
|
3738
|
+
/** Persist a channel's full nonce/amount + withdraw-timer state to the store. */
|
|
3739
|
+
persist(channelId) {
|
|
3740
|
+
if (!this.store) return;
|
|
3741
|
+
const t = this.channels.get(channelId);
|
|
3742
|
+
if (!t) return;
|
|
3743
|
+
this.store.save(channelId, {
|
|
3744
|
+
nonce: t.nonce,
|
|
3745
|
+
cumulativeAmount: t.cumulativeAmount,
|
|
3746
|
+
...t.closedAt !== void 0 ? { closedAt: t.closedAt } : {},
|
|
3747
|
+
...t.settleableAt !== void 0 ? { settleableAt: t.settleableAt } : {},
|
|
3748
|
+
...t.settledAt !== void 0 ? { settledAt: t.settledAt } : {}
|
|
3749
|
+
});
|
|
3750
|
+
}
|
|
3751
|
+
/**
|
|
3752
|
+
* Record that a channel was closed (withdraw flow): stores `closedAt` +
|
|
3753
|
+
* `settleableAt` (unix SECONDS) so the grace timer survives a daemon restart.
|
|
3754
|
+
*/
|
|
3755
|
+
setChannelClosed(channelId, closedAt, settleableAt) {
|
|
3756
|
+
const tracking = this.channels.get(channelId);
|
|
3757
|
+
if (!tracking) {
|
|
3758
|
+
throw new Error(`Channel "${channelId}" is not being tracked.`);
|
|
3759
|
+
}
|
|
3760
|
+
tracking.closedAt = closedAt;
|
|
3761
|
+
tracking.settleableAt = settleableAt;
|
|
3762
|
+
this.persist(channelId);
|
|
3763
|
+
}
|
|
3764
|
+
/** Record that a channel was settled (collateral released). */
|
|
3765
|
+
setChannelSettled(channelId, settledAt) {
|
|
3766
|
+
const tracking = this.channels.get(channelId);
|
|
3767
|
+
if (!tracking) {
|
|
3768
|
+
throw new Error(`Channel "${channelId}" is not being tracked.`);
|
|
3769
|
+
}
|
|
3770
|
+
tracking.settledAt = settledAt;
|
|
3771
|
+
this.persist(channelId);
|
|
3772
|
+
}
|
|
3773
|
+
/** The `settleableAt` timestamp (unix seconds) for a closed channel, if set. */
|
|
3774
|
+
getSettleableAt(channelId) {
|
|
3775
|
+
return this.channels.get(channelId)?.settleableAt;
|
|
3776
|
+
}
|
|
3777
|
+
/**
|
|
3778
|
+
* Where a channel sits in the withdraw journey, from the tracked timers:
|
|
3779
|
+
* `open` (never closed) → `closing` (closed, grace not elapsed) →
|
|
3780
|
+
* `settleable` (grace elapsed) → `settled`. `nowSec` is injectable for tests.
|
|
3781
|
+
*/
|
|
3782
|
+
getChannelCloseState(channelId, nowSec = BigInt(Math.floor(Date.now() / 1e3))) {
|
|
3783
|
+
const t = this.channels.get(channelId);
|
|
3784
|
+
if (!t || t.closedAt === void 0) return "open";
|
|
3785
|
+
if (t.settledAt !== void 0) return "settled";
|
|
3786
|
+
if (t.settleableAt !== void 0 && nowSec >= t.settleableAt) return "settleable";
|
|
3787
|
+
return "closing";
|
|
3788
|
+
}
|
|
3627
3789
|
/**
|
|
3628
3790
|
* Gets all tracked channel IDs.
|
|
3629
3791
|
*/
|
|
@@ -3649,7 +3811,10 @@ var JsonFileChannelStore = class {
|
|
|
3649
3811
|
const data = this.readFile();
|
|
3650
3812
|
data[channelId] = {
|
|
3651
3813
|
nonce: tracking.nonce,
|
|
3652
|
-
cumulativeAmount: tracking.cumulativeAmount.toString()
|
|
3814
|
+
cumulativeAmount: tracking.cumulativeAmount.toString(),
|
|
3815
|
+
...tracking.closedAt !== void 0 ? { closedAt: tracking.closedAt.toString() } : {},
|
|
3816
|
+
...tracking.settleableAt !== void 0 ? { settleableAt: tracking.settleableAt.toString() } : {},
|
|
3817
|
+
...tracking.settledAt !== void 0 ? { settledAt: tracking.settledAt.toString() } : {}
|
|
3653
3818
|
};
|
|
3654
3819
|
this.writeFile(data);
|
|
3655
3820
|
}
|
|
@@ -3659,7 +3824,10 @@ var JsonFileChannelStore = class {
|
|
|
3659
3824
|
if (!entry) return void 0;
|
|
3660
3825
|
return {
|
|
3661
3826
|
nonce: entry.nonce,
|
|
3662
|
-
cumulativeAmount: BigInt(entry.cumulativeAmount)
|
|
3827
|
+
cumulativeAmount: BigInt(entry.cumulativeAmount),
|
|
3828
|
+
...entry.closedAt !== void 0 ? { closedAt: BigInt(entry.closedAt) } : {},
|
|
3829
|
+
...entry.settleableAt !== void 0 ? { settleableAt: BigInt(entry.settleableAt) } : {},
|
|
3830
|
+
...entry.settledAt !== void 0 ? { settledAt: BigInt(entry.settledAt) } : {}
|
|
3663
3831
|
};
|
|
3664
3832
|
}
|
|
3665
3833
|
list() {
|
|
@@ -4789,6 +4957,60 @@ var ToonClient = class {
|
|
|
4789
4957
|
depositTotal: result.depositTotal.toString()
|
|
4790
4958
|
};
|
|
4791
4959
|
}
|
|
4960
|
+
/**
|
|
4961
|
+
* Close a channel to begin the settlement grace period (first half of
|
|
4962
|
+
* withdraw). Records `closedAt`/`settleableAt` (unix seconds) on the tracked
|
|
4963
|
+
* channel — persisted, so the grace timer survives a daemon restart. Spends
|
|
4964
|
+
* on-chain. EVM today; Solana/Mina are follow-ups.
|
|
4965
|
+
*/
|
|
4966
|
+
async closeChannel(channelId) {
|
|
4967
|
+
if (!this.channelManager) throw new Error("ChannelManager not initialized");
|
|
4968
|
+
if (!this.onChainChannelClient) {
|
|
4969
|
+
throw new Error("On-chain channel client not configured (no chainRpcUrls).");
|
|
4970
|
+
}
|
|
4971
|
+
const r = await this.onChainChannelClient.closeChannel(channelId);
|
|
4972
|
+
this.channelManager.setChannelClosed(channelId, r.closedAt, r.settleableAt);
|
|
4973
|
+
return {
|
|
4974
|
+
channelId,
|
|
4975
|
+
...r.txHash ? { txHash: r.txHash } : {},
|
|
4976
|
+
closedAt: r.closedAt.toString(),
|
|
4977
|
+
settleableAt: r.settleableAt.toString()
|
|
4978
|
+
};
|
|
4979
|
+
}
|
|
4980
|
+
/**
|
|
4981
|
+
* Settle a closed channel to release collateral (second half of withdraw).
|
|
4982
|
+
* THE time guard: never settle before `settleableAt`. A too-early call throws
|
|
4983
|
+
* a retryable error (carrying the remaining seconds) BEFORE spending gas — the
|
|
4984
|
+
* contract would revert anyway. Spends on-chain. EVM today.
|
|
4985
|
+
*/
|
|
4986
|
+
async settleChannel(channelId) {
|
|
4987
|
+
if (!this.channelManager) throw new Error("ChannelManager not initialized");
|
|
4988
|
+
if (!this.onChainChannelClient) {
|
|
4989
|
+
throw new Error("On-chain channel client not configured (no chainRpcUrls).");
|
|
4990
|
+
}
|
|
4991
|
+
const settleableAt = this.channelManager.getSettleableAt(channelId);
|
|
4992
|
+
if (settleableAt === void 0) {
|
|
4993
|
+
throw new Error(`Channel "${channelId}" is not closed; call closeChannel first.`);
|
|
4994
|
+
}
|
|
4995
|
+
const nowSec = BigInt(Math.floor(Date.now() / 1e3));
|
|
4996
|
+
if (nowSec < settleableAt) {
|
|
4997
|
+
const remaining = settleableAt - nowSec;
|
|
4998
|
+
throw Object.assign(
|
|
4999
|
+
new Error(
|
|
5000
|
+
`Channel "${channelId}" is not settleable yet \u2014 ${remaining}s remain (settleable at ${settleableAt}).`
|
|
5001
|
+
),
|
|
5002
|
+
{ name: "SettleTooEarlyError", retryable: true, settleableAt: settleableAt.toString() }
|
|
5003
|
+
);
|
|
5004
|
+
}
|
|
5005
|
+
const r = await this.onChainChannelClient.settleChannel(channelId);
|
|
5006
|
+
this.channelManager.setChannelSettled(channelId, nowSec);
|
|
5007
|
+
return { channelId, ...r.txHash ? { txHash: r.txHash } : {} };
|
|
5008
|
+
}
|
|
5009
|
+
/** Where a tracked channel sits in the withdraw journey. */
|
|
5010
|
+
getChannelCloseState(channelId) {
|
|
5011
|
+
if (!this.channelManager) throw new Error("ChannelManager not initialized");
|
|
5012
|
+
return this.channelManager.getChannelCloseState(channelId);
|
|
5013
|
+
}
|
|
4792
5014
|
/**
|
|
4793
5015
|
* Read the on-chain settlement-token balance of this client's OWN wallet on
|
|
4794
5016
|
* each configured chain (EVM token, Solana SPL, native MINA). A free read — no
|