@toon-protocol/connector 3.20.0 → 3.28.6

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 (175) hide show
  1. package/README.md +6 -6
  2. package/dist/auth/rfc9421/content-digest.d.ts +16 -0
  3. package/dist/auth/rfc9421/content-digest.d.ts.map +1 -0
  4. package/dist/auth/rfc9421/content-digest.js +35 -0
  5. package/dist/auth/rfc9421/content-digest.js.map +1 -0
  6. package/dist/auth/rfc9421/index.d.ts +9 -0
  7. package/dist/auth/rfc9421/index.d.ts.map +1 -0
  8. package/dist/auth/rfc9421/index.js +24 -0
  9. package/dist/auth/rfc9421/index.js.map +1 -0
  10. package/dist/auth/rfc9421/sign.d.ts +20 -0
  11. package/dist/auth/rfc9421/sign.d.ts.map +1 -0
  12. package/dist/auth/rfc9421/sign.js +43 -0
  13. package/dist/auth/rfc9421/sign.js.map +1 -0
  14. package/dist/auth/rfc9421/signature-base.d.ts +21 -0
  15. package/dist/auth/rfc9421/signature-base.d.ts.map +1 -0
  16. package/dist/auth/rfc9421/signature-base.js +57 -0
  17. package/dist/auth/rfc9421/signature-base.js.map +1 -0
  18. package/dist/auth/rfc9421/verify.d.ts +19 -0
  19. package/dist/auth/rfc9421/verify.d.ts.map +1 -0
  20. package/dist/auth/rfc9421/verify.js +215 -0
  21. package/dist/auth/rfc9421/verify.js.map +1 -0
  22. package/dist/btp/btp-client-manager.d.ts +1 -1
  23. package/dist/btp/btp-client-manager.d.ts.map +1 -1
  24. package/dist/btp/btp-client.d.ts +2 -2
  25. package/dist/btp/btp-client.d.ts.map +1 -1
  26. package/dist/btp/btp-client.js.map +1 -1
  27. package/dist/cli/admin-client-factory.d.ts +10 -0
  28. package/dist/cli/admin-client-factory.d.ts.map +1 -0
  29. package/dist/cli/admin-client-factory.js +33 -0
  30. package/dist/cli/admin-client-factory.js.map +1 -0
  31. package/dist/cli/commands/app.d.ts +6 -0
  32. package/dist/cli/commands/app.d.ts.map +1 -0
  33. package/dist/cli/commands/app.js +132 -0
  34. package/dist/cli/commands/app.js.map +1 -0
  35. package/dist/cli/commands/route.d.ts +3 -0
  36. package/dist/cli/commands/route.d.ts.map +1 -0
  37. package/dist/cli/commands/route.js +63 -0
  38. package/dist/cli/commands/route.js.map +1 -0
  39. package/dist/cli/commands/up.d.ts +3 -0
  40. package/dist/cli/commands/up.d.ts.map +1 -0
  41. package/dist/cli/commands/up.js +27 -0
  42. package/dist/cli/commands/up.js.map +1 -0
  43. package/dist/cli/index.js +32 -16
  44. package/dist/cli/index.js.map +1 -1
  45. package/dist/client/connector-admin-client.d.ts +17 -12
  46. package/dist/client/connector-admin-client.d.ts.map +1 -1
  47. package/dist/client/connector-admin-client.js.map +1 -1
  48. package/dist/config/config-loader.d.ts +0 -3
  49. package/dist/config/config-loader.d.ts.map +1 -1
  50. package/dist/config/config-loader.js +41 -161
  51. package/dist/config/config-loader.js.map +1 -1
  52. package/dist/config/environment-validator.js +6 -6
  53. package/dist/config/environment-validator.js.map +1 -1
  54. package/dist/config/types.d.ts +47 -17
  55. package/dist/config/types.d.ts.map +1 -1
  56. package/dist/config/types.js +93 -1
  57. package/dist/config/types.js.map +1 -1
  58. package/dist/core/connector-node.d.ts +13 -1
  59. package/dist/core/connector-node.d.ts.map +1 -1
  60. package/dist/core/connector-node.js +279 -171
  61. package/dist/core/connector-node.js.map +1 -1
  62. package/dist/core/handlers/http-proxy-handler.d.ts +47 -0
  63. package/dist/core/handlers/http-proxy-handler.d.ts.map +1 -0
  64. package/dist/core/handlers/http-proxy-handler.js +223 -0
  65. package/dist/core/handlers/http-proxy-handler.js.map +1 -0
  66. package/dist/core/local-delivery-client.js +9 -9
  67. package/dist/core/local-delivery-client.js.map +1 -1
  68. package/dist/core/packet-handler.d.ts +7 -0
  69. package/dist/core/packet-handler.d.ts.map +1 -1
  70. package/dist/core/packet-handler.js +55 -3
  71. package/dist/core/packet-handler.js.map +1 -1
  72. package/dist/core/registry-db-schema.d.ts +2 -1
  73. package/dist/core/registry-db-schema.d.ts.map +1 -1
  74. package/dist/core/registry-db-schema.js +9 -0
  75. package/dist/core/registry-db-schema.js.map +1 -1
  76. package/dist/core/registry-store.d.ts +1 -0
  77. package/dist/core/registry-store.d.ts.map +1 -1
  78. package/dist/core/registry-store.js +6 -4
  79. package/dist/core/registry-store.js.map +1 -1
  80. package/dist/core/route-upstream-registry.d.ts +17 -0
  81. package/dist/core/route-upstream-registry.d.ts.map +1 -0
  82. package/dist/core/route-upstream-registry.js +51 -0
  83. package/dist/core/route-upstream-registry.js.map +1 -0
  84. package/dist/discovery/ilp-peer-info-event.d.ts +22 -0
  85. package/dist/discovery/ilp-peer-info-event.d.ts.map +1 -0
  86. package/dist/discovery/ilp-peer-info-event.js +21 -0
  87. package/dist/discovery/ilp-peer-info-event.js.map +1 -0
  88. package/dist/discovery/index.d.ts +8 -0
  89. package/dist/discovery/index.d.ts.map +1 -1
  90. package/dist/discovery/index.js +16 -1
  91. package/dist/discovery/index.js.map +1 -1
  92. package/dist/discovery/self-announce-builder.d.ts +14 -0
  93. package/dist/discovery/self-announce-builder.d.ts.map +1 -0
  94. package/dist/discovery/self-announce-builder.js +88 -0
  95. package/dist/discovery/self-announce-builder.js.map +1 -0
  96. package/dist/discovery/self-announce-publish.d.ts +17 -0
  97. package/dist/discovery/self-announce-publish.d.ts.map +1 -0
  98. package/dist/discovery/self-announce-publish.js +35 -0
  99. package/dist/discovery/self-announce-publish.js.map +1 -0
  100. package/dist/discovery/self-announce-service.d.ts +36 -0
  101. package/dist/discovery/self-announce-service.d.ts.map +1 -0
  102. package/dist/discovery/self-announce-service.js +118 -0
  103. package/dist/discovery/self-announce-service.js.map +1 -0
  104. package/dist/http/admin-api-inventory.d.ts.map +1 -1
  105. package/dist/http/admin-api-inventory.js +0 -42
  106. package/dist/http/admin-api-inventory.js.map +1 -1
  107. package/dist/http/admin-api.d.ts +29 -13
  108. package/dist/http/admin-api.d.ts.map +1 -1
  109. package/dist/http/admin-api.js +141 -76
  110. package/dist/http/admin-api.js.map +1 -1
  111. package/dist/http/admin-server.d.ts +3 -4
  112. package/dist/http/admin-server.d.ts.map +1 -1
  113. package/dist/http/admin-server.js +4 -6
  114. package/dist/http/admin-server.js.map +1 -1
  115. package/dist/http/ilp-http-adapter.d.ts +10 -0
  116. package/dist/http/ilp-http-adapter.d.ts.map +1 -1
  117. package/dist/http/ilp-http-adapter.js +118 -0
  118. package/dist/http/ilp-http-adapter.js.map +1 -1
  119. package/dist/http/types.d.ts +1 -1
  120. package/dist/http/types.d.ts.map +1 -1
  121. package/dist/http/x402-greeting.d.ts +46 -0
  122. package/dist/http/x402-greeting.d.ts.map +1 -0
  123. package/dist/http/x402-greeting.js +77 -0
  124. package/dist/http/x402-greeting.js.map +1 -0
  125. package/dist/lib.d.ts +13 -2
  126. package/dist/lib.d.ts.map +1 -1
  127. package/dist/lib.js +41 -1
  128. package/dist/lib.js.map +1 -1
  129. package/dist/main.d.ts +3 -1
  130. package/dist/main.d.ts.map +1 -1
  131. package/dist/main.js +3 -1
  132. package/dist/main.js.map +1 -1
  133. package/dist/settlement/channel-manager.d.ts.map +1 -1
  134. package/dist/settlement/channel-manager.js +1 -1
  135. package/dist/settlement/channel-manager.js.map +1 -1
  136. package/dist/settlement/provider/solana-payment-channel-provider.d.ts.map +1 -1
  137. package/dist/settlement/provider/solana-payment-channel-provider.js +1 -1
  138. package/dist/settlement/provider/solana-payment-channel-provider.js.map +1 -1
  139. package/dist/settlement/solana-payment-channel-sdk.d.ts +1 -1
  140. package/dist/settlement/solana-payment-channel-sdk.d.ts.map +1 -1
  141. package/dist/settlement/solana-payment-channel-sdk.js +2 -2
  142. package/dist/settlement/solana-payment-channel-sdk.js.map +1 -1
  143. package/dist/transport/http-peer-transport.d.ts +46 -0
  144. package/dist/transport/http-peer-transport.d.ts.map +1 -0
  145. package/dist/transport/http-peer-transport.js +185 -0
  146. package/dist/transport/http-peer-transport.js.map +1 -0
  147. package/dist/transport/index.d.ts +0 -3
  148. package/dist/transport/index.d.ts.map +1 -1
  149. package/dist/transport/index.js +1 -8
  150. package/dist/transport/index.js.map +1 -1
  151. package/dist/wallet/mnemonic-keys.d.ts +15 -0
  152. package/dist/wallet/mnemonic-keys.d.ts.map +1 -0
  153. package/dist/wallet/mnemonic-keys.js +150 -0
  154. package/dist/wallet/mnemonic-keys.js.map +1 -0
  155. package/package.json +11 -8
  156. package/dist/transport/hs-desc-monitor.d.ts +0 -12
  157. package/dist/transport/hs-desc-monitor.d.ts.map +0 -1
  158. package/dist/transport/hs-desc-monitor.js +0 -105
  159. package/dist/transport/hs-desc-monitor.js.map +0 -1
  160. package/dist/transport/managed-anon-client.d.ts +0 -93
  161. package/dist/transport/managed-anon-client.d.ts.map +0 -1
  162. package/dist/transport/managed-anon-client.js +0 -522
  163. package/dist/transport/managed-anon-client.js.map +0 -1
  164. package/dist/transport/socks-transport-provider.d.ts +0 -29
  165. package/dist/transport/socks-transport-provider.d.ts.map +0 -1
  166. package/dist/transport/socks-transport-provider.js +0 -136
  167. package/dist/transport/socks-transport-provider.js.map +0 -1
  168. package/dist/transport/socks-url.d.ts +0 -6
  169. package/dist/transport/socks-url.d.ts.map +0 -1
  170. package/dist/transport/socks-url.js +0 -29
  171. package/dist/transport/socks-url.js.map +0 -1
  172. package/dist/transport/socks5-connect.d.ts +0 -12
  173. package/dist/transport/socks5-connect.d.ts.map +0 -1
  174. package/dist/transport/socks5-connect.js +0 -89
  175. package/dist/transport/socks5-connect.js.map +0 -1
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ConnectorNode = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const fs_1 = require("fs");
6
- const nodePath = tslib_1.__importStar(require("path"));
7
6
  const routing_table_1 = require("../routing/routing-table");
8
7
  const relation_route_validator_1 = require("../routing/relation-route-validator");
9
8
  const btp_client_manager_1 = require("../btp/btp-client-manager");
@@ -11,7 +10,10 @@ const btp_server_1 = require("../btp/btp-server");
11
10
  const packet_handler_1 = require("./packet-handler");
12
11
  const shared_1 = require("@toon-protocol/shared");
13
12
  const types_1 = require("../config/types");
13
+ const route_upstream_registry_1 = require("./route-upstream-registry");
14
+ const http_proxy_handler_1 = require("./handlers/http-proxy-handler");
14
15
  const transport_1 = require("../transport");
16
+ const http_peer_transport_1 = require("../transport/http-peer-transport");
15
17
  const payment_handler_1 = require("./payment-handler");
16
18
  const types_2 = require("../settlement/types");
17
19
  const admin_api_1 = require("../http/admin-api");
@@ -42,14 +44,20 @@ const solana_payment_channel_provider_1 = require("../settlement/provider/solana
42
44
  const claim_sender_db_schema_1 = require("../settlement/claim-sender-db-schema");
43
45
  const inbound_claim_validator_1 = require("../btp/inbound-claim-validator");
44
46
  const nip59_claim_wrapper_1 = require("../settlement/privacy/nip59-claim-wrapper");
47
+ const mnemonic_keys_1 = require("../wallet/mnemonic-keys");
48
+ const self_announce_service_1 = require("../discovery/self-announce-service");
49
+ const self_announce_publish_1 = require("../discovery/self-announce-publish");
45
50
  const utils_1 = require("@noble/hashes/utils");
51
+ const SELF_ANNOUNCE_PREPARE_TIMEOUT_MS = 30_000;
46
52
  const dns_1 = require("dns");
47
53
  const package_json_1 = tslib_1.__importDefault(require("../../package.json"));
48
54
  class ConnectorNode {
49
55
  _config;
50
56
  _logger;
51
57
  _routingTable;
58
+ _routeTerminationRegistry;
52
59
  _btpClientManager;
60
+ _httpPeerClientManager;
53
61
  _packetHandler;
54
62
  _btpServer;
55
63
  _healthServer;
@@ -73,17 +81,21 @@ class ConnectorNode {
73
81
  _btpServerStarted = false;
74
82
  _defaultSettlementTokenId = 'M2M';
75
83
  _transportProvider = null;
76
- _managedAnonClient = null;
77
84
  _transportProviderReady = false;
78
85
  _transportType = null;
79
86
  _lastTransportHealthy = true;
80
87
  _transportHealthInterval = null;
81
88
  _transportHealthIntervalMs;
89
+ _mnemonicOverride;
90
+ _mnemonicAccountIndex;
91
+ _selfAnnounceService = null;
82
92
  get defaultSettlementTokenId() {
83
93
  return this._defaultSettlementTokenId;
84
94
  }
85
95
  constructor(config, logger, opts) {
86
96
  this._transportHealthIntervalMs = opts?.transportHealthIntervalMs ?? 30000;
97
+ this._mnemonicOverride = opts?.mnemonic;
98
+ this._mnemonicAccountIndex = opts?.mnemonicAccountIndex;
87
99
  let resolvedConfig;
88
100
  try {
89
101
  if (typeof config === 'string') {
@@ -115,22 +127,22 @@ class ConnectorNode {
115
127
  priority: route.priority,
116
128
  }));
117
129
  this._routingTable = new routing_table_1.RoutingTable(routingTableEntries, logger.child({ component: 'RoutingTable' }));
130
+ this._routeTerminationRegistry = new route_upstream_registry_1.RouteTerminationRegistry(resolvedConfig.routes.map((route) => ({
131
+ prefix: route.prefix,
132
+ termination: (0, types_1.toRouteTermination)(route),
133
+ })));
118
134
  this._btpClientManager = new btp_client_manager_1.BTPClientManager(resolvedConfig.nodeId, logger.child({ component: 'BTPClientManager' }));
119
- this._btpClientManager.setAgentFactory((peer) => {
120
- const effective = peer.transport ?? this._transportType ?? 'direct';
121
- if (effective === 'socks5' &&
122
- (!this._transportProvider || this._transportType !== 'socks5')) {
123
- this._logger.error({
124
- event: 'btp_agent_factory_invariant_violation',
125
- peerId: peer.id,
126
- requestedTransport: 'socks5',
127
- connectorTransport: this._transportType,
128
- }, 'Peer requested SOCKS5 transport but connector has no SOCKS5 provider — refusing to fall through to direct dial');
129
- throw new Error('SOCKS5 transport requested for peer but no SOCKS5 provider configured');
130
- }
131
- return effective === 'socks5' ? this._transportProvider.createAgent(peer.url) : undefined;
132
- });
135
+ this._btpClientManager.setAgentFactory(() => undefined);
136
+ const httpEgressTransport = {
137
+ createAgent: () => undefined,
138
+ getExternalUrl: () => this._transportProvider?.getExternalUrl() ?? '',
139
+ start: async () => { },
140
+ stop: async () => { },
141
+ healthCheck: async () => this._transportProvider?.healthCheck() ?? true,
142
+ };
143
+ this._httpPeerClientManager = new http_peer_transport_1.HttpPeerClientManager(resolvedConfig.nodeId, logger.child({ component: 'HttpPeerClientManager' }), httpEgressTransport);
133
144
  this._packetHandler = new packet_handler_1.PacketHandler(this._routingTable, this._btpClientManager, resolvedConfig.nodeId, logger.child({ component: 'PacketHandler' }));
145
+ this._packetHandler.setHttpEgress(this._httpPeerClientManager);
134
146
  this._btpServer = new btp_server_1.BTPServer(logger.child({ component: 'BTPServer' }), this._packetHandler);
135
147
  this._packetHandler.setBTPServer(this._btpServer);
136
148
  const localDeliveryEnabled = resolvedConfig.localDelivery?.enabled || process.env.LOCAL_DELIVERY_ENABLED === 'true';
@@ -146,11 +158,35 @@ class ConnectorNode {
146
158
  };
147
159
  this._packetHandler.setLocalDelivery(localDeliveryConfig);
148
160
  }
161
+ if (this._routeTerminationRegistry.size > 0) {
162
+ if (localDeliveryEnabled) {
163
+ this._logger.warn({
164
+ event: 'route_termination_proxy_skipped',
165
+ terminatedRoutes: this._routeTerminationRegistry.size,
166
+ reason: 'global_local_delivery_handler_configured',
167
+ }, 'Terminated routes are configured but a global localDelivery.handlerUrl ' +
168
+ 'is also enabled; preserving the existing handlerUrl path and NOT ' +
169
+ 'installing the per-route HTTP proxy (requires PacketHandler fall-through — human decision)');
170
+ }
171
+ else {
172
+ const proxy = new http_proxy_handler_1.HttpProxyHandler({
173
+ upstreamResolver: this._routeTerminationRegistry.resolveUpstream,
174
+ logger: this._logger,
175
+ });
176
+ this.setLocalDeliveryHandler(proxy.handler);
177
+ this._logger.info({
178
+ event: 'route_termination_proxy_installed',
179
+ terminatedRoutes: this._routeTerminationRegistry.size,
180
+ prefixes: this._routeTerminationRegistry.prefixes(),
181
+ }, 'HttpProxyHandler installed as local-delivery handler for terminated routes');
182
+ }
183
+ }
149
184
  this._btpClientManager.setPacketHandler(this._packetHandler);
150
185
  this._ilpMetrics = new metrics_registry_1.IlpMetricsRegistry({ collectDefaults: false });
151
186
  for (const peer of resolvedConfig.peers) {
152
187
  this._ilpMetrics.registerPeer(peer.id);
153
188
  this._packetHandler.setPeerRelation(peer.id, peer.relation ?? 'peer');
189
+ this._packetHandler.setPeerProtocol(peer.id, peer.peerProtocol ?? 'btp');
154
190
  }
155
191
  this._packetHandler.setIlpMetrics(this._ilpMetrics);
156
192
  this._healthServer = new health_server_1.HealthServer(logger.child({ component: 'HealthServer' }), this, {
@@ -234,6 +270,118 @@ class ConnectorNode {
234
270
  };
235
271
  }
236
272
  }
273
+ _applyMnemonicSigningMode() {
274
+ const mnemonic = this._mnemonicOverride ?? process.env.TOON_MNEMONIC;
275
+ if (!mnemonic) {
276
+ return;
277
+ }
278
+ const providers = this._config.chainProviders;
279
+ if (!providers || providers.length === 0) {
280
+ this._logger.warn({ event: 'mnemonic_mode_no_chain_providers' }, 'TOON_MNEMONIC is set but no chainProviders are configured; no settlement keys to derive');
281
+ return;
282
+ }
283
+ const keys = (0, mnemonic_keys_1.deriveChainKeysFromMnemonic)(mnemonic, this._mnemonicAccountIndex ?? 0);
284
+ let evmInjected = false;
285
+ let solanaInjected = false;
286
+ let minaInjected = false;
287
+ for (const provider of providers) {
288
+ switch (provider.chainType) {
289
+ case 'evm':
290
+ provider.keyId = keys.evm.privateKey;
291
+ evmInjected = true;
292
+ break;
293
+ case 'solana':
294
+ provider.keyId = keys.solana.privateKey;
295
+ solanaInjected = true;
296
+ break;
297
+ case 'mina':
298
+ provider.keyId = keys.mina.privateKey;
299
+ minaInjected = true;
300
+ break;
301
+ }
302
+ }
303
+ this._logger.info({
304
+ event: 'mnemonic_signing_mode_enabled',
305
+ evm: evmInjected,
306
+ solana: solanaInjected,
307
+ mina: minaInjected,
308
+ evmAddress: evmInjected ? keys.evm.address : undefined,
309
+ solanaAddress: solanaInjected ? keys.solana.address : undefined,
310
+ }, 'Mnemonic signing mode active: derived per-chain settlement keys from TOON_MNEMONIC');
311
+ }
312
+ _resolveNostrSecretKey() {
313
+ const evmProvider = this._config.chainProviders?.find((p) => p.chainType === 'evm');
314
+ const keyId = evmProvider?.keyId;
315
+ if (keyId && /^(0x)?[0-9a-fA-F]{64}$/.test(keyId)) {
316
+ return (0, utils_1.hexToBytes)(keyId.replace(/^0x/, ''));
317
+ }
318
+ const mnemonic = this._mnemonicOverride ?? process.env.TOON_MNEMONIC;
319
+ if (mnemonic) {
320
+ try {
321
+ const keys = (0, mnemonic_keys_1.deriveChainKeysFromMnemonic)(mnemonic, this._mnemonicAccountIndex ?? 0);
322
+ return (0, utils_1.hexToBytes)(keys.evm.privateKey.replace(/^0x/, ''));
323
+ }
324
+ catch (err) {
325
+ this._logger.warn({
326
+ event: 'self_announce_key_derive_failed',
327
+ err: err instanceof Error ? err.message : String(err),
328
+ }, 'Failed to derive Nostr key for self-announce');
329
+ return null;
330
+ }
331
+ }
332
+ return null;
333
+ }
334
+ _startSelfAnnounce() {
335
+ const selfAnnounce = this._config.selfAnnounce;
336
+ if (!selfAnnounce?.enabled) {
337
+ return;
338
+ }
339
+ const secretKey = this._resolveNostrSecretKey();
340
+ if (!secretKey) {
341
+ this._logger.warn({ event: 'self_announce_no_identity' }, 'selfAnnounce.enabled but no Nostr signing identity (set TOON_MNEMONIC or an EVM keyId); not announcing');
342
+ return;
343
+ }
344
+ try {
345
+ this._selfAnnounceService = new self_announce_service_1.SelfAnnounceService({
346
+ config: this._config,
347
+ selfAnnounce,
348
+ secretKey,
349
+ publish: (event) => this._publishAnnouncement(event),
350
+ logger: this._logger,
351
+ });
352
+ this._selfAnnounceService.start();
353
+ }
354
+ catch (err) {
355
+ this._selfAnnounceService = null;
356
+ this._logger.warn({
357
+ event: 'self_announce_start_failed',
358
+ err: err instanceof Error ? err.message : String(err),
359
+ }, 'Failed to start self-announce service; continuing without it');
360
+ }
361
+ }
362
+ async _publishAnnouncement(event) {
363
+ const selfAnnounce = this._config.selfAnnounce;
364
+ const announceTo = selfAnnounce.announceTo;
365
+ const isLocallyTerminated = this._routeTerminationRegistry.match(announceTo) !== undefined;
366
+ const plan = (0, self_announce_publish_1.planAnnouncePublish)({
367
+ announceTo,
368
+ event,
369
+ isLocallyTerminated,
370
+ ...(selfAnnounce.announcePrice ? { remotePriceAtomic: selfAnnounce.announcePrice } : {}),
371
+ });
372
+ const expiresAt = new Date(Date.now() + SELF_ANNOUNCE_PREPARE_TIMEOUT_MS);
373
+ const response = await this.sendPacket({
374
+ destination: plan.destination,
375
+ amount: plan.amount,
376
+ expiresAt,
377
+ data: plan.data,
378
+ });
379
+ if (response.type === shared_1.PacketType.FULFILL) {
380
+ return { mode: plan.mode, ok: true };
381
+ }
382
+ const reject = response;
383
+ return { mode: plan.mode, ok: false, detail: `${reject.code}: ${reject.message ?? ''}`.trim() };
384
+ }
237
385
  async start() {
238
386
  this._logger.info({
239
387
  event: 'connector_starting',
@@ -243,6 +391,7 @@ class ConnectorNode {
243
391
  }, 'Starting connector node');
244
392
  try {
245
393
  (0, types_1.validateChainProviders)(this._config, this._logger);
394
+ this._applyMnemonicSigningMode();
246
395
  const createdProvider = this._createTransportProvider(this._config.transport);
247
396
  this._transportProvider = createdProvider;
248
397
  this._transportType =
@@ -716,6 +865,15 @@ class ConnectorNode {
716
865
  else {
717
866
  this._logger.info({ event: 'payment_channels_disabled' }, 'Payment channel infrastructure disabled (missing configuration)');
718
867
  }
868
+ const terminationChainIds = {};
869
+ for (const cp of this._config.chainProviders ?? []) {
870
+ if (cp.chainType === 'evm' && !terminationChainIds.evm) {
871
+ terminationChainIds.evm = cp.chainId;
872
+ }
873
+ else if (cp.chainType === 'solana' && !terminationChainIds.solana) {
874
+ terminationChainIds.solana = cp.chainId;
875
+ }
876
+ }
719
877
  const ilpHttpAdapter = new ilp_http_adapter_1.IlpHttpAdapter({
720
878
  logger: this._logger,
721
879
  nodeId: this._config.nodeId,
@@ -724,6 +882,8 @@ class ConnectorNode {
724
882
  recordClaim: this._claimReceiver
725
883
  ? (peerId, protocolData) => this._claimReceiver.ingestProtocolData(peerId, protocolData)
726
884
  : undefined,
885
+ resolveTermination: (prepare) => this._routeTerminationRegistry.match(prepare.destination) ?? null,
886
+ terminationChainIds,
727
887
  });
728
888
  this._btpServer.setIlpHttpHandler((req, res) => ilpHttpAdapter.handle(req, res));
729
889
  await this._btpServer.start(this._config.btpServerPort);
@@ -763,11 +923,12 @@ class ConnectorNode {
763
923
  packetSender: (params) => this.sendPacket(params),
764
924
  isReady: () => this._btpServerStarted,
765
925
  metricsRegistry: this._ilpMetrics,
766
- managedAnonClient: this._managedAnonClient ?? undefined,
767
- transportType: this._transportType ?? 'direct',
768
926
  setPeerRelation: (peerId, relation) => this._packetHandler.setPeerRelation(peerId, relation),
769
927
  getPeerRelation: (peerId) => this._packetHandler.getPeerRelation(peerId),
770
928
  registryStore: this._registryStore ?? undefined,
929
+ httpPeerEgress: this._httpPeerClientManager,
930
+ setPeerProtocol: (peerId, protocol) => this._packetHandler.setPeerProtocol(peerId, protocol),
931
+ routeTerminationRegistry: this._routeTerminationRegistry,
771
932
  });
772
933
  await this._adminServer.start();
773
934
  this._logger.info({
@@ -782,6 +943,17 @@ class ConnectorNode {
782
943
  }
783
944
  const peerConnections = [];
784
945
  for (const peerConfig of this._config.peers) {
946
+ if (peerConfig.peerProtocol === 'ilp-http') {
947
+ const httpPeer = {
948
+ id: peerConfig.id,
949
+ httpUrl: peerConfig.httpUrl,
950
+ httpPath: peerConfig.httpPath,
951
+ authToken: peerConfig.authToken,
952
+ httpTimeoutMs: peerConfig.httpTimeoutMs,
953
+ };
954
+ peerConnections.push(this._httpPeerClientManager.addPeer(httpPeer));
955
+ continue;
956
+ }
785
957
  const peer = {
786
958
  id: peerConfig.id,
787
959
  url: peerConfig.url,
@@ -829,6 +1001,7 @@ class ConnectorNode {
829
1001
  await Promise.allSettled(channelCreationPromises);
830
1002
  this._logger.info({ event: 'payment_channels_initialized' }, 'Payment channel creation completed');
831
1003
  }
1004
+ this._startSelfAnnounce();
832
1005
  this._updateHealthStatus();
833
1006
  this._logger.info({
834
1007
  event: 'connector_ready',
@@ -877,6 +1050,10 @@ class ConnectorNode {
877
1050
  nodeId: this._config.nodeId,
878
1051
  }, 'Stopping connector node');
879
1052
  try {
1053
+ if (this._selfAnnounceService) {
1054
+ this._selfAnnounceService.stop();
1055
+ this._selfAnnounceService = null;
1056
+ }
880
1057
  if (this._settlementMonitor) {
881
1058
  await this._settlementMonitor.stop();
882
1059
  this._logger.info({ event: 'settlement_monitor_stopped' }, 'Settlement monitor stopped');
@@ -987,133 +1164,19 @@ class ConnectorNode {
987
1164
  get routingTable() {
988
1165
  return this._routingTable;
989
1166
  }
1167
+ get routeTerminationRegistry() {
1168
+ return this._routeTerminationRegistry;
1169
+ }
990
1170
  get btpClientManager() {
991
1171
  return this._btpClientManager;
992
1172
  }
993
1173
  get transportProvider() {
994
1174
  return this._transportProviderReady ? this._transportProvider : null;
995
1175
  }
996
- _createTransportProvider(cfg) {
997
- if (cfg === undefined || cfg.type === 'direct') {
998
- const externalUrl = `ws://localhost:${this._config.btpServerPort}`;
999
- this._logger.debug({ event: 'direct_transport_external_url_synthesized', externalUrl }, 'DirectTransportProvider externalUrl synthesized from btpServerPort (local placeholder)');
1000
- return new transport_1.DirectTransportProvider(externalUrl);
1001
- }
1002
- if (cfg.type === 'socks5') {
1003
- let managedClient;
1004
- let externalUrl = cfg.externalUrl;
1005
- if (cfg.managed === true) {
1006
- let cachedFactory;
1007
- let prewarmError;
1008
- const prewarmPromise = (0, transport_1.createDefaultAnonFactory)().then((f) => {
1009
- cachedFactory = f;
1010
- }, (err) => {
1011
- prewarmError = err;
1012
- });
1013
- const anonFactory = (opts) => {
1014
- if (cachedFactory) {
1015
- return cachedFactory(opts);
1016
- }
1017
- if (prewarmError) {
1018
- if (prewarmError.code === 'MODULE_NOT_FOUND') {
1019
- throw prewarmError;
1020
- }
1021
- throw new Error(`Failed to load optional dependency "@anyone-protocol/anyone-client": ` +
1022
- `${prewarmError.message ?? String(prewarmError)}`, { cause: prewarmError });
1023
- }
1024
- try {
1025
- const pkg = '@anyone-protocol/anyone-client';
1026
- const mod = require(pkg);
1027
- const AnonCtor = mod?.Anon ?? mod?.default?.Anon ?? mod?.default;
1028
- if (typeof AnonCtor !== 'function') {
1029
- throw new Error('@anyone-protocol/anyone-client did not export an `Anon` constructor');
1030
- }
1031
- cachedFactory = (o) => new AnonCtor(o);
1032
- return cachedFactory(opts);
1033
- }
1034
- catch (err) {
1035
- const cause = err;
1036
- if (cause?.code === 'MODULE_NOT_FOUND') {
1037
- throw cause;
1038
- }
1039
- if (cause?.code === 'ERR_REQUIRE_ESM') {
1040
- throw new Error(`@anyone-protocol/anyone-client is an ESM-only package; ` +
1041
- `the async lazy-import pre-warm had not completed when the factory ` +
1042
- `was invoked. This is a timing bug — please file an issue.`, { cause });
1043
- }
1044
- throw new Error(`Failed to load optional dependency "@anyone-protocol/anyone-client": ` +
1045
- `${cause?.message ?? String(err)}`, { cause });
1046
- }
1047
- };
1048
- void prewarmPromise;
1049
- managedClient = new transport_1.ManagedAnonClient({
1050
- socksProxy: cfg.socksProxy,
1051
- hiddenServiceDir: cfg.managedOptions?.hiddenServiceDir,
1052
- hiddenServicePort: cfg.managedOptions?.hiddenServicePort,
1053
- controlPort: cfg.managedOptions?.controlPort,
1054
- binaryPath: cfg.managedOptions?.binaryPath,
1055
- startupTimeoutMs: cfg.managedOptions?.startupTimeoutMs,
1056
- stopTimeoutMs: cfg.managedOptions?.stopTimeoutMs,
1057
- logger: this._logger,
1058
- anonFactory,
1059
- });
1060
- this._managedAnonClient = managedClient;
1061
- if (externalUrl === 'auto') {
1062
- externalUrl = 'wss://pending.invalid/btp';
1063
- }
1064
- }
1065
- else if (externalUrl === 'auto') {
1066
- throw new Error('_createTransportProvider: transport.externalUrl "auto" requires managed: true');
1067
- }
1068
- let resolveExternalUrlOnStart;
1069
- if (cfg.externalUrl === 'auto') {
1070
- const hsDir = cfg.managedOptions?.hiddenServiceDir;
1071
- if (!hsDir) {
1072
- throw new Error('_createTransportProvider: transport.externalUrl "auto" requires managedOptions.hiddenServiceDir');
1073
- }
1074
- const hostnameReadDeadlineMs = cfg.managedOptions?.startupTimeoutMs ?? 30_000;
1075
- const HIDDEN_SERVICE_HOSTNAME_RE = /^[a-z2-7]{16}(?:[a-z2-7]{40})?\.(?:anon|onion)$/;
1076
- resolveExternalUrlOnStart = async () => {
1077
- const hostnameFile = nodePath.join(hsDir, 'hostname');
1078
- const deadline = Date.now() + hostnameReadDeadlineMs;
1079
- let lastErr;
1080
- while (Date.now() < deadline) {
1081
- try {
1082
- const raw = await fs_1.promises.readFile(hostnameFile, 'utf8');
1083
- const firstLine = raw.split(/\r?\n/, 1)[0]?.trim() ?? '';
1084
- const hostname = firstLine;
1085
- if (!hostname) {
1086
- lastErr = new Error('hostname file is empty');
1087
- }
1088
- else if (!HIDDEN_SERVICE_HOSTNAME_RE.test(hostname)) {
1089
- lastErr = new Error(`hostname file contents did not match the expected hidden service format ` +
1090
- `(length=${hostname.length}); ignoring and retrying`);
1091
- }
1092
- else {
1093
- return `wss://${hostname}/btp`;
1094
- }
1095
- }
1096
- catch (err) {
1097
- lastErr = err;
1098
- }
1099
- await new Promise((r) => setTimeout(r, 250));
1100
- }
1101
- const reason = lastErr instanceof Error ? lastErr.message : String(lastErr ?? 'unknown');
1102
- throw new Error(`hidden service hostname file "${hostnameFile}" did not become readable ` +
1103
- `within ${hostnameReadDeadlineMs}ms (last error: ${reason})`);
1104
- };
1105
- }
1106
- return new transport_1.SocksTransportProvider({
1107
- socksProxy: cfg.socksProxy,
1108
- externalUrl,
1109
- logger: this._logger,
1110
- managedClient,
1111
- resolveExternalUrlOnStart,
1112
- });
1113
- }
1114
- const _exhaustive = cfg;
1115
- throw new Error(`Unsupported transport type: ${JSON.stringify(_exhaustive)} ` +
1116
- '(Story 35.4 _createTransportProvider exhaustiveness guard)');
1176
+ _createTransportProvider(_cfg) {
1177
+ const externalUrl = `ws://localhost:${this._config.btpServerPort}`;
1178
+ this._logger.debug({ event: 'direct_transport_external_url_synthesized', externalUrl }, 'DirectTransportProvider externalUrl synthesized from btpServerPort (local placeholder)');
1179
+ return new transport_1.DirectTransportProvider(externalUrl);
1117
1180
  }
1118
1181
  get paymentChannelSDK() {
1119
1182
  return this._paymentChannelSDK;
@@ -1226,29 +1289,38 @@ class ConnectorNode {
1226
1289
  if (!this._btpServerStarted) {
1227
1290
  throw new config_loader_1.ConnectorNotStartedError('Connector is not started. Call start() before registerPeer().');
1228
1291
  }
1292
+ const peerProtocol = config.peerProtocol ?? 'btp';
1293
+ if (peerProtocol !== 'btp' && peerProtocol !== 'ilp-http') {
1294
+ throw new Error(`Invalid peerProtocol: must be 'btp' or 'ilp-http' (got '${peerProtocol}')`);
1295
+ }
1229
1296
  if (!config.id || typeof config.id !== 'string') {
1230
1297
  throw new Error('Missing or invalid peer id');
1231
1298
  }
1232
- if (!config.url || typeof config.url !== 'string') {
1233
- throw new Error('Missing or invalid peer url');
1234
- }
1235
1299
  if (config.authToken === undefined ||
1236
1300
  config.authToken === null ||
1237
1301
  typeof config.authToken !== 'string') {
1238
1302
  throw new Error('authToken must be a string (can be empty for no auth)');
1239
1303
  }
1240
- const PLAIN_WS_PREFIX = 'ws' + '://';
1241
- const SECURE_WS_PREFIX = 'wss' + '://';
1242
- if (!config.url.startsWith(PLAIN_WS_PREFIX) && !config.url.startsWith(SECURE_WS_PREFIX)) {
1243
- throw new Error(`URL must start with ${PLAIN_WS_PREFIX} or ${SECURE_WS_PREFIX}`);
1304
+ if (peerProtocol === 'ilp-http') {
1305
+ if (!config.httpUrl || typeof config.httpUrl !== 'string') {
1306
+ throw new Error("peerProtocol 'ilp-http' requires httpUrl (http(s) endpoint)");
1307
+ }
1308
+ if (!/^https?:\/\/.+/.test(config.httpUrl)) {
1309
+ throw new Error('httpUrl must start with http:// or https://');
1310
+ }
1244
1311
  }
1245
- if (config.transport !== undefined &&
1246
- config.transport !== 'direct' &&
1247
- config.transport !== 'socks5') {
1248
- throw new Error(`Invalid transport: must be 'direct' or 'socks5' (got '${config.transport}')`);
1312
+ else {
1313
+ if (!config.url || typeof config.url !== 'string') {
1314
+ throw new Error('Missing or invalid peer url');
1315
+ }
1316
+ const PLAIN_WS_PREFIX = 'ws' + '://';
1317
+ const SECURE_WS_PREFIX = 'wss' + '://';
1318
+ if (!config.url.startsWith(PLAIN_WS_PREFIX) && !config.url.startsWith(SECURE_WS_PREFIX)) {
1319
+ throw new Error(`URL must start with ${PLAIN_WS_PREFIX} or ${SECURE_WS_PREFIX}`);
1320
+ }
1249
1321
  }
1250
- if (config.transport === 'socks5' && this._transportType !== 'socks5') {
1251
- throw new Error("transport: 'socks5' requires connector-level transport.type 'socks5'");
1322
+ if (config.transport !== undefined && config.transport !== 'direct') {
1323
+ throw new Error(`Invalid transport: must be 'direct' (got '${String(config.transport)}')`);
1252
1324
  }
1253
1325
  if (config.relation !== undefined &&
1254
1326
  config.relation !== 'parent' &&
@@ -1286,33 +1358,53 @@ class ConnectorNode {
1286
1358
  throw new Error(relationValidation.error);
1287
1359
  }
1288
1360
  }
1289
- const existingPeers = this._btpClientManager.getPeerIds();
1361
+ const existingPeers = peerProtocol === 'ilp-http'
1362
+ ? this._httpPeerClientManager.getPeerIds()
1363
+ : this._btpClientManager.getPeerIds();
1290
1364
  const isUpdate = existingPeers.includes(config.id);
1291
1365
  if (!isUpdate) {
1292
- const peer = {
1293
- id: config.id,
1294
- url: config.url,
1295
- authToken: config.authToken,
1296
- connected: false,
1297
- lastSeen: new Date(),
1298
- transport: config.transport,
1299
- };
1300
- await this._btpClientManager.addPeer(peer);
1301
- this._ilpMetrics.registerPeer(config.id);
1302
- this._logger.info({
1303
- event: 'peer_registered',
1304
- peerId: config.id,
1305
- url: config.url,
1306
- transport: config.transport ?? null,
1307
- }, `Registered peer: ${config.id}`);
1366
+ if (peerProtocol === 'ilp-http') {
1367
+ const httpPeer = {
1368
+ id: config.id,
1369
+ httpUrl: config.httpUrl,
1370
+ httpPath: config.httpPath,
1371
+ authToken: config.authToken,
1372
+ httpTimeoutMs: config.httpTimeoutMs,
1373
+ };
1374
+ await this._httpPeerClientManager.addPeer(httpPeer);
1375
+ this._ilpMetrics.registerPeer(config.id);
1376
+ this._logger.info({ event: 'peer_registered', peerId: config.id, peerProtocol, httpUrl: config.httpUrl }, `Registered ILP-over-HTTP peer: ${config.id}`);
1377
+ }
1378
+ else {
1379
+ const peer = {
1380
+ id: config.id,
1381
+ url: config.url,
1382
+ authToken: config.authToken,
1383
+ connected: false,
1384
+ lastSeen: new Date(),
1385
+ transport: config.transport,
1386
+ };
1387
+ await this._btpClientManager.addPeer(peer);
1388
+ this._ilpMetrics.registerPeer(config.id);
1389
+ this._logger.info({
1390
+ event: 'peer_registered',
1391
+ peerId: config.id,
1392
+ url: config.url,
1393
+ transport: config.transport ?? null,
1394
+ }, `Registered peer: ${config.id}`);
1395
+ }
1308
1396
  }
1309
1397
  else {
1310
1398
  this._logger.info({
1311
1399
  event: 'peer_reregistered',
1312
1400
  peerId: config.id,
1313
- transport: this._btpClientManager.getPeerTransport(config.id) ?? null,
1401
+ peerProtocol,
1402
+ transport: peerProtocol === 'ilp-http'
1403
+ ? null
1404
+ : (this._btpClientManager.getPeerTransport(config.id) ?? null),
1314
1405
  }, `Re-registering peer: ${config.id}`);
1315
1406
  }
1407
+ this._packetHandler.setPeerProtocol(config.id, peerProtocol);
1316
1408
  if (effectiveRoutes) {
1317
1409
  for (const route of effectiveRoutes) {
1318
1410
  this._routingTable.addRoute(route.prefix, config.id, route.priority ?? 0);
@@ -1334,7 +1426,9 @@ class ConnectorNode {
1334
1426
  });
1335
1427
  const routes = this._routingTable.getAllRoutes();
1336
1428
  const peerRoutes = routes.filter((r) => r.nextHop === config.id);
1337
- const connected = this._btpClientManager.isConnected(config.id);
1429
+ const connected = peerProtocol === 'ilp-http'
1430
+ ? this._httpPeerClientManager.isConnected(config.id)
1431
+ : this._btpClientManager.isConnected(config.id);
1338
1432
  const peerInfo = {
1339
1433
  id: config.id,
1340
1434
  connected,
@@ -1498,11 +1592,13 @@ class ConnectorNode {
1498
1592
  });
1499
1593
  }
1500
1594
  for (const route of this._config.routes) {
1595
+ const termination = (0, types_1.toRouteTermination)(route);
1501
1596
  this._registryStore.saveRoute({
1502
1597
  prefix: route.prefix,
1503
1598
  nextHop: route.nextHop,
1504
1599
  priority: route.priority ?? 0,
1505
1600
  source: 'config',
1601
+ terminationJson: termination ? JSON.stringify(termination) : undefined,
1506
1602
  });
1507
1603
  }
1508
1604
  const existingPeerIds = new Set(this._btpClientManager.getPeerIds());
@@ -1544,6 +1640,18 @@ class ConnectorNode {
1544
1640
  nextHop: route.nextHop,
1545
1641
  priority: route.priority,
1546
1642
  });
1643
+ if (route.terminationJson) {
1644
+ try {
1645
+ this._routeTerminationRegistry.set(route.prefix, JSON.parse(route.terminationJson));
1646
+ }
1647
+ catch (parseError) {
1648
+ this._logger.warn({
1649
+ event: 'registry_route_termination_replay_failed',
1650
+ prefix: route.prefix,
1651
+ error: parseError instanceof Error ? parseError.message : String(parseError),
1652
+ }, `Failed to restore persisted route termination: ${route.prefix}`);
1653
+ }
1654
+ }
1547
1655
  replayedRoutes++;
1548
1656
  }
1549
1657
  catch (error) {