@toon-protocol/connector 2.1.0 → 2.3.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.
Files changed (82) hide show
  1. package/README.md +117 -46
  2. package/dist/btp/btp-client-manager.d.ts +3 -0
  3. package/dist/btp/btp-client-manager.d.ts.map +1 -1
  4. package/dist/btp/btp-client-manager.js +19 -4
  5. package/dist/btp/btp-client-manager.js.map +1 -1
  6. package/dist/btp/btp-client.d.ts +4 -1
  7. package/dist/btp/btp-client.d.ts.map +1 -1
  8. package/dist/btp/btp-client.js +24 -10
  9. package/dist/btp/btp-client.js.map +1 -1
  10. package/dist/btp/inbound-claim-validator.d.ts +4 -1
  11. package/dist/btp/inbound-claim-validator.d.ts.map +1 -1
  12. package/dist/btp/inbound-claim-validator.js +23 -3
  13. package/dist/btp/inbound-claim-validator.js.map +1 -1
  14. package/dist/config/config-loader.d.ts +4 -0
  15. package/dist/config/config-loader.d.ts.map +1 -1
  16. package/dist/config/config-loader.js +166 -1
  17. package/dist/config/config-loader.js.map +1 -1
  18. package/dist/config/index.d.ts +1 -0
  19. package/dist/config/index.d.ts.map +1 -1
  20. package/dist/config/types.d.ts +22 -15
  21. package/dist/config/types.d.ts.map +1 -1
  22. package/dist/config/types.js +2 -5
  23. package/dist/config/types.js.map +1 -1
  24. package/dist/core/connector-node.d.ts +12 -1
  25. package/dist/core/connector-node.d.ts.map +1 -1
  26. package/dist/core/connector-node.js +259 -25
  27. package/dist/core/connector-node.js.map +1 -1
  28. package/dist/core/packet-handler.d.ts +10 -1
  29. package/dist/core/packet-handler.d.ts.map +1 -1
  30. package/dist/core/packet-handler.js +67 -2
  31. package/dist/core/packet-handler.js.map +1 -1
  32. package/dist/http/types.d.ts +4 -0
  33. package/dist/http/types.d.ts.map +1 -1
  34. package/dist/lib.d.ts +1 -1
  35. package/dist/lib.d.ts.map +1 -1
  36. package/dist/settlement/claim-receiver.d.ts +5 -1
  37. package/dist/settlement/claim-receiver.d.ts.map +1 -1
  38. package/dist/settlement/claim-receiver.js +28 -1
  39. package/dist/settlement/claim-receiver.js.map +1 -1
  40. package/dist/settlement/per-packet-claim-service.d.ts +6 -1
  41. package/dist/settlement/per-packet-claim-service.d.ts.map +1 -1
  42. package/dist/settlement/per-packet-claim-service.js +38 -8
  43. package/dist/settlement/per-packet-claim-service.js.map +1 -1
  44. package/dist/settlement/privacy/nip59-claim-wrapper.d.ts +10 -0
  45. package/dist/settlement/privacy/nip59-claim-wrapper.d.ts.map +1 -1
  46. package/dist/settlement/privacy/nip59-claim-wrapper.js +110 -2
  47. package/dist/settlement/privacy/nip59-claim-wrapper.js.map +1 -1
  48. package/dist/settlement/provider/payment-channel-provider.d.ts +9 -0
  49. package/dist/settlement/provider/payment-channel-provider.d.ts.map +1 -1
  50. package/dist/transport/direct-transport-provider.d.ts +12 -0
  51. package/dist/transport/direct-transport-provider.d.ts.map +1 -0
  52. package/dist/transport/direct-transport-provider.js +27 -0
  53. package/dist/transport/direct-transport-provider.js.map +1 -0
  54. package/dist/transport/index.d.ts +7 -0
  55. package/dist/transport/index.d.ts.map +1 -0
  56. package/dist/transport/index.js +16 -0
  57. package/dist/transport/index.js.map +1 -0
  58. package/dist/transport/managed-anon-client.d.ts +47 -0
  59. package/dist/transport/managed-anon-client.d.ts.map +1 -0
  60. package/dist/transport/managed-anon-client.js +264 -0
  61. package/dist/transport/managed-anon-client.js.map +1 -0
  62. package/dist/transport/probe-tcp-port.d.ts +3 -0
  63. package/dist/transport/probe-tcp-port.d.ts.map +1 -0
  64. package/dist/transport/probe-tcp-port.js +59 -0
  65. package/dist/transport/probe-tcp-port.js.map +1 -0
  66. package/dist/transport/socks-transport-provider.d.ts +29 -0
  67. package/dist/transport/socks-transport-provider.d.ts.map +1 -0
  68. package/dist/transport/socks-transport-provider.js +136 -0
  69. package/dist/transport/socks-transport-provider.js.map +1 -0
  70. package/dist/transport/socks-url.d.ts +6 -0
  71. package/dist/transport/socks-url.d.ts.map +1 -0
  72. package/dist/transport/socks-url.js +29 -0
  73. package/dist/transport/socks-url.js.map +1 -0
  74. package/dist/transport/transport-provider.d.ts +9 -0
  75. package/dist/transport/transport-provider.d.ts.map +1 -0
  76. package/dist/transport/transport-provider.js +3 -0
  77. package/dist/transport/transport-provider.js.map +1 -0
  78. package/dist/utils/redact.d.ts +3 -0
  79. package/dist/utils/redact.d.ts.map +1 -0
  80. package/dist/utils/redact.js +21 -0
  81. package/dist/utils/redact.js.map +1 -0
  82. package/package.json +4 -2
@@ -2,12 +2,15 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ConnectorNode = void 0;
4
4
  const tslib_1 = require("tslib");
5
+ const fs_1 = require("fs");
6
+ const nodePath = tslib_1.__importStar(require("path"));
5
7
  const routing_table_1 = require("../routing/routing-table");
6
8
  const btp_client_manager_1 = require("../btp/btp-client-manager");
7
9
  const btp_server_1 = require("../btp/btp-server");
8
10
  const packet_handler_1 = require("./packet-handler");
9
11
  const shared_1 = require("@toon-protocol/shared");
10
12
  const types_1 = require("../config/types");
13
+ const transport_1 = require("../transport");
11
14
  const payment_handler_1 = require("./payment-handler");
12
15
  const types_2 = require("../settlement/types");
13
16
  const admin_api_1 = require("../http/admin-api");
@@ -30,6 +33,8 @@ const chain_provider_registry_1 = require("../settlement/provider/chain-provider
30
33
  const evm_payment_channel_provider_1 = require("../settlement/provider/evm-payment-channel-provider");
31
34
  const claim_sender_db_schema_1 = require("../settlement/claim-sender-db-schema");
32
35
  const inbound_claim_validator_1 = require("../btp/inbound-claim-validator");
36
+ const nip59_claim_wrapper_1 = require("../settlement/privacy/nip59-claim-wrapper");
37
+ const utils_1 = require("@noble/hashes/utils");
33
38
  const dns_1 = require("dns");
34
39
  const package_json_1 = tslib_1.__importDefault(require("../../package.json"));
35
40
  class ConnectorNode {
@@ -54,10 +59,17 @@ class ConnectorNode {
54
59
  _startTime = new Date();
55
60
  _btpServerStarted = false;
56
61
  _defaultSettlementTokenId = 'M2M';
62
+ _transportProvider = null;
63
+ _transportProviderReady = false;
64
+ _transportType = null;
65
+ _lastTransportHealthy = true;
66
+ _transportHealthInterval = null;
67
+ _transportHealthIntervalMs;
57
68
  get defaultSettlementTokenId() {
58
69
  return this._defaultSettlementTokenId;
59
70
  }
60
- constructor(config, logger) {
71
+ constructor(config, logger, opts) {
72
+ this._transportHealthIntervalMs = opts?.transportHealthIntervalMs ?? 30000;
61
73
  let resolvedConfig;
62
74
  try {
63
75
  if (typeof config === 'string') {
@@ -90,6 +102,7 @@ class ConnectorNode {
90
102
  }));
91
103
  this._routingTable = new routing_table_1.RoutingTable(routingTableEntries, logger.child({ component: 'RoutingTable' }));
92
104
  this._btpClientManager = new btp_client_manager_1.BTPClientManager(resolvedConfig.nodeId, logger.child({ component: 'BTPClientManager' }));
105
+ this._btpClientManager.setAgentFactory((peerUrl) => this._transportProvider?.createAgent(peerUrl));
93
106
  this._packetHandler = new packet_handler_1.PacketHandler(this._routingTable, this._btpClientManager, resolvedConfig.nodeId, logger.child({ component: 'PacketHandler' }));
94
107
  this._btpServer = new btp_server_1.BTPServer(logger.child({ component: 'BTPServer' }), this._packetHandler);
95
108
  this._packetHandler.setBTPServer(this._btpServer);
@@ -195,12 +208,56 @@ class ConnectorNode {
195
208
  }, 'Starting connector node');
196
209
  try {
197
210
  (0, types_1.validateChainProviders)(this._config, this._logger);
198
- const settlementEnabled = this._config.settlementInfra?.enabled ?? process.env.SETTLEMENT_ENABLED === 'true';
199
- const baseRpcUrl = this._config.settlementInfra?.rpcUrl ?? process.env.BASE_L2_RPC_URL;
200
- const registryAddress = this._config.settlementInfra?.registryAddress ?? process.env.TOKEN_NETWORK_REGISTRY;
201
- const m2mTokenAddress = this._config.settlementInfra?.tokenAddress ?? process.env.M2M_TOKEN_ADDRESS;
202
- const treasuryPrivateKey = this._config.settlementInfra?.privateKey ?? process.env.TREASURY_EVM_PRIVATE_KEY;
203
- if (settlementEnabled &&
211
+ const createdProvider = this._createTransportProvider(this._config.transport);
212
+ this._transportProvider = createdProvider;
213
+ this._transportType =
214
+ this._config.transport === undefined ? 'direct' : this._config.transport.type;
215
+ try {
216
+ await createdProvider.start();
217
+ }
218
+ catch (err) {
219
+ this._transportProvider = null;
220
+ this._transportType = null;
221
+ this._transportProviderReady = false;
222
+ throw err;
223
+ }
224
+ this._transportProviderReady = true;
225
+ this._lastTransportHealthy = true;
226
+ this._transportHealthInterval = setInterval(() => {
227
+ const provider = this._transportProvider;
228
+ if (!provider || !this._transportProviderReady)
229
+ return;
230
+ provider
231
+ .healthCheck()
232
+ .then((healthy) => {
233
+ if (this._transportProviderReady && this._transportProvider === provider) {
234
+ this._lastTransportHealthy = healthy;
235
+ }
236
+ })
237
+ .catch(() => {
238
+ if (this._transportProviderReady && this._transportProvider === provider) {
239
+ this._lastTransportHealthy = false;
240
+ }
241
+ });
242
+ }, this._transportHealthIntervalMs);
243
+ this._transportHealthInterval.unref?.();
244
+ const evmProviderConfig = this._config.chainProviders?.find((p) => p.chainType === 'evm');
245
+ const legacySettlementVars = [
246
+ 'BASE_L2_RPC_URL',
247
+ 'SETTLEMENT_ENABLED',
248
+ 'TOKEN_NETWORK_REGISTRY',
249
+ 'M2M_TOKEN_ADDRESS',
250
+ 'TREASURY_EVM_PRIVATE_KEY',
251
+ ];
252
+ const detectedLegacyVars = legacySettlementVars.filter((v) => process.env[v]);
253
+ if (detectedLegacyVars.length > 0) {
254
+ this._logger.warn({ event: 'legacy_env_vars_detected', vars: detectedLegacyVars }, 'Detected legacy settlement env vars -- these are no longer used. Configure chainProviders with an EVM entry instead.');
255
+ }
256
+ const baseRpcUrl = evmProviderConfig?.rpcUrl;
257
+ const registryAddress = evmProviderConfig?.registryAddress;
258
+ const m2mTokenAddress = evmProviderConfig?.tokenAddress;
259
+ const treasuryPrivateKey = evmProviderConfig?.keyId;
260
+ if (evmProviderConfig &&
204
261
  baseRpcUrl &&
205
262
  registryAddress &&
206
263
  m2mTokenAddress &&
@@ -285,9 +342,8 @@ class ConnectorNode {
285
342
  const tokenAddressMap = new Map();
286
343
  tokenAddressMap.set(this._defaultSettlementTokenId, m2mTokenAddress);
287
344
  tokenAddressMap.set(m2mTokenAddress, m2mTokenAddress);
288
- const defaultSettlementTimeout = this._config.settlementInfra?.settlementTimeoutSecs ?? 86400;
289
- const initialDepositMultiplier = this._config.settlementInfra?.initialDepositMultiplier ??
290
- parseInt(process.env.INITIAL_DEPOSIT_MULTIPLIER ?? '1', 10);
345
+ const defaultSettlementTimeout = evmProviderConfig.settlementOptions?.settlementTimeoutSecs ?? 86400;
346
+ const initialDepositMultiplier = evmProviderConfig.settlementOptions?.initialDepositMultiplier ?? 1;
291
347
  let accountManager;
292
348
  const tigerBeetleClusterId = process.env.TIGERBEETLE_CLUSTER_ID;
293
349
  const tigerBeetleReplicas = process.env.TIGERBEETLE_REPLICAS;
@@ -346,7 +402,7 @@ class ConnectorNode {
346
402
  this._accountManager = accountManager;
347
403
  }
348
404
  const peerIds = Array.from(peerIdToAddressMap.keys());
349
- const settlementThreshold = BigInt(this._config.settlementInfra?.threshold ?? process.env.SETTLEMENT_THRESHOLD ?? '1000000');
405
+ const settlementThreshold = BigInt(evmProviderConfig.settlementOptions?.threshold ?? '1000000');
350
406
  this._logger.info({
351
407
  event: 'settlement_monitor_config',
352
408
  peerIds,
@@ -363,7 +419,7 @@ class ConnectorNode {
363
419
  const chainProviderChainId = this._config.chainProviders?.find((cp) => cp.chainType === 'evm')?.chainId;
364
420
  const primaryChainIdStr = primaryChainId
365
421
  ? `evm:${primaryChainId}`
366
- : chainProviderChainId ?? 'evm:unknown';
422
+ : (chainProviderChainId ?? 'evm:unknown');
367
423
  const chainRegistry = new chain_provider_registry_1.ChainProviderRegistry();
368
424
  const evmProvider = new evm_payment_channel_provider_1.EVMPaymentChannelProvider(this._paymentChannelSDK, primaryChainIdStr, m2mTokenAddress, this._logger);
369
425
  chainRegistry.register(evmProvider);
@@ -381,6 +437,23 @@ class ConnectorNode {
381
437
  peerIdToChainMap.set(peerId, primaryChainIdStr);
382
438
  }
383
439
  }
440
+ const nip59Enabled = this._config.nip59?.enabled ?? false;
441
+ const nip59Wrapper = new nip59_claim_wrapper_1.NIP59ClaimWrapper({
442
+ nip59Enabled,
443
+ logger: this._logger,
444
+ });
445
+ const nodeSecp256k1PrivKey = treasuryPrivateKey
446
+ ? (0, utils_1.hexToBytes)(treasuryPrivateKey.replace(/^0x/, ''))
447
+ : undefined;
448
+ const peerIdToNip59PubKey = new Map();
449
+ for (const peer of this._config.peers) {
450
+ if (peer.nip59PublicKey) {
451
+ peerIdToNip59PubKey.set(peer.id, (0, utils_1.hexToBytes)(peer.nip59PublicKey));
452
+ }
453
+ }
454
+ if (nip59Enabled) {
455
+ this._logger.info({ event: 'nip59_enabled', peerCount: peerIdToNip59PubKey.size }, 'NIP-59 transport privacy enabled for claim wrapping');
456
+ }
384
457
  this._settlementExecutor = new settlement_executor_1.SettlementExecutor({
385
458
  nodeId: this._config.nodeId,
386
459
  defaultSettlementTimeout,
@@ -430,7 +503,7 @@ class ConnectorNode {
430
503
  for (const indexSql of claim_sender_db_schema_1.SENT_CLAIMS_INDEXES) {
431
504
  claimDb.exec(indexSql);
432
505
  }
433
- const perPacketClaimService = new per_packet_claim_service_1.PerPacketClaimService(chainRegistry, this._channelManager, claimDb, this._logger, this._config.nodeId, peerIdToChainMap);
506
+ const perPacketClaimService = new per_packet_claim_service_1.PerPacketClaimService(chainRegistry, this._channelManager, claimDb, this._logger, this._config.nodeId, peerIdToChainMap, nip59Wrapper, nodeSecp256k1PrivKey, peerIdToNip59PubKey);
434
507
  this._packetHandler.setPerPacketClaimService(perPacketClaimService);
435
508
  this._settlementExecutor?.setPerPacketClaimService(perPacketClaimService);
436
509
  this._logger.info({ event: 'per_packet_claims_enabled' }, 'Per-packet claim service wired to PacketHandler and SettlementExecutor');
@@ -441,7 +514,7 @@ class ConnectorNode {
441
514
  throw error;
442
515
  }
443
516
  }
444
- const inboundClaimValidator = new inbound_claim_validator_1.InboundClaimValidator(this._paymentChannelSDK, this._config.nodeId, this._logger, this._channelManager ?? undefined);
517
+ const inboundClaimValidator = new inbound_claim_validator_1.InboundClaimValidator(this._paymentChannelSDK, this._config.nodeId, this._logger, this._channelManager ?? undefined, nip59Wrapper, nodeSecp256k1PrivKey);
445
518
  this._btpServer.setInboundClaimValidator((protocolData, ilpPacket, peerId) => inboundClaimValidator.validate(protocolData, ilpPacket, peerId));
446
519
  this._logger.info({ event: 'inbound_claim_validator_enabled' }, 'Inbound claim validator wired to BTP server');
447
520
  if (this._paymentChannelSDK) {
@@ -451,7 +524,7 @@ class ConnectorNode {
451
524
  const receivedClaimDbPath = `./data/received-claims-${this._config.nodeId}.db`;
452
525
  const receivedClaimDb = new BetterSqlite3(receivedClaimDbPath);
453
526
  (0, claim_receiver_db_schema_1.initializeClaimReceiverSchema)(receivedClaimDb);
454
- const claimReceiver = new claim_receiver_1.ClaimReceiver(receivedClaimDb, chainRegistry, this._logger, this._channelManager ?? undefined, peerIdToAddressMap);
527
+ const claimReceiver = new claim_receiver_1.ClaimReceiver(receivedClaimDb, chainRegistry, this._logger, this._channelManager ?? undefined, peerIdToAddressMap, nip59Wrapper, nodeSecp256k1PrivKey);
455
528
  claimReceiver.registerWithBTPServer(this._btpServer);
456
529
  if (this._settlementMonitor) {
457
530
  this._settlementMonitor.setClaimReceiver(claimReceiver);
@@ -468,7 +541,7 @@ class ConnectorNode {
468
541
  if (accountManager) {
469
542
  const settlementConfig = {
470
543
  connectorFeePercentage: this._config.settlement?.connectorFeePercentage ?? 0.1,
471
- enableSettlement: settlementEnabled,
544
+ enableSettlement: true,
472
545
  tigerBeetleClusterId: tigerBeetleClusterId ? parseInt(tigerBeetleClusterId, 10) : 0,
473
546
  tigerBeetleReplicas: tigerBeetleReplicas
474
547
  ? tigerBeetleReplicas.split(',').map((s) => s.trim())
@@ -594,6 +667,24 @@ class ConnectorNode {
594
667
  nodeId: this._config.nodeId,
595
668
  error: errorMessage,
596
669
  }, 'Failed to start connector node');
670
+ if (this._transportHealthInterval) {
671
+ clearInterval(this._transportHealthInterval);
672
+ this._transportHealthInterval = null;
673
+ }
674
+ if (this._transportProvider) {
675
+ this._transportProviderReady = false;
676
+ try {
677
+ await this._transportProvider.stop();
678
+ }
679
+ catch (stopErr) {
680
+ const stopMsg = stopErr instanceof Error ? stopErr.message : String(stopErr);
681
+ this._logger.warn({ event: 'transport_rollback_stop_failed', error: stopMsg }, 'Transport provider stop() failed during start() rollback; continuing');
682
+ }
683
+ finally {
684
+ this._transportProvider = null;
685
+ this._transportType = null;
686
+ }
687
+ }
597
688
  this._healthStatus = 'unhealthy';
598
689
  throw error;
599
690
  }
@@ -654,6 +745,20 @@ class ConnectorNode {
654
745
  }
655
746
  await this._healthServer.stop();
656
747
  await this._btpServer.stop();
748
+ if (this._transportHealthInterval) {
749
+ clearInterval(this._transportHealthInterval);
750
+ this._transportHealthInterval = null;
751
+ }
752
+ if (this._transportProvider) {
753
+ this._transportProviderReady = false;
754
+ try {
755
+ await this._transportProvider.stop();
756
+ }
757
+ finally {
758
+ this._transportProvider = null;
759
+ this._transportType = null;
760
+ }
761
+ }
657
762
  this._logger.info({
658
763
  event: 'connector_stopped',
659
764
  nodeId: this._config.nodeId,
@@ -685,6 +790,12 @@ class ConnectorNode {
685
790
  nodeId: this._config.nodeId,
686
791
  version: package_json_1.default.version,
687
792
  };
793
+ if (this._transportProviderReady && this._transportProvider && this._transportType) {
794
+ healthStatus.transport = {
795
+ type: this._transportType,
796
+ healthy: this._transportType === 'direct' ? true : this._lastTransportHealthy,
797
+ };
798
+ }
688
799
  return healthStatus;
689
800
  }
690
801
  get routingTable() {
@@ -693,6 +804,129 @@ class ConnectorNode {
693
804
  get btpClientManager() {
694
805
  return this._btpClientManager;
695
806
  }
807
+ get transportProvider() {
808
+ return this._transportProviderReady ? this._transportProvider : null;
809
+ }
810
+ _createTransportProvider(cfg) {
811
+ if (cfg === undefined || cfg.type === 'direct') {
812
+ const externalUrl = `ws://localhost:${this._config.btpServerPort}`;
813
+ this._logger.debug({ event: 'direct_transport_external_url_synthesized', externalUrl }, 'DirectTransportProvider externalUrl synthesized from btpServerPort (local placeholder)');
814
+ return new transport_1.DirectTransportProvider(externalUrl);
815
+ }
816
+ if (cfg.type === 'socks5') {
817
+ let managedClient;
818
+ let externalUrl = cfg.externalUrl;
819
+ if (cfg.managed === true) {
820
+ let cachedFactory;
821
+ let prewarmError;
822
+ const prewarmPromise = (0, transport_1.createDefaultAnonFactory)().then((f) => {
823
+ cachedFactory = f;
824
+ }, (err) => {
825
+ prewarmError = err;
826
+ });
827
+ const anonFactory = (opts) => {
828
+ if (cachedFactory) {
829
+ return cachedFactory(opts);
830
+ }
831
+ if (prewarmError) {
832
+ if (prewarmError.code === 'MODULE_NOT_FOUND') {
833
+ throw prewarmError;
834
+ }
835
+ throw new Error(`Failed to load optional dependency "@anyone-protocol/anyone-client": ` +
836
+ `${prewarmError.message ?? String(prewarmError)}`, { cause: prewarmError });
837
+ }
838
+ try {
839
+ const pkg = '@anyone-protocol/anyone-client';
840
+ const mod = require(pkg);
841
+ const AnonCtor = mod?.Anon ?? mod?.default?.Anon ?? mod?.default;
842
+ if (typeof AnonCtor !== 'function') {
843
+ throw new Error('@anyone-protocol/anyone-client did not export an `Anon` constructor');
844
+ }
845
+ cachedFactory = (o) => new AnonCtor(o);
846
+ return cachedFactory(opts);
847
+ }
848
+ catch (err) {
849
+ const cause = err;
850
+ if (cause?.code === 'MODULE_NOT_FOUND') {
851
+ throw cause;
852
+ }
853
+ if (cause?.code === 'ERR_REQUIRE_ESM') {
854
+ throw new Error(`@anyone-protocol/anyone-client is an ESM-only package; ` +
855
+ `the async lazy-import pre-warm had not completed when the factory ` +
856
+ `was invoked. This is a timing bug — please file an issue.`, { cause });
857
+ }
858
+ throw new Error(`Failed to load optional dependency "@anyone-protocol/anyone-client": ` +
859
+ `${cause?.message ?? String(err)}`, { cause });
860
+ }
861
+ };
862
+ void prewarmPromise;
863
+ managedClient = new transport_1.ManagedAnonClient({
864
+ socksProxy: cfg.socksProxy,
865
+ hiddenServiceDir: cfg.managedOptions?.hiddenServiceDir,
866
+ hiddenServicePort: cfg.managedOptions?.hiddenServicePort,
867
+ binaryPath: cfg.managedOptions?.binaryPath,
868
+ startupTimeoutMs: cfg.managedOptions?.startupTimeoutMs,
869
+ stopTimeoutMs: cfg.managedOptions?.stopTimeoutMs,
870
+ logger: this._logger,
871
+ anonFactory,
872
+ });
873
+ if (externalUrl === 'auto') {
874
+ externalUrl = 'wss://pending.invalid/btp';
875
+ }
876
+ }
877
+ else if (externalUrl === 'auto') {
878
+ throw new Error('_createTransportProvider: transport.externalUrl "auto" requires managed: true');
879
+ }
880
+ let resolveExternalUrlOnStart;
881
+ if (cfg.externalUrl === 'auto') {
882
+ const hsDir = cfg.managedOptions?.hiddenServiceDir;
883
+ if (!hsDir) {
884
+ throw new Error('_createTransportProvider: transport.externalUrl "auto" requires managedOptions.hiddenServiceDir');
885
+ }
886
+ const hostnameReadDeadlineMs = cfg.managedOptions?.startupTimeoutMs ?? 30_000;
887
+ const HIDDEN_SERVICE_HOSTNAME_RE = /^[a-z2-7]{16}(?:[a-z2-7]{40})?\.(?:anon|onion)$/;
888
+ resolveExternalUrlOnStart = async () => {
889
+ const hostnameFile = nodePath.join(hsDir, 'hostname');
890
+ const deadline = Date.now() + hostnameReadDeadlineMs;
891
+ let lastErr;
892
+ while (Date.now() < deadline) {
893
+ try {
894
+ const raw = await fs_1.promises.readFile(hostnameFile, 'utf8');
895
+ const firstLine = raw.split(/\r?\n/, 1)[0]?.trim() ?? '';
896
+ const hostname = firstLine;
897
+ if (!hostname) {
898
+ lastErr = new Error('hostname file is empty');
899
+ }
900
+ else if (!HIDDEN_SERVICE_HOSTNAME_RE.test(hostname)) {
901
+ lastErr = new Error(`hostname file contents did not match the expected hidden service format ` +
902
+ `(length=${hostname.length}); ignoring and retrying`);
903
+ }
904
+ else {
905
+ return `wss://${hostname}/btp`;
906
+ }
907
+ }
908
+ catch (err) {
909
+ lastErr = err;
910
+ }
911
+ await new Promise((r) => setTimeout(r, 250));
912
+ }
913
+ const reason = lastErr instanceof Error ? lastErr.message : String(lastErr ?? 'unknown');
914
+ throw new Error(`hidden service hostname file "${hostnameFile}" did not become readable ` +
915
+ `within ${hostnameReadDeadlineMs}ms (last error: ${reason})`);
916
+ };
917
+ }
918
+ return new transport_1.SocksTransportProvider({
919
+ socksProxy: cfg.socksProxy,
920
+ externalUrl,
921
+ logger: this._logger,
922
+ managedClient,
923
+ resolveExternalUrlOnStart,
924
+ });
925
+ }
926
+ const _exhaustive = cfg;
927
+ throw new Error(`Unsupported transport type: ${JSON.stringify(_exhaustive)} ` +
928
+ '(Story 35.4 _createTransportProvider exhaustiveness guard)');
929
+ }
696
930
  get paymentChannelSDK() {
697
931
  return this._paymentChannelSDK;
698
932
  }
@@ -706,11 +940,9 @@ class ConnectorNode {
706
940
  return this._chainSDKs.get(chainId) ?? null;
707
941
  }
708
942
  async _createInMemoryAccountManager() {
709
- const snapshotPath = this._config.settlementInfra?.ledgerSnapshotPath ??
710
- process.env.LEDGER_SNAPSHOT_PATH ??
711
- './data/ledger-snapshot.json';
712
- const persistIntervalMs = this._config.settlementInfra?.ledgerPersistIntervalMs ??
713
- parseInt(process.env.LEDGER_PERSIST_INTERVAL_MS || '30000', 10);
943
+ const evmProvider = this._config.chainProviders?.find((p) => p.chainType === 'evm');
944
+ const snapshotPath = evmProvider?.settlementOptions?.ledgerSnapshotPath ?? './data/ledger-snapshot.json';
945
+ const persistIntervalMs = evmProvider?.settlementOptions?.ledgerPersistIntervalMs ?? 30000;
714
946
  let inMemoryClient;
715
947
  try {
716
948
  inMemoryClient = new in_memory_ledger_client_1.InMemoryLedgerClient({
@@ -814,8 +1046,10 @@ class ConnectorNode {
814
1046
  typeof config.authToken !== 'string') {
815
1047
  throw new Error('authToken must be a string (can be empty for no auth)');
816
1048
  }
817
- if (!config.url.startsWith('ws://') && !config.url.startsWith('wss://')) {
818
- throw new Error('URL must start with ws:// or wss://');
1049
+ const PLAIN_WS_PREFIX = 'ws' + '://';
1050
+ const SECURE_WS_PREFIX = 'wss' + '://';
1051
+ if (!config.url.startsWith(PLAIN_WS_PREFIX) && !config.url.startsWith(SECURE_WS_PREFIX)) {
1052
+ throw new Error(`URL must start with ${PLAIN_WS_PREFIX} or ${SECURE_WS_PREFIX}`);
819
1053
  }
820
1054
  if (config.routes) {
821
1055
  for (const route of config.routes) {
@@ -981,7 +1215,7 @@ class ConnectorNode {
981
1215
  throw new config_loader_1.ConnectorNotStartedError('Connector is not started. Call start() before openChannel().');
982
1216
  }
983
1217
  if (!this._channelManager) {
984
- throw new Error('Settlement infrastructure not enabled');
1218
+ throw new Error('No EVM chain provider configured -- openChannel requires a chainProviders entry with chainType: "evm"');
985
1219
  }
986
1220
  const existingPeers = this._btpClientManager.getPeerIds();
987
1221
  if (!existingPeers.includes(params.peerId)) {
@@ -1012,7 +1246,7 @@ class ConnectorNode {
1012
1246
  throw new config_loader_1.ConnectorNotStartedError('Connector is not started. Call start() before getChannelState().');
1013
1247
  }
1014
1248
  if (!this._channelManager) {
1015
- throw new Error('Settlement infrastructure not enabled');
1249
+ throw new Error('No EVM chain provider configured -- openChannel requires a chainProviders entry with chainType: "evm"');
1016
1250
  }
1017
1251
  const metadata = this._channelManager.getChannelById(channelId);
1018
1252
  if (!metadata) {