@toon-protocol/client 0.4.2 → 0.5.0
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 +45 -2
- package/dist/index.js +146 -44
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -160,6 +160,12 @@ interface SignedBalanceProof extends BalanceProofParams {
|
|
|
160
160
|
signature: string;
|
|
161
161
|
/** Address of the signer */
|
|
162
162
|
signerAddress: string;
|
|
163
|
+
/** Chain ID used in EIP-712 domain (e.g. 421614 for Arb Sepolia) */
|
|
164
|
+
chainId: number;
|
|
165
|
+
/** TokenNetwork contract address used in EIP-712 domain */
|
|
166
|
+
tokenNetworkAddress: string;
|
|
167
|
+
/** ERC-20 token address (e.g. USDC) for self-describing claim verification */
|
|
168
|
+
tokenAddress?: string;
|
|
163
169
|
}
|
|
164
170
|
|
|
165
171
|
/**
|
|
@@ -271,6 +277,22 @@ declare class ToonClient {
|
|
|
271
277
|
* Gets list of tracked payment channel IDs.
|
|
272
278
|
*/
|
|
273
279
|
getTrackedChannels(): string[];
|
|
280
|
+
/**
|
|
281
|
+
* Gets the current nonce for a tracked channel.
|
|
282
|
+
*/
|
|
283
|
+
getChannelNonce(channelId: string): number;
|
|
284
|
+
/**
|
|
285
|
+
* Gets the cumulative transferred amount for a tracked channel.
|
|
286
|
+
*/
|
|
287
|
+
getChannelCumulativeAmount(channelId: string): bigint;
|
|
288
|
+
/**
|
|
289
|
+
* Extracts chain context (chainId + tokenNetworkAddress) from a chain key like 'evm:base:421614'.
|
|
290
|
+
*/
|
|
291
|
+
private getChainContext;
|
|
292
|
+
/**
|
|
293
|
+
* Gets the default chain context from the first supported chain in config.
|
|
294
|
+
*/
|
|
295
|
+
private getDefaultChainContext;
|
|
274
296
|
/**
|
|
275
297
|
* Sends an ILP payment, optionally with a balance proof claim via BTP.
|
|
276
298
|
*
|
|
@@ -645,9 +667,16 @@ declare class HttpConnectorAdmin implements ConnectorAdminClient {
|
|
|
645
667
|
/**
|
|
646
668
|
* EVM claim message for BTP protocol data.
|
|
647
669
|
* Matches @toon-protocol/connector's EVMClaimMessage interface.
|
|
670
|
+
*
|
|
671
|
+
* The connector's validateClaimMessage() requires envelope fields
|
|
672
|
+
* (version, messageId, timestamp) plus EVM claim fields, and optionally
|
|
673
|
+
* chainId + tokenNetworkAddress for self-describing signature verification.
|
|
648
674
|
*/
|
|
649
675
|
interface EVMClaimMessage {
|
|
676
|
+
version: '1.0';
|
|
650
677
|
blockchain: 'evm';
|
|
678
|
+
messageId: string;
|
|
679
|
+
timestamp: string;
|
|
651
680
|
senderId: string;
|
|
652
681
|
channelId: string;
|
|
653
682
|
nonce: number;
|
|
@@ -656,6 +685,12 @@ interface EVMClaimMessage {
|
|
|
656
685
|
locksRoot: string;
|
|
657
686
|
signature: string;
|
|
658
687
|
signerAddress: string;
|
|
688
|
+
/** Chain ID for self-describing EIP-712 verification */
|
|
689
|
+
chainId: number;
|
|
690
|
+
/** TokenNetwork address for self-describing EIP-712 verification */
|
|
691
|
+
tokenNetworkAddress: string;
|
|
692
|
+
/** ERC-20 token address for self-describing claim verification */
|
|
693
|
+
tokenAddress?: string;
|
|
659
694
|
}
|
|
660
695
|
/**
|
|
661
696
|
* EVM signer for EIP-712 balance proofs and on-chain transactions.
|
|
@@ -681,12 +716,13 @@ declare class EvmSigner {
|
|
|
681
716
|
signBalanceProof(params: BalanceProofParams & {
|
|
682
717
|
chainId: number;
|
|
683
718
|
tokenNetworkAddress: string;
|
|
719
|
+
tokenAddress?: string;
|
|
684
720
|
}): Promise<SignedBalanceProof>;
|
|
685
721
|
/**
|
|
686
722
|
* Builds an EVMClaimMessage from a signed balance proof.
|
|
687
723
|
* Static so it can be called without an EvmSigner instance.
|
|
688
724
|
*
|
|
689
|
-
* @param proof - Signed balance proof
|
|
725
|
+
* @param proof - Signed balance proof (includes chainId and tokenNetworkAddress)
|
|
690
726
|
* @param senderId - Nostr pubkey or identifier of the sender
|
|
691
727
|
* @returns EVMClaimMessage compatible with BTP_CLAIM_PROTOCOL
|
|
692
728
|
*/
|
|
@@ -757,6 +793,7 @@ declare class BtpRuntimeClient implements IlpClient {
|
|
|
757
793
|
amount: string;
|
|
758
794
|
data: string;
|
|
759
795
|
timeout?: number;
|
|
796
|
+
executionCondition?: Buffer;
|
|
760
797
|
}, claim: EVMClaimMessage): Promise<IlpSendResult>;
|
|
761
798
|
/**
|
|
762
799
|
* Single-attempt ILP packet send. Reconnects if not connected.
|
|
@@ -764,6 +801,7 @@ declare class BtpRuntimeClient implements IlpClient {
|
|
|
764
801
|
private _sendIlpPacketOnce;
|
|
765
802
|
/**
|
|
766
803
|
* Single-attempt claim + ILP packet send. Reconnects if not connected.
|
|
804
|
+
* Embeds the claim in the same BTP message as the ILP PREPARE packet.
|
|
767
805
|
*/
|
|
768
806
|
private _sendIlpPacketWithClaimOnce;
|
|
769
807
|
}
|
|
@@ -837,10 +875,15 @@ declare class ChannelManager {
|
|
|
837
875
|
* Called after bootstrap returns a channelId.
|
|
838
876
|
*
|
|
839
877
|
* @param channelId - Payment channel identifier
|
|
878
|
+
* @param chainContext - Chain context for EIP-712 signing (chainId + tokenNetworkAddress)
|
|
840
879
|
* @param initialNonce - Starting nonce (default: 0)
|
|
841
880
|
* @param initialAmount - Starting cumulative amount (default: 0n)
|
|
842
881
|
*/
|
|
843
|
-
trackChannel(channelId: string,
|
|
882
|
+
trackChannel(channelId: string, chainContext?: {
|
|
883
|
+
chainId: number;
|
|
884
|
+
tokenNetworkAddress: string;
|
|
885
|
+
tokenAddress?: string;
|
|
886
|
+
}, initialNonce?: number, initialAmount?: bigint): void;
|
|
844
887
|
/**
|
|
845
888
|
* Signs a balance proof for the given channel.
|
|
846
889
|
* Auto-increments nonce and adds to cumulative amount.
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// src/ToonClient.ts
|
|
2
|
+
import { createHash } from "crypto";
|
|
2
3
|
import { generateSecretKey as generateSecretKey2, getPublicKey } from "nostr-tools/pure";
|
|
3
4
|
|
|
4
5
|
// src/config.ts
|
|
@@ -508,7 +509,7 @@ var BtpRuntimeClient = class {
|
|
|
508
509
|
type: ILP_PACKET_TYPE.PREPARE,
|
|
509
510
|
amount: BigInt(params.amount),
|
|
510
511
|
destination: params.destination,
|
|
511
|
-
executionCondition: Buffer.alloc(32),
|
|
512
|
+
executionCondition: params.executionCondition ?? Buffer.alloc(32),
|
|
512
513
|
expiresAt: new Date(Date.now() + (params.timeout ?? 3e4)),
|
|
513
514
|
data: Buffer.from(params.data, "base64")
|
|
514
515
|
};
|
|
@@ -534,6 +535,7 @@ var BtpRuntimeClient = class {
|
|
|
534
535
|
}
|
|
535
536
|
/**
|
|
536
537
|
* Single-attempt claim + ILP packet send. Reconnects if not connected.
|
|
538
|
+
* Embeds the claim in the same BTP message as the ILP PREPARE packet.
|
|
537
539
|
*/
|
|
538
540
|
async _sendIlpPacketWithClaimOnce(params, claim) {
|
|
539
541
|
if (!this._isConnected) {
|
|
@@ -542,12 +544,37 @@ var BtpRuntimeClient = class {
|
|
|
542
544
|
if (!this.btpClient) {
|
|
543
545
|
throw new Error("BTP client not connected");
|
|
544
546
|
}
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
547
|
+
const packet = {
|
|
548
|
+
type: ILP_PACKET_TYPE.PREPARE,
|
|
549
|
+
amount: BigInt(params.amount),
|
|
550
|
+
destination: params.destination,
|
|
551
|
+
executionCondition: params.executionCondition ?? Buffer.alloc(32),
|
|
552
|
+
expiresAt: new Date(Date.now() + (params.timeout ?? 3e4)),
|
|
553
|
+
data: Buffer.from(params.data, "base64")
|
|
554
|
+
};
|
|
555
|
+
const protocolData = [
|
|
556
|
+
{
|
|
557
|
+
protocolName: BTP_CLAIM_PROTOCOL.NAME,
|
|
558
|
+
contentType: BTP_CLAIM_PROTOCOL.CONTENT_TYPE,
|
|
559
|
+
data: Buffer.from(JSON.stringify(claim))
|
|
560
|
+
}
|
|
561
|
+
];
|
|
562
|
+
const response = await this.btpClient.sendPacket(packet, protocolData);
|
|
563
|
+
if (response.type === ILP_PACKET_TYPE.FULFILL) {
|
|
564
|
+
const fulfill = response;
|
|
565
|
+
return {
|
|
566
|
+
accepted: true,
|
|
567
|
+
fulfillment: fulfill.fulfillment.toString("base64"),
|
|
568
|
+
data: fulfill.data.length > 0 ? fulfill.data.toString("base64") : void 0
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
const reject = response;
|
|
572
|
+
return {
|
|
573
|
+
accepted: false,
|
|
574
|
+
code: reject.code,
|
|
575
|
+
message: reject.message,
|
|
576
|
+
data: reject.data.length > 0 ? reject.data.toString("base64") : void 0
|
|
577
|
+
};
|
|
551
578
|
}
|
|
552
579
|
};
|
|
553
580
|
|
|
@@ -877,20 +904,26 @@ var EvmSigner = class {
|
|
|
877
904
|
lockedAmount: params.lockedAmount,
|
|
878
905
|
locksRoot: params.locksRoot,
|
|
879
906
|
signature,
|
|
880
|
-
signerAddress: this._account.address
|
|
907
|
+
signerAddress: this._account.address,
|
|
908
|
+
chainId: params.chainId,
|
|
909
|
+
tokenNetworkAddress: params.tokenNetworkAddress,
|
|
910
|
+
...params.tokenAddress && { tokenAddress: params.tokenAddress }
|
|
881
911
|
};
|
|
882
912
|
}
|
|
883
913
|
/**
|
|
884
914
|
* Builds an EVMClaimMessage from a signed balance proof.
|
|
885
915
|
* Static so it can be called without an EvmSigner instance.
|
|
886
916
|
*
|
|
887
|
-
* @param proof - Signed balance proof
|
|
917
|
+
* @param proof - Signed balance proof (includes chainId and tokenNetworkAddress)
|
|
888
918
|
* @param senderId - Nostr pubkey or identifier of the sender
|
|
889
919
|
* @returns EVMClaimMessage compatible with BTP_CLAIM_PROTOCOL
|
|
890
920
|
*/
|
|
891
921
|
static buildClaimMessage(proof, senderId) {
|
|
892
922
|
return {
|
|
923
|
+
version: "1.0",
|
|
893
924
|
blockchain: "evm",
|
|
925
|
+
messageId: crypto.randomUUID(),
|
|
926
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, ".000Z"),
|
|
894
927
|
senderId,
|
|
895
928
|
channelId: proof.channelId,
|
|
896
929
|
nonce: proof.nonce,
|
|
@@ -898,7 +931,10 @@ var EvmSigner = class {
|
|
|
898
931
|
lockedAmount: proof.lockedAmount.toString(),
|
|
899
932
|
locksRoot: proof.locksRoot,
|
|
900
933
|
signature: proof.signature,
|
|
901
|
-
signerAddress: proof.signerAddress
|
|
934
|
+
signerAddress: proof.signerAddress,
|
|
935
|
+
chainId: proof.chainId,
|
|
936
|
+
tokenNetworkAddress: proof.tokenNetworkAddress,
|
|
937
|
+
...proof.tokenAddress && { tokenAddress: proof.tokenAddress }
|
|
902
938
|
};
|
|
903
939
|
}
|
|
904
940
|
};
|
|
@@ -983,23 +1019,32 @@ var ChannelManager = class {
|
|
|
983
1019
|
* Called after bootstrap returns a channelId.
|
|
984
1020
|
*
|
|
985
1021
|
* @param channelId - Payment channel identifier
|
|
1022
|
+
* @param chainContext - Chain context for EIP-712 signing (chainId + tokenNetworkAddress)
|
|
986
1023
|
* @param initialNonce - Starting nonce (default: 0)
|
|
987
1024
|
* @param initialAmount - Starting cumulative amount (default: 0n)
|
|
988
1025
|
*/
|
|
989
|
-
trackChannel(channelId, initialNonce = 0, initialAmount = 0n) {
|
|
1026
|
+
trackChannel(channelId, chainContext, initialNonce = 0, initialAmount = 0n) {
|
|
1027
|
+
const cId = chainContext?.chainId ?? 31337;
|
|
1028
|
+
const tnAddr = chainContext?.tokenNetworkAddress ?? "0x0000000000000000000000000000000000000000";
|
|
990
1029
|
if (this.store) {
|
|
991
1030
|
const persisted = this.store.load(channelId);
|
|
992
1031
|
if (persisted) {
|
|
993
1032
|
this.channels.set(channelId, {
|
|
994
1033
|
nonce: persisted.nonce,
|
|
995
|
-
cumulativeAmount: persisted.cumulativeAmount
|
|
1034
|
+
cumulativeAmount: persisted.cumulativeAmount,
|
|
1035
|
+
chainId: cId,
|
|
1036
|
+
tokenNetworkAddress: tnAddr,
|
|
1037
|
+
tokenAddress: chainContext?.tokenAddress
|
|
996
1038
|
});
|
|
997
1039
|
return;
|
|
998
1040
|
}
|
|
999
1041
|
}
|
|
1000
1042
|
this.channels.set(channelId, {
|
|
1001
1043
|
nonce: initialNonce,
|
|
1002
|
-
cumulativeAmount: initialAmount
|
|
1044
|
+
cumulativeAmount: initialAmount,
|
|
1045
|
+
chainId: cId,
|
|
1046
|
+
tokenNetworkAddress: tnAddr,
|
|
1047
|
+
tokenAddress: chainContext?.tokenAddress
|
|
1003
1048
|
});
|
|
1004
1049
|
}
|
|
1005
1050
|
/**
|
|
@@ -1032,9 +1077,9 @@ var ChannelManager = class {
|
|
|
1032
1077
|
transferredAmount: tracking.cumulativeAmount,
|
|
1033
1078
|
lockedAmount: 0n,
|
|
1034
1079
|
locksRoot: "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
1035
|
-
chainId:
|
|
1036
|
-
|
|
1037
|
-
|
|
1080
|
+
chainId: tracking.chainId,
|
|
1081
|
+
tokenNetworkAddress: tracking.tokenNetworkAddress,
|
|
1082
|
+
tokenAddress: tracking.tokenAddress
|
|
1038
1083
|
});
|
|
1039
1084
|
}
|
|
1040
1085
|
/**
|
|
@@ -1182,12 +1227,15 @@ var ToonClient = class {
|
|
|
1182
1227
|
const { bootstrapService, discoveryTracker, runtimeClient, btpClient } = initialization;
|
|
1183
1228
|
if (this.channelManager) {
|
|
1184
1229
|
const cm = this.channelManager;
|
|
1230
|
+
const nostrPubkey = this.getPublicKey();
|
|
1231
|
+
const defaultChainCtx = this.getDefaultChainContext();
|
|
1185
1232
|
bootstrapService.setClaimSigner(
|
|
1186
1233
|
async (channelId, amount) => {
|
|
1187
1234
|
if (!cm.isTracking(channelId)) {
|
|
1188
|
-
cm.trackChannel(channelId);
|
|
1235
|
+
cm.trackChannel(channelId, defaultChainCtx);
|
|
1189
1236
|
}
|
|
1190
|
-
|
|
1237
|
+
const proof = await cm.signBalanceProof(channelId, amount);
|
|
1238
|
+
return EvmSigner.buildClaimMessage(proof, nostrPubkey);
|
|
1191
1239
|
}
|
|
1192
1240
|
);
|
|
1193
1241
|
}
|
|
@@ -1195,7 +1243,11 @@ var ToonClient = class {
|
|
|
1195
1243
|
if (this.channelManager) {
|
|
1196
1244
|
for (const result of bootstrapResults) {
|
|
1197
1245
|
if (result.channelId && !this.channelManager.isTracking(result.channelId)) {
|
|
1198
|
-
this.
|
|
1246
|
+
const chainCtx = this.getChainContext(result.negotiatedChain);
|
|
1247
|
+
this.channelManager.trackChannel(
|
|
1248
|
+
result.channelId,
|
|
1249
|
+
chainCtx
|
|
1250
|
+
);
|
|
1199
1251
|
}
|
|
1200
1252
|
}
|
|
1201
1253
|
}
|
|
@@ -1241,27 +1293,33 @@ var ToonClient = class {
|
|
|
1241
1293
|
const basePricePerByte = 10n;
|
|
1242
1294
|
const amount = String(BigInt(toonData.length) * basePricePerByte);
|
|
1243
1295
|
const destination = options?.destination ?? this.config.destinationAddress;
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1296
|
+
const fulfillment = createHash("sha256").update(Buffer.from(toonData)).digest();
|
|
1297
|
+
const executionCondition = createHash("sha256").update(fulfillment).digest();
|
|
1298
|
+
if (!options?.claim) {
|
|
1299
|
+
throw new ToonClientError(
|
|
1300
|
+
"Signed balance proof required. Call signBalanceProof() first.",
|
|
1301
|
+
"MISSING_CLAIM"
|
|
1249
1302
|
);
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
},
|
|
1256
|
-
claimMessage
|
|
1303
|
+
}
|
|
1304
|
+
if (!this.state.btpClient) {
|
|
1305
|
+
throw new ToonClientError(
|
|
1306
|
+
"BTP client required for publishing. Configure btpUrl.",
|
|
1307
|
+
"NO_BTP_CLIENT"
|
|
1257
1308
|
);
|
|
1258
|
-
}
|
|
1259
|
-
|
|
1309
|
+
}
|
|
1310
|
+
const claimMessage = EvmSigner.buildClaimMessage(
|
|
1311
|
+
options.claim,
|
|
1312
|
+
this.getPublicKey()
|
|
1313
|
+
);
|
|
1314
|
+
const response = await this.state.btpClient.sendIlpPacketWithClaim(
|
|
1315
|
+
{
|
|
1260
1316
|
destination,
|
|
1261
1317
|
amount,
|
|
1262
|
-
data: Buffer.from(toonData).toString("base64")
|
|
1263
|
-
|
|
1264
|
-
|
|
1318
|
+
data: Buffer.from(toonData).toString("base64"),
|
|
1319
|
+
executionCondition
|
|
1320
|
+
},
|
|
1321
|
+
claimMessage
|
|
1322
|
+
);
|
|
1265
1323
|
if (!response.accepted) {
|
|
1266
1324
|
return {
|
|
1267
1325
|
success: false,
|
|
@@ -1305,6 +1363,41 @@ var ToonClient = class {
|
|
|
1305
1363
|
getTrackedChannels() {
|
|
1306
1364
|
return this.channelManager?.getTrackedChannels() ?? [];
|
|
1307
1365
|
}
|
|
1366
|
+
/**
|
|
1367
|
+
* Gets the current nonce for a tracked channel.
|
|
1368
|
+
*/
|
|
1369
|
+
getChannelNonce(channelId) {
|
|
1370
|
+
if (!this.channelManager) throw new Error("ChannelManager not initialized");
|
|
1371
|
+
return this.channelManager.getNonce(channelId);
|
|
1372
|
+
}
|
|
1373
|
+
/**
|
|
1374
|
+
* Gets the cumulative transferred amount for a tracked channel.
|
|
1375
|
+
*/
|
|
1376
|
+
getChannelCumulativeAmount(channelId) {
|
|
1377
|
+
if (!this.channelManager) throw new Error("ChannelManager not initialized");
|
|
1378
|
+
return this.channelManager.getCumulativeAmount(channelId);
|
|
1379
|
+
}
|
|
1380
|
+
/**
|
|
1381
|
+
* Extracts chain context (chainId + tokenNetworkAddress) from a chain key like 'evm:base:421614'.
|
|
1382
|
+
*/
|
|
1383
|
+
getChainContext(negotiatedChain) {
|
|
1384
|
+
if (!negotiatedChain) return void 0;
|
|
1385
|
+
const parts = negotiatedChain.split(":");
|
|
1386
|
+
const numericChainId = parts.length >= 3 ? parseInt(parts[2], 10) : NaN;
|
|
1387
|
+
if (isNaN(numericChainId)) return void 0;
|
|
1388
|
+
const tokenNetworkAddress = this.config.tokenNetworks?.[negotiatedChain];
|
|
1389
|
+
if (!tokenNetworkAddress) return void 0;
|
|
1390
|
+
const tokenAddress = this.config.preferredTokens?.[negotiatedChain];
|
|
1391
|
+
return { chainId: numericChainId, tokenNetworkAddress, tokenAddress };
|
|
1392
|
+
}
|
|
1393
|
+
/**
|
|
1394
|
+
* Gets the default chain context from the first supported chain in config.
|
|
1395
|
+
*/
|
|
1396
|
+
getDefaultChainContext() {
|
|
1397
|
+
const chains = this.config.supportedChains;
|
|
1398
|
+
if (!chains?.length) return void 0;
|
|
1399
|
+
return this.getChainContext(chains[0]);
|
|
1400
|
+
}
|
|
1308
1401
|
/**
|
|
1309
1402
|
* Sends an ILP payment, optionally with a balance proof claim via BTP.
|
|
1310
1403
|
*
|
|
@@ -1324,17 +1417,26 @@ var ToonClient = class {
|
|
|
1324
1417
|
amount: params.amount,
|
|
1325
1418
|
data: params.data ?? ""
|
|
1326
1419
|
};
|
|
1327
|
-
if (params.claim
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1420
|
+
if (!params.claim) {
|
|
1421
|
+
throw new ToonClientError(
|
|
1422
|
+
"Signed balance proof required. Call signBalanceProof() first.",
|
|
1423
|
+
"MISSING_CLAIM"
|
|
1331
1424
|
);
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1425
|
+
}
|
|
1426
|
+
if (!this.state.btpClient) {
|
|
1427
|
+
throw new ToonClientError(
|
|
1428
|
+
"BTP client required for sending payments. Configure btpUrl.",
|
|
1429
|
+
"NO_BTP_CLIENT"
|
|
1335
1430
|
);
|
|
1336
1431
|
}
|
|
1337
|
-
|
|
1432
|
+
const claimMessage = EvmSigner.buildClaimMessage(
|
|
1433
|
+
params.claim,
|
|
1434
|
+
this.getPublicKey()
|
|
1435
|
+
);
|
|
1436
|
+
return this.state.btpClient.sendIlpPacketWithClaim(
|
|
1437
|
+
ilpParams,
|
|
1438
|
+
claimMessage
|
|
1439
|
+
);
|
|
1338
1440
|
}
|
|
1339
1441
|
/**
|
|
1340
1442
|
* Stops the ToonClient and cleans up resources.
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/ToonClient.ts","../src/config.ts","../src/errors.ts","../src/modes/http.ts","../src/utils/retry.ts","../src/adapters/HttpRuntimeClient.ts","../src/adapters/BtpRuntimeClient.ts","../src/channel/OnChainChannelClient.ts","../src/signing/evm-signer.ts","../src/channel/ChannelManager.ts","../src/channel/ChannelStore.ts","../src/adapters/HttpConnectorAdmin.ts"],"sourcesContent":["import { generateSecretKey, getPublicKey } from 'nostr-tools/pure';\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport type {\n BootstrapService,\n DiscoveryTracker,\n IlpSendResult,\n IlpClient,\n} from '@toon-protocol/core';\nimport { validateConfig, applyDefaults } from './config.js';\nimport type { ResolvedConfig } from './config.js';\nimport { initializeHttpMode } from './modes/http.js';\nimport { ToonClientError } from './errors.js';\nimport { EvmSigner } from './signing/evm-signer.js';\nimport { ChannelManager } from './channel/ChannelManager.js';\nimport { JsonFileChannelStore } from './channel/ChannelStore.js';\nimport type { BtpRuntimeClient } from './adapters/BtpRuntimeClient.js';\nimport type {\n ToonClientConfig,\n ToonStartResult,\n PublishEventResult,\n SignedBalanceProof,\n} from './types.js';\n\n/**\n * Internal state for ToonClient after initialization.\n */\ninterface ToonClientState {\n bootstrapService: BootstrapService;\n discoveryTracker: DiscoveryTracker;\n runtimeClient: IlpClient;\n peersDiscovered: number;\n btpClient?: BtpRuntimeClient;\n}\n\n/**\n * ToonClient - High-level client for interacting with TOON network.\n *\n * This story implements HTTP mode only. Embedded mode will be added in a future epic.\n *\n * @example HTTP Mode\n * ```typescript\n * import { ToonClient } from '@toon-protocol/client';\n * import { generateSecretKey, getPublicKey } from 'nostr-tools/pure';\n * import { encodeEvent, decodeEvent } from '@toon-protocol/relay';\n *\n * const secretKey = generateSecretKey();\n * const pubkey = getPublicKey(secretKey);\n *\n * const client = new ToonClient({\n * connectorUrl: 'http://localhost:8080',\n * secretKey,\n * ilpInfo: {\n * pubkey,\n * ilpAddress: `g.toon.${pubkey.slice(0, 8)}`,\n * btpEndpoint: 'ws://localhost:3000',\n * },\n * toonEncoder: encodeEvent,\n * toonDecoder: decodeEvent,\n * });\n *\n * await client.start(); // Bootstrap peers, start monitoring\n *\n * // Publish to default destination (from config)\n * await client.publishEvent(signedEvent);\n *\n * // Publish to specific destination (multi-hop routing)\n * await client.publishEvent(signedEvent, { destination: 'g.toon.peer1' });\n *\n * await client.stop(); // Cleanup\n * ```\n */\nexport class ToonClient {\n private readonly config: ResolvedConfig;\n private state: ToonClientState | null = null;\n private readonly evmSigner?: EvmSigner;\n private channelManager?: ChannelManager;\n\n /**\n * Creates a new ToonClient instance.\n *\n * @param config - Client configuration\n * @throws {ValidationError} If configuration is invalid\n */\n constructor(config: ToonClientConfig) {\n // Validate config (will reject embedded mode, require connectorUrl)\n validateConfig(config);\n\n // Apply defaults to optional fields (auto-generates secretKey if needed)\n this.config = applyDefaults(config);\n\n // Create EVM signer if private key provided\n if (this.config.evmPrivateKey) {\n this.evmSigner = new EvmSigner(this.config.evmPrivateKey);\n }\n }\n\n /**\n * Generates a new Nostr keypair.\n *\n * @returns Object with secretKey (Uint8Array) and pubkey (hex string)\n */\n static generateKeypair(): { secretKey: Uint8Array; pubkey: string } {\n const secretKey = generateSecretKey();\n const pubkey = getPublicKey(secretKey);\n return { secretKey, pubkey };\n }\n\n /**\n * Gets the Nostr public key derived from the secret key.\n * Works before start() is called.\n */\n getPublicKey(): string {\n return getPublicKey(this.config.secretKey);\n }\n\n /**\n * Gets the EVM address derived from the Nostr secret key (or explicit evmPrivateKey override).\n */\n getEvmAddress(): string | undefined {\n return this.evmSigner?.address;\n }\n\n /**\n * Starts the ToonClient.\n *\n * This will:\n * 1. Initialize HTTP mode components (runtime client, admin client, bootstrap, monitor)\n * 2. Bootstrap the network (discover peers, register, and open channels)\n * 3. Start monitoring relay for new peers (kind:10032 events)\n *\n * @returns Result with number of peers discovered and mode\n * @throws {ToonClientError} If client is already started\n * @throws {ToonClientError} If initialization fails\n */\n async start(): Promise<ToonStartResult> {\n if (this.state !== null) {\n throw new ToonClientError('Client already started', 'INVALID_STATE');\n }\n\n try {\n // Create channel manager FIRST (before bootstrap) so it can sign claims during settlement\n if (this.evmSigner) {\n const store = this.config.channelStorePath\n ? new JsonFileChannelStore(this.config.channelStorePath)\n : undefined;\n this.channelManager = new ChannelManager(this.evmSigner, store);\n }\n\n // Initialize HTTP mode components\n const initialization = await initializeHttpMode(this.config);\n\n const { bootstrapService, discoveryTracker, runtimeClient, btpClient } =\n initialization;\n\n // Wire claim signer to bootstrap service if we have channel manager\n if (this.channelManager) {\n const cm = this.channelManager;\n bootstrapService.setClaimSigner(\n async (channelId: string, amount: bigint) => {\n // Track the channel if not already tracked\n if (!cm.isTracking(channelId)) {\n cm.trackChannel(channelId);\n }\n // Sign balance proof and return claim\n return cm.signBalanceProof(channelId, amount);\n }\n );\n }\n\n // Start bootstrap process (discover peers, register with settlement, announce)\n const bootstrapResults = await bootstrapService.bootstrap();\n\n // Track any additional channels from bootstrap results\n if (this.channelManager) {\n for (const result of bootstrapResults) {\n if (\n result.channelId &&\n !this.channelManager.isTracking(result.channelId)\n ) {\n this.channelManager.trackChannel(result.channelId);\n }\n }\n }\n\n // Store state\n this.state = {\n bootstrapService,\n discoveryTracker,\n runtimeClient,\n peersDiscovered: bootstrapResults.length,\n btpClient: btpClient ?? undefined,\n };\n\n return {\n peersDiscovered: bootstrapResults.length,\n mode: 'http',\n };\n } catch (error) {\n throw new ToonClientError(\n 'Failed to start client',\n 'INITIALIZATION_ERROR',\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Publishes a Nostr event to the relay via ILP payment.\n *\n * The event must already be finalized (signed with id, pubkey, sig).\n *\n * @param event - Signed Nostr event to publish\n * @param options - Optional options including destination and signed balance proof claim\n * @returns Result with success status, event ID, and fulfillment\n * @throws {ToonClientError} If client is not started\n * @throws {ToonClientError} If event publishing fails\n */\n async publishEvent(\n event: NostrEvent,\n options?: { destination?: string; claim?: SignedBalanceProof }\n ): Promise<PublishEventResult> {\n if (!this.state) {\n throw new ToonClientError(\n 'Client not started. Call start() first.',\n 'INVALID_STATE'\n );\n }\n\n try {\n // Encode event to TOON format\n const toonData = this.config.toonEncoder(event);\n\n // Calculate payment amount: basePricePerByte * encoded size\n const basePricePerByte = 10n;\n const amount = String(BigInt(toonData.length) * basePricePerByte);\n\n // Use provided destination or fall back to config default\n const destination =\n options?.destination ?? this.config.destinationAddress;\n\n let response: IlpSendResult;\n\n // If claim provided and BTP client available, send with claim\n if (options?.claim && this.state.btpClient) {\n const claimMessage = EvmSigner.buildClaimMessage(\n options.claim,\n this.getPublicKey()\n );\n response = await this.state.btpClient.sendIlpPacketWithClaim(\n {\n destination,\n amount,\n data: Buffer.from(toonData).toString('base64'),\n },\n claimMessage\n );\n } else {\n // Send ILP packet via runtime client\n response = await this.state.runtimeClient.sendIlpPacket({\n destination,\n amount,\n data: Buffer.from(toonData).toString('base64'),\n });\n }\n\n if (!response.accepted) {\n return {\n success: false,\n error: `Event rejected: ${response.code} - ${response.message}`,\n };\n }\n\n return {\n success: true,\n eventId: event.id,\n fulfillment: response.fulfillment,\n };\n } catch (error) {\n throw new ToonClientError(\n 'Failed to publish event',\n 'PUBLISH_ERROR',\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Signs a balance proof for the given channel with the specified amount.\n * Delegates to ChannelManager which auto-increments nonce and tracks cumulative amount.\n *\n * @param channelId - Payment channel identifier\n * @param amount - Additional amount to add to cumulative transferred amount\n * @returns Signed balance proof\n * @throws {ToonClientError} If no EVM signer configured or channel not tracked\n */\n async signBalanceProof(\n channelId: string,\n amount: bigint\n ): Promise<SignedBalanceProof> {\n if (!this.channelManager) {\n throw new ToonClientError(\n 'No EVM signer configured. Provide evmPrivateKey in config.',\n 'NO_EVM_SIGNER'\n );\n }\n return this.channelManager.signBalanceProof(channelId, amount);\n }\n\n /**\n * Gets list of tracked payment channel IDs.\n */\n getTrackedChannels(): string[] {\n return this.channelManager?.getTrackedChannels() ?? [];\n }\n\n /**\n * Sends an ILP payment, optionally with a balance proof claim via BTP.\n *\n * @param params - Payment parameters\n * @returns ILP send result\n * @throws {ToonClientError} If client is not started\n */\n async sendPayment(params: {\n destination: string;\n amount: string;\n data?: string;\n claim?: SignedBalanceProof;\n }): Promise<IlpSendResult> {\n if (!this.state) {\n throw new ToonClientError(\n 'Client not started. Call start() first.',\n 'INVALID_STATE'\n );\n }\n\n const ilpParams = {\n destination: params.destination,\n amount: params.amount,\n data: params.data ?? '',\n };\n\n if (params.claim && this.state.btpClient) {\n const claimMessage = EvmSigner.buildClaimMessage(\n params.claim,\n this.getPublicKey()\n );\n return this.state.btpClient.sendIlpPacketWithClaim(\n ilpParams,\n claimMessage\n );\n }\n\n return this.state.runtimeClient.sendIlpPacket(ilpParams);\n }\n\n /**\n * Stops the ToonClient and cleans up resources.\n *\n * This will:\n * 1. Disconnect BTP client if connected\n * 2. Clear internal state\n *\n * @throws {ToonClientError} If client is not started\n */\n async stop(): Promise<void> {\n if (!this.state) {\n throw new ToonClientError('Client not started', 'INVALID_STATE');\n }\n\n try {\n // Disconnect BTP client if connected\n if (this.state.btpClient) {\n await this.state.btpClient.disconnect();\n }\n\n // Clear state\n this.state = null;\n } catch (error) {\n throw new ToonClientError(\n 'Failed to stop client',\n 'STOP_ERROR',\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Returns true if the client is currently started.\n */\n isStarted(): boolean {\n return this.state !== null;\n }\n\n /**\n * Gets the number of peers discovered during bootstrap.\n *\n * @returns Number of peers discovered\n * @throws {ToonClientError} If client is not started\n */\n getPeersCount(): number {\n if (!this.state) {\n throw new ToonClientError(\n 'Client not started. Call start() first.',\n 'INVALID_STATE'\n );\n }\n\n return this.state.peersDiscovered;\n }\n\n /**\n * Gets the list of peers discovered by the relay monitor.\n *\n * @returns Array of discovered peer objects\n * @throws {ToonClientError} If client is not started\n */\n getDiscoveredPeers() {\n if (!this.state) {\n throw new ToonClientError(\n 'Client not started. Call start() first.',\n 'INVALID_STATE'\n );\n }\n\n return this.state.discoveryTracker.getDiscoveredPeers();\n }\n}\n","import { generateSecretKey } from 'nostr-tools/pure';\nimport { ValidationError } from './errors.js';\nimport type { ToonClientConfig } from './types.js';\n\n/**\n * Settlement info produced by buildSettlementInfo().\n * Extends the core SettlementConfig shape with ilpAddress for client use.\n */\nexport interface ClientSettlementInfo {\n ilpAddress?: string;\n supportedChains?: string[];\n settlementAddresses?: Record<string, string>;\n preferredTokens?: Record<string, string>;\n tokenNetworks?: Record<string, string>;\n}\n\n/**\n * Validates ToonClient configuration.\n *\n * This story implements HTTP mode only. Embedded mode validation will be added in a future epic.\n *\n * @throws {ValidationError} If configuration is invalid\n */\nexport function validateConfig(config: ToonClientConfig): void {\n // Reject embedded mode (not implemented in this story)\n if (config.connector !== undefined) {\n throw new ValidationError(\n 'Embedded mode not yet implemented in ToonClient. Use connectorUrl for HTTP mode.'\n );\n }\n\n // Require connectorUrl for HTTP mode\n if (!config.connectorUrl) {\n throw new ValidationError(\n 'connectorUrl is required for HTTP mode. Example: \"http://localhost:8080\"'\n );\n }\n\n // Validate connectorUrl format\n try {\n const url = new URL(config.connectorUrl);\n if (!url.protocol.startsWith('http')) {\n throw new Error('Must be HTTP or HTTPS');\n }\n } catch (error) {\n throw new ValidationError(\n `Invalid connectorUrl: must be a valid HTTP/HTTPS URL (e.g., \"http://localhost:8080\"). ` +\n `Error: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n\n // Validate secretKey only when provided\n if (config.secretKey !== undefined) {\n if (!config.secretKey || config.secretKey.length !== 32) {\n throw new ValidationError(\n 'secretKey must be 32 bytes (Nostr private key)'\n );\n }\n }\n\n if (!config.ilpInfo?.ilpAddress) {\n throw new ValidationError('ilpInfo.ilpAddress is required');\n }\n\n if (!config.toonEncoder || typeof config.toonEncoder !== 'function') {\n throw new ValidationError('toonEncoder function is required');\n }\n\n if (!config.toonDecoder || typeof config.toonDecoder !== 'function') {\n throw new ValidationError('toonDecoder function is required');\n }\n\n // Validate evmPrivateKey format when provided\n if (config.evmPrivateKey !== undefined) {\n if (config.evmPrivateKey instanceof Uint8Array) {\n if (config.evmPrivateKey.length !== 32) {\n throw new ValidationError('evmPrivateKey must be 32 bytes');\n }\n } else if (typeof config.evmPrivateKey === 'string') {\n const hex = config.evmPrivateKey.startsWith('0x')\n ? config.evmPrivateKey.slice(2)\n : config.evmPrivateKey;\n if (!/^[0-9a-fA-F]{64}$/.test(hex)) {\n throw new ValidationError('evmPrivateKey must be a 32-byte hex string');\n }\n } else {\n throw new ValidationError(\n 'evmPrivateKey must be a hex string or Uint8Array'\n );\n }\n }\n\n // Validate btpUrl when provided\n if (config.btpUrl !== undefined) {\n try {\n const url = new URL(config.btpUrl);\n if (!url.protocol.startsWith('ws')) {\n throw new Error('Must be WS or WSS');\n }\n } catch (error) {\n throw new ValidationError(\n `Invalid btpUrl: must be a valid WebSocket URL (e.g., \"ws://localhost:3000\"). ` +\n `Error: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n\n // Validate chainRpcUrls keys match supportedChains when both present\n if (config.chainRpcUrls && config.supportedChains) {\n for (const chain of Object.keys(config.chainRpcUrls)) {\n if (!config.supportedChains.includes(chain)) {\n throw new ValidationError(\n `chainRpcUrls key \"${chain}\" is not in supportedChains`\n );\n }\n }\n }\n}\n\n/**\n * The resolved config type after defaults are applied.\n * secretKey is guaranteed to be present (auto-generated if omitted).\n */\nexport type ResolvedConfig = Required<\n Omit<\n ToonClientConfig,\n | 'connector'\n | 'evmPrivateKey'\n | 'supportedChains'\n | 'settlementAddresses'\n | 'preferredTokens'\n | 'tokenNetworks'\n | 'btpUrl'\n | 'btpAuthToken'\n | 'btpPeerId'\n | 'chainRpcUrls'\n | 'initialDeposit'\n | 'settlementTimeout'\n | 'channelStorePath'\n | 'knownPeers'\n | 'destinationAddress'\n >\n> & {\n connector?: unknown;\n /** Always present after applyDefaults() — derived from secretKey if not explicitly provided */\n evmPrivateKey: string | Uint8Array;\n supportedChains?: string[];\n settlementAddresses?: Record<string, string>;\n preferredTokens?: Record<string, string>;\n tokenNetworks?: Record<string, string>;\n btpUrl?: string;\n btpAuthToken?: string;\n btpPeerId?: string;\n chainRpcUrls?: Record<string, string>;\n initialDeposit?: string;\n settlementTimeout?: number;\n channelStorePath?: string;\n knownPeers?: {\n pubkey: string;\n relayUrl: string;\n btpEndpoint?: string;\n }[];\n destinationAddress: string;\n};\n\n/**\n * Applies default values to optional configuration fields.\n * Auto-generates a Nostr keypair when secretKey is omitted.\n * Derives btpUrl from connectorUrl when not provided.\n */\nexport function applyDefaults(config: ToonClientConfig): ResolvedConfig {\n // Auto-generate Nostr keypair when secretKey is omitted\n const secretKey = config.secretKey ?? generateSecretKey();\n\n // Derive btpUrl from connectorUrl when not explicitly provided\n // http://host:8080 → ws://host:3000\n let btpUrl = config.btpUrl;\n if (!btpUrl && config.connectorUrl) {\n try {\n const url = new URL(config.connectorUrl);\n const wsProtocol = url.protocol === 'https:' ? 'wss:' : 'ws:';\n btpUrl = `${wsProtocol}//${url.hostname}:3000`;\n } catch {\n // connectorUrl already validated, this shouldn't happen\n }\n }\n\n // Derive destinationAddress from connectorUrl port when not explicitly provided\n // This provides sensible defaults for local development:\n // - http://localhost:8080 → g.toon.genesis (genesis node)\n // - http://localhost:8090 → g.toon.peer1 (peer1 node)\n // For production, explicitly set destinationAddress in config\n let destinationAddress = config.destinationAddress;\n if (!destinationAddress && config.connectorUrl) {\n try {\n const url = new URL(config.connectorUrl);\n if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') {\n // Map common local ports to known nodes\n if (url.port === '8080') {\n destinationAddress = 'g.toon.genesis';\n } else if (url.port === '8090') {\n destinationAddress = 'g.toon.peer1';\n } else if (url.port === '8100') {\n destinationAddress = 'g.toon.peer2';\n } else {\n // Fallback: use ilpInfo.ilpAddress if available\n destinationAddress =\n config.ilpInfo?.ilpAddress || 'g.toon.relay';\n }\n } else {\n // Production: default to ilpInfo.ilpAddress\n destinationAddress = config.ilpInfo?.ilpAddress || 'g.toon.relay';\n }\n } catch {\n destinationAddress = config.ilpInfo?.ilpAddress || 'g.toon.relay';\n }\n }\n\n // Derive EVM private key from Nostr secret key when not explicitly provided.\n // Both Nostr and EVM use secp256k1, so a single 32-byte key works for both.\n const evmPrivateKey = config.evmPrivateKey ?? secretKey;\n\n return {\n ...config,\n secretKey,\n evmPrivateKey,\n connectorUrl: config.connectorUrl as string, // Already validated as required\n relayUrl: config.relayUrl ?? 'ws://localhost:7100',\n queryTimeout: config.queryTimeout ?? 30000,\n maxRetries: config.maxRetries ?? 3,\n retryDelay: config.retryDelay ?? 1000,\n btpUrl,\n destinationAddress: destinationAddress as string, // Always set by logic above\n };\n}\n\n/**\n * Builds SettlementConfig from client config.\n * Returns undefined if no settlement-related config is present.\n */\nexport function buildSettlementInfo(\n config: ToonClientConfig\n): ClientSettlementInfo | undefined {\n if (\n !config.supportedChains?.length &&\n !config.settlementAddresses &&\n !config.preferredTokens &&\n !config.tokenNetworks\n ) {\n return undefined;\n }\n\n return {\n ilpAddress: config.ilpInfo?.ilpAddress,\n supportedChains: config.supportedChains,\n settlementAddresses: config.settlementAddresses,\n preferredTokens: config.preferredTokens,\n tokenNetworks: config.tokenNetworks,\n };\n}\n","/**\n * Base error class for all TOON client errors.\n */\nexport class ToonClientError extends Error {\n constructor(\n message: string,\n public readonly code: string,\n cause?: Error\n ) {\n super(message, { cause });\n this.name = 'ToonClientError';\n }\n}\n\n/**\n * Network error for connection failures (ECONNREFUSED, ETIMEDOUT).\n * These errors trigger retry logic with exponential backoff.\n */\nexport class NetworkError extends ToonClientError {\n constructor(message: string, cause?: Error) {\n super(message, 'NETWORK_ERROR', cause);\n this.name = 'NetworkError';\n }\n}\n\n/**\n * Connector error for 5xx server errors.\n * These errors indicate the connector is unavailable or malfunctioning.\n */\nexport class ConnectorError extends ToonClientError {\n constructor(message: string, cause?: Error) {\n super(message, 'CONNECTOR_ERROR', cause);\n this.name = 'ConnectorError';\n }\n}\n\n/**\n * Validation error for invalid input parameters.\n * These errors are thrown before making any HTTP requests.\n */\nexport class ValidationError extends ToonClientError {\n constructor(message: string, cause?: Error) {\n super(message, 'VALIDATION_ERROR', cause);\n this.name = 'ValidationError';\n }\n}\n\n/**\n * Unauthorized error for 401 responses from connector admin API.\n * Indicates missing or invalid authentication credentials.\n */\nexport class UnauthorizedError extends ToonClientError {\n constructor(message: string, cause?: Error) {\n super(message, 'UNAUTHORIZED', cause);\n this.name = 'UnauthorizedError';\n }\n}\n\n/**\n * Peer not found error for 404 responses when removing a peer.\n * Indicates the specified peer ID does not exist in the connector.\n */\nexport class PeerNotFoundError extends ToonClientError {\n constructor(message: string, cause?: Error) {\n super(message, 'PEER_NOT_FOUND', cause);\n this.name = 'PeerNotFoundError';\n }\n}\n\n/**\n * Peer already exists error for 409 responses when adding a peer.\n * Indicates a peer with the same ID already exists in the connector.\n */\nexport class PeerAlreadyExistsError extends ToonClientError {\n constructor(message: string, cause?: Error) {\n super(message, 'PEER_ALREADY_EXISTS', cause);\n this.name = 'PeerAlreadyExistsError';\n }\n}\n","import { BootstrapService, createDiscoveryTracker } from '@toon-protocol/core';\nimport type { BootstrapServiceConfig } from '@toon-protocol/core';\nimport { HttpRuntimeClient } from '../adapters/HttpRuntimeClient.js';\nimport { BtpRuntimeClient } from '../adapters/BtpRuntimeClient.js';\nimport { OnChainChannelClient } from '../channel/OnChainChannelClient.js';\nimport { EvmSigner } from '../signing/evm-signer.js';\nimport { buildSettlementInfo } from '../config.js';\nimport type { ResolvedConfig } from '../config.js';\nimport type { HttpModeInitialization } from './types.js';\n\n/**\n * Initializes HTTP mode for ToonClient.\n *\n * HTTP mode uses external connector service via HTTP/WebSocket.\n * This function creates all necessary clients and services for operating in HTTP mode.\n *\n * @param config - ToonClient configuration (must have connectorUrl)\n * @returns Initialized HTTP mode components\n */\nexport async function initializeHttpMode(\n config: ResolvedConfig\n): Promise<HttpModeInitialization> {\n // Derive admin URL from connector URL (change port 8080 → 8081)\n const connectorUrl = config.connectorUrl;\n\n // Build settlement info from config\n const settlementInfo = buildSettlementInfo(config);\n\n // Create BTP runtime client — this is the primary transport for the client SDK.\n // The client connects to the connector via BTP WebSocket to send ILP packets.\n // HTTP is not used for ILP packet transport.\n let btpClient: BtpRuntimeClient | null = null;\n if (config.btpUrl) {\n btpClient = new BtpRuntimeClient({\n btpUrl: config.btpUrl,\n peerId: config.btpPeerId ?? `client`,\n authToken: config.btpAuthToken ?? '',\n });\n await btpClient.connect();\n }\n\n // BTP is the runtime client for sending ILP packets\n const runtimeClient =\n btpClient ??\n new HttpRuntimeClient({\n connectorUrl,\n timeout: config.queryTimeout,\n maxRetries: config.maxRetries,\n retryDelay: config.retryDelay,\n });\n\n // Create on-chain channel client when chain RPC URLs are configured.\n // evmPrivateKey is always present (derived from secretKey by default).\n let onChainChannelClient: OnChainChannelClient | null = null;\n if (config.chainRpcUrls) {\n const evmSigner = new EvmSigner(config.evmPrivateKey);\n onChainChannelClient = new OnChainChannelClient({\n evmSigner,\n chainRpcUrls: config.chainRpcUrls,\n });\n }\n\n // Create BootstrapService\n const bootstrapConfig: BootstrapServiceConfig = {\n knownPeers: (config.knownPeers || []).map((p) => ({\n pubkey: p.pubkey,\n relayUrl: p.relayUrl,\n btpEndpoint: p.btpEndpoint ?? '',\n })),\n queryTimeout: config.queryTimeout,\n ardriveEnabled: true,\n defaultRelayUrl: config.relayUrl,\n settlementInfo,\n ownIlpAddress: config.ilpInfo.ilpAddress,\n toonEncoder: config.toonEncoder,\n toonDecoder: config.toonDecoder,\n basePricePerByte: 10n, // Default pricing\n };\n\n const bootstrapService = new BootstrapService(\n bootstrapConfig,\n config.secretKey,\n config.ilpInfo\n );\n\n // Wire ILP client into bootstrap service\n bootstrapService.setIlpClient(runtimeClient);\n\n // Wire on-chain channel client if available\n if (onChainChannelClient) {\n bootstrapService.setChannelClient(onChainChannelClient);\n }\n\n // Do NOT wire ConnectorAdmin — addPeer() at line 472 is skipped when connectorAdmin is null\n // This is intentional: the client is a standalone peer, not an admin interface\n\n // Create DiscoveryTracker\n const discoveryTracker = createDiscoveryTracker({\n secretKey: config.secretKey,\n settlementInfo,\n });\n\n return {\n bootstrapService,\n discoveryTracker,\n runtimeClient,\n adminClient: null,\n btpClient,\n onChainChannelClient,\n };\n}\n","/**\n * Configuration options for retry behavior with exponential backoff.\n */\nexport interface RetryOptions {\n /** Maximum number of retry attempts (default: 3) */\n maxRetries: number;\n /** Initial delay in milliseconds between retries (default: 1000) */\n retryDelay: number;\n /** Use exponential backoff for delays (default: true) */\n exponentialBackoff?: boolean;\n /** Maximum delay cap in milliseconds (default: 30000) */\n maxDelay?: number;\n /** Custom predicate to determine if an error should trigger a retry */\n shouldRetry?: (error: Error) => boolean;\n}\n\n/**\n * Executes an async operation with retry logic and exponential backoff.\n *\n * @param operation - The async function to execute\n * @param options - Retry configuration options\n * @returns The result of the successful operation\n * @throws The last error if all retries are exhausted\n *\n * @example\n * ```typescript\n * const result = await withRetry(\n * async () => fetchData(),\n * {\n * maxRetries: 3,\n * retryDelay: 1000,\n * shouldRetry: (err) => err.name === 'NetworkError'\n * }\n * );\n * ```\n */\nexport async function withRetry<T>(\n operation: () => Promise<T>,\n options: RetryOptions\n): Promise<T> {\n const {\n maxRetries,\n retryDelay,\n exponentialBackoff = true,\n maxDelay = 30000,\n shouldRetry,\n } = options;\n\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n return await operation();\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n\n // Check if we should retry this error\n if (shouldRetry && !shouldRetry(lastError)) {\n throw lastError;\n }\n\n // If this was the last attempt, throw the error\n if (attempt === maxRetries) {\n throw lastError;\n }\n\n // Calculate delay with exponential backoff\n const currentDelay = exponentialBackoff\n ? Math.min(retryDelay * Math.pow(2, attempt), maxDelay)\n : retryDelay;\n\n // Wait before retrying\n await new Promise((resolve) => setTimeout(resolve, currentDelay));\n }\n }\n\n // This should never be reached, but TypeScript needs it\n throw lastError ?? new Error('Unknown error during retry');\n}\n","import type { IlpClient, IlpSendResult } from '@toon-protocol/core';\nimport { NetworkError, ConnectorError, ValidationError } from '../errors.js';\nimport { withRetry } from '../utils/retry.js';\n\n/**\n * Configuration options for HttpRuntimeClient.\n */\nexport interface HttpRuntimeClientConfig {\n /** Connector runtime API base URL (e.g., 'http://localhost:8080') */\n connectorUrl: string;\n /** Request timeout in milliseconds (default: 30000) */\n timeout?: number;\n /** Maximum retry attempts for network failures (default: 3) */\n maxRetries?: number;\n /** Initial retry delay in milliseconds (default: 1000) */\n retryDelay?: number;\n /** HTTP client implementation (for testing) */\n httpClient?: typeof fetch;\n}\n\n/**\n * HTTP client for sending ILP packets to an external connector runtime API.\n *\n * Implements the IlpClient interface for use with TOON agents\n * that need to send ILP packets without embedding a full connector.\n *\n * Features:\n * - Request validation (destination, amount, data)\n * - Retry logic with exponential backoff for transient network failures\n * - Typed error handling (NetworkError, ConnectorError, ValidationError)\n * - Connection pooling and keep-alive (via Node.js fetch)\n *\n * @example\n * ```typescript\n * const client = new HttpRuntimeClient({\n * connectorUrl: 'http://localhost:8080'\n * });\n *\n * const result = await client.sendIlpPacket({\n * destination: 'g.toon.alice',\n * amount: '1000',\n * data: 'base64EncodedToonData==',\n * });\n *\n * if (result.accepted) {\n * console.log('Payment accepted:', result.fulfillment);\n * } else {\n * console.error('Payment rejected:', result.code, result.message);\n * }\n * ```\n */\nexport class HttpRuntimeClient implements IlpClient {\n private readonly connectorUrl: string;\n private readonly timeout: number;\n private readonly retryConfig: { maxRetries: number; retryDelay: number };\n private readonly httpClient: typeof fetch;\n\n constructor(config: HttpRuntimeClientConfig) {\n // Normalize connector URL (remove trailing slash)\n this.connectorUrl = config.connectorUrl.replace(/\\/$/, '');\n this.timeout = config.timeout ?? 30000;\n this.retryConfig = {\n maxRetries: config.maxRetries ?? 3,\n retryDelay: config.retryDelay ?? 1000,\n };\n this.httpClient = config.httpClient ?? fetch;\n }\n\n /**\n * Send an ILP packet to the connector runtime API.\n *\n * @param params - ILP packet parameters\n * @returns ILP packet response with acceptance status\n * @throws {ValidationError} If request parameters are invalid\n * @throws {NetworkError} If network connection fails after retries\n * @throws {ConnectorError} If connector returns 5xx server error\n */\n async sendIlpPacket(params: {\n destination: string;\n amount: string;\n data: string;\n timeout?: number;\n }): Promise<IlpSendResult> {\n // Validate request parameters\n this.validateRequest(params);\n\n // Wrap HTTP request with retry logic\n return withRetry(async () => this.sendHttpRequest(params), {\n maxRetries: this.retryConfig.maxRetries,\n retryDelay: this.retryConfig.retryDelay,\n exponentialBackoff: true,\n shouldRetry: (error) => {\n // Only retry on network errors (ECONNREFUSED, ETIMEDOUT)\n // Do not retry on validation errors, 4xx, or 5xx errors\n return error instanceof NetworkError;\n },\n });\n }\n\n /**\n * Validate ILP packet request parameters.\n *\n * @throws {ValidationError} If any parameter is invalid\n */\n private validateRequest(params: {\n destination: string;\n amount: string;\n data: string;\n }): void {\n // Validate destination: non-empty, valid ILP address format\n if (!params.destination || params.destination.trim() === '') {\n throw new ValidationError('Destination cannot be empty');\n }\n if (!params.destination.startsWith('g.')) {\n throw new ValidationError(\n `Invalid ILP address format: \"${params.destination}\" (must start with \"g.\")`\n );\n }\n\n // Validate amount: non-empty, parseable as bigint, positive\n if (!params.amount || params.amount.trim() === '') {\n throw new ValidationError('Amount cannot be empty');\n }\n try {\n const amountBigInt = BigInt(params.amount);\n if (amountBigInt <= 0n) {\n throw new ValidationError(\n `Amount must be positive: \"${params.amount}\"`\n );\n }\n } catch (error) {\n if (error instanceof ValidationError) throw error;\n throw new ValidationError(\n `Amount must be a valid integer: \"${params.amount}\"`,\n error instanceof Error ? error : undefined\n );\n }\n\n // Validate data: non-empty, valid Base64 encoding\n if (!params.data || params.data.trim() === '') {\n throw new ValidationError('Data cannot be empty');\n }\n try {\n // Attempt to decode Base64 to validate format\n Buffer.from(params.data, 'base64');\n // Verify it's actually Base64 (not just any string Buffer accepts)\n if (!/^[A-Za-z0-9+/]*={0,2}$/.test(params.data)) {\n throw new ValidationError(\n `Data must be valid Base64 encoding: \"${params.data}\"`\n );\n }\n } catch (error) {\n if (error instanceof ValidationError) throw error;\n throw new ValidationError(\n `Data must be valid Base64 encoding: \"${params.data}\"`,\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Send HTTP POST request to connector runtime API.\n *\n * @throws {NetworkError} On connection failures (ECONNREFUSED, ETIMEDOUT)\n * @throws {ConnectorError} On 5xx server errors\n * @returns IlpSendResult with acceptance status\n */\n private async sendHttpRequest(params: {\n destination: string;\n amount: string;\n data: string;\n timeout?: number;\n }): Promise<IlpSendResult> {\n const requestTimeout = params.timeout ?? this.timeout;\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), requestTimeout);\n\n try {\n // NOTE: Using admin endpoint /admin/ilp/send since connector doesn't have public /ilp endpoint yet\n const response = await this.httpClient(\n `${this.connectorUrl}/admin/ilp/send`,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n destination: params.destination,\n amount: params.amount,\n data: params.data,\n }),\n signal: controller.signal,\n }\n );\n\n clearTimeout(timeoutId);\n\n // Handle response by status code\n if (response.ok) {\n // 200 OK: Parse response as IlpSendResult\n const result = (await response.json()) as Record<string, unknown>;\n return {\n accepted: (result['accepted'] as boolean) ?? false,\n fulfillment: result['fulfillment'] as string | undefined,\n data: result['data'] as string | undefined,\n code: result['code'] as string | undefined,\n message: result['message'] as string | undefined,\n };\n } else if (response.status >= 400 && response.status < 500) {\n // 4xx: Client error - return as failed ILP response (no retry)\n const errorBody = (await response.json().catch(() => ({}))) as Record<\n string,\n unknown\n >;\n return {\n accepted: false,\n code: `HTTP_${response.status}`,\n message:\n (errorBody['message'] as string) ??\n (errorBody['error'] as string) ??\n response.statusText,\n };\n } else if (response.status >= 500 && response.status < 600) {\n // 5xx: Server error - throw ConnectorError (no retry)\n const errorBody = (await response.json().catch(() => ({}))) as Record<\n string,\n unknown\n >;\n throw new ConnectorError(\n `Connector server error (${response.status}): ${\n (errorBody['message'] as string) ??\n (errorBody['error'] as string) ??\n response.statusText\n }`\n );\n }\n\n // Unexpected status code (not 2xx, 4xx, or 5xx)\n throw new ConnectorError(\n `Unexpected HTTP status: ${response.status} ${response.statusText}`\n );\n } catch (error) {\n clearTimeout(timeoutId);\n\n // Handle AbortController timeout\n if (error instanceof Error && error.name === 'AbortError') {\n throw new NetworkError(\n `Request timeout after ${requestTimeout}ms`,\n error\n );\n }\n\n // Handle network errors (ECONNREFUSED, ETIMEDOUT, etc.)\n if (\n error instanceof TypeError &&\n (error.message.includes('fetch failed') ||\n error.message.includes('ECONNREFUSED') ||\n error.message.includes('ETIMEDOUT') ||\n error.message.includes('network'))\n ) {\n throw new NetworkError(\n `Network connection failed: ${error.message}`,\n error\n );\n }\n\n // Re-throw known error types\n if (\n error instanceof NetworkError ||\n error instanceof ConnectorError ||\n error instanceof ValidationError\n ) {\n throw error;\n }\n\n // Unknown error\n throw new ConnectorError(\n `Unexpected error during HTTP request: ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n }\n}\n","import { BTPClient } from '@toon-protocol/connector';\nimport type { ILPPreparePacket } from '@toon-protocol/connector';\nimport type { IlpClient, IlpSendResult } from '@toon-protocol/core';\nimport type { EVMClaimMessage } from '../signing/evm-signer.js';\nimport { withRetry } from '../utils/retry.js';\n\n/** BTP Peer — matches @toon-protocol/connector's Peer interface */\ninterface Peer {\n id: string;\n url: string;\n authToken: string;\n connected: boolean;\n lastSeen: Date;\n}\n\n/** BTP claim protocol constants — matches @toon-protocol/connector's BTP_CLAIM_PROTOCOL */\nconst BTP_CLAIM_PROTOCOL = {\n NAME: 'payment-channel-claim',\n CONTENT_TYPE: 1,\n} as const;\n\n/** Pino-compatible logger interface */\ninterface ConsoleLogger {\n level: string;\n silent: (...args: unknown[]) => void;\n info: typeof console.info;\n warn: typeof console.warn;\n error: typeof console.error;\n debug: typeof console.debug;\n trace: typeof console.debug;\n fatal: typeof console.error;\n child: () => ConsoleLogger;\n}\n\n/** Creates a pino-compatible logger wrapper around console */\nfunction createConsoleLogger(): ConsoleLogger {\n const noop = (..._args: unknown[]) => {\n // intentional no-op for pino's silent log level\n };\n const logger: ConsoleLogger = {\n level: 'info',\n silent: noop,\n info: console.info.bind(console),\n warn: console.warn.bind(console),\n error: console.error.bind(console),\n debug: console.debug.bind(console),\n trace: console.debug.bind(console),\n fatal: console.error.bind(console),\n child: () => createConsoleLogger(),\n };\n return logger;\n}\n\n/** ILP packet type constants — matches @toon-protocol/connector's PacketType enum */\nconst ILP_PACKET_TYPE = {\n PREPARE: 12,\n FULFILL: 13,\n REJECT: 14,\n} as const;\n\n/** Shape of a BTP fulfill response */\ninterface BtpFulfillResponse {\n type: typeof ILP_PACKET_TYPE.FULFILL;\n fulfillment: Buffer;\n data: Buffer;\n}\n\n/** Shape of a BTP reject response */\ninterface BtpRejectResponse {\n type: typeof ILP_PACKET_TYPE.REJECT;\n code: string;\n message: string;\n data: Buffer;\n}\n\nexport interface BtpRuntimeClientConfig {\n btpUrl: string;\n peerId: string;\n authToken: string;\n logger?: ConsoleLogger;\n /** Max reconnection attempts on send failure (default: 3) */\n maxRetries?: number;\n /** Delay between reconnection attempts in ms (default: 1000) */\n retryDelay?: number;\n}\n\n/**\n * Returns true if the error is a connection-level error worth retrying.\n * ILP application-level rejects (F02, T01, etc.) are NOT retried.\n */\nfunction isConnectionError(error: Error): boolean {\n const msg = error.message.toLowerCase();\n return (\n msg.includes('not connected') ||\n msg.includes('connection') ||\n msg.includes('websocket') ||\n msg.includes('econnrefused') ||\n msg.includes('econnreset') ||\n msg.includes('socket hang up') ||\n msg.includes('timeout')\n );\n}\n\n/**\n * BTP transport implementing IlpClient.\n * Wraps BTPClient from @toon-protocol/connector with auto-reconnect on connection loss.\n */\nexport class BtpRuntimeClient implements IlpClient {\n private btpClient: BTPClient | null = null;\n private readonly config: BtpRuntimeClientConfig;\n private _isConnected = false;\n private readonly logger: ConsoleLogger;\n\n constructor(config: BtpRuntimeClientConfig) {\n this.config = config;\n this.logger = config.logger ?? createConsoleLogger();\n }\n\n /**\n * Connects to the BTP peer via WebSocket.\n */\n async connect(): Promise<void> {\n const peer: Peer = {\n id: this.config.peerId,\n url: this.config.btpUrl,\n authToken: this.config.authToken,\n connected: false,\n lastSeen: new Date(),\n };\n\n // Cast logger: ConsoleLogger implements the subset of pino's Logger\n // that BTPClient actually uses at runtime (info, warn, error, debug, child)\n this.btpClient = new BTPClient(\n peer,\n this.config.peerId,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n this.logger as any\n );\n\n await this.btpClient.connect();\n this._isConnected = true;\n }\n\n /**\n * Attempts to reconnect by creating a fresh BTPClient and connecting.\n */\n async reconnect(): Promise<void> {\n // Clean up old client if it exists\n if (this.btpClient) {\n try {\n await this.btpClient.disconnect();\n } catch {\n // Ignore disconnect errors during reconnect\n }\n this.btpClient = null;\n this._isConnected = false;\n }\n\n this.logger.info('[BtpRuntimeClient] Reconnecting...');\n await this.connect();\n }\n\n /**\n * Disconnects from the BTP peer.\n */\n async disconnect(): Promise<void> {\n if (this.btpClient) {\n await this.btpClient.disconnect();\n this._isConnected = false;\n this.btpClient = null;\n }\n }\n\n get isConnected(): boolean {\n return this._isConnected;\n }\n\n /**\n * Sends an ILP packet via BTP with auto-reconnect on connection errors.\n * Satisfies IlpClient interface.\n */\n async sendIlpPacket(params: {\n destination: string;\n amount: string;\n data: string;\n timeout?: number;\n }): Promise<IlpSendResult> {\n return withRetry(() => this._sendIlpPacketOnce(params), {\n maxRetries: this.config.maxRetries ?? 3,\n retryDelay: this.config.retryDelay ?? 1000,\n shouldRetry: (error) => {\n if (!isConnectionError(error)) return false;\n // Mark as disconnected so reconnect happens on next attempt\n this._isConnected = false;\n return true;\n },\n });\n }\n\n /**\n * Sends a balance proof claim via BTP protocol data, then sends an ILP packet.\n * Auto-reconnects on connection errors.\n */\n async sendIlpPacketWithClaim(\n params: {\n destination: string;\n amount: string;\n data: string;\n timeout?: number;\n },\n claim: EVMClaimMessage\n ): Promise<IlpSendResult> {\n return withRetry(() => this._sendIlpPacketWithClaimOnce(params, claim), {\n maxRetries: this.config.maxRetries ?? 3,\n retryDelay: this.config.retryDelay ?? 1000,\n shouldRetry: (error) => {\n if (!isConnectionError(error)) return false;\n this._isConnected = false;\n return true;\n },\n });\n }\n\n /**\n * Single-attempt ILP packet send. Reconnects if not connected.\n */\n private async _sendIlpPacketOnce(params: {\n destination: string;\n amount: string;\n data: string;\n timeout?: number;\n }): Promise<IlpSendResult> {\n if (!this._isConnected) {\n await this.reconnect();\n }\n\n const packet = {\n type: ILP_PACKET_TYPE.PREPARE,\n amount: BigInt(params.amount),\n destination: params.destination,\n executionCondition: Buffer.alloc(32),\n expiresAt: new Date(Date.now() + (params.timeout ?? 30000)),\n data: Buffer.from(params.data, 'base64'),\n } as ILPPreparePacket;\n\n const response = await this.btpClient?.sendPacket(packet);\n if (!response) {\n throw new Error('BTP client not connected');\n }\n\n if (response.type === ILP_PACKET_TYPE.FULFILL) {\n const fulfill = response as unknown as BtpFulfillResponse;\n return {\n accepted: true,\n fulfillment: fulfill.fulfillment.toString('base64'),\n data:\n fulfill.data.length > 0 ? fulfill.data.toString('base64') : undefined,\n };\n }\n\n // Reject packet — ILP application-level error, not a connection error\n const reject = response as unknown as BtpRejectResponse;\n return {\n accepted: false,\n code: reject.code,\n message: reject.message,\n data: reject.data.length > 0 ? reject.data.toString('base64') : undefined,\n };\n }\n\n /**\n * Single-attempt claim + ILP packet send. Reconnects if not connected.\n */\n private async _sendIlpPacketWithClaimOnce(\n params: {\n destination: string;\n amount: string;\n data: string;\n timeout?: number;\n },\n claim: EVMClaimMessage\n ): Promise<IlpSendResult> {\n if (!this._isConnected) {\n await this.reconnect();\n }\n\n // Send claim as BTP protocol data first\n if (!this.btpClient) {\n throw new Error('BTP client not connected');\n }\n await this.btpClient.sendProtocolData(\n BTP_CLAIM_PROTOCOL.NAME,\n BTP_CLAIM_PROTOCOL.CONTENT_TYPE,\n Buffer.from(JSON.stringify(claim))\n );\n\n // Then send the ILP packet\n return this._sendIlpPacketOnce(params);\n }\n}\n","import {\n createPublicClient,\n createWalletClient,\n http,\n maxUint256,\n decodeEventLog,\n defineChain,\n type Hex,\n type TransactionReceipt,\n} from 'viem';\nimport type {\n ConnectorChannelClient,\n OpenChannelParams,\n OpenChannelResult,\n ChannelState,\n} from '@toon-protocol/core';\nimport type { EvmSigner } from '../signing/evm-signer.js';\n\n// TokenNetwork ABI — only the functions we need\nconst TOKEN_NETWORK_ABI = [\n {\n name: 'openChannel',\n type: 'function',\n stateMutability: 'nonpayable',\n inputs: [\n { name: 'participant2', type: 'address' },\n { name: 'settlementTimeout', type: 'uint256' },\n ],\n outputs: [{ type: 'bytes32' }],\n },\n {\n name: 'setTotalDeposit',\n type: 'function',\n stateMutability: 'nonpayable',\n inputs: [\n { name: 'channelId', type: 'bytes32' },\n { name: 'participant', type: 'address' },\n { name: 'totalDeposit', type: 'uint256' },\n ],\n outputs: [],\n },\n {\n name: 'channels',\n type: 'function',\n stateMutability: 'view',\n inputs: [{ type: 'bytes32' }],\n outputs: [\n { name: 'settlementTimeout', type: 'uint256' },\n { name: 'state', type: 'uint8' },\n { name: 'closedAt', type: 'uint256' },\n { name: 'openedAt', type: 'uint256' },\n { name: 'participant1', type: 'address' },\n { name: 'participant2', type: 'address' },\n ],\n },\n {\n name: 'ChannelOpened',\n type: 'event',\n inputs: [\n { name: 'channelId', type: 'bytes32', indexed: true },\n { name: 'participant1', type: 'address', indexed: true },\n { name: 'participant2', type: 'address', indexed: true },\n { name: 'settlementTimeout', type: 'uint256', indexed: false },\n ],\n },\n] as const;\n\n// ERC20 ABI — only approve and allowance\nconst ERC20_ABI = [\n {\n name: 'approve',\n type: 'function',\n stateMutability: 'nonpayable',\n inputs: [\n { name: 'spender', type: 'address' },\n { name: 'amount', type: 'uint256' },\n ],\n outputs: [{ type: 'bool' }],\n },\n {\n name: 'allowance',\n type: 'function',\n stateMutability: 'view',\n inputs: [\n { name: 'owner', type: 'address' },\n { name: 'spender', type: 'address' },\n ],\n outputs: [{ type: 'uint256' }],\n },\n] as const;\n\n/** Maps on-chain state uint8 to ChannelState status */\nconst STATE_MAP: Record<number, ChannelState['status']> = {\n 0: 'settled',\n 1: 'open',\n 2: 'closed',\n 3: 'settled',\n};\n\nexport interface OnChainChannelClientConfig {\n evmSigner: EvmSigner;\n chainRpcUrls: Record<string, string>;\n}\n\n/**\n * Implements ConnectorChannelClient using viem for direct on-chain\n * interaction with TokenNetwork smart contract.\n *\n * Fully non-custodial — the client deposits its own funds on-chain.\n */\nexport class OnChainChannelClient implements ConnectorChannelClient {\n private readonly evmSigner: EvmSigner;\n private readonly chainRpcUrls: Record<string, string>;\n private readonly channelContext = new Map<\n string,\n { chain: string; tokenNetworkAddress: string }\n >();\n\n constructor(config: OnChainChannelClientConfig) {\n this.evmSigner = config.evmSigner;\n this.chainRpcUrls = config.chainRpcUrls;\n }\n\n /**\n * Parse chain identifier to extract chainId.\n * Format: \"evm:{network}:{chainId}\" e.g., \"evm:anvil:31337\"\n */\n private parseChainId(chain: string): number {\n const parts = chain.split(':');\n if (parts.length < 3) {\n throw new Error(\n `Invalid chain format: \"${chain}\". Expected \"evm:{network}:{chainId}\".`\n );\n }\n const chainIdStr = parts[2];\n if (!chainIdStr) {\n throw new Error(\n `Invalid chain format: \"${chain}\". Expected \"evm:{network}:{chainId}\".`\n );\n }\n const chainId = parseInt(chainIdStr, 10);\n if (isNaN(chainId)) {\n throw new Error(`Invalid chainId in chain \"${chain}\".`);\n }\n return chainId;\n }\n\n /**\n * Create viem clients for a given chain.\n */\n private createClients(chain: string) {\n const rpcUrl = this.chainRpcUrls[chain];\n if (!rpcUrl) {\n throw new Error(\n `No RPC URL configured for chain \"${chain}\". Available: ${Object.keys(this.chainRpcUrls).join(', ')}`\n );\n }\n\n const chainId = this.parseChainId(chain);\n\n const viemChain = defineChain({\n id: chainId,\n name: chain,\n nativeCurrency: { name: 'ETH', symbol: 'ETH', decimals: 18 },\n rpcUrls: { default: { http: [rpcUrl] } },\n });\n\n const publicClient = createPublicClient({\n transport: http(rpcUrl),\n chain: viemChain,\n });\n\n const walletClient = createWalletClient({\n account: this.evmSigner.account,\n transport: http(rpcUrl),\n chain: viemChain,\n });\n\n return { publicClient, walletClient };\n }\n\n /**\n * Opens a new payment channel on-chain.\n *\n * 1. Approve token spend if needed\n * 2. Call TokenNetwork.openChannel()\n * 3. Extract channelId from ChannelOpened event\n * 4. Deposit initial funds if specified\n */\n async openChannel(params: OpenChannelParams): Promise<OpenChannelResult> {\n const {\n chain,\n tokenNetwork,\n peerAddress,\n initialDeposit,\n settlementTimeout,\n } = params;\n\n if (!tokenNetwork) {\n throw new Error(\n 'tokenNetwork address is required for on-chain channel opening'\n );\n }\n\n const { publicClient, walletClient } = this.createClients(chain);\n const tokenNetworkAddr = tokenNetwork as Hex;\n const deposit = initialDeposit ? BigInt(initialDeposit) : 0n;\n\n // If deposit > 0, ensure token approval\n if (deposit > 0n && params.token) {\n const tokenAddr = params.token as Hex;\n const myAddress = this.evmSigner.address as Hex;\n\n const currentAllowance = await publicClient.readContract({\n address: tokenAddr,\n abi: ERC20_ABI,\n functionName: 'allowance',\n args: [myAddress, tokenNetworkAddr],\n });\n\n if ((currentAllowance as bigint) < deposit) {\n const approveHash = await walletClient.writeContract({\n address: tokenAddr,\n abi: ERC20_ABI,\n functionName: 'approve',\n args: [tokenNetworkAddr, maxUint256],\n });\n await publicClient.waitForTransactionReceipt({ hash: approveHash });\n }\n }\n\n // Open channel\n const timeout = BigInt(settlementTimeout ?? 86400);\n const openHash = await walletClient.writeContract({\n address: tokenNetworkAddr,\n abi: TOKEN_NETWORK_ABI,\n functionName: 'openChannel',\n args: [peerAddress as Hex, timeout],\n });\n\n const receipt: TransactionReceipt =\n await publicClient.waitForTransactionReceipt({ hash: openHash });\n\n // Extract channelId from ChannelOpened event\n let channelId: string | undefined;\n for (const log of receipt.logs) {\n try {\n const decoded = decodeEventLog({\n abi: TOKEN_NETWORK_ABI,\n data: log.data,\n topics: log.topics,\n });\n if (decoded.eventName === 'ChannelOpened') {\n channelId = (decoded.args as Record<string, unknown>)[\n 'channelId'\n ] as string;\n break;\n }\n } catch {\n // Not our event, skip\n }\n }\n\n if (!channelId) {\n throw new Error('Failed to extract channelId from ChannelOpened event');\n }\n\n // Cache context for getChannelState\n this.channelContext.set(channelId, {\n chain,\n tokenNetworkAddress: tokenNetwork,\n });\n\n // Deposit initial funds if specified\n if (deposit > 0n) {\n const depositHash = await walletClient.writeContract({\n address: tokenNetworkAddr,\n abi: TOKEN_NETWORK_ABI,\n functionName: 'setTotalDeposit',\n args: [channelId as Hex, this.evmSigner.address as Hex, deposit],\n });\n await publicClient.waitForTransactionReceipt({ hash: depositHash });\n }\n\n return { channelId, status: 'opening' };\n }\n\n /**\n * Gets the current state of a payment channel from on-chain data.\n */\n async getChannelState(channelId: string): Promise<ChannelState> {\n const context = this.channelContext.get(channelId);\n if (!context) {\n throw new Error(\n `No context for channel \"${channelId}\". Channel must be opened via this client first.`\n );\n }\n\n const { publicClient } = this.createClients(context.chain);\n\n const result = await publicClient.readContract({\n address: context.tokenNetworkAddress as Hex,\n abi: TOKEN_NETWORK_ABI,\n functionName: 'channels',\n args: [channelId as Hex],\n });\n\n const [, state] = result as [\n bigint,\n number,\n bigint,\n bigint,\n string,\n string,\n ];\n const status = STATE_MAP[state] ?? 'settled';\n\n return {\n channelId,\n status,\n chain: context.chain,\n };\n }\n}\n","import { privateKeyToAccount, type PrivateKeyAccount } from 'viem/accounts';\nimport { type Hex, toHex } from 'viem';\nimport type { BalanceProofParams, SignedBalanceProof } from '../types.js';\n\n/**\n * EVM claim message for BTP protocol data.\n * Matches @toon-protocol/connector's EVMClaimMessage interface.\n */\nexport interface EVMClaimMessage {\n blockchain: 'evm';\n senderId: string;\n channelId: string;\n nonce: number;\n transferredAmount: string;\n lockedAmount: string;\n locksRoot: string;\n signature: string;\n signerAddress: string;\n}\n\n/**\n * EIP-712 domain for TokenNetwork balance proofs.\n * Must match connector's eip712-helper.js getDomainSeparator().\n */\nfunction getBalanceProofDomain(chainId: number, tokenNetworkAddress: string) {\n return {\n name: 'TokenNetwork' as const,\n version: '1' as const,\n chainId,\n verifyingContract: tokenNetworkAddress as Hex,\n };\n}\n\n/**\n * EIP-712 types for balance proofs.\n * Must match connector's eip712-helper.js getBalanceProofTypes().\n */\nconst BALANCE_PROOF_TYPES = {\n BalanceProof: [\n { name: 'channelId', type: 'bytes32' },\n { name: 'nonce', type: 'uint256' },\n { name: 'transferredAmount', type: 'uint256' },\n { name: 'lockedAmount', type: 'uint256' },\n { name: 'locksRoot', type: 'bytes32' },\n ],\n} as const;\n\n/**\n * EVM signer for EIP-712 balance proofs and on-chain transactions.\n *\n * Encapsulates the private key — no getPrivateKey() method is exposed.\n */\nexport class EvmSigner {\n private readonly _account: PrivateKeyAccount;\n\n /**\n * @param privateKey - EVM private key as hex string (with or without 0x prefix) or Uint8Array\n */\n constructor(privateKey: string | Uint8Array) {\n let hexKey: Hex;\n if (privateKey instanceof Uint8Array) {\n hexKey = toHex(privateKey);\n } else {\n hexKey = (\n privateKey.startsWith('0x') ? privateKey : `0x${privateKey}`\n ) as Hex;\n }\n this._account = privateKeyToAccount(hexKey);\n }\n\n /** Derived 0x EVM address */\n get address(): string {\n return this._account.address;\n }\n\n /** Viem PrivateKeyAccount — usable with walletClient for on-chain transactions */\n get account(): PrivateKeyAccount {\n return this._account;\n }\n\n /**\n * Signs a balance proof using EIP-712 typed data.\n *\n * @param params - Balance proof parameters plus chain context\n * @returns Signed balance proof with signature\n */\n async signBalanceProof(\n params: BalanceProofParams & {\n chainId: number;\n tokenNetworkAddress: string;\n }\n ): Promise<SignedBalanceProof> {\n const domain = getBalanceProofDomain(\n params.chainId,\n params.tokenNetworkAddress\n );\n\n const signature = await this._account.signTypedData({\n domain,\n types: BALANCE_PROOF_TYPES,\n primaryType: 'BalanceProof',\n message: {\n channelId: params.channelId as Hex,\n nonce: BigInt(params.nonce),\n transferredAmount: params.transferredAmount,\n lockedAmount: params.lockedAmount,\n locksRoot: params.locksRoot as Hex,\n },\n });\n\n return {\n channelId: params.channelId,\n nonce: params.nonce,\n transferredAmount: params.transferredAmount,\n lockedAmount: params.lockedAmount,\n locksRoot: params.locksRoot,\n signature,\n signerAddress: this._account.address,\n };\n }\n\n /**\n * Builds an EVMClaimMessage from a signed balance proof.\n * Static so it can be called without an EvmSigner instance.\n *\n * @param proof - Signed balance proof\n * @param senderId - Nostr pubkey or identifier of the sender\n * @returns EVMClaimMessage compatible with BTP_CLAIM_PROTOCOL\n */\n static buildClaimMessage(\n proof: SignedBalanceProof,\n senderId: string\n ): EVMClaimMessage {\n return {\n blockchain: 'evm',\n senderId,\n channelId: proof.channelId,\n nonce: proof.nonce,\n transferredAmount: proof.transferredAmount.toString(),\n lockedAmount: proof.lockedAmount.toString(),\n locksRoot: proof.locksRoot,\n signature: proof.signature,\n signerAddress: proof.signerAddress,\n };\n }\n}\n","import type { EvmSigner } from '../signing/evm-signer.js';\nimport type { SignedBalanceProof } from '../types.js';\nimport type { ChannelStore } from './ChannelStore.js';\n\ninterface ChannelTracking {\n nonce: number;\n cumulativeAmount: bigint;\n}\n\n/**\n * Local nonce tracking and claim signing.\n *\n * Does NOT make any network calls — it only manages state\n * and delegates signing to EvmSigner.\n */\nexport class ChannelManager {\n private readonly evmSigner: EvmSigner;\n private readonly channels = new Map<string, ChannelTracking>();\n private readonly store?: ChannelStore;\n\n constructor(evmSigner: EvmSigner, store?: ChannelStore) {\n this.evmSigner = evmSigner;\n this.store = store;\n }\n\n /**\n * Start tracking a channel.\n * Called after bootstrap returns a channelId.\n *\n * @param channelId - Payment channel identifier\n * @param initialNonce - Starting nonce (default: 0)\n * @param initialAmount - Starting cumulative amount (default: 0n)\n */\n trackChannel(channelId: string, initialNonce = 0, initialAmount = 0n): void {\n // If store has persisted state for this channel, resume from it\n if (this.store) {\n const persisted = this.store.load(channelId);\n if (persisted) {\n this.channels.set(channelId, {\n nonce: persisted.nonce,\n cumulativeAmount: persisted.cumulativeAmount,\n });\n return;\n }\n }\n\n this.channels.set(channelId, {\n nonce: initialNonce,\n cumulativeAmount: initialAmount,\n });\n }\n\n /**\n * Signs a balance proof for the given channel.\n * Auto-increments nonce and adds to cumulative amount.\n *\n * @param channelId - Payment channel identifier\n * @param additionalAmount - Amount to add to cumulative transferred amount\n * @returns Signed balance proof\n * @throws Error if channel is not being tracked\n */\n async signBalanceProof(\n channelId: string,\n additionalAmount: bigint\n ): Promise<SignedBalanceProof> {\n const tracking = this.channels.get(channelId);\n if (!tracking) {\n throw new Error(\n `Channel \"${channelId}\" is not being tracked. Call trackChannel() first.`\n );\n }\n\n tracking.nonce += 1;\n tracking.cumulativeAmount += additionalAmount;\n\n // Persist updated state\n if (this.store) {\n this.store.save(channelId, {\n nonce: tracking.nonce,\n cumulativeAmount: tracking.cumulativeAmount,\n });\n }\n\n // Note: chainId and tokenNetworkAddress are needed for EIP-712 signing.\n // In the full flow, these would come from the channel context.\n // For now, we use placeholder values that the caller can override.\n return this.evmSigner.signBalanceProof({\n channelId,\n nonce: tracking.nonce,\n transferredAmount: tracking.cumulativeAmount,\n lockedAmount: 0n,\n locksRoot:\n '0x0000000000000000000000000000000000000000000000000000000000000000',\n chainId: 31337, // Default — will be configurable via channel context\n tokenNetworkAddress: '0x0000000000000000000000000000000000000000',\n });\n }\n\n /**\n * Gets the current nonce for a tracked channel.\n */\n getNonce(channelId: string): number {\n const tracking = this.channels.get(channelId);\n if (!tracking) {\n throw new Error(`Channel \"${channelId}\" is not being tracked.`);\n }\n return tracking.nonce;\n }\n\n /**\n * Gets the cumulative transferred amount for a tracked channel.\n */\n getCumulativeAmount(channelId: string): bigint {\n const tracking = this.channels.get(channelId);\n if (!tracking) {\n throw new Error(`Channel \"${channelId}\" is not being tracked.`);\n }\n return tracking.cumulativeAmount;\n }\n\n /**\n * Gets all tracked channel IDs.\n */\n getTrackedChannels(): string[] {\n return Array.from(this.channels.keys());\n }\n\n /**\n * Returns true if the channel is being tracked.\n */\n isTracking(channelId: string): boolean {\n return this.channels.has(channelId);\n }\n}\n","import { readFileSync, writeFileSync, existsSync } from 'node:fs';\n\nexport interface ChannelStoreEntry {\n nonce: number;\n cumulativeAmount: bigint;\n}\n\n/**\n * Persistence interface for payment channel nonce/amount state.\n */\nexport interface ChannelStore {\n save(channelId: string, tracking: ChannelStoreEntry): void;\n load(channelId: string): ChannelStoreEntry | undefined;\n list(): string[];\n delete(channelId: string): void;\n}\n\ninterface JsonEntry {\n nonce: number;\n /** Stored as string to preserve bigint precision */\n cumulativeAmount: string;\n}\n\n/**\n * JSON file-backed ChannelStore.\n * Uses synchronous I/O to match ChannelManager's sync API surface.\n */\nexport class JsonFileChannelStore implements ChannelStore {\n private readonly filePath: string;\n\n constructor(filePath: string) {\n this.filePath = filePath;\n }\n\n save(channelId: string, tracking: ChannelStoreEntry): void {\n const data = this.readFile();\n data[channelId] = {\n nonce: tracking.nonce,\n cumulativeAmount: tracking.cumulativeAmount.toString(),\n };\n this.writeFile(data);\n }\n\n load(channelId: string): ChannelStoreEntry | undefined {\n const data = this.readFile();\n const entry = data[channelId];\n if (!entry) return undefined;\n return {\n nonce: entry.nonce,\n cumulativeAmount: BigInt(entry.cumulativeAmount),\n };\n }\n\n list(): string[] {\n return Object.keys(this.readFile());\n }\n\n delete(channelId: string): void {\n const data = this.readFile();\n const { [channelId]: _, ...rest } = data;\n this.writeFile(rest);\n }\n\n private readFile(): Record<string, JsonEntry> {\n if (!existsSync(this.filePath)) {\n return {};\n }\n const raw = readFileSync(this.filePath, 'utf-8');\n return JSON.parse(raw) as Record<string, JsonEntry>;\n }\n\n private writeFile(data: Record<string, JsonEntry>): void {\n writeFileSync(this.filePath, JSON.stringify(data, null, 2), 'utf-8');\n }\n}\n","import type { ConnectorAdminClient } from '@toon-protocol/core';\nimport {\n ValidationError,\n NetworkError,\n ConnectorError,\n UnauthorizedError,\n PeerNotFoundError,\n PeerAlreadyExistsError,\n} from '../errors.js';\nimport { withRetry } from '../utils/retry.js';\n\n/**\n * Configuration for HttpConnectorAdmin.\n */\nexport interface HttpConnectorAdminConfig {\n /** Admin API base URL (e.g., 'http://localhost:8081') */\n adminUrl: string;\n /** Request timeout in milliseconds (default: 30000) */\n timeout?: number;\n /** Maximum retry attempts for network failures (default: 3) */\n maxRetries?: number;\n /** Initial retry delay in milliseconds (default: 1000) */\n retryDelay?: number;\n /** HTTP client for testing (default: global fetch) */\n httpClient?: typeof fetch;\n}\n\n/**\n * Result of a bulk peer operation.\n */\nexport interface PeerOperationResult {\n /** Peer ID that was operated on */\n peerId: string;\n /** Whether the operation succeeded */\n success: boolean;\n /** Error that occurred (if failed) */\n error?: Error;\n}\n\n/**\n * HTTP-based connector admin client for managing ILP peers via REST API.\n *\n * Implements the ConnectorAdminClient interface using HTTP requests to the\n * connector's admin API (typically port 8081).\n *\n * @example\n * ```typescript\n * // Embedded mode (DirectConnectorAdmin)\n * const adminClient = new DirectConnectorAdmin(connectorNode);\n *\n * // HTTP mode (HttpConnectorAdmin)\n * const adminClient = new HttpConnectorAdmin({\n * adminUrl: 'http://localhost:8081'\n * });\n *\n * // Add peer\n * await adminClient.addPeer({\n * id: 'nostr-abc123',\n * url: 'btp+ws://alice.example.com:3000',\n * authToken: 'secret-token',\n * routes: [{ prefix: 'g.toon.alice' }]\n * });\n *\n * // Remove peer\n * await adminClient.removePeer('nostr-abc123');\n * ```\n *\n * @throws {ValidationError} Input validation failed (before HTTP request)\n * @throws {NetworkError} Connection failed (ECONNREFUSED, ETIMEDOUT)\n * @throws {UnauthorizedError} Admin API returned 401 (missing/invalid auth)\n * @throws {PeerAlreadyExistsError} Admin API returned 409 (duplicate peer)\n * @throws {PeerNotFoundError} Admin API returned 404 (peer not found)\n * @throws {ConnectorError} Admin API returned 5xx (server error)\n */\nexport class HttpConnectorAdmin implements ConnectorAdminClient {\n private readonly adminUrl: string;\n private readonly timeout: number;\n private readonly retryConfig: { maxRetries: number; retryDelay: number };\n private readonly httpClient: typeof fetch;\n\n constructor(config: HttpConnectorAdminConfig) {\n this.adminUrl = config.adminUrl.replace(/\\/$/, ''); // Remove trailing slash\n this.timeout = config.timeout ?? 30000;\n this.retryConfig = {\n maxRetries: config.maxRetries ?? 3,\n retryDelay: config.retryDelay ?? 1000,\n };\n this.httpClient = config.httpClient ?? fetch;\n }\n\n /**\n * Add a peer to the connector via the admin API.\n *\n * Validates peer config parameters and sends HTTP POST to /admin/peers.\n *\n * @param config - Peer configuration\n * @param config.id - Unique peer identifier (non-empty string)\n * @param config.url - BTP WebSocket URL (must start with 'btp+ws://' or 'btp+wss://')\n * @param config.authToken - Authentication token (non-empty string)\n * @param config.routes - Optional routing table entries\n * @param config.settlement - Optional settlement configuration\n *\n * @throws {ValidationError} Invalid peer config (missing id, invalid url, etc.)\n * @throws {PeerAlreadyExistsError} Peer with same ID already exists (409 Conflict)\n * @throws {UnauthorizedError} Admin API authentication failed (401)\n * @throws {NetworkError} Connection to admin API failed\n * @throws {ConnectorError} Admin API server error (5xx)\n */\n async addPeer(config: {\n id: string;\n url: string;\n authToken: string;\n routes?: { prefix: string; priority?: number }[];\n settlement?: {\n preference: string;\n evmAddress?: string;\n tokenAddress?: string;\n tokenNetworkAddress?: string;\n chainId?: number;\n channelId?: string;\n initialDeposit?: string;\n };\n }): Promise<void> {\n // Validate required fields\n if (\n !config.id ||\n typeof config.id !== 'string' ||\n config.id.trim() === ''\n ) {\n throw new ValidationError('Peer id must be a non-empty string');\n }\n\n if (\n !config.url ||\n typeof config.url !== 'string' ||\n config.url.trim() === ''\n ) {\n throw new ValidationError('Peer url must be a non-empty string');\n }\n\n // Validate BTP URL format (accept both ws:// and btp+ws:// formats)\n const hasWsPrefix =\n config.url.startsWith('ws://') || config.url.startsWith('wss://');\n const hasBtpPrefix =\n config.url.startsWith('btp+ws://') || config.url.startsWith('btp+wss://');\n\n if (!hasWsPrefix && !hasBtpPrefix) {\n throw new ValidationError(\n `Invalid BTP URL format: \"${config.url}\". Must start with 'ws://', 'wss://', 'btp+ws://', or 'btp+wss://'`\n );\n }\n\n // authToken can be empty string for BTP (doesn't require authentication)\n if (\n config.authToken === undefined ||\n config.authToken === null ||\n typeof config.authToken !== 'string'\n ) {\n throw new ValidationError(\n 'Peer authToken must be a string (can be empty for no auth)'\n );\n }\n\n // Validate routes (if provided)\n if (config.routes !== undefined) {\n if (!Array.isArray(config.routes)) {\n throw new ValidationError('Peer routes must be an array');\n }\n\n for (const route of config.routes) {\n if (\n !route.prefix ||\n typeof route.prefix !== 'string' ||\n route.prefix.trim() === ''\n ) {\n throw new ValidationError('Route prefix must be a non-empty string');\n }\n if (\n route.priority !== undefined &&\n typeof route.priority !== 'number'\n ) {\n throw new ValidationError('Route priority must be a number');\n }\n }\n }\n\n // Validate settlement (if provided)\n if (config.settlement !== undefined) {\n if (typeof config.settlement !== 'object' || config.settlement === null) {\n throw new ValidationError('Peer settlement must be an object');\n }\n\n if (\n !config.settlement.preference ||\n typeof config.settlement.preference !== 'string'\n ) {\n throw new ValidationError(\n 'Settlement preference must be a non-empty string'\n );\n }\n }\n\n // Send HTTP POST request with retry logic\n const url = `${this.adminUrl}/admin/peers`;\n\n await withRetry(async () => this.sendAddPeerRequest(url, config), {\n maxRetries: this.retryConfig.maxRetries,\n retryDelay: this.retryConfig.retryDelay,\n exponentialBackoff: true,\n shouldRetry: (error) => {\n // Only retry on network errors (ECONNREFUSED, ETIMEDOUT)\n // Do not retry on validation errors, 4xx, or 5xx errors\n return error instanceof NetworkError;\n },\n });\n }\n\n /**\n * Remove a peer from the connector via the admin API.\n *\n * Sends HTTP DELETE to /admin/peers/:id.\n *\n * @param peerId - Unique peer identifier to remove (non-empty string)\n *\n * @throws {ValidationError} Invalid peerId (empty string)\n * @throws {PeerNotFoundError} Peer does not exist (404 Not Found)\n * @throws {UnauthorizedError} Admin API authentication failed (401)\n * @throws {NetworkError} Connection to admin API failed\n * @throws {ConnectorError} Admin API server error (5xx)\n */\n async removePeer(peerId: string): Promise<void> {\n // Validate peerId\n if (!peerId || typeof peerId !== 'string' || peerId.trim() === '') {\n throw new ValidationError('peerId must be a non-empty string');\n }\n\n // Send HTTP DELETE request with retry logic\n const url = `${this.adminUrl}/admin/peers/${encodeURIComponent(peerId)}`;\n\n await withRetry(async () => this.sendRemovePeerRequest(url, peerId), {\n maxRetries: this.retryConfig.maxRetries,\n retryDelay: this.retryConfig.retryDelay,\n exponentialBackoff: true,\n shouldRetry: (error) => {\n // Only retry on network errors\n return error instanceof NetworkError;\n },\n });\n }\n\n /**\n * Add multiple peers in parallel for efficient bootstrapping.\n *\n * Uses Promise.allSettled() to execute peer additions concurrently,\n * returning results for each operation regardless of individual failures.\n *\n * @param configs - Array of peer configurations to add\n * @returns Array of results indicating success/failure for each peer\n *\n * @example\n * ```typescript\n * const results = await admin.addPeers([\n * { id: 'peer1', url: 'btp+ws://...', authToken: 'token1' },\n * { id: 'peer2', url: 'btp+ws://...', authToken: 'token2' },\n * ]);\n *\n * results.forEach(result => {\n * if (result.success) {\n * console.log(`Added peer: ${result.peerId}`);\n * } else {\n * console.error(`Failed to add ${result.peerId}:`, result.error);\n * }\n * });\n * ```\n */\n async addPeers(\n configs: {\n id: string;\n url: string;\n authToken: string;\n routes?: { prefix: string; priority?: number }[];\n settlement?: {\n preference: string;\n evmAddress?: string;\n tokenAddress?: string;\n tokenNetworkAddress?: string;\n chainId?: number;\n channelId?: string;\n initialDeposit?: string;\n };\n }[]\n ): Promise<PeerOperationResult[]> {\n const results = await Promise.allSettled(\n configs.map((config) => this.addPeer(config))\n );\n\n return results.map((result, index) => {\n const config = configs[index];\n return {\n peerId: config ? config.id : 'unknown',\n success: result.status === 'fulfilled',\n error: result.status === 'rejected' ? result.reason : undefined,\n };\n });\n }\n\n /**\n * Remove multiple peers in parallel.\n *\n * Uses Promise.allSettled() to execute peer removals concurrently,\n * returning results for each operation regardless of individual failures.\n *\n * @param peerIds - Array of peer IDs to remove\n * @returns Array of results indicating success/failure for each peer\n *\n * @example\n * ```typescript\n * const results = await admin.removePeers(['peer1', 'peer2', 'peer3']);\n *\n * const succeeded = results.filter(r => r.success).length;\n * console.log(`Removed ${succeeded}/${results.length} peers`);\n * ```\n */\n async removePeers(peerIds: string[]): Promise<PeerOperationResult[]> {\n const results = await Promise.allSettled(\n peerIds.map((peerId) => this.removePeer(peerId))\n );\n\n return results.map((result, index) => {\n const peerId = peerIds[index];\n return {\n peerId: peerId ?? 'unknown',\n success: result.status === 'fulfilled',\n error: result.status === 'rejected' ? result.reason : undefined,\n };\n });\n }\n\n /**\n * Send HTTP POST request to add a peer.\n * Separated for retry logic wrapping.\n */\n private async sendAddPeerRequest(\n url: string,\n config: {\n id: string;\n url: string;\n authToken: string;\n routes?: { prefix: string; priority?: number }[];\n settlement?: {\n preference: string;\n evmAddress?: string;\n tokenAddress?: string;\n tokenNetworkAddress?: string;\n chainId?: number;\n channelId?: string;\n initialDeposit?: string;\n };\n }\n ): Promise<void> {\n // Normalize URL for connector API (expects ws:// or wss://)\n // Strip btp+ prefix if present, or use as-is if already plain ws://\n let connectorUrl = config.url;\n if (connectorUrl.startsWith('btp+')) {\n connectorUrl = connectorUrl.replace(/^btp\\+/, '');\n }\n\n try {\n const response = await this.httpClient(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n ...config,\n url: connectorUrl,\n }),\n signal: AbortSignal.timeout(this.timeout),\n });\n\n if (response.ok) {\n return; // Success (201 Created)\n }\n\n // Handle error responses\n await this.handleErrorResponse(response, `POST ${url}`, config.id);\n } catch (error) {\n this.handleNetworkError(error, url, 'addPeer');\n }\n }\n\n /**\n * Send HTTP DELETE request to remove a peer.\n * Separated for retry logic wrapping.\n */\n private async sendRemovePeerRequest(\n url: string,\n peerId: string\n ): Promise<void> {\n try {\n const response = await this.httpClient(url, {\n method: 'DELETE',\n signal: AbortSignal.timeout(this.timeout),\n });\n\n if (response.ok) {\n return; // Success (204 No Content)\n }\n\n // Handle error responses\n await this.handleErrorResponse(response, `DELETE ${url}`, peerId);\n } catch (error) {\n this.handleNetworkError(error, url, 'removePeer');\n }\n }\n\n /**\n * Handle network errors from HTTP requests.\n *\n * Converts connection failures, timeouts, and unknown errors to NetworkError.\n * Re-throws existing ToonClientError instances.\n *\n * @param error - Error thrown by HTTP client\n * @param url - Request URL (for error messages)\n * @param operation - Operation name (for error messages)\n * @throws {NetworkError} Network connection or timeout error\n */\n private handleNetworkError(\n error: unknown,\n url: string,\n operation: string\n ): never {\n // Timeout errors (AbortSignal)\n if (error instanceof Error && error.name === 'AbortError') {\n throw new NetworkError(\n `Request to ${url} timed out after ${this.timeout}ms`,\n error\n );\n }\n\n // Connection errors (ECONNREFUSED, ETIMEDOUT, DNS failures)\n if (\n error instanceof Error &&\n (error.message.includes('ECONNREFUSED') ||\n error.message.includes('ETIMEDOUT') ||\n error.message.includes('ENOTFOUND'))\n ) {\n throw new NetworkError(\n `Failed to connect to connector admin API at ${url}: ${error.message}`,\n error\n );\n }\n\n // Re-throw if already a ToonClientError\n if (\n error instanceof ValidationError ||\n error instanceof PeerAlreadyExistsError ||\n error instanceof PeerNotFoundError ||\n error instanceof UnauthorizedError ||\n error instanceof ConnectorError\n ) {\n throw error;\n }\n\n // Unknown error - wrap in NetworkError\n throw new NetworkError(\n `Unexpected error during ${operation}: ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n\n /**\n * Handle HTTP error responses from the admin API.\n *\n * Converts HTTP status codes to appropriate error types.\n *\n * @param response - HTTP response from admin API\n * @param endpoint - Endpoint being called (for error messages)\n * @param peerId - Peer ID (for error messages)\n * @throws {UnauthorizedError} 401 Unauthorized\n * @throws {PeerNotFoundError} 404 Not Found\n * @throws {PeerAlreadyExistsError} 409 Conflict\n * @throws {ConnectorError} 5xx Server Error\n */\n private async handleErrorResponse(\n response: Response,\n endpoint: string,\n peerId: string\n ): Promise<never> {\n const status = response.status;\n const statusText = response.statusText;\n\n // Try to extract error message from response body\n let errorMessage = '';\n try {\n const body = await response.text();\n if (body) {\n errorMessage = ` - ${body}`;\n }\n } catch {\n // Ignore body parsing errors\n }\n\n switch (status) {\n case 401:\n throw new UnauthorizedError(\n `Admin API authentication failed for ${endpoint}: ${statusText}${errorMessage}`\n );\n\n case 404:\n throw new PeerNotFoundError(\n `Peer not found: \"${peerId}\" (${endpoint}): ${statusText}${errorMessage}`\n );\n\n case 409:\n throw new PeerAlreadyExistsError(\n `Peer already exists: \"${peerId}\" (${endpoint}): ${statusText}${errorMessage}`\n );\n\n default:\n if (status >= 500) {\n throw new ConnectorError(\n `Connector admin API error (${endpoint}): ${status} ${statusText}${errorMessage}`\n );\n }\n\n // Other 4xx errors (400, 403, etc.)\n throw new ConnectorError(\n `Admin API error (${endpoint}): ${status} ${statusText}${errorMessage}`\n );\n }\n }\n}\n"],"mappings":"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","names":["generateSecretKey","generateSecretKey"]}
|
|
1
|
+
{"version":3,"sources":["../src/ToonClient.ts","../src/config.ts","../src/errors.ts","../src/modes/http.ts","../src/utils/retry.ts","../src/adapters/HttpRuntimeClient.ts","../src/adapters/BtpRuntimeClient.ts","../src/channel/OnChainChannelClient.ts","../src/signing/evm-signer.ts","../src/channel/ChannelManager.ts","../src/channel/ChannelStore.ts","../src/adapters/HttpConnectorAdmin.ts"],"sourcesContent":["import { createHash } from 'crypto';\nimport { generateSecretKey, getPublicKey } from 'nostr-tools/pure';\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport type {\n BootstrapService,\n DiscoveryTracker,\n IlpSendResult,\n IlpClient,\n} from '@toon-protocol/core';\nimport { validateConfig, applyDefaults } from './config.js';\nimport type { ResolvedConfig } from './config.js';\nimport { initializeHttpMode } from './modes/http.js';\nimport { ToonClientError } from './errors.js';\nimport { EvmSigner } from './signing/evm-signer.js';\nimport { ChannelManager } from './channel/ChannelManager.js';\nimport { JsonFileChannelStore } from './channel/ChannelStore.js';\nimport type { BtpRuntimeClient } from './adapters/BtpRuntimeClient.js';\nimport type {\n ToonClientConfig,\n ToonStartResult,\n PublishEventResult,\n SignedBalanceProof,\n} from './types.js';\n\n/**\n * Internal state for ToonClient after initialization.\n */\ninterface ToonClientState {\n bootstrapService: BootstrapService;\n discoveryTracker: DiscoveryTracker;\n runtimeClient: IlpClient;\n peersDiscovered: number;\n btpClient?: BtpRuntimeClient;\n}\n\n/**\n * ToonClient - High-level client for interacting with TOON network.\n *\n * This story implements HTTP mode only. Embedded mode will be added in a future epic.\n *\n * @example HTTP Mode\n * ```typescript\n * import { ToonClient } from '@toon-protocol/client';\n * import { generateSecretKey, getPublicKey } from 'nostr-tools/pure';\n * import { encodeEvent, decodeEvent } from '@toon-protocol/relay';\n *\n * const secretKey = generateSecretKey();\n * const pubkey = getPublicKey(secretKey);\n *\n * const client = new ToonClient({\n * connectorUrl: 'http://localhost:8080',\n * secretKey,\n * ilpInfo: {\n * pubkey,\n * ilpAddress: `g.toon.${pubkey.slice(0, 8)}`,\n * btpEndpoint: 'ws://localhost:3000',\n * },\n * toonEncoder: encodeEvent,\n * toonDecoder: decodeEvent,\n * });\n *\n * await client.start(); // Bootstrap peers, start monitoring\n *\n * // Publish to default destination (from config)\n * await client.publishEvent(signedEvent);\n *\n * // Publish to specific destination (multi-hop routing)\n * await client.publishEvent(signedEvent, { destination: 'g.toon.peer1' });\n *\n * await client.stop(); // Cleanup\n * ```\n */\nexport class ToonClient {\n private readonly config: ResolvedConfig;\n private state: ToonClientState | null = null;\n private readonly evmSigner?: EvmSigner;\n private channelManager?: ChannelManager;\n\n /**\n * Creates a new ToonClient instance.\n *\n * @param config - Client configuration\n * @throws {ValidationError} If configuration is invalid\n */\n constructor(config: ToonClientConfig) {\n // Validate config (will reject embedded mode, require connectorUrl)\n validateConfig(config);\n\n // Apply defaults to optional fields (auto-generates secretKey if needed)\n this.config = applyDefaults(config);\n\n // Create EVM signer if private key provided\n if (this.config.evmPrivateKey) {\n this.evmSigner = new EvmSigner(this.config.evmPrivateKey);\n }\n }\n\n /**\n * Generates a new Nostr keypair.\n *\n * @returns Object with secretKey (Uint8Array) and pubkey (hex string)\n */\n static generateKeypair(): { secretKey: Uint8Array; pubkey: string } {\n const secretKey = generateSecretKey();\n const pubkey = getPublicKey(secretKey);\n return { secretKey, pubkey };\n }\n\n /**\n * Gets the Nostr public key derived from the secret key.\n * Works before start() is called.\n */\n getPublicKey(): string {\n return getPublicKey(this.config.secretKey);\n }\n\n /**\n * Gets the EVM address derived from the Nostr secret key (or explicit evmPrivateKey override).\n */\n getEvmAddress(): string | undefined {\n return this.evmSigner?.address;\n }\n\n /**\n * Starts the ToonClient.\n *\n * This will:\n * 1. Initialize HTTP mode components (runtime client, admin client, bootstrap, monitor)\n * 2. Bootstrap the network (discover peers, register, and open channels)\n * 3. Start monitoring relay for new peers (kind:10032 events)\n *\n * @returns Result with number of peers discovered and mode\n * @throws {ToonClientError} If client is already started\n * @throws {ToonClientError} If initialization fails\n */\n async start(): Promise<ToonStartResult> {\n if (this.state !== null) {\n throw new ToonClientError('Client already started', 'INVALID_STATE');\n }\n\n try {\n // Create channel manager FIRST (before bootstrap) so it can sign claims during settlement\n if (this.evmSigner) {\n const store = this.config.channelStorePath\n ? new JsonFileChannelStore(this.config.channelStorePath)\n : undefined;\n this.channelManager = new ChannelManager(this.evmSigner, store);\n }\n\n // Initialize HTTP mode components\n const initialization = await initializeHttpMode(this.config);\n\n const { bootstrapService, discoveryTracker, runtimeClient, btpClient } =\n initialization;\n\n // Wire claim signer to bootstrap service if we have channel manager\n if (this.channelManager) {\n const cm = this.channelManager;\n const nostrPubkey = this.getPublicKey();\n // Derive default chain context from config (first supported chain)\n const defaultChainCtx = this.getDefaultChainContext();\n bootstrapService.setClaimSigner(\n async (channelId: string, amount: bigint) => {\n // Track the channel if not already tracked\n if (!cm.isTracking(channelId)) {\n cm.trackChannel(channelId, defaultChainCtx);\n }\n // Sign balance proof and build full claim message\n const proof = await cm.signBalanceProof(channelId, amount);\n return EvmSigner.buildClaimMessage(proof, nostrPubkey);\n }\n );\n }\n\n // Start bootstrap process (discover peers, register with settlement, announce)\n const bootstrapResults = await bootstrapService.bootstrap();\n\n // Track any additional channels from bootstrap results\n if (this.channelManager) {\n for (const result of bootstrapResults) {\n if (\n result.channelId &&\n !this.channelManager.isTracking(result.channelId)\n ) {\n const chainCtx = this.getChainContext(result.negotiatedChain);\n this.channelManager.trackChannel(\n result.channelId,\n chainCtx\n );\n }\n }\n }\n\n // Store state\n this.state = {\n bootstrapService,\n discoveryTracker,\n runtimeClient,\n peersDiscovered: bootstrapResults.length,\n btpClient: btpClient ?? undefined,\n };\n\n return {\n peersDiscovered: bootstrapResults.length,\n mode: 'http',\n };\n } catch (error) {\n throw new ToonClientError(\n 'Failed to start client',\n 'INITIALIZATION_ERROR',\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Publishes a Nostr event to the relay via ILP payment.\n *\n * The event must already be finalized (signed with id, pubkey, sig).\n *\n * @param event - Signed Nostr event to publish\n * @param options - Optional options including destination and signed balance proof claim\n * @returns Result with success status, event ID, and fulfillment\n * @throws {ToonClientError} If client is not started\n * @throws {ToonClientError} If event publishing fails\n */\n async publishEvent(\n event: NostrEvent,\n options?: { destination?: string; claim?: SignedBalanceProof }\n ): Promise<PublishEventResult> {\n if (!this.state) {\n throw new ToonClientError(\n 'Client not started. Call start() first.',\n 'INVALID_STATE'\n );\n }\n\n try {\n // Encode event to TOON format\n const toonData = this.config.toonEncoder(event);\n\n // Calculate payment amount: basePricePerByte * encoded size\n const basePricePerByte = 10n;\n const amount = String(BigInt(toonData.length) * basePricePerByte);\n\n // Use provided destination or fall back to config default\n const destination =\n options?.destination ?? this.config.destinationAddress;\n\n // Compute ILP execution condition from raw TOON data bytes.\n // The connector's PaymentHandlerAdapter computes:\n // fulfillment = SHA-256(raw_toon_bytes)\n // So condition = SHA-256(fulfillment) = SHA-256(SHA-256(raw_toon_bytes)).\n const fulfillment = createHash('sha256').update(Buffer.from(toonData)).digest();\n const executionCondition = createHash('sha256').update(fulfillment).digest();\n\n // Require claim + BTP — plain sendIlpPacket is only valid for\n // node-to-node forwarding (town.ts), not client-to-node.\n if (!options?.claim) {\n throw new ToonClientError(\n 'Signed balance proof required. Call signBalanceProof() first.',\n 'MISSING_CLAIM'\n );\n }\n if (!this.state.btpClient) {\n throw new ToonClientError(\n 'BTP client required for publishing. Configure btpUrl.',\n 'NO_BTP_CLIENT'\n );\n }\n\n const claimMessage = EvmSigner.buildClaimMessage(\n options.claim,\n this.getPublicKey()\n );\n const response = await this.state.btpClient.sendIlpPacketWithClaim(\n {\n destination,\n amount,\n data: Buffer.from(toonData).toString('base64'),\n executionCondition,\n },\n claimMessage\n );\n\n if (!response.accepted) {\n return {\n success: false,\n error: `Event rejected: ${response.code} - ${response.message}`,\n };\n }\n\n return {\n success: true,\n eventId: event.id,\n fulfillment: response.fulfillment,\n };\n } catch (error) {\n throw new ToonClientError(\n 'Failed to publish event',\n 'PUBLISH_ERROR',\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Signs a balance proof for the given channel with the specified amount.\n * Delegates to ChannelManager which auto-increments nonce and tracks cumulative amount.\n *\n * @param channelId - Payment channel identifier\n * @param amount - Additional amount to add to cumulative transferred amount\n * @returns Signed balance proof\n * @throws {ToonClientError} If no EVM signer configured or channel not tracked\n */\n async signBalanceProof(\n channelId: string,\n amount: bigint\n ): Promise<SignedBalanceProof> {\n if (!this.channelManager) {\n throw new ToonClientError(\n 'No EVM signer configured. Provide evmPrivateKey in config.',\n 'NO_EVM_SIGNER'\n );\n }\n return this.channelManager.signBalanceProof(channelId, amount);\n }\n\n /**\n * Gets list of tracked payment channel IDs.\n */\n getTrackedChannels(): string[] {\n return this.channelManager?.getTrackedChannels() ?? [];\n }\n\n /**\n * Gets the current nonce for a tracked channel.\n */\n getChannelNonce(channelId: string): number {\n if (!this.channelManager) throw new Error('ChannelManager not initialized');\n return this.channelManager.getNonce(channelId);\n }\n\n /**\n * Gets the cumulative transferred amount for a tracked channel.\n */\n getChannelCumulativeAmount(channelId: string): bigint {\n if (!this.channelManager) throw new Error('ChannelManager not initialized');\n return this.channelManager.getCumulativeAmount(channelId);\n }\n\n /**\n * Extracts chain context (chainId + tokenNetworkAddress) from a chain key like 'evm:base:421614'.\n */\n private getChainContext(\n negotiatedChain?: string\n ): { chainId: number; tokenNetworkAddress: string; tokenAddress?: string } | undefined {\n if (!negotiatedChain) return undefined;\n const parts = negotiatedChain.split(':');\n const numericChainId = parts.length >= 3 ? parseInt(parts[2]!, 10) : NaN;\n if (isNaN(numericChainId)) return undefined;\n const tokenNetworkAddress =\n this.config.tokenNetworks?.[negotiatedChain];\n if (!tokenNetworkAddress) return undefined;\n const tokenAddress = this.config.preferredTokens?.[negotiatedChain];\n return { chainId: numericChainId, tokenNetworkAddress, tokenAddress };\n }\n\n /**\n * Gets the default chain context from the first supported chain in config.\n */\n private getDefaultChainContext():\n | { chainId: number; tokenNetworkAddress: string; tokenAddress?: string }\n | undefined {\n const chains = this.config.supportedChains;\n if (!chains?.length) return undefined;\n return this.getChainContext(chains[0]);\n }\n\n /**\n * Sends an ILP payment, optionally with a balance proof claim via BTP.\n *\n * @param params - Payment parameters\n * @returns ILP send result\n * @throws {ToonClientError} If client is not started\n */\n async sendPayment(params: {\n destination: string;\n amount: string;\n data?: string;\n claim?: SignedBalanceProof;\n }): Promise<IlpSendResult> {\n if (!this.state) {\n throw new ToonClientError(\n 'Client not started. Call start() first.',\n 'INVALID_STATE'\n );\n }\n\n const ilpParams = {\n destination: params.destination,\n amount: params.amount,\n data: params.data ?? '',\n };\n\n // Require claim + BTP — plain sendIlpPacket is only valid for\n // node-to-node forwarding (town.ts), not client-to-node.\n if (!params.claim) {\n throw new ToonClientError(\n 'Signed balance proof required. Call signBalanceProof() first.',\n 'MISSING_CLAIM'\n );\n }\n if (!this.state.btpClient) {\n throw new ToonClientError(\n 'BTP client required for sending payments. Configure btpUrl.',\n 'NO_BTP_CLIENT'\n );\n }\n\n const claimMessage = EvmSigner.buildClaimMessage(\n params.claim,\n this.getPublicKey()\n );\n return this.state.btpClient.sendIlpPacketWithClaim(\n ilpParams,\n claimMessage\n );\n }\n\n /**\n * Stops the ToonClient and cleans up resources.\n *\n * This will:\n * 1. Disconnect BTP client if connected\n * 2. Clear internal state\n *\n * @throws {ToonClientError} If client is not started\n */\n async stop(): Promise<void> {\n if (!this.state) {\n throw new ToonClientError('Client not started', 'INVALID_STATE');\n }\n\n try {\n // Disconnect BTP client if connected\n if (this.state.btpClient) {\n await this.state.btpClient.disconnect();\n }\n\n // Clear state\n this.state = null;\n } catch (error) {\n throw new ToonClientError(\n 'Failed to stop client',\n 'STOP_ERROR',\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Returns true if the client is currently started.\n */\n isStarted(): boolean {\n return this.state !== null;\n }\n\n /**\n * Gets the number of peers discovered during bootstrap.\n *\n * @returns Number of peers discovered\n * @throws {ToonClientError} If client is not started\n */\n getPeersCount(): number {\n if (!this.state) {\n throw new ToonClientError(\n 'Client not started. Call start() first.',\n 'INVALID_STATE'\n );\n }\n\n return this.state.peersDiscovered;\n }\n\n /**\n * Gets the list of peers discovered by the relay monitor.\n *\n * @returns Array of discovered peer objects\n * @throws {ToonClientError} If client is not started\n */\n getDiscoveredPeers() {\n if (!this.state) {\n throw new ToonClientError(\n 'Client not started. Call start() first.',\n 'INVALID_STATE'\n );\n }\n\n return this.state.discoveryTracker.getDiscoveredPeers();\n }\n}\n","import { generateSecretKey } from 'nostr-tools/pure';\nimport { ValidationError } from './errors.js';\nimport type { ToonClientConfig } from './types.js';\n\n/**\n * Settlement info produced by buildSettlementInfo().\n * Extends the core SettlementConfig shape with ilpAddress for client use.\n */\nexport interface ClientSettlementInfo {\n ilpAddress?: string;\n supportedChains?: string[];\n settlementAddresses?: Record<string, string>;\n preferredTokens?: Record<string, string>;\n tokenNetworks?: Record<string, string>;\n}\n\n/**\n * Validates ToonClient configuration.\n *\n * This story implements HTTP mode only. Embedded mode validation will be added in a future epic.\n *\n * @throws {ValidationError} If configuration is invalid\n */\nexport function validateConfig(config: ToonClientConfig): void {\n // Reject embedded mode (not implemented in this story)\n if (config.connector !== undefined) {\n throw new ValidationError(\n 'Embedded mode not yet implemented in ToonClient. Use connectorUrl for HTTP mode.'\n );\n }\n\n // Require connectorUrl for HTTP mode\n if (!config.connectorUrl) {\n throw new ValidationError(\n 'connectorUrl is required for HTTP mode. Example: \"http://localhost:8080\"'\n );\n }\n\n // Validate connectorUrl format\n try {\n const url = new URL(config.connectorUrl);\n if (!url.protocol.startsWith('http')) {\n throw new Error('Must be HTTP or HTTPS');\n }\n } catch (error) {\n throw new ValidationError(\n `Invalid connectorUrl: must be a valid HTTP/HTTPS URL (e.g., \"http://localhost:8080\"). ` +\n `Error: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n\n // Validate secretKey only when provided\n if (config.secretKey !== undefined) {\n if (!config.secretKey || config.secretKey.length !== 32) {\n throw new ValidationError(\n 'secretKey must be 32 bytes (Nostr private key)'\n );\n }\n }\n\n if (!config.ilpInfo?.ilpAddress) {\n throw new ValidationError('ilpInfo.ilpAddress is required');\n }\n\n if (!config.toonEncoder || typeof config.toonEncoder !== 'function') {\n throw new ValidationError('toonEncoder function is required');\n }\n\n if (!config.toonDecoder || typeof config.toonDecoder !== 'function') {\n throw new ValidationError('toonDecoder function is required');\n }\n\n // Validate evmPrivateKey format when provided\n if (config.evmPrivateKey !== undefined) {\n if (config.evmPrivateKey instanceof Uint8Array) {\n if (config.evmPrivateKey.length !== 32) {\n throw new ValidationError('evmPrivateKey must be 32 bytes');\n }\n } else if (typeof config.evmPrivateKey === 'string') {\n const hex = config.evmPrivateKey.startsWith('0x')\n ? config.evmPrivateKey.slice(2)\n : config.evmPrivateKey;\n if (!/^[0-9a-fA-F]{64}$/.test(hex)) {\n throw new ValidationError('evmPrivateKey must be a 32-byte hex string');\n }\n } else {\n throw new ValidationError(\n 'evmPrivateKey must be a hex string or Uint8Array'\n );\n }\n }\n\n // Validate btpUrl when provided\n if (config.btpUrl !== undefined) {\n try {\n const url = new URL(config.btpUrl);\n if (!url.protocol.startsWith('ws')) {\n throw new Error('Must be WS or WSS');\n }\n } catch (error) {\n throw new ValidationError(\n `Invalid btpUrl: must be a valid WebSocket URL (e.g., \"ws://localhost:3000\"). ` +\n `Error: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n\n // Validate chainRpcUrls keys match supportedChains when both present\n if (config.chainRpcUrls && config.supportedChains) {\n for (const chain of Object.keys(config.chainRpcUrls)) {\n if (!config.supportedChains.includes(chain)) {\n throw new ValidationError(\n `chainRpcUrls key \"${chain}\" is not in supportedChains`\n );\n }\n }\n }\n}\n\n/**\n * The resolved config type after defaults are applied.\n * secretKey is guaranteed to be present (auto-generated if omitted).\n */\nexport type ResolvedConfig = Required<\n Omit<\n ToonClientConfig,\n | 'connector'\n | 'evmPrivateKey'\n | 'supportedChains'\n | 'settlementAddresses'\n | 'preferredTokens'\n | 'tokenNetworks'\n | 'btpUrl'\n | 'btpAuthToken'\n | 'btpPeerId'\n | 'chainRpcUrls'\n | 'initialDeposit'\n | 'settlementTimeout'\n | 'channelStorePath'\n | 'knownPeers'\n | 'destinationAddress'\n >\n> & {\n connector?: unknown;\n /** Always present after applyDefaults() — derived from secretKey if not explicitly provided */\n evmPrivateKey: string | Uint8Array;\n supportedChains?: string[];\n settlementAddresses?: Record<string, string>;\n preferredTokens?: Record<string, string>;\n tokenNetworks?: Record<string, string>;\n btpUrl?: string;\n btpAuthToken?: string;\n btpPeerId?: string;\n chainRpcUrls?: Record<string, string>;\n initialDeposit?: string;\n settlementTimeout?: number;\n channelStorePath?: string;\n knownPeers?: {\n pubkey: string;\n relayUrl: string;\n btpEndpoint?: string;\n }[];\n destinationAddress: string;\n};\n\n/**\n * Applies default values to optional configuration fields.\n * Auto-generates a Nostr keypair when secretKey is omitted.\n * Derives btpUrl from connectorUrl when not provided.\n */\nexport function applyDefaults(config: ToonClientConfig): ResolvedConfig {\n // Auto-generate Nostr keypair when secretKey is omitted\n const secretKey = config.secretKey ?? generateSecretKey();\n\n // Derive btpUrl from connectorUrl when not explicitly provided\n // http://host:8080 → ws://host:3000\n let btpUrl = config.btpUrl;\n if (!btpUrl && config.connectorUrl) {\n try {\n const url = new URL(config.connectorUrl);\n const wsProtocol = url.protocol === 'https:' ? 'wss:' : 'ws:';\n btpUrl = `${wsProtocol}//${url.hostname}:3000`;\n } catch {\n // connectorUrl already validated, this shouldn't happen\n }\n }\n\n // Derive destinationAddress from connectorUrl port when not explicitly provided\n // This provides sensible defaults for local development:\n // - http://localhost:8080 → g.toon.genesis (genesis node)\n // - http://localhost:8090 → g.toon.peer1 (peer1 node)\n // For production, explicitly set destinationAddress in config\n let destinationAddress = config.destinationAddress;\n if (!destinationAddress && config.connectorUrl) {\n try {\n const url = new URL(config.connectorUrl);\n if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') {\n // Map common local ports to known nodes\n if (url.port === '8080') {\n destinationAddress = 'g.toon.genesis';\n } else if (url.port === '8090') {\n destinationAddress = 'g.toon.peer1';\n } else if (url.port === '8100') {\n destinationAddress = 'g.toon.peer2';\n } else {\n // Fallback: use ilpInfo.ilpAddress if available\n destinationAddress = config.ilpInfo?.ilpAddress || 'g.toon.relay';\n }\n } else {\n // Production: default to ilpInfo.ilpAddress\n destinationAddress = config.ilpInfo?.ilpAddress || 'g.toon.relay';\n }\n } catch {\n destinationAddress = config.ilpInfo?.ilpAddress || 'g.toon.relay';\n }\n }\n\n // Derive EVM private key from Nostr secret key when not explicitly provided.\n // Both Nostr and EVM use secp256k1, so a single 32-byte key works for both.\n const evmPrivateKey = config.evmPrivateKey ?? secretKey;\n\n return {\n ...config,\n secretKey,\n evmPrivateKey,\n connectorUrl: config.connectorUrl as string, // Already validated as required\n relayUrl: config.relayUrl ?? 'ws://localhost:7100',\n queryTimeout: config.queryTimeout ?? 30000,\n maxRetries: config.maxRetries ?? 3,\n retryDelay: config.retryDelay ?? 1000,\n btpUrl,\n destinationAddress: destinationAddress as string, // Always set by logic above\n };\n}\n\n/**\n * Builds SettlementConfig from client config.\n * Returns undefined if no settlement-related config is present.\n */\nexport function buildSettlementInfo(\n config: ToonClientConfig\n): ClientSettlementInfo | undefined {\n if (\n !config.supportedChains?.length &&\n !config.settlementAddresses &&\n !config.preferredTokens &&\n !config.tokenNetworks\n ) {\n return undefined;\n }\n\n return {\n ilpAddress: config.ilpInfo?.ilpAddress,\n supportedChains: config.supportedChains,\n settlementAddresses: config.settlementAddresses,\n preferredTokens: config.preferredTokens,\n tokenNetworks: config.tokenNetworks,\n };\n}\n","/**\n * Base error class for all TOON client errors.\n */\nexport class ToonClientError extends Error {\n constructor(\n message: string,\n public readonly code: string,\n cause?: Error\n ) {\n super(message, { cause });\n this.name = 'ToonClientError';\n }\n}\n\n/**\n * Network error for connection failures (ECONNREFUSED, ETIMEDOUT).\n * These errors trigger retry logic with exponential backoff.\n */\nexport class NetworkError extends ToonClientError {\n constructor(message: string, cause?: Error) {\n super(message, 'NETWORK_ERROR', cause);\n this.name = 'NetworkError';\n }\n}\n\n/**\n * Connector error for 5xx server errors.\n * These errors indicate the connector is unavailable or malfunctioning.\n */\nexport class ConnectorError extends ToonClientError {\n constructor(message: string, cause?: Error) {\n super(message, 'CONNECTOR_ERROR', cause);\n this.name = 'ConnectorError';\n }\n}\n\n/**\n * Validation error for invalid input parameters.\n * These errors are thrown before making any HTTP requests.\n */\nexport class ValidationError extends ToonClientError {\n constructor(message: string, cause?: Error) {\n super(message, 'VALIDATION_ERROR', cause);\n this.name = 'ValidationError';\n }\n}\n\n/**\n * Unauthorized error for 401 responses from connector admin API.\n * Indicates missing or invalid authentication credentials.\n */\nexport class UnauthorizedError extends ToonClientError {\n constructor(message: string, cause?: Error) {\n super(message, 'UNAUTHORIZED', cause);\n this.name = 'UnauthorizedError';\n }\n}\n\n/**\n * Peer not found error for 404 responses when removing a peer.\n * Indicates the specified peer ID does not exist in the connector.\n */\nexport class PeerNotFoundError extends ToonClientError {\n constructor(message: string, cause?: Error) {\n super(message, 'PEER_NOT_FOUND', cause);\n this.name = 'PeerNotFoundError';\n }\n}\n\n/**\n * Peer already exists error for 409 responses when adding a peer.\n * Indicates a peer with the same ID already exists in the connector.\n */\nexport class PeerAlreadyExistsError extends ToonClientError {\n constructor(message: string, cause?: Error) {\n super(message, 'PEER_ALREADY_EXISTS', cause);\n this.name = 'PeerAlreadyExistsError';\n }\n}\n","import { BootstrapService, createDiscoveryTracker } from '@toon-protocol/core';\nimport type { BootstrapServiceConfig } from '@toon-protocol/core';\nimport { HttpRuntimeClient } from '../adapters/HttpRuntimeClient.js';\nimport { BtpRuntimeClient } from '../adapters/BtpRuntimeClient.js';\nimport { OnChainChannelClient } from '../channel/OnChainChannelClient.js';\nimport { EvmSigner } from '../signing/evm-signer.js';\nimport { buildSettlementInfo } from '../config.js';\nimport type { ResolvedConfig } from '../config.js';\nimport type { HttpModeInitialization } from './types.js';\n\n/**\n * Initializes HTTP mode for ToonClient.\n *\n * HTTP mode uses external connector service via HTTP/WebSocket.\n * This function creates all necessary clients and services for operating in HTTP mode.\n *\n * @param config - ToonClient configuration (must have connectorUrl)\n * @returns Initialized HTTP mode components\n */\nexport async function initializeHttpMode(\n config: ResolvedConfig\n): Promise<HttpModeInitialization> {\n // Derive admin URL from connector URL (change port 8080 → 8081)\n const connectorUrl = config.connectorUrl;\n\n // Build settlement info from config\n const settlementInfo = buildSettlementInfo(config);\n\n // Create BTP runtime client — this is the primary transport for the client SDK.\n // The client connects to the connector via BTP WebSocket to send ILP packets.\n // HTTP is not used for ILP packet transport.\n let btpClient: BtpRuntimeClient | null = null;\n if (config.btpUrl) {\n btpClient = new BtpRuntimeClient({\n btpUrl: config.btpUrl,\n peerId: config.btpPeerId ?? `client`,\n authToken: config.btpAuthToken ?? '',\n });\n await btpClient.connect();\n }\n\n // BTP is the runtime client for sending ILP packets\n const runtimeClient =\n btpClient ??\n new HttpRuntimeClient({\n connectorUrl,\n timeout: config.queryTimeout,\n maxRetries: config.maxRetries,\n retryDelay: config.retryDelay,\n });\n\n // Create on-chain channel client when chain RPC URLs are configured.\n // evmPrivateKey is always present (derived from secretKey by default).\n let onChainChannelClient: OnChainChannelClient | null = null;\n if (config.chainRpcUrls) {\n const evmSigner = new EvmSigner(config.evmPrivateKey);\n onChainChannelClient = new OnChainChannelClient({\n evmSigner,\n chainRpcUrls: config.chainRpcUrls,\n });\n }\n\n // Create BootstrapService\n const bootstrapConfig: BootstrapServiceConfig = {\n knownPeers: (config.knownPeers || []).map((p) => ({\n pubkey: p.pubkey,\n relayUrl: p.relayUrl,\n btpEndpoint: p.btpEndpoint ?? '',\n })),\n queryTimeout: config.queryTimeout,\n ardriveEnabled: true,\n defaultRelayUrl: config.relayUrl,\n settlementInfo,\n ownIlpAddress: config.ilpInfo.ilpAddress,\n toonEncoder: config.toonEncoder,\n toonDecoder: config.toonDecoder,\n basePricePerByte: 10n, // Default pricing\n };\n\n const bootstrapService = new BootstrapService(\n bootstrapConfig,\n config.secretKey,\n config.ilpInfo\n );\n\n // Wire ILP client into bootstrap service\n bootstrapService.setIlpClient(runtimeClient);\n\n // Wire on-chain channel client if available\n if (onChainChannelClient) {\n bootstrapService.setChannelClient(onChainChannelClient);\n }\n\n // Do NOT wire ConnectorAdmin — addPeer() at line 472 is skipped when connectorAdmin is null\n // This is intentional: the client is a standalone peer, not an admin interface\n\n // Create DiscoveryTracker\n const discoveryTracker = createDiscoveryTracker({\n secretKey: config.secretKey,\n settlementInfo,\n });\n\n return {\n bootstrapService,\n discoveryTracker,\n runtimeClient,\n adminClient: null,\n btpClient,\n onChainChannelClient,\n };\n}\n","/**\n * Configuration options for retry behavior with exponential backoff.\n */\nexport interface RetryOptions {\n /** Maximum number of retry attempts (default: 3) */\n maxRetries: number;\n /** Initial delay in milliseconds between retries (default: 1000) */\n retryDelay: number;\n /** Use exponential backoff for delays (default: true) */\n exponentialBackoff?: boolean;\n /** Maximum delay cap in milliseconds (default: 30000) */\n maxDelay?: number;\n /** Custom predicate to determine if an error should trigger a retry */\n shouldRetry?: (error: Error) => boolean;\n}\n\n/**\n * Executes an async operation with retry logic and exponential backoff.\n *\n * @param operation - The async function to execute\n * @param options - Retry configuration options\n * @returns The result of the successful operation\n * @throws The last error if all retries are exhausted\n *\n * @example\n * ```typescript\n * const result = await withRetry(\n * async () => fetchData(),\n * {\n * maxRetries: 3,\n * retryDelay: 1000,\n * shouldRetry: (err) => err.name === 'NetworkError'\n * }\n * );\n * ```\n */\nexport async function withRetry<T>(\n operation: () => Promise<T>,\n options: RetryOptions\n): Promise<T> {\n const {\n maxRetries,\n retryDelay,\n exponentialBackoff = true,\n maxDelay = 30000,\n shouldRetry,\n } = options;\n\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n return await operation();\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n\n // Check if we should retry this error\n if (shouldRetry && !shouldRetry(lastError)) {\n throw lastError;\n }\n\n // If this was the last attempt, throw the error\n if (attempt === maxRetries) {\n throw lastError;\n }\n\n // Calculate delay with exponential backoff\n const currentDelay = exponentialBackoff\n ? Math.min(retryDelay * Math.pow(2, attempt), maxDelay)\n : retryDelay;\n\n // Wait before retrying\n await new Promise((resolve) => setTimeout(resolve, currentDelay));\n }\n }\n\n // This should never be reached, but TypeScript needs it\n throw lastError ?? new Error('Unknown error during retry');\n}\n","import type { IlpClient, IlpSendResult } from '@toon-protocol/core';\nimport { NetworkError, ConnectorError, ValidationError } from '../errors.js';\nimport { withRetry } from '../utils/retry.js';\n\n/**\n * Configuration options for HttpRuntimeClient.\n */\nexport interface HttpRuntimeClientConfig {\n /** Connector runtime API base URL (e.g., 'http://localhost:8080') */\n connectorUrl: string;\n /** Request timeout in milliseconds (default: 30000) */\n timeout?: number;\n /** Maximum retry attempts for network failures (default: 3) */\n maxRetries?: number;\n /** Initial retry delay in milliseconds (default: 1000) */\n retryDelay?: number;\n /** HTTP client implementation (for testing) */\n httpClient?: typeof fetch;\n}\n\n/**\n * HTTP client for sending ILP packets to an external connector runtime API.\n *\n * Implements the IlpClient interface for use with TOON agents\n * that need to send ILP packets without embedding a full connector.\n *\n * Features:\n * - Request validation (destination, amount, data)\n * - Retry logic with exponential backoff for transient network failures\n * - Typed error handling (NetworkError, ConnectorError, ValidationError)\n * - Connection pooling and keep-alive (via Node.js fetch)\n *\n * @example\n * ```typescript\n * const client = new HttpRuntimeClient({\n * connectorUrl: 'http://localhost:8080'\n * });\n *\n * const result = await client.sendIlpPacket({\n * destination: 'g.toon.alice',\n * amount: '1000',\n * data: 'base64EncodedToonData==',\n * });\n *\n * if (result.accepted) {\n * console.log('Payment accepted:', result.fulfillment);\n * } else {\n * console.error('Payment rejected:', result.code, result.message);\n * }\n * ```\n */\nexport class HttpRuntimeClient implements IlpClient {\n private readonly connectorUrl: string;\n private readonly timeout: number;\n private readonly retryConfig: { maxRetries: number; retryDelay: number };\n private readonly httpClient: typeof fetch;\n\n constructor(config: HttpRuntimeClientConfig) {\n // Normalize connector URL (remove trailing slash)\n this.connectorUrl = config.connectorUrl.replace(/\\/$/, '');\n this.timeout = config.timeout ?? 30000;\n this.retryConfig = {\n maxRetries: config.maxRetries ?? 3,\n retryDelay: config.retryDelay ?? 1000,\n };\n this.httpClient = config.httpClient ?? fetch;\n }\n\n /**\n * Send an ILP packet to the connector runtime API.\n *\n * @param params - ILP packet parameters\n * @returns ILP packet response with acceptance status\n * @throws {ValidationError} If request parameters are invalid\n * @throws {NetworkError} If network connection fails after retries\n * @throws {ConnectorError} If connector returns 5xx server error\n */\n async sendIlpPacket(params: {\n destination: string;\n amount: string;\n data: string;\n timeout?: number;\n }): Promise<IlpSendResult> {\n // Validate request parameters\n this.validateRequest(params);\n\n // Wrap HTTP request with retry logic\n return withRetry(async () => this.sendHttpRequest(params), {\n maxRetries: this.retryConfig.maxRetries,\n retryDelay: this.retryConfig.retryDelay,\n exponentialBackoff: true,\n shouldRetry: (error) => {\n // Only retry on network errors (ECONNREFUSED, ETIMEDOUT)\n // Do not retry on validation errors, 4xx, or 5xx errors\n return error instanceof NetworkError;\n },\n });\n }\n\n /**\n * Validate ILP packet request parameters.\n *\n * @throws {ValidationError} If any parameter is invalid\n */\n private validateRequest(params: {\n destination: string;\n amount: string;\n data: string;\n }): void {\n // Validate destination: non-empty, valid ILP address format\n if (!params.destination || params.destination.trim() === '') {\n throw new ValidationError('Destination cannot be empty');\n }\n if (!params.destination.startsWith('g.')) {\n throw new ValidationError(\n `Invalid ILP address format: \"${params.destination}\" (must start with \"g.\")`\n );\n }\n\n // Validate amount: non-empty, parseable as bigint, positive\n if (!params.amount || params.amount.trim() === '') {\n throw new ValidationError('Amount cannot be empty');\n }\n try {\n const amountBigInt = BigInt(params.amount);\n if (amountBigInt <= 0n) {\n throw new ValidationError(\n `Amount must be positive: \"${params.amount}\"`\n );\n }\n } catch (error) {\n if (error instanceof ValidationError) throw error;\n throw new ValidationError(\n `Amount must be a valid integer: \"${params.amount}\"`,\n error instanceof Error ? error : undefined\n );\n }\n\n // Validate data: non-empty, valid Base64 encoding\n if (!params.data || params.data.trim() === '') {\n throw new ValidationError('Data cannot be empty');\n }\n try {\n // Attempt to decode Base64 to validate format\n Buffer.from(params.data, 'base64');\n // Verify it's actually Base64 (not just any string Buffer accepts)\n if (!/^[A-Za-z0-9+/]*={0,2}$/.test(params.data)) {\n throw new ValidationError(\n `Data must be valid Base64 encoding: \"${params.data}\"`\n );\n }\n } catch (error) {\n if (error instanceof ValidationError) throw error;\n throw new ValidationError(\n `Data must be valid Base64 encoding: \"${params.data}\"`,\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Send HTTP POST request to connector runtime API.\n *\n * @throws {NetworkError} On connection failures (ECONNREFUSED, ETIMEDOUT)\n * @throws {ConnectorError} On 5xx server errors\n * @returns IlpSendResult with acceptance status\n */\n private async sendHttpRequest(params: {\n destination: string;\n amount: string;\n data: string;\n timeout?: number;\n }): Promise<IlpSendResult> {\n const requestTimeout = params.timeout ?? this.timeout;\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), requestTimeout);\n\n try {\n // NOTE: Using admin endpoint /admin/ilp/send since connector doesn't have public /ilp endpoint yet\n const response = await this.httpClient(\n `${this.connectorUrl}/admin/ilp/send`,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n destination: params.destination,\n amount: params.amount,\n data: params.data,\n }),\n signal: controller.signal,\n }\n );\n\n clearTimeout(timeoutId);\n\n // Handle response by status code\n if (response.ok) {\n // 200 OK: Parse response as IlpSendResult\n const result = (await response.json()) as Record<string, unknown>;\n return {\n accepted: (result['accepted'] as boolean) ?? false,\n fulfillment: result['fulfillment'] as string | undefined,\n data: result['data'] as string | undefined,\n code: result['code'] as string | undefined,\n message: result['message'] as string | undefined,\n };\n } else if (response.status >= 400 && response.status < 500) {\n // 4xx: Client error - return as failed ILP response (no retry)\n const errorBody = (await response.json().catch(() => ({}))) as Record<\n string,\n unknown\n >;\n return {\n accepted: false,\n code: `HTTP_${response.status}`,\n message:\n (errorBody['message'] as string) ??\n (errorBody['error'] as string) ??\n response.statusText,\n };\n } else if (response.status >= 500 && response.status < 600) {\n // 5xx: Server error - throw ConnectorError (no retry)\n const errorBody = (await response.json().catch(() => ({}))) as Record<\n string,\n unknown\n >;\n throw new ConnectorError(\n `Connector server error (${response.status}): ${\n (errorBody['message'] as string) ??\n (errorBody['error'] as string) ??\n response.statusText\n }`\n );\n }\n\n // Unexpected status code (not 2xx, 4xx, or 5xx)\n throw new ConnectorError(\n `Unexpected HTTP status: ${response.status} ${response.statusText}`\n );\n } catch (error) {\n clearTimeout(timeoutId);\n\n // Handle AbortController timeout\n if (error instanceof Error && error.name === 'AbortError') {\n throw new NetworkError(\n `Request timeout after ${requestTimeout}ms`,\n error\n );\n }\n\n // Handle network errors (ECONNREFUSED, ETIMEDOUT, etc.)\n if (\n error instanceof TypeError &&\n (error.message.includes('fetch failed') ||\n error.message.includes('ECONNREFUSED') ||\n error.message.includes('ETIMEDOUT') ||\n error.message.includes('network'))\n ) {\n throw new NetworkError(\n `Network connection failed: ${error.message}`,\n error\n );\n }\n\n // Re-throw known error types\n if (\n error instanceof NetworkError ||\n error instanceof ConnectorError ||\n error instanceof ValidationError\n ) {\n throw error;\n }\n\n // Unknown error\n throw new ConnectorError(\n `Unexpected error during HTTP request: ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n }\n}\n","import { BTPClient } from '@toon-protocol/connector';\nimport type { ILPPreparePacket } from '@toon-protocol/connector';\nimport type { IlpClient, IlpSendResult } from '@toon-protocol/core';\nimport type { EVMClaimMessage } from '../signing/evm-signer.js';\nimport { withRetry } from '../utils/retry.js';\n\n/** BTP Peer — matches @toon-protocol/connector's Peer interface */\ninterface Peer {\n id: string;\n url: string;\n authToken: string;\n connected: boolean;\n lastSeen: Date;\n}\n\n/** BTP claim protocol constants — matches @toon-protocol/connector's BTP_CLAIM_PROTOCOL */\nconst BTP_CLAIM_PROTOCOL = {\n NAME: 'payment-channel-claim',\n CONTENT_TYPE: 1,\n} as const;\n\n/** Pino-compatible logger interface */\ninterface ConsoleLogger {\n level: string;\n silent: (...args: unknown[]) => void;\n info: typeof console.info;\n warn: typeof console.warn;\n error: typeof console.error;\n debug: typeof console.debug;\n trace: typeof console.debug;\n fatal: typeof console.error;\n child: () => ConsoleLogger;\n}\n\n/** Creates a pino-compatible logger wrapper around console */\nfunction createConsoleLogger(): ConsoleLogger {\n const noop = (..._args: unknown[]) => {\n // intentional no-op for pino's silent log level\n };\n const logger: ConsoleLogger = {\n level: 'info',\n silent: noop,\n info: console.info.bind(console),\n warn: console.warn.bind(console),\n error: console.error.bind(console),\n debug: console.debug.bind(console),\n trace: console.debug.bind(console),\n fatal: console.error.bind(console),\n child: () => createConsoleLogger(),\n };\n return logger;\n}\n\n/** ILP packet type constants — matches @toon-protocol/connector's PacketType enum */\nconst ILP_PACKET_TYPE = {\n PREPARE: 12,\n FULFILL: 13,\n REJECT: 14,\n} as const;\n\n/** Shape of a BTP fulfill response */\ninterface BtpFulfillResponse {\n type: typeof ILP_PACKET_TYPE.FULFILL;\n fulfillment: Buffer;\n data: Buffer;\n}\n\n/** Shape of a BTP reject response */\ninterface BtpRejectResponse {\n type: typeof ILP_PACKET_TYPE.REJECT;\n code: string;\n message: string;\n data: Buffer;\n}\n\nexport interface BtpRuntimeClientConfig {\n btpUrl: string;\n peerId: string;\n authToken: string;\n logger?: ConsoleLogger;\n /** Max reconnection attempts on send failure (default: 3) */\n maxRetries?: number;\n /** Delay between reconnection attempts in ms (default: 1000) */\n retryDelay?: number;\n}\n\n/**\n * Returns true if the error is a connection-level error worth retrying.\n * ILP application-level rejects (F02, T01, etc.) are NOT retried.\n */\nfunction isConnectionError(error: Error): boolean {\n const msg = error.message.toLowerCase();\n return (\n msg.includes('not connected') ||\n msg.includes('connection') ||\n msg.includes('websocket') ||\n msg.includes('econnrefused') ||\n msg.includes('econnreset') ||\n msg.includes('socket hang up') ||\n msg.includes('timeout')\n );\n}\n\n/**\n * BTP transport implementing IlpClient.\n * Wraps BTPClient from @toon-protocol/connector with auto-reconnect on connection loss.\n */\nexport class BtpRuntimeClient implements IlpClient {\n private btpClient: BTPClient | null = null;\n private readonly config: BtpRuntimeClientConfig;\n private _isConnected = false;\n private readonly logger: ConsoleLogger;\n\n constructor(config: BtpRuntimeClientConfig) {\n this.config = config;\n this.logger = config.logger ?? createConsoleLogger();\n }\n\n /**\n * Connects to the BTP peer via WebSocket.\n */\n async connect(): Promise<void> {\n const peer: Peer = {\n id: this.config.peerId,\n url: this.config.btpUrl,\n authToken: this.config.authToken,\n connected: false,\n lastSeen: new Date(),\n };\n\n // Cast logger: ConsoleLogger implements the subset of pino's Logger\n // that BTPClient actually uses at runtime (info, warn, error, debug, child)\n this.btpClient = new BTPClient(\n peer,\n this.config.peerId,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n this.logger as any\n );\n\n await this.btpClient.connect();\n this._isConnected = true;\n }\n\n /**\n * Attempts to reconnect by creating a fresh BTPClient and connecting.\n */\n async reconnect(): Promise<void> {\n // Clean up old client if it exists\n if (this.btpClient) {\n try {\n await this.btpClient.disconnect();\n } catch {\n // Ignore disconnect errors during reconnect\n }\n this.btpClient = null;\n this._isConnected = false;\n }\n\n this.logger.info('[BtpRuntimeClient] Reconnecting...');\n await this.connect();\n }\n\n /**\n * Disconnects from the BTP peer.\n */\n async disconnect(): Promise<void> {\n if (this.btpClient) {\n await this.btpClient.disconnect();\n this._isConnected = false;\n this.btpClient = null;\n }\n }\n\n get isConnected(): boolean {\n return this._isConnected;\n }\n\n /**\n * Sends an ILP packet via BTP with auto-reconnect on connection errors.\n * Satisfies IlpClient interface.\n */\n async sendIlpPacket(params: {\n destination: string;\n amount: string;\n data: string;\n timeout?: number;\n }): Promise<IlpSendResult> {\n return withRetry(() => this._sendIlpPacketOnce(params), {\n maxRetries: this.config.maxRetries ?? 3,\n retryDelay: this.config.retryDelay ?? 1000,\n shouldRetry: (error) => {\n if (!isConnectionError(error)) return false;\n // Mark as disconnected so reconnect happens on next attempt\n this._isConnected = false;\n return true;\n },\n });\n }\n\n /**\n * Sends a balance proof claim via BTP protocol data, then sends an ILP packet.\n * Auto-reconnects on connection errors.\n */\n async sendIlpPacketWithClaim(\n params: {\n destination: string;\n amount: string;\n data: string;\n timeout?: number;\n executionCondition?: Buffer;\n },\n claim: EVMClaimMessage\n ): Promise<IlpSendResult> {\n return withRetry(() => this._sendIlpPacketWithClaimOnce(params, claim), {\n maxRetries: this.config.maxRetries ?? 3,\n retryDelay: this.config.retryDelay ?? 1000,\n shouldRetry: (error) => {\n if (!isConnectionError(error)) return false;\n this._isConnected = false;\n return true;\n },\n });\n }\n\n /**\n * Single-attempt ILP packet send. Reconnects if not connected.\n */\n private async _sendIlpPacketOnce(params: {\n destination: string;\n amount: string;\n data: string;\n timeout?: number;\n executionCondition?: Buffer;\n }): Promise<IlpSendResult> {\n if (!this._isConnected) {\n await this.reconnect();\n }\n\n const packet = {\n type: ILP_PACKET_TYPE.PREPARE,\n amount: BigInt(params.amount),\n destination: params.destination,\n executionCondition: params.executionCondition ?? Buffer.alloc(32),\n expiresAt: new Date(Date.now() + (params.timeout ?? 30000)),\n data: Buffer.from(params.data, 'base64'),\n } as ILPPreparePacket;\n\n const response = await this.btpClient?.sendPacket(packet);\n if (!response) {\n throw new Error('BTP client not connected');\n }\n\n if (response.type === ILP_PACKET_TYPE.FULFILL) {\n const fulfill = response as unknown as BtpFulfillResponse;\n return {\n accepted: true,\n fulfillment: fulfill.fulfillment.toString('base64'),\n data:\n fulfill.data.length > 0 ? fulfill.data.toString('base64') : undefined,\n };\n }\n\n // Reject packet — ILP application-level error, not a connection error\n const reject = response as unknown as BtpRejectResponse;\n return {\n accepted: false,\n code: reject.code,\n message: reject.message,\n data: reject.data.length > 0 ? reject.data.toString('base64') : undefined,\n };\n }\n\n /**\n * Single-attempt claim + ILP packet send. Reconnects if not connected.\n * Embeds the claim in the same BTP message as the ILP PREPARE packet.\n */\n private async _sendIlpPacketWithClaimOnce(\n params: {\n destination: string;\n amount: string;\n data: string;\n timeout?: number;\n executionCondition?: Buffer;\n },\n claim: EVMClaimMessage\n ): Promise<IlpSendResult> {\n if (!this._isConnected) {\n await this.reconnect();\n }\n\n if (!this.btpClient) {\n throw new Error('BTP client not connected');\n }\n\n const packet = {\n type: ILP_PACKET_TYPE.PREPARE,\n amount: BigInt(params.amount),\n destination: params.destination,\n executionCondition: params.executionCondition ?? Buffer.alloc(32),\n expiresAt: new Date(Date.now() + (params.timeout ?? 30000)),\n data: Buffer.from(params.data, 'base64'),\n } as ILPPreparePacket;\n\n // Send ILP packet with claim embedded in the same BTP message\n const protocolData = [\n {\n protocolName: BTP_CLAIM_PROTOCOL.NAME,\n contentType: BTP_CLAIM_PROTOCOL.CONTENT_TYPE,\n data: Buffer.from(JSON.stringify(claim)),\n },\n ];\n\n const response = await this.btpClient.sendPacket(packet, protocolData);\n\n if (response.type === ILP_PACKET_TYPE.FULFILL) {\n const fulfill = response as unknown as BtpFulfillResponse;\n return {\n accepted: true,\n fulfillment: fulfill.fulfillment.toString('base64'),\n data:\n fulfill.data.length > 0 ? fulfill.data.toString('base64') : undefined,\n };\n }\n\n const reject = response as unknown as BtpRejectResponse;\n return {\n accepted: false,\n code: reject.code,\n message: reject.message,\n data: reject.data.length > 0 ? reject.data.toString('base64') : undefined,\n };\n }\n}\n","import {\n createPublicClient,\n createWalletClient,\n http,\n maxUint256,\n decodeEventLog,\n defineChain,\n type Hex,\n type TransactionReceipt,\n} from 'viem';\nimport type {\n ConnectorChannelClient,\n OpenChannelParams,\n OpenChannelResult,\n ChannelState,\n} from '@toon-protocol/core';\nimport type { EvmSigner } from '../signing/evm-signer.js';\n\n// TokenNetwork ABI — only the functions we need\nconst TOKEN_NETWORK_ABI = [\n {\n name: 'openChannel',\n type: 'function',\n stateMutability: 'nonpayable',\n inputs: [\n { name: 'participant2', type: 'address' },\n { name: 'settlementTimeout', type: 'uint256' },\n ],\n outputs: [{ type: 'bytes32' }],\n },\n {\n name: 'setTotalDeposit',\n type: 'function',\n stateMutability: 'nonpayable',\n inputs: [\n { name: 'channelId', type: 'bytes32' },\n { name: 'participant', type: 'address' },\n { name: 'totalDeposit', type: 'uint256' },\n ],\n outputs: [],\n },\n {\n name: 'channels',\n type: 'function',\n stateMutability: 'view',\n inputs: [{ type: 'bytes32' }],\n outputs: [\n { name: 'settlementTimeout', type: 'uint256' },\n { name: 'state', type: 'uint8' },\n { name: 'closedAt', type: 'uint256' },\n { name: 'openedAt', type: 'uint256' },\n { name: 'participant1', type: 'address' },\n { name: 'participant2', type: 'address' },\n ],\n },\n {\n name: 'ChannelOpened',\n type: 'event',\n inputs: [\n { name: 'channelId', type: 'bytes32', indexed: true },\n { name: 'participant1', type: 'address', indexed: true },\n { name: 'participant2', type: 'address', indexed: true },\n { name: 'settlementTimeout', type: 'uint256', indexed: false },\n ],\n },\n] as const;\n\n// ERC20 ABI — only approve and allowance\nconst ERC20_ABI = [\n {\n name: 'approve',\n type: 'function',\n stateMutability: 'nonpayable',\n inputs: [\n { name: 'spender', type: 'address' },\n { name: 'amount', type: 'uint256' },\n ],\n outputs: [{ type: 'bool' }],\n },\n {\n name: 'allowance',\n type: 'function',\n stateMutability: 'view',\n inputs: [\n { name: 'owner', type: 'address' },\n { name: 'spender', type: 'address' },\n ],\n outputs: [{ type: 'uint256' }],\n },\n] as const;\n\n/** Maps on-chain state uint8 to ChannelState status */\nconst STATE_MAP: Record<number, ChannelState['status']> = {\n 0: 'settled',\n 1: 'open',\n 2: 'closed',\n 3: 'settled',\n};\n\nexport interface OnChainChannelClientConfig {\n evmSigner: EvmSigner;\n chainRpcUrls: Record<string, string>;\n}\n\n/**\n * Implements ConnectorChannelClient using viem for direct on-chain\n * interaction with TokenNetwork smart contract.\n *\n * Fully non-custodial — the client deposits its own funds on-chain.\n */\nexport class OnChainChannelClient implements ConnectorChannelClient {\n private readonly evmSigner: EvmSigner;\n private readonly chainRpcUrls: Record<string, string>;\n private readonly channelContext = new Map<\n string,\n { chain: string; tokenNetworkAddress: string }\n >();\n\n constructor(config: OnChainChannelClientConfig) {\n this.evmSigner = config.evmSigner;\n this.chainRpcUrls = config.chainRpcUrls;\n }\n\n /**\n * Parse chain identifier to extract chainId.\n * Format: \"evm:{network}:{chainId}\" e.g., \"evm:anvil:31337\"\n */\n private parseChainId(chain: string): number {\n const parts = chain.split(':');\n if (parts.length < 3) {\n throw new Error(\n `Invalid chain format: \"${chain}\". Expected \"evm:{network}:{chainId}\".`\n );\n }\n const chainIdStr = parts[2];\n if (!chainIdStr) {\n throw new Error(\n `Invalid chain format: \"${chain}\". Expected \"evm:{network}:{chainId}\".`\n );\n }\n const chainId = parseInt(chainIdStr, 10);\n if (isNaN(chainId)) {\n throw new Error(`Invalid chainId in chain \"${chain}\".`);\n }\n return chainId;\n }\n\n /**\n * Create viem clients for a given chain.\n */\n private createClients(chain: string) {\n const rpcUrl = this.chainRpcUrls[chain];\n if (!rpcUrl) {\n throw new Error(\n `No RPC URL configured for chain \"${chain}\". Available: ${Object.keys(this.chainRpcUrls).join(', ')}`\n );\n }\n\n const chainId = this.parseChainId(chain);\n\n const viemChain = defineChain({\n id: chainId,\n name: chain,\n nativeCurrency: { name: 'ETH', symbol: 'ETH', decimals: 18 },\n rpcUrls: { default: { http: [rpcUrl] } },\n });\n\n const publicClient = createPublicClient({\n transport: http(rpcUrl),\n chain: viemChain,\n });\n\n const walletClient = createWalletClient({\n account: this.evmSigner.account,\n transport: http(rpcUrl),\n chain: viemChain,\n });\n\n return { publicClient, walletClient };\n }\n\n /**\n * Opens a new payment channel on-chain.\n *\n * 1. Approve token spend if needed\n * 2. Call TokenNetwork.openChannel()\n * 3. Extract channelId from ChannelOpened event\n * 4. Deposit initial funds if specified\n */\n async openChannel(params: OpenChannelParams): Promise<OpenChannelResult> {\n const {\n chain,\n tokenNetwork,\n peerAddress,\n initialDeposit,\n settlementTimeout,\n } = params;\n\n if (!tokenNetwork) {\n throw new Error(\n 'tokenNetwork address is required for on-chain channel opening'\n );\n }\n\n const { publicClient, walletClient } = this.createClients(chain);\n const tokenNetworkAddr = tokenNetwork as Hex;\n const deposit = initialDeposit ? BigInt(initialDeposit) : 0n;\n\n // If deposit > 0, ensure token approval\n if (deposit > 0n && params.token) {\n const tokenAddr = params.token as Hex;\n const myAddress = this.evmSigner.address as Hex;\n\n const currentAllowance = await publicClient.readContract({\n address: tokenAddr,\n abi: ERC20_ABI,\n functionName: 'allowance',\n args: [myAddress, tokenNetworkAddr],\n });\n\n if ((currentAllowance as bigint) < deposit) {\n const approveHash = await walletClient.writeContract({\n address: tokenAddr,\n abi: ERC20_ABI,\n functionName: 'approve',\n args: [tokenNetworkAddr, maxUint256],\n });\n await publicClient.waitForTransactionReceipt({ hash: approveHash });\n }\n }\n\n // Open channel\n const timeout = BigInt(settlementTimeout ?? 86400);\n const openHash = await walletClient.writeContract({\n address: tokenNetworkAddr,\n abi: TOKEN_NETWORK_ABI,\n functionName: 'openChannel',\n args: [peerAddress as Hex, timeout],\n });\n\n const receipt: TransactionReceipt =\n await publicClient.waitForTransactionReceipt({ hash: openHash });\n\n // Extract channelId from ChannelOpened event\n let channelId: string | undefined;\n for (const log of receipt.logs) {\n try {\n const decoded = decodeEventLog({\n abi: TOKEN_NETWORK_ABI,\n data: log.data,\n topics: log.topics,\n });\n if (decoded.eventName === 'ChannelOpened') {\n channelId = (decoded.args as Record<string, unknown>)[\n 'channelId'\n ] as string;\n break;\n }\n } catch {\n // Not our event, skip\n }\n }\n\n if (!channelId) {\n throw new Error('Failed to extract channelId from ChannelOpened event');\n }\n\n // Cache context for getChannelState\n this.channelContext.set(channelId, {\n chain,\n tokenNetworkAddress: tokenNetwork,\n });\n\n // Deposit initial funds if specified\n if (deposit > 0n) {\n const depositHash = await walletClient.writeContract({\n address: tokenNetworkAddr,\n abi: TOKEN_NETWORK_ABI,\n functionName: 'setTotalDeposit',\n args: [channelId as Hex, this.evmSigner.address as Hex, deposit],\n });\n await publicClient.waitForTransactionReceipt({ hash: depositHash });\n }\n\n return { channelId, status: 'opening' };\n }\n\n /**\n * Gets the current state of a payment channel from on-chain data.\n */\n async getChannelState(channelId: string): Promise<ChannelState> {\n const context = this.channelContext.get(channelId);\n if (!context) {\n throw new Error(\n `No context for channel \"${channelId}\". Channel must be opened via this client first.`\n );\n }\n\n const { publicClient } = this.createClients(context.chain);\n\n const result = await publicClient.readContract({\n address: context.tokenNetworkAddress as Hex,\n abi: TOKEN_NETWORK_ABI,\n functionName: 'channels',\n args: [channelId as Hex],\n });\n\n const [, state] = result as [\n bigint,\n number,\n bigint,\n bigint,\n string,\n string,\n ];\n const status = STATE_MAP[state] ?? 'settled';\n\n return {\n channelId,\n status,\n chain: context.chain,\n };\n }\n}\n","import { privateKeyToAccount, type PrivateKeyAccount } from 'viem/accounts';\nimport { type Hex, toHex } from 'viem';\nimport type { BalanceProofParams, SignedBalanceProof } from '../types.js';\n\n/**\n * EVM claim message for BTP protocol data.\n * Matches @toon-protocol/connector's EVMClaimMessage interface.\n *\n * The connector's validateClaimMessage() requires envelope fields\n * (version, messageId, timestamp) plus EVM claim fields, and optionally\n * chainId + tokenNetworkAddress for self-describing signature verification.\n */\nexport interface EVMClaimMessage {\n version: '1.0';\n blockchain: 'evm';\n messageId: string;\n timestamp: string;\n senderId: string;\n channelId: string;\n nonce: number;\n transferredAmount: string;\n lockedAmount: string;\n locksRoot: string;\n signature: string;\n signerAddress: string;\n /** Chain ID for self-describing EIP-712 verification */\n chainId: number;\n /** TokenNetwork address for self-describing EIP-712 verification */\n tokenNetworkAddress: string;\n /** ERC-20 token address for self-describing claim verification */\n tokenAddress?: string;\n}\n\n/**\n * EIP-712 domain for TokenNetwork balance proofs.\n * Must match connector's eip712-helper.js getDomainSeparator().\n */\nfunction getBalanceProofDomain(chainId: number, tokenNetworkAddress: string) {\n return {\n name: 'TokenNetwork' as const,\n version: '1' as const,\n chainId,\n verifyingContract: tokenNetworkAddress as Hex,\n };\n}\n\n/**\n * EIP-712 types for balance proofs.\n * Must match connector's eip712-helper.js getBalanceProofTypes().\n */\nconst BALANCE_PROOF_TYPES = {\n BalanceProof: [\n { name: 'channelId', type: 'bytes32' },\n { name: 'nonce', type: 'uint256' },\n { name: 'transferredAmount', type: 'uint256' },\n { name: 'lockedAmount', type: 'uint256' },\n { name: 'locksRoot', type: 'bytes32' },\n ],\n} as const;\n\n/**\n * EVM signer for EIP-712 balance proofs and on-chain transactions.\n *\n * Encapsulates the private key — no getPrivateKey() method is exposed.\n */\nexport class EvmSigner {\n private readonly _account: PrivateKeyAccount;\n\n /**\n * @param privateKey - EVM private key as hex string (with or without 0x prefix) or Uint8Array\n */\n constructor(privateKey: string | Uint8Array) {\n let hexKey: Hex;\n if (privateKey instanceof Uint8Array) {\n hexKey = toHex(privateKey);\n } else {\n hexKey = (\n privateKey.startsWith('0x') ? privateKey : `0x${privateKey}`\n ) as Hex;\n }\n this._account = privateKeyToAccount(hexKey);\n }\n\n /** Derived 0x EVM address */\n get address(): string {\n return this._account.address;\n }\n\n /** Viem PrivateKeyAccount — usable with walletClient for on-chain transactions */\n get account(): PrivateKeyAccount {\n return this._account;\n }\n\n /**\n * Signs a balance proof using EIP-712 typed data.\n *\n * @param params - Balance proof parameters plus chain context\n * @returns Signed balance proof with signature\n */\n async signBalanceProof(\n params: BalanceProofParams & {\n chainId: number;\n tokenNetworkAddress: string;\n tokenAddress?: string;\n }\n ): Promise<SignedBalanceProof> {\n const domain = getBalanceProofDomain(\n params.chainId,\n params.tokenNetworkAddress\n );\n\n const signature = await this._account.signTypedData({\n domain,\n types: BALANCE_PROOF_TYPES,\n primaryType: 'BalanceProof',\n message: {\n channelId: params.channelId as Hex,\n nonce: BigInt(params.nonce),\n transferredAmount: params.transferredAmount,\n lockedAmount: params.lockedAmount,\n locksRoot: params.locksRoot as Hex,\n },\n });\n\n return {\n channelId: params.channelId,\n nonce: params.nonce,\n transferredAmount: params.transferredAmount,\n lockedAmount: params.lockedAmount,\n locksRoot: params.locksRoot,\n signature,\n signerAddress: this._account.address,\n chainId: params.chainId,\n tokenNetworkAddress: params.tokenNetworkAddress,\n ...(params.tokenAddress && { tokenAddress: params.tokenAddress }),\n };\n }\n\n /**\n * Builds an EVMClaimMessage from a signed balance proof.\n * Static so it can be called without an EvmSigner instance.\n *\n * @param proof - Signed balance proof (includes chainId and tokenNetworkAddress)\n * @param senderId - Nostr pubkey or identifier of the sender\n * @returns EVMClaimMessage compatible with BTP_CLAIM_PROTOCOL\n */\n static buildClaimMessage(\n proof: SignedBalanceProof,\n senderId: string\n ): EVMClaimMessage {\n return {\n version: '1.0',\n blockchain: 'evm',\n messageId: crypto.randomUUID(),\n timestamp: new Date().toISOString().replace(/\\.\\d{3}Z$/, '.000Z'),\n senderId,\n channelId: proof.channelId,\n nonce: proof.nonce,\n transferredAmount: proof.transferredAmount.toString(),\n lockedAmount: proof.lockedAmount.toString(),\n locksRoot: proof.locksRoot,\n signature: proof.signature,\n signerAddress: proof.signerAddress,\n chainId: proof.chainId,\n tokenNetworkAddress: proof.tokenNetworkAddress,\n ...(proof.tokenAddress && { tokenAddress: proof.tokenAddress }),\n };\n }\n}\n","import type { EvmSigner } from '../signing/evm-signer.js';\nimport type { SignedBalanceProof } from '../types.js';\nimport type { ChannelStore } from './ChannelStore.js';\n\ninterface ChannelTracking {\n nonce: number;\n cumulativeAmount: bigint;\n chainId: number;\n tokenNetworkAddress: string;\n tokenAddress?: string;\n}\n\n/**\n * Local nonce tracking and claim signing.\n *\n * Does NOT make any network calls — it only manages state\n * and delegates signing to EvmSigner.\n */\nexport class ChannelManager {\n private readonly evmSigner: EvmSigner;\n private readonly channels = new Map<string, ChannelTracking>();\n private readonly store?: ChannelStore;\n\n constructor(evmSigner: EvmSigner, store?: ChannelStore) {\n this.evmSigner = evmSigner;\n this.store = store;\n }\n\n /**\n * Start tracking a channel.\n * Called after bootstrap returns a channelId.\n *\n * @param channelId - Payment channel identifier\n * @param chainContext - Chain context for EIP-712 signing (chainId + tokenNetworkAddress)\n * @param initialNonce - Starting nonce (default: 0)\n * @param initialAmount - Starting cumulative amount (default: 0n)\n */\n trackChannel(\n channelId: string,\n chainContext?: { chainId: number; tokenNetworkAddress: string; tokenAddress?: string },\n initialNonce = 0,\n initialAmount = 0n\n ): void {\n const cId = chainContext?.chainId ?? 31337;\n const tnAddr =\n chainContext?.tokenNetworkAddress ??\n '0x0000000000000000000000000000000000000000';\n\n // If store has persisted state for this channel, resume from it\n if (this.store) {\n const persisted = this.store.load(channelId);\n if (persisted) {\n this.channels.set(channelId, {\n nonce: persisted.nonce,\n cumulativeAmount: persisted.cumulativeAmount,\n chainId: cId,\n tokenNetworkAddress: tnAddr,\n tokenAddress: chainContext?.tokenAddress,\n });\n return;\n }\n }\n\n this.channels.set(channelId, {\n nonce: initialNonce,\n cumulativeAmount: initialAmount,\n chainId: cId,\n tokenNetworkAddress: tnAddr,\n tokenAddress: chainContext?.tokenAddress,\n });\n }\n\n /**\n * Signs a balance proof for the given channel.\n * Auto-increments nonce and adds to cumulative amount.\n *\n * @param channelId - Payment channel identifier\n * @param additionalAmount - Amount to add to cumulative transferred amount\n * @returns Signed balance proof\n * @throws Error if channel is not being tracked\n */\n async signBalanceProof(\n channelId: string,\n additionalAmount: bigint\n ): Promise<SignedBalanceProof> {\n const tracking = this.channels.get(channelId);\n if (!tracking) {\n throw new Error(\n `Channel \"${channelId}\" is not being tracked. Call trackChannel() first.`\n );\n }\n\n tracking.nonce += 1;\n tracking.cumulativeAmount += additionalAmount;\n\n // Persist updated state\n if (this.store) {\n this.store.save(channelId, {\n nonce: tracking.nonce,\n cumulativeAmount: tracking.cumulativeAmount,\n });\n }\n\n return this.evmSigner.signBalanceProof({\n channelId,\n nonce: tracking.nonce,\n transferredAmount: tracking.cumulativeAmount,\n lockedAmount: 0n,\n locksRoot:\n '0x0000000000000000000000000000000000000000000000000000000000000000',\n chainId: tracking.chainId,\n tokenNetworkAddress: tracking.tokenNetworkAddress,\n tokenAddress: tracking.tokenAddress,\n });\n }\n\n /**\n * Gets the current nonce for a tracked channel.\n */\n getNonce(channelId: string): number {\n const tracking = this.channels.get(channelId);\n if (!tracking) {\n throw new Error(`Channel \"${channelId}\" is not being tracked.`);\n }\n return tracking.nonce;\n }\n\n /**\n * Gets the cumulative transferred amount for a tracked channel.\n */\n getCumulativeAmount(channelId: string): bigint {\n const tracking = this.channels.get(channelId);\n if (!tracking) {\n throw new Error(`Channel \"${channelId}\" is not being tracked.`);\n }\n return tracking.cumulativeAmount;\n }\n\n /**\n * Gets all tracked channel IDs.\n */\n getTrackedChannels(): string[] {\n return Array.from(this.channels.keys());\n }\n\n /**\n * Returns true if the channel is being tracked.\n */\n isTracking(channelId: string): boolean {\n return this.channels.has(channelId);\n }\n}\n","import { readFileSync, writeFileSync, existsSync } from 'node:fs';\n\nexport interface ChannelStoreEntry {\n nonce: number;\n cumulativeAmount: bigint;\n}\n\n/**\n * Persistence interface for payment channel nonce/amount state.\n */\nexport interface ChannelStore {\n save(channelId: string, tracking: ChannelStoreEntry): void;\n load(channelId: string): ChannelStoreEntry | undefined;\n list(): string[];\n delete(channelId: string): void;\n}\n\ninterface JsonEntry {\n nonce: number;\n /** Stored as string to preserve bigint precision */\n cumulativeAmount: string;\n}\n\n/**\n * JSON file-backed ChannelStore.\n * Uses synchronous I/O to match ChannelManager's sync API surface.\n */\nexport class JsonFileChannelStore implements ChannelStore {\n private readonly filePath: string;\n\n constructor(filePath: string) {\n this.filePath = filePath;\n }\n\n save(channelId: string, tracking: ChannelStoreEntry): void {\n const data = this.readFile();\n data[channelId] = {\n nonce: tracking.nonce,\n cumulativeAmount: tracking.cumulativeAmount.toString(),\n };\n this.writeFile(data);\n }\n\n load(channelId: string): ChannelStoreEntry | undefined {\n const data = this.readFile();\n const entry = data[channelId];\n if (!entry) return undefined;\n return {\n nonce: entry.nonce,\n cumulativeAmount: BigInt(entry.cumulativeAmount),\n };\n }\n\n list(): string[] {\n return Object.keys(this.readFile());\n }\n\n delete(channelId: string): void {\n const data = this.readFile();\n const { [channelId]: _, ...rest } = data;\n this.writeFile(rest);\n }\n\n private readFile(): Record<string, JsonEntry> {\n if (!existsSync(this.filePath)) {\n return {};\n }\n const raw = readFileSync(this.filePath, 'utf-8');\n return JSON.parse(raw) as Record<string, JsonEntry>;\n }\n\n private writeFile(data: Record<string, JsonEntry>): void {\n writeFileSync(this.filePath, JSON.stringify(data, null, 2), 'utf-8');\n }\n}\n","import type { ConnectorAdminClient } from '@toon-protocol/core';\nimport {\n ValidationError,\n NetworkError,\n ConnectorError,\n UnauthorizedError,\n PeerNotFoundError,\n PeerAlreadyExistsError,\n} from '../errors.js';\nimport { withRetry } from '../utils/retry.js';\n\n/**\n * Configuration for HttpConnectorAdmin.\n */\nexport interface HttpConnectorAdminConfig {\n /** Admin API base URL (e.g., 'http://localhost:8081') */\n adminUrl: string;\n /** Request timeout in milliseconds (default: 30000) */\n timeout?: number;\n /** Maximum retry attempts for network failures (default: 3) */\n maxRetries?: number;\n /** Initial retry delay in milliseconds (default: 1000) */\n retryDelay?: number;\n /** HTTP client for testing (default: global fetch) */\n httpClient?: typeof fetch;\n}\n\n/**\n * Result of a bulk peer operation.\n */\nexport interface PeerOperationResult {\n /** Peer ID that was operated on */\n peerId: string;\n /** Whether the operation succeeded */\n success: boolean;\n /** Error that occurred (if failed) */\n error?: Error;\n}\n\n/**\n * HTTP-based connector admin client for managing ILP peers via REST API.\n *\n * Implements the ConnectorAdminClient interface using HTTP requests to the\n * connector's admin API (typically port 8081).\n *\n * @example\n * ```typescript\n * // Embedded mode (DirectConnectorAdmin)\n * const adminClient = new DirectConnectorAdmin(connectorNode);\n *\n * // HTTP mode (HttpConnectorAdmin)\n * const adminClient = new HttpConnectorAdmin({\n * adminUrl: 'http://localhost:8081'\n * });\n *\n * // Add peer\n * await adminClient.addPeer({\n * id: 'nostr-abc123',\n * url: 'btp+ws://alice.example.com:3000',\n * authToken: 'secret-token',\n * routes: [{ prefix: 'g.toon.alice' }]\n * });\n *\n * // Remove peer\n * await adminClient.removePeer('nostr-abc123');\n * ```\n *\n * @throws {ValidationError} Input validation failed (before HTTP request)\n * @throws {NetworkError} Connection failed (ECONNREFUSED, ETIMEDOUT)\n * @throws {UnauthorizedError} Admin API returned 401 (missing/invalid auth)\n * @throws {PeerAlreadyExistsError} Admin API returned 409 (duplicate peer)\n * @throws {PeerNotFoundError} Admin API returned 404 (peer not found)\n * @throws {ConnectorError} Admin API returned 5xx (server error)\n */\nexport class HttpConnectorAdmin implements ConnectorAdminClient {\n private readonly adminUrl: string;\n private readonly timeout: number;\n private readonly retryConfig: { maxRetries: number; retryDelay: number };\n private readonly httpClient: typeof fetch;\n\n constructor(config: HttpConnectorAdminConfig) {\n this.adminUrl = config.adminUrl.replace(/\\/$/, ''); // Remove trailing slash\n this.timeout = config.timeout ?? 30000;\n this.retryConfig = {\n maxRetries: config.maxRetries ?? 3,\n retryDelay: config.retryDelay ?? 1000,\n };\n this.httpClient = config.httpClient ?? fetch;\n }\n\n /**\n * Add a peer to the connector via the admin API.\n *\n * Validates peer config parameters and sends HTTP POST to /admin/peers.\n *\n * @param config - Peer configuration\n * @param config.id - Unique peer identifier (non-empty string)\n * @param config.url - BTP WebSocket URL (must start with 'btp+ws://' or 'btp+wss://')\n * @param config.authToken - Authentication token (non-empty string)\n * @param config.routes - Optional routing table entries\n * @param config.settlement - Optional settlement configuration\n *\n * @throws {ValidationError} Invalid peer config (missing id, invalid url, etc.)\n * @throws {PeerAlreadyExistsError} Peer with same ID already exists (409 Conflict)\n * @throws {UnauthorizedError} Admin API authentication failed (401)\n * @throws {NetworkError} Connection to admin API failed\n * @throws {ConnectorError} Admin API server error (5xx)\n */\n async addPeer(config: {\n id: string;\n url: string;\n authToken: string;\n routes?: { prefix: string; priority?: number }[];\n settlement?: {\n preference: string;\n evmAddress?: string;\n tokenAddress?: string;\n tokenNetworkAddress?: string;\n chainId?: number;\n channelId?: string;\n initialDeposit?: string;\n };\n }): Promise<void> {\n // Validate required fields\n if (\n !config.id ||\n typeof config.id !== 'string' ||\n config.id.trim() === ''\n ) {\n throw new ValidationError('Peer id must be a non-empty string');\n }\n\n if (\n !config.url ||\n typeof config.url !== 'string' ||\n config.url.trim() === ''\n ) {\n throw new ValidationError('Peer url must be a non-empty string');\n }\n\n // Validate BTP URL format (accept both ws:// and btp+ws:// formats)\n const hasWsPrefix =\n config.url.startsWith('ws://') || config.url.startsWith('wss://');\n const hasBtpPrefix =\n config.url.startsWith('btp+ws://') || config.url.startsWith('btp+wss://');\n\n if (!hasWsPrefix && !hasBtpPrefix) {\n throw new ValidationError(\n `Invalid BTP URL format: \"${config.url}\". Must start with 'ws://', 'wss://', 'btp+ws://', or 'btp+wss://'`\n );\n }\n\n // authToken can be empty string for BTP (doesn't require authentication)\n if (\n config.authToken === undefined ||\n config.authToken === null ||\n typeof config.authToken !== 'string'\n ) {\n throw new ValidationError(\n 'Peer authToken must be a string (can be empty for no auth)'\n );\n }\n\n // Validate routes (if provided)\n if (config.routes !== undefined) {\n if (!Array.isArray(config.routes)) {\n throw new ValidationError('Peer routes must be an array');\n }\n\n for (const route of config.routes) {\n if (\n !route.prefix ||\n typeof route.prefix !== 'string' ||\n route.prefix.trim() === ''\n ) {\n throw new ValidationError('Route prefix must be a non-empty string');\n }\n if (\n route.priority !== undefined &&\n typeof route.priority !== 'number'\n ) {\n throw new ValidationError('Route priority must be a number');\n }\n }\n }\n\n // Validate settlement (if provided)\n if (config.settlement !== undefined) {\n if (typeof config.settlement !== 'object' || config.settlement === null) {\n throw new ValidationError('Peer settlement must be an object');\n }\n\n if (\n !config.settlement.preference ||\n typeof config.settlement.preference !== 'string'\n ) {\n throw new ValidationError(\n 'Settlement preference must be a non-empty string'\n );\n }\n }\n\n // Send HTTP POST request with retry logic\n const url = `${this.adminUrl}/admin/peers`;\n\n await withRetry(async () => this.sendAddPeerRequest(url, config), {\n maxRetries: this.retryConfig.maxRetries,\n retryDelay: this.retryConfig.retryDelay,\n exponentialBackoff: true,\n shouldRetry: (error) => {\n // Only retry on network errors (ECONNREFUSED, ETIMEDOUT)\n // Do not retry on validation errors, 4xx, or 5xx errors\n return error instanceof NetworkError;\n },\n });\n }\n\n /**\n * Remove a peer from the connector via the admin API.\n *\n * Sends HTTP DELETE to /admin/peers/:id.\n *\n * @param peerId - Unique peer identifier to remove (non-empty string)\n *\n * @throws {ValidationError} Invalid peerId (empty string)\n * @throws {PeerNotFoundError} Peer does not exist (404 Not Found)\n * @throws {UnauthorizedError} Admin API authentication failed (401)\n * @throws {NetworkError} Connection to admin API failed\n * @throws {ConnectorError} Admin API server error (5xx)\n */\n async removePeer(peerId: string): Promise<void> {\n // Validate peerId\n if (!peerId || typeof peerId !== 'string' || peerId.trim() === '') {\n throw new ValidationError('peerId must be a non-empty string');\n }\n\n // Send HTTP DELETE request with retry logic\n const url = `${this.adminUrl}/admin/peers/${encodeURIComponent(peerId)}`;\n\n await withRetry(async () => this.sendRemovePeerRequest(url, peerId), {\n maxRetries: this.retryConfig.maxRetries,\n retryDelay: this.retryConfig.retryDelay,\n exponentialBackoff: true,\n shouldRetry: (error) => {\n // Only retry on network errors\n return error instanceof NetworkError;\n },\n });\n }\n\n /**\n * Add multiple peers in parallel for efficient bootstrapping.\n *\n * Uses Promise.allSettled() to execute peer additions concurrently,\n * returning results for each operation regardless of individual failures.\n *\n * @param configs - Array of peer configurations to add\n * @returns Array of results indicating success/failure for each peer\n *\n * @example\n * ```typescript\n * const results = await admin.addPeers([\n * { id: 'peer1', url: 'btp+ws://...', authToken: 'token1' },\n * { id: 'peer2', url: 'btp+ws://...', authToken: 'token2' },\n * ]);\n *\n * results.forEach(result => {\n * if (result.success) {\n * console.log(`Added peer: ${result.peerId}`);\n * } else {\n * console.error(`Failed to add ${result.peerId}:`, result.error);\n * }\n * });\n * ```\n */\n async addPeers(\n configs: {\n id: string;\n url: string;\n authToken: string;\n routes?: { prefix: string; priority?: number }[];\n settlement?: {\n preference: string;\n evmAddress?: string;\n tokenAddress?: string;\n tokenNetworkAddress?: string;\n chainId?: number;\n channelId?: string;\n initialDeposit?: string;\n };\n }[]\n ): Promise<PeerOperationResult[]> {\n const results = await Promise.allSettled(\n configs.map((config) => this.addPeer(config))\n );\n\n return results.map((result, index) => {\n const config = configs[index];\n return {\n peerId: config ? config.id : 'unknown',\n success: result.status === 'fulfilled',\n error: result.status === 'rejected' ? result.reason : undefined,\n };\n });\n }\n\n /**\n * Remove multiple peers in parallel.\n *\n * Uses Promise.allSettled() to execute peer removals concurrently,\n * returning results for each operation regardless of individual failures.\n *\n * @param peerIds - Array of peer IDs to remove\n * @returns Array of results indicating success/failure for each peer\n *\n * @example\n * ```typescript\n * const results = await admin.removePeers(['peer1', 'peer2', 'peer3']);\n *\n * const succeeded = results.filter(r => r.success).length;\n * console.log(`Removed ${succeeded}/${results.length} peers`);\n * ```\n */\n async removePeers(peerIds: string[]): Promise<PeerOperationResult[]> {\n const results = await Promise.allSettled(\n peerIds.map((peerId) => this.removePeer(peerId))\n );\n\n return results.map((result, index) => {\n const peerId = peerIds[index];\n return {\n peerId: peerId ?? 'unknown',\n success: result.status === 'fulfilled',\n error: result.status === 'rejected' ? result.reason : undefined,\n };\n });\n }\n\n /**\n * Send HTTP POST request to add a peer.\n * Separated for retry logic wrapping.\n */\n private async sendAddPeerRequest(\n url: string,\n config: {\n id: string;\n url: string;\n authToken: string;\n routes?: { prefix: string; priority?: number }[];\n settlement?: {\n preference: string;\n evmAddress?: string;\n tokenAddress?: string;\n tokenNetworkAddress?: string;\n chainId?: number;\n channelId?: string;\n initialDeposit?: string;\n };\n }\n ): Promise<void> {\n // Normalize URL for connector API (expects ws:// or wss://)\n // Strip btp+ prefix if present, or use as-is if already plain ws://\n let connectorUrl = config.url;\n if (connectorUrl.startsWith('btp+')) {\n connectorUrl = connectorUrl.replace(/^btp\\+/, '');\n }\n\n try {\n const response = await this.httpClient(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n ...config,\n url: connectorUrl,\n }),\n signal: AbortSignal.timeout(this.timeout),\n });\n\n if (response.ok) {\n return; // Success (201 Created)\n }\n\n // Handle error responses\n await this.handleErrorResponse(response, `POST ${url}`, config.id);\n } catch (error) {\n this.handleNetworkError(error, url, 'addPeer');\n }\n }\n\n /**\n * Send HTTP DELETE request to remove a peer.\n * Separated for retry logic wrapping.\n */\n private async sendRemovePeerRequest(\n url: string,\n peerId: string\n ): Promise<void> {\n try {\n const response = await this.httpClient(url, {\n method: 'DELETE',\n signal: AbortSignal.timeout(this.timeout),\n });\n\n if (response.ok) {\n return; // Success (204 No Content)\n }\n\n // Handle error responses\n await this.handleErrorResponse(response, `DELETE ${url}`, peerId);\n } catch (error) {\n this.handleNetworkError(error, url, 'removePeer');\n }\n }\n\n /**\n * Handle network errors from HTTP requests.\n *\n * Converts connection failures, timeouts, and unknown errors to NetworkError.\n * Re-throws existing ToonClientError instances.\n *\n * @param error - Error thrown by HTTP client\n * @param url - Request URL (for error messages)\n * @param operation - Operation name (for error messages)\n * @throws {NetworkError} Network connection or timeout error\n */\n private handleNetworkError(\n error: unknown,\n url: string,\n operation: string\n ): never {\n // Timeout errors (AbortSignal)\n if (error instanceof Error && error.name === 'AbortError') {\n throw new NetworkError(\n `Request to ${url} timed out after ${this.timeout}ms`,\n error\n );\n }\n\n // Connection errors (ECONNREFUSED, ETIMEDOUT, DNS failures)\n if (\n error instanceof Error &&\n (error.message.includes('ECONNREFUSED') ||\n error.message.includes('ETIMEDOUT') ||\n error.message.includes('ENOTFOUND'))\n ) {\n throw new NetworkError(\n `Failed to connect to connector admin API at ${url}: ${error.message}`,\n error\n );\n }\n\n // Re-throw if already a ToonClientError\n if (\n error instanceof ValidationError ||\n error instanceof PeerAlreadyExistsError ||\n error instanceof PeerNotFoundError ||\n error instanceof UnauthorizedError ||\n error instanceof ConnectorError\n ) {\n throw error;\n }\n\n // Unknown error - wrap in NetworkError\n throw new NetworkError(\n `Unexpected error during ${operation}: ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n\n /**\n * Handle HTTP error responses from the admin API.\n *\n * Converts HTTP status codes to appropriate error types.\n *\n * @param response - HTTP response from admin API\n * @param endpoint - Endpoint being called (for error messages)\n * @param peerId - Peer ID (for error messages)\n * @throws {UnauthorizedError} 401 Unauthorized\n * @throws {PeerNotFoundError} 404 Not Found\n * @throws {PeerAlreadyExistsError} 409 Conflict\n * @throws {ConnectorError} 5xx Server Error\n */\n private async handleErrorResponse(\n response: Response,\n endpoint: string,\n peerId: string\n ): Promise<never> {\n const status = response.status;\n const statusText = response.statusText;\n\n // Try to extract error message from response body\n let errorMessage = '';\n try {\n const body = await response.text();\n if (body) {\n errorMessage = ` - ${body}`;\n }\n } catch {\n // Ignore body parsing errors\n }\n\n switch (status) {\n case 401:\n throw new UnauthorizedError(\n `Admin API authentication failed for ${endpoint}: ${statusText}${errorMessage}`\n );\n\n case 404:\n throw new PeerNotFoundError(\n `Peer not found: \"${peerId}\" (${endpoint}): ${statusText}${errorMessage}`\n );\n\n case 409:\n throw new PeerAlreadyExistsError(\n `Peer already exists: \"${peerId}\" (${endpoint}): ${statusText}${errorMessage}`\n );\n\n default:\n if (status >= 500) {\n throw new ConnectorError(\n `Connector admin API error (${endpoint}): ${status} ${statusText}${errorMessage}`\n );\n }\n\n // Other 4xx errors (400, 403, etc.)\n throw new ConnectorError(\n `Admin API error (${endpoint}): ${status} ${statusText}${errorMessage}`\n );\n }\n }\n}\n"],"mappings":"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","names":["generateSecretKey","generateSecretKey"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toon-protocol/client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "TOON client for ILP-gated Nostr publishing with multi-hop routing and payment channels",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Jonathan Green",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"test:coverage": "vitest run --coverage"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@toon-protocol/connector": "^1.
|
|
40
|
+
"@toon-protocol/connector": "^1.8.1",
|
|
41
41
|
"@toon-protocol/core": "workspace:*",
|
|
42
42
|
"nostr-tools": "^2.20.0",
|
|
43
43
|
"viem": "^2.0.0"
|