@antseed/node 0.1.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 (443) hide show
  1. package/README.md +411 -0
  2. package/contracts/AntseedEscrow.sol +310 -0
  3. package/contracts/MockUSDC.sol +64 -0
  4. package/contracts/README.md +102 -0
  5. package/dist/config/encryption.d.ts +17 -0
  6. package/dist/config/encryption.d.ts.map +1 -0
  7. package/dist/config/encryption.js +49 -0
  8. package/dist/config/encryption.js.map +1 -0
  9. package/dist/config/plugin-config-manager.d.ts +31 -0
  10. package/dist/config/plugin-config-manager.d.ts.map +1 -0
  11. package/dist/config/plugin-config-manager.js +135 -0
  12. package/dist/config/plugin-config-manager.js.map +1 -0
  13. package/dist/config/plugin-loader.d.ts +25 -0
  14. package/dist/config/plugin-loader.d.ts.map +1 -0
  15. package/dist/config/plugin-loader.js +64 -0
  16. package/dist/config/plugin-loader.js.map +1 -0
  17. package/dist/discovery/announcer.d.ts +44 -0
  18. package/dist/discovery/announcer.d.ts.map +1 -0
  19. package/dist/discovery/announcer.js +129 -0
  20. package/dist/discovery/announcer.js.map +1 -0
  21. package/dist/discovery/bootstrap.d.ts +13 -0
  22. package/dist/discovery/bootstrap.d.ts.map +1 -0
  23. package/dist/discovery/bootstrap.js +39 -0
  24. package/dist/discovery/bootstrap.js.map +1 -0
  25. package/dist/discovery/default-metadata-resolver.d.ts +16 -0
  26. package/dist/discovery/default-metadata-resolver.d.ts.map +1 -0
  27. package/dist/discovery/default-metadata-resolver.js +16 -0
  28. package/dist/discovery/default-metadata-resolver.js.map +1 -0
  29. package/dist/discovery/dht-health.d.ts +38 -0
  30. package/dist/discovery/dht-health.d.ts.map +1 -0
  31. package/dist/discovery/dht-health.js +101 -0
  32. package/dist/discovery/dht-health.js.map +1 -0
  33. package/dist/discovery/dht-node.d.ts +34 -0
  34. package/dist/discovery/dht-node.d.ts.map +1 -0
  35. package/dist/discovery/dht-node.js +168 -0
  36. package/dist/discovery/dht-node.js.map +1 -0
  37. package/dist/discovery/http-metadata-resolver.d.ts +15 -0
  38. package/dist/discovery/http-metadata-resolver.d.ts.map +1 -0
  39. package/dist/discovery/http-metadata-resolver.js +33 -0
  40. package/dist/discovery/http-metadata-resolver.js.map +1 -0
  41. package/dist/discovery/index.d.ts +16 -0
  42. package/dist/discovery/index.d.ts.map +1 -0
  43. package/dist/discovery/index.js +15 -0
  44. package/dist/discovery/index.js.map +1 -0
  45. package/dist/discovery/metadata-codec.d.ts +22 -0
  46. package/dist/discovery/metadata-codec.d.ts.map +1 -0
  47. package/dist/discovery/metadata-codec.js +390 -0
  48. package/dist/discovery/metadata-codec.js.map +1 -0
  49. package/dist/discovery/metadata-resolver.d.ts +9 -0
  50. package/dist/discovery/metadata-resolver.d.ts.map +1 -0
  51. package/dist/discovery/metadata-resolver.js +2 -0
  52. package/dist/discovery/metadata-resolver.js.map +1 -0
  53. package/dist/discovery/metadata-server.d.ts +16 -0
  54. package/dist/discovery/metadata-server.d.ts.map +1 -0
  55. package/dist/discovery/metadata-server.js +59 -0
  56. package/dist/discovery/metadata-server.js.map +1 -0
  57. package/dist/discovery/metadata-validator.d.ts +12 -0
  58. package/dist/discovery/metadata-validator.d.ts.map +1 -0
  59. package/dist/discovery/metadata-validator.js +153 -0
  60. package/dist/discovery/metadata-validator.js.map +1 -0
  61. package/dist/discovery/peer-lookup.d.ts +26 -0
  62. package/dist/discovery/peer-lookup.d.ts.map +1 -0
  63. package/dist/discovery/peer-lookup.js +86 -0
  64. package/dist/discovery/peer-lookup.js.map +1 -0
  65. package/dist/discovery/peer-metadata.d.ts +31 -0
  66. package/dist/discovery/peer-metadata.d.ts.map +1 -0
  67. package/dist/discovery/peer-metadata.js +2 -0
  68. package/dist/discovery/peer-metadata.js.map +1 -0
  69. package/dist/discovery/peer-selector.d.ts +33 -0
  70. package/dist/discovery/peer-selector.d.ts.map +1 -0
  71. package/dist/discovery/peer-selector.js +80 -0
  72. package/dist/discovery/peer-selector.js.map +1 -0
  73. package/dist/discovery/profile-manager.d.ts +50 -0
  74. package/dist/discovery/profile-manager.d.ts.map +1 -0
  75. package/dist/discovery/profile-manager.js +105 -0
  76. package/dist/discovery/profile-manager.js.map +1 -0
  77. package/dist/discovery/profile-search.d.ts +27 -0
  78. package/dist/discovery/profile-search.d.ts.map +1 -0
  79. package/dist/discovery/profile-search.js +75 -0
  80. package/dist/discovery/profile-search.js.map +1 -0
  81. package/dist/discovery/reputation-verifier.d.ts +25 -0
  82. package/dist/discovery/reputation-verifier.d.ts.map +1 -0
  83. package/dist/discovery/reputation-verifier.js +27 -0
  84. package/dist/discovery/reputation-verifier.js.map +1 -0
  85. package/dist/index.d.ts +37 -0
  86. package/dist/index.d.ts.map +1 -0
  87. package/dist/index.js +32 -0
  88. package/dist/index.js.map +1 -0
  89. package/dist/interfaces/buyer-router.d.ts +21 -0
  90. package/dist/interfaces/buyer-router.d.ts.map +1 -0
  91. package/dist/interfaces/buyer-router.js +2 -0
  92. package/dist/interfaces/buyer-router.js.map +1 -0
  93. package/dist/interfaces/plugin.d.ts +31 -0
  94. package/dist/interfaces/plugin.d.ts.map +1 -0
  95. package/dist/interfaces/plugin.js +2 -0
  96. package/dist/interfaces/plugin.js.map +1 -0
  97. package/dist/interfaces/seller-provider.d.ts +69 -0
  98. package/dist/interfaces/seller-provider.d.ts.map +1 -0
  99. package/dist/interfaces/seller-provider.js +2 -0
  100. package/dist/interfaces/seller-provider.js.map +1 -0
  101. package/dist/metering/index.d.ts +7 -0
  102. package/dist/metering/index.d.ts.map +1 -0
  103. package/dist/metering/index.js +7 -0
  104. package/dist/metering/index.js.map +1 -0
  105. package/dist/metering/receipt-generator.d.ts +49 -0
  106. package/dist/metering/receipt-generator.d.ts.map +1 -0
  107. package/dist/metering/receipt-generator.js +74 -0
  108. package/dist/metering/receipt-generator.js.map +1 -0
  109. package/dist/metering/receipt-verifier.d.ts +52 -0
  110. package/dist/metering/receipt-verifier.d.ts.map +1 -0
  111. package/dist/metering/receipt-verifier.js +63 -0
  112. package/dist/metering/receipt-verifier.js.map +1 -0
  113. package/dist/metering/session-tracker.d.ts +59 -0
  114. package/dist/metering/session-tracker.d.ts.map +1 -0
  115. package/dist/metering/session-tracker.js +119 -0
  116. package/dist/metering/session-tracker.js.map +1 -0
  117. package/dist/metering/storage.d.ts +72 -0
  118. package/dist/metering/storage.d.ts.map +1 -0
  119. package/dist/metering/storage.js +446 -0
  120. package/dist/metering/storage.js.map +1 -0
  121. package/dist/metering/token-counter.d.ts +50 -0
  122. package/dist/metering/token-counter.d.ts.map +1 -0
  123. package/dist/metering/token-counter.js +96 -0
  124. package/dist/metering/token-counter.js.map +1 -0
  125. package/dist/metering/usage-aggregator.d.ts +46 -0
  126. package/dist/metering/usage-aggregator.d.ts.map +1 -0
  127. package/dist/metering/usage-aggregator.js +170 -0
  128. package/dist/metering/usage-aggregator.js.map +1 -0
  129. package/dist/node.d.ts +179 -0
  130. package/dist/node.d.ts.map +1 -0
  131. package/dist/node.js +1328 -0
  132. package/dist/node.js.map +1 -0
  133. package/dist/p2p/connection-auth.d.ts +35 -0
  134. package/dist/p2p/connection-auth.d.ts.map +1 -0
  135. package/dist/p2p/connection-auth.js +102 -0
  136. package/dist/p2p/connection-auth.js.map +1 -0
  137. package/dist/p2p/connection-manager.d.ts +101 -0
  138. package/dist/p2p/connection-manager.d.ts.map +1 -0
  139. package/dist/p2p/connection-manager.js +726 -0
  140. package/dist/p2p/connection-manager.js.map +1 -0
  141. package/dist/p2p/handshake.d.ts +47 -0
  142. package/dist/p2p/handshake.d.ts.map +1 -0
  143. package/dist/p2p/handshake.js +107 -0
  144. package/dist/p2p/handshake.js.map +1 -0
  145. package/dist/p2p/ice-config.d.ts +27 -0
  146. package/dist/p2p/ice-config.d.ts.map +1 -0
  147. package/dist/p2p/ice-config.js +43 -0
  148. package/dist/p2p/ice-config.js.map +1 -0
  149. package/dist/p2p/identity.d.ts +27 -0
  150. package/dist/p2p/identity.d.ts.map +1 -0
  151. package/dist/p2p/identity.js +76 -0
  152. package/dist/p2p/identity.js.map +1 -0
  153. package/dist/p2p/index.d.ts +12 -0
  154. package/dist/p2p/index.d.ts.map +1 -0
  155. package/dist/p2p/index.js +11 -0
  156. package/dist/p2p/index.js.map +1 -0
  157. package/dist/p2p/keepalive.d.ts +49 -0
  158. package/dist/p2p/keepalive.d.ts.map +1 -0
  159. package/dist/p2p/keepalive.js +93 -0
  160. package/dist/p2p/keepalive.js.map +1 -0
  161. package/dist/p2p/message-protocol.d.ts +50 -0
  162. package/dist/p2p/message-protocol.d.ts.map +1 -0
  163. package/dist/p2p/message-protocol.js +134 -0
  164. package/dist/p2p/message-protocol.js.map +1 -0
  165. package/dist/p2p/nat-traversal.d.ts +51 -0
  166. package/dist/p2p/nat-traversal.d.ts.map +1 -0
  167. package/dist/p2p/nat-traversal.js +135 -0
  168. package/dist/p2p/nat-traversal.js.map +1 -0
  169. package/dist/p2p/payment-codec.d.ts +20 -0
  170. package/dist/p2p/payment-codec.d.ts.map +1 -0
  171. package/dist/p2p/payment-codec.js +130 -0
  172. package/dist/p2p/payment-codec.js.map +1 -0
  173. package/dist/p2p/payment-mux.d.ts +49 -0
  174. package/dist/p2p/payment-mux.d.ts.map +1 -0
  175. package/dist/p2p/payment-mux.js +131 -0
  176. package/dist/p2p/payment-mux.js.map +1 -0
  177. package/dist/p2p/reconnect.d.ts +48 -0
  178. package/dist/p2p/reconnect.d.ts.map +1 -0
  179. package/dist/p2p/reconnect.js +89 -0
  180. package/dist/p2p/reconnect.js.map +1 -0
  181. package/dist/payments/balance-manager.d.ts +17 -0
  182. package/dist/payments/balance-manager.d.ts.map +1 -0
  183. package/dist/payments/balance-manager.js +54 -0
  184. package/dist/payments/balance-manager.js.map +1 -0
  185. package/dist/payments/buyer-payment-manager.d.ts +122 -0
  186. package/dist/payments/buyer-payment-manager.d.ts.map +1 -0
  187. package/dist/payments/buyer-payment-manager.js +280 -0
  188. package/dist/payments/buyer-payment-manager.js.map +1 -0
  189. package/dist/payments/disputes.d.ts +18 -0
  190. package/dist/payments/disputes.d.ts.map +1 -0
  191. package/dist/payments/disputes.js +47 -0
  192. package/dist/payments/disputes.js.map +1 -0
  193. package/dist/payments/evm/escrow-client.d.ts +61 -0
  194. package/dist/payments/evm/escrow-client.d.ts.map +1 -0
  195. package/dist/payments/evm/escrow-client.js +170 -0
  196. package/dist/payments/evm/escrow-client.js.map +1 -0
  197. package/dist/payments/evm/keypair.d.ts +21 -0
  198. package/dist/payments/evm/keypair.d.ts.map +1 -0
  199. package/dist/payments/evm/keypair.js +29 -0
  200. package/dist/payments/evm/keypair.js.map +1 -0
  201. package/dist/payments/evm/signatures.d.ts +11 -0
  202. package/dist/payments/evm/signatures.d.ts.map +1 -0
  203. package/dist/payments/evm/signatures.js +56 -0
  204. package/dist/payments/evm/signatures.js.map +1 -0
  205. package/dist/payments/evm/wallet.d.ts +5 -0
  206. package/dist/payments/evm/wallet.d.ts.map +1 -0
  207. package/dist/payments/evm/wallet.js +31 -0
  208. package/dist/payments/evm/wallet.js.map +1 -0
  209. package/dist/payments/index.d.ts +13 -0
  210. package/dist/payments/index.d.ts.map +1 -0
  211. package/dist/payments/index.js +14 -0
  212. package/dist/payments/index.js.map +1 -0
  213. package/dist/payments/settlement.d.ts +6 -0
  214. package/dist/payments/settlement.d.ts.map +1 -0
  215. package/dist/payments/settlement.js +25 -0
  216. package/dist/payments/settlement.js.map +1 -0
  217. package/dist/payments/types.d.ts +66 -0
  218. package/dist/payments/types.d.ts.map +1 -0
  219. package/dist/payments/types.js +2 -0
  220. package/dist/payments/types.js.map +1 -0
  221. package/dist/proxy/index.d.ts +4 -0
  222. package/dist/proxy/index.d.ts.map +1 -0
  223. package/dist/proxy/index.js +4 -0
  224. package/dist/proxy/index.js.map +1 -0
  225. package/dist/proxy/provider-detection.d.ts +20 -0
  226. package/dist/proxy/provider-detection.d.ts.map +1 -0
  227. package/dist/proxy/provider-detection.js +61 -0
  228. package/dist/proxy/provider-detection.js.map +1 -0
  229. package/dist/proxy/proxy-mux.d.ts +35 -0
  230. package/dist/proxy/proxy-mux.d.ts.map +1 -0
  231. package/dist/proxy/proxy-mux.js +137 -0
  232. package/dist/proxy/proxy-mux.js.map +1 -0
  233. package/dist/proxy/request-codec.d.ts +33 -0
  234. package/dist/proxy/request-codec.d.ts.map +1 -0
  235. package/dist/proxy/request-codec.js +238 -0
  236. package/dist/proxy/request-codec.js.map +1 -0
  237. package/dist/reputation/index.d.ts +7 -0
  238. package/dist/reputation/index.d.ts.map +1 -0
  239. package/dist/reputation/index.js +6 -0
  240. package/dist/reputation/index.js.map +1 -0
  241. package/dist/reputation/rating-manager.d.ts +20 -0
  242. package/dist/reputation/rating-manager.d.ts.map +1 -0
  243. package/dist/reputation/rating-manager.js +91 -0
  244. package/dist/reputation/rating-manager.js.map +1 -0
  245. package/dist/reputation/report-manager.d.ts +21 -0
  246. package/dist/reputation/report-manager.d.ts.map +1 -0
  247. package/dist/reputation/report-manager.js +70 -0
  248. package/dist/reputation/report-manager.js.map +1 -0
  249. package/dist/reputation/trust-engine.d.ts +36 -0
  250. package/dist/reputation/trust-engine.d.ts.map +1 -0
  251. package/dist/reputation/trust-engine.js +95 -0
  252. package/dist/reputation/trust-engine.js.map +1 -0
  253. package/dist/reputation/trust-score.d.ts +43 -0
  254. package/dist/reputation/trust-score.d.ts.map +1 -0
  255. package/dist/reputation/trust-score.js +34 -0
  256. package/dist/reputation/trust-score.js.map +1 -0
  257. package/dist/reputation/uptime-tracker.d.ts +51 -0
  258. package/dist/reputation/uptime-tracker.d.ts.map +1 -0
  259. package/dist/reputation/uptime-tracker.js +123 -0
  260. package/dist/reputation/uptime-tracker.js.map +1 -0
  261. package/dist/routing/default-router.d.ts +21 -0
  262. package/dist/routing/default-router.d.ts.map +1 -0
  263. package/dist/routing/default-router.js +60 -0
  264. package/dist/routing/default-router.js.map +1 -0
  265. package/dist/types/buyer.d.ts +36 -0
  266. package/dist/types/buyer.d.ts.map +1 -0
  267. package/dist/types/buyer.js +2 -0
  268. package/dist/types/buyer.js.map +1 -0
  269. package/dist/types/capability.d.ts +25 -0
  270. package/dist/types/capability.d.ts.map +1 -0
  271. package/dist/types/capability.js +2 -0
  272. package/dist/types/capability.js.map +1 -0
  273. package/dist/types/connection.d.ts +27 -0
  274. package/dist/types/connection.d.ts.map +1 -0
  275. package/dist/types/connection.js +11 -0
  276. package/dist/types/connection.js.map +1 -0
  277. package/dist/types/http.d.ts +19 -0
  278. package/dist/types/http.d.ts.map +1 -0
  279. package/dist/types/http.js +2 -0
  280. package/dist/types/http.js.map +1 -0
  281. package/dist/types/index.d.ts +15 -0
  282. package/dist/types/index.d.ts.map +1 -0
  283. package/dist/types/index.js +15 -0
  284. package/dist/types/index.js.map +1 -0
  285. package/dist/types/metering.d.ts +170 -0
  286. package/dist/types/metering.d.ts.map +1 -0
  287. package/dist/types/metering.js +2 -0
  288. package/dist/types/metering.js.map +1 -0
  289. package/dist/types/peer-profile.d.ts +24 -0
  290. package/dist/types/peer-profile.d.ts.map +1 -0
  291. package/dist/types/peer-profile.js +2 -0
  292. package/dist/types/peer-profile.js.map +1 -0
  293. package/dist/types/peer.d.ts +56 -0
  294. package/dist/types/peer.d.ts.map +1 -0
  295. package/dist/types/peer.js +11 -0
  296. package/dist/types/peer.js.map +1 -0
  297. package/dist/types/plugin-config.d.ts +31 -0
  298. package/dist/types/plugin-config.d.ts.map +1 -0
  299. package/dist/types/plugin-config.js +2 -0
  300. package/dist/types/plugin-config.js.map +1 -0
  301. package/dist/types/protocol.d.ts +141 -0
  302. package/dist/types/protocol.d.ts.map +1 -0
  303. package/dist/types/protocol.js +42 -0
  304. package/dist/types/protocol.js.map +1 -0
  305. package/dist/types/provider.d.ts +38 -0
  306. package/dist/types/provider.d.ts.map +1 -0
  307. package/dist/types/provider.js +11 -0
  308. package/dist/types/provider.js.map +1 -0
  309. package/dist/types/rating.d.ts +21 -0
  310. package/dist/types/rating.d.ts.map +1 -0
  311. package/dist/types/rating.js +2 -0
  312. package/dist/types/rating.js.map +1 -0
  313. package/dist/types/report.d.ts +20 -0
  314. package/dist/types/report.d.ts.map +1 -0
  315. package/dist/types/report.js +2 -0
  316. package/dist/types/report.js.map +1 -0
  317. package/dist/types/seller.d.ts +36 -0
  318. package/dist/types/seller.d.ts.map +1 -0
  319. package/dist/types/seller.js +2 -0
  320. package/dist/types/seller.js.map +1 -0
  321. package/dist/types/staking.d.ts +16 -0
  322. package/dist/types/staking.d.ts.map +1 -0
  323. package/dist/types/staking.js +6 -0
  324. package/dist/types/staking.js.map +1 -0
  325. package/dist/utils/debug.d.ts +4 -0
  326. package/dist/utils/debug.d.ts.map +1 -0
  327. package/dist/utils/debug.js +25 -0
  328. package/dist/utils/debug.js.map +1 -0
  329. package/dist/utils/hex.d.ts +3 -0
  330. package/dist/utils/hex.d.ts.map +1 -0
  331. package/dist/utils/hex.js +15 -0
  332. package/dist/utils/hex.js.map +1 -0
  333. package/package.json +62 -0
  334. package/scripts/ensure-node-native-modules.mjs +153 -0
  335. package/scripts/patch-ethers.js +44 -0
  336. package/src/config/encryption.test.ts +49 -0
  337. package/src/config/encryption.ts +53 -0
  338. package/src/config/plugin-config-manager.test.ts +92 -0
  339. package/src/config/plugin-config-manager.ts +153 -0
  340. package/src/config/plugin-loader.ts +90 -0
  341. package/src/discovery/announcer.ts +169 -0
  342. package/src/discovery/bootstrap.ts +57 -0
  343. package/src/discovery/default-metadata-resolver.ts +18 -0
  344. package/src/discovery/dht-health.ts +136 -0
  345. package/src/discovery/dht-node.ts +191 -0
  346. package/src/discovery/http-metadata-resolver.ts +47 -0
  347. package/src/discovery/index.ts +15 -0
  348. package/src/discovery/metadata-codec.ts +453 -0
  349. package/src/discovery/metadata-resolver.ts +7 -0
  350. package/src/discovery/metadata-server.ts +73 -0
  351. package/src/discovery/metadata-validator.ts +172 -0
  352. package/src/discovery/peer-lookup.ts +122 -0
  353. package/src/discovery/peer-metadata.ts +34 -0
  354. package/src/discovery/peer-selector.ts +134 -0
  355. package/src/discovery/profile-manager.ts +131 -0
  356. package/src/discovery/profile-search.ts +100 -0
  357. package/src/discovery/reputation-verifier.ts +54 -0
  358. package/src/index.ts +61 -0
  359. package/src/interfaces/buyer-router.ts +21 -0
  360. package/src/interfaces/plugin.ts +36 -0
  361. package/src/interfaces/seller-provider.ts +81 -0
  362. package/src/metering/index.ts +6 -0
  363. package/src/metering/receipt-generator.ts +105 -0
  364. package/src/metering/receipt-verifier.ts +102 -0
  365. package/src/metering/session-tracker.ts +145 -0
  366. package/src/metering/storage.ts +600 -0
  367. package/src/metering/token-counter.ts +127 -0
  368. package/src/metering/usage-aggregator.ts +236 -0
  369. package/src/node.ts +1698 -0
  370. package/src/p2p/connection-auth.ts +152 -0
  371. package/src/p2p/connection-manager.ts +916 -0
  372. package/src/p2p/handshake.ts +162 -0
  373. package/src/p2p/ice-config.ts +59 -0
  374. package/src/p2p/identity.ts +110 -0
  375. package/src/p2p/index.ts +11 -0
  376. package/src/p2p/keepalive.ts +118 -0
  377. package/src/p2p/message-protocol.ts +171 -0
  378. package/src/p2p/nat-traversal.ts +169 -0
  379. package/src/p2p/payment-codec.ts +165 -0
  380. package/src/p2p/payment-mux.ts +153 -0
  381. package/src/p2p/reconnect.ts +117 -0
  382. package/src/payments/balance-manager.ts +77 -0
  383. package/src/payments/buyer-payment-manager.ts +414 -0
  384. package/src/payments/disputes.ts +72 -0
  385. package/src/payments/evm/escrow-client.ts +263 -0
  386. package/src/payments/evm/keypair.ts +31 -0
  387. package/src/payments/evm/signatures.ts +103 -0
  388. package/src/payments/evm/wallet.ts +42 -0
  389. package/src/payments/index.ts +50 -0
  390. package/src/payments/settlement.ts +40 -0
  391. package/src/payments/types.ts +79 -0
  392. package/src/proxy/index.ts +3 -0
  393. package/src/proxy/provider-detection.ts +78 -0
  394. package/src/proxy/proxy-mux.ts +173 -0
  395. package/src/proxy/request-codec.ts +294 -0
  396. package/src/reputation/index.ts +6 -0
  397. package/src/reputation/rating-manager.ts +118 -0
  398. package/src/reputation/report-manager.ts +91 -0
  399. package/src/reputation/trust-engine.ts +120 -0
  400. package/src/reputation/trust-score.ts +74 -0
  401. package/src/reputation/uptime-tracker.ts +155 -0
  402. package/src/routing/default-router.ts +75 -0
  403. package/src/types/bittorrent-dht.d.ts +19 -0
  404. package/src/types/buyer.ts +37 -0
  405. package/src/types/capability.ts +34 -0
  406. package/src/types/connection.ts +29 -0
  407. package/src/types/http.ts +20 -0
  408. package/src/types/index.ts +14 -0
  409. package/src/types/metering.ts +175 -0
  410. package/src/types/nat-api.d.ts +29 -0
  411. package/src/types/peer-profile.ts +25 -0
  412. package/src/types/peer.ts +62 -0
  413. package/src/types/plugin-config.ts +31 -0
  414. package/src/types/protocol.ts +162 -0
  415. package/src/types/provider.ts +40 -0
  416. package/src/types/rating.ts +23 -0
  417. package/src/types/report.ts +30 -0
  418. package/src/types/seller.ts +38 -0
  419. package/src/types/staking.ts +23 -0
  420. package/src/utils/debug.ts +30 -0
  421. package/src/utils/hex.ts +14 -0
  422. package/tests/balance-manager.test.ts +156 -0
  423. package/tests/bootstrap.test.ts +108 -0
  424. package/tests/buyer-payment-manager.test.ts +358 -0
  425. package/tests/connection-auth.test.ts +87 -0
  426. package/tests/default-router.test.ts +148 -0
  427. package/tests/evm-keypair.test.ts +173 -0
  428. package/tests/identity.test.ts +133 -0
  429. package/tests/message-protocol.test.ts +212 -0
  430. package/tests/metadata-codec.test.ts +165 -0
  431. package/tests/metadata-validator.test.ts +261 -0
  432. package/tests/metering-storage.test.ts +244 -0
  433. package/tests/payment-codec.test.ts +95 -0
  434. package/tests/payment-mux.test.ts +191 -0
  435. package/tests/peer-selector.test.ts +184 -0
  436. package/tests/provider-detection.test.ts +107 -0
  437. package/tests/proxy-mux-security.test.ts +38 -0
  438. package/tests/receipt.test.ts +215 -0
  439. package/tests/reputation-integration.test.ts +195 -0
  440. package/tests/request-codec.test.ts +144 -0
  441. package/tests/token-counter.test.ts +122 -0
  442. package/tsconfig.json +9 -0
  443. package/vitest.config.ts +7 -0
package/dist/node.js ADDED
@@ -0,0 +1,1328 @@
1
+ import { EventEmitter } from "node:events";
2
+ import { createHash, randomUUID } from "node:crypto";
3
+ import { homedir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { loadOrCreateIdentity } from "./p2p/identity.js";
6
+ import { MeteringStorage } from "./metering/storage.js";
7
+ import { ReceiptGenerator } from "./metering/receipt-generator.js";
8
+ import { ConnectionState } from "./types/connection.js";
9
+ import { DHTNode, DEFAULT_DHT_CONFIG, } from "./discovery/dht-node.js";
10
+ import { toBootstrapConfig, OFFICIAL_BOOTSTRAP_NODES } from "./discovery/bootstrap.js";
11
+ import { ConnectionManager, } from "./p2p/connection-manager.js";
12
+ import { PeerAnnouncer, } from "./discovery/announcer.js";
13
+ import { PeerLookup, DEFAULT_LOOKUP_CONFIG, } from "./discovery/peer-lookup.js";
14
+ import { HttpMetadataResolver } from "./discovery/http-metadata-resolver.js";
15
+ import { ProxyMux } from "./proxy/proxy-mux.js";
16
+ import { PaymentMux } from "./p2p/payment-mux.js";
17
+ import { FrameDecoder } from "./p2p/message-protocol.js";
18
+ import { NatTraversal } from "./p2p/nat-traversal.js";
19
+ import { signUtf8Ed25519 } from "./p2p/identity.js";
20
+ import { verifyMessage, getBytes } from "ethers";
21
+ import { BalanceManager, BaseEscrowClient, identityToEvmWallet, buildLockMessageHash, buildReceiptMessage, buildAckMessage, signMessageEd25519, verifyMessageEd25519, } from "./payments/index.js";
22
+ import { hexToBytes, bytesToHex } from "./utils/hex.js";
23
+ import { debugLog, debugWarn } from "./utils/debug.js";
24
+ import { BuyerPaymentManager } from "./payments/buyer-payment-manager.js";
25
+ import { identityToEvmAddress } from "./payments/evm/keypair.js";
26
+ export class AntseedNode extends EventEmitter {
27
+ _config;
28
+ _identity = null;
29
+ _dht = null;
30
+ _connectionManager = null;
31
+ _providers = [];
32
+ _router = null;
33
+ _started = false;
34
+ _announcer = null;
35
+ _peerLookup = null;
36
+ _muxes = new Map();
37
+ _decoders = new Map();
38
+ _nat = null;
39
+ _metering = null;
40
+ _receiptGenerator = null;
41
+ _balanceManager = null;
42
+ _escrowClient = null;
43
+ _paymentMuxes = new Map();
44
+ /** Per-buyer session tracking: buyerPeerId → seller session state */
45
+ _sessions = new Map();
46
+ _settlementTimers = new Map();
47
+ /** Buyer-side payment manager (initialized when buyer has payment config). */
48
+ _buyerPaymentManager = null;
49
+ /** Tracks which seller peers the buyer has already initiated a lock for. */
50
+ _buyerLockedPeers = new Set();
51
+ constructor(config) {
52
+ super();
53
+ this._config = config;
54
+ }
55
+ get peerId() {
56
+ return this._identity?.peerId ?? null;
57
+ }
58
+ get identity() {
59
+ return this._identity;
60
+ }
61
+ registerProvider(provider) {
62
+ this._providers.push(provider);
63
+ }
64
+ setRouter(router) {
65
+ this._router = router;
66
+ }
67
+ get router() {
68
+ return this._router;
69
+ }
70
+ /** Buyer-side payment manager (null if payments not enabled or not in buyer mode). */
71
+ get buyerPaymentManager() {
72
+ return this._buyerPaymentManager;
73
+ }
74
+ /** Actual DHT port after binding (0 means not started). */
75
+ get dhtPort() {
76
+ return this._dht?.getPort() ?? 0;
77
+ }
78
+ /** Actual signaling/connection port after binding (0 means not started). */
79
+ get signalingPort() {
80
+ return this._connectionManager?.getListeningPort() ?? 0;
81
+ }
82
+ /**
83
+ * Active seller sessions currently tracked in-memory.
84
+ * Includes open sessions before they are finalized/settled.
85
+ */
86
+ getActiveSellerSessions() {
87
+ const snapshots = [];
88
+ for (const [buyerPeerId, session] of this._sessions.entries()) {
89
+ snapshots.push({
90
+ sessionId: session.sessionId,
91
+ buyerPeerId,
92
+ provider: session.provider,
93
+ startedAt: session.startedAt,
94
+ lastActivityAt: session.lastActivityAt,
95
+ totalRequests: session.totalRequests,
96
+ totalTokens: session.totalTokens,
97
+ avgLatencyMs: session.totalRequests > 0 ? session.totalLatencyMs / session.totalRequests : 0,
98
+ settling: Boolean(session.settling),
99
+ lockCommitted: session.lockCommitted,
100
+ lockedAmountUSDC: session.lockedAmount.toString(),
101
+ runningTotalUSDC: session.runningTotal.toString(),
102
+ ackedRequestCount: session.ackedRequestCount,
103
+ });
104
+ }
105
+ return snapshots;
106
+ }
107
+ /** Number of active in-memory seller sessions that are not currently settling. */
108
+ getActiveSellerSessionCount() {
109
+ let count = 0;
110
+ for (const session of this._sessions.values()) {
111
+ if (!session.settling) {
112
+ count += 1;
113
+ }
114
+ }
115
+ return count;
116
+ }
117
+ async start() {
118
+ if (this._started) {
119
+ throw new Error("Node already started");
120
+ }
121
+ const dataDir = this._config.dataDir ?? join(homedir(), ".antseed");
122
+ // Load or create identity
123
+ this._identity = await loadOrCreateIdentity(dataDir);
124
+ debugLog(`[Node] Identity loaded: ${this._identity.peerId.slice(0, 12)}...`);
125
+ // Determine bootstrap nodes
126
+ const bootstrapNodes = this._config.bootstrapNodes ?? toBootstrapConfig(OFFICIAL_BOOTSTRAP_NODES);
127
+ debugLog(`[Node] Starting as ${this._config.role} with ${bootstrapNodes.length} bootstrap node(s)`);
128
+ if (this._config.role === "seller") {
129
+ await this._startSeller(bootstrapNodes);
130
+ }
131
+ else {
132
+ await this._startBuyer(bootstrapNodes);
133
+ }
134
+ this._started = true;
135
+ debugLog(`[Node] Started successfully`);
136
+ this.emit("started");
137
+ }
138
+ async stop() {
139
+ if (!this._started) {
140
+ return;
141
+ }
142
+ // End all active buyer payment sessions before shutdown
143
+ await this._endAllBuyerSessions();
144
+ await this._finalizeAllSessions("node-stop");
145
+ for (const timer of this._settlementTimers.values()) {
146
+ clearTimeout(timer);
147
+ }
148
+ this._settlementTimers.clear();
149
+ // Remove NAT port mappings
150
+ if (this._nat) {
151
+ await this._nat.cleanup();
152
+ this._nat = null;
153
+ }
154
+ // Stop announcer
155
+ if (this._announcer) {
156
+ this._announcer.stopPeriodicAnnounce();
157
+ this._announcer = null;
158
+ }
159
+ // Close all proxy muxes
160
+ this._muxes.clear();
161
+ this._paymentMuxes.clear();
162
+ this._decoders.clear();
163
+ // Close all connections
164
+ if (this._connectionManager) {
165
+ this._connectionManager.closeAll();
166
+ this._connectionManager = null;
167
+ }
168
+ // Stop DHT
169
+ if (this._dht) {
170
+ await this._dht.stop();
171
+ this._dht = null;
172
+ }
173
+ if (this._balanceManager) {
174
+ try {
175
+ const dataDir = this._config.dataDir ?? join(homedir(), ".antseed");
176
+ await this._balanceManager.save(join(dataDir, "payments"));
177
+ }
178
+ catch (err) {
179
+ debugWarn(`[Node] Failed to persist payment balances: ${err instanceof Error ? err.message : err}`);
180
+ }
181
+ }
182
+ if (this._metering) {
183
+ try {
184
+ this._metering.close();
185
+ }
186
+ catch {
187
+ // ignore close errors
188
+ }
189
+ this._metering = null;
190
+ }
191
+ this._peerLookup = null;
192
+ this._receiptGenerator = null;
193
+ this._balanceManager = null;
194
+ this._escrowClient = null;
195
+ this._buyerPaymentManager = null;
196
+ this._buyerLockedPeers.clear();
197
+ this._started = false;
198
+ this.emit("stopped");
199
+ }
200
+ async discoverPeers(model) {
201
+ if (!this._peerLookup) {
202
+ throw new Error("Node not started or not in buyer mode");
203
+ }
204
+ // If a model is specified, search by model across known provider topics.
205
+ // Otherwise search for a generic topic. The PeerLookup uses provider names,
206
+ // so without a model we search broadly.
207
+ const searchTerm = model ?? "*";
208
+ debugLog(`[Node] Discovering peers (search: "${searchTerm}")...`);
209
+ const results = await this._peerLookup.findSellers(searchTerm);
210
+ debugLog(`[Node] DHT returned ${results.length} result(s)`);
211
+ // Deduplicate by peerId (DHT can return the same peer from multiple topic lookups)
212
+ const seen = new Set();
213
+ const peers = [];
214
+ for (const r of results) {
215
+ const p = this._lookupResultToPeerInfo(r);
216
+ if (!seen.has(p.peerId)) {
217
+ seen.add(p.peerId);
218
+ peers.push(p);
219
+ }
220
+ }
221
+ // Optional reputation verification: replace claimed data with verified on-chain data
222
+ if (this._escrowClient) {
223
+ for (const p of peers) {
224
+ if (p.evmAddress && p.onChainReputation !== undefined) {
225
+ try {
226
+ const rep = await this._escrowClient.getReputation(p.evmAddress);
227
+ p.onChainReputation = rep.weightedAverage;
228
+ p.onChainSessionCount = rep.sessionCount;
229
+ p.onChainDisputeCount = rep.disputeCount;
230
+ p.trustScore = rep.weightedAverage;
231
+ }
232
+ catch {
233
+ // Use claimed data if verification fails
234
+ }
235
+ }
236
+ }
237
+ }
238
+ for (const p of peers) {
239
+ debugLog(`[Node] peer ${p.peerId.slice(0, 12)}... providers=[${p.providers.join(",")}] addr=${p.publicAddress ?? "?"}`);
240
+ }
241
+ return peers;
242
+ }
243
+ async sendRequest(peer, req) {
244
+ if (!req.requestId || typeof req.requestId !== "string") {
245
+ throw new Error("requestId must be a non-empty string");
246
+ }
247
+ if (!this._connectionManager || !this._identity) {
248
+ throw new Error("Node not started");
249
+ }
250
+ debugLog(`[Node] sendRequest ${req.method} ${req.path} → peer ${peer.peerId.slice(0, 12)}... (reqId=${req.requestId.slice(0, 8)})`);
251
+ const conn = await this._getOrCreateConnection(peer);
252
+ debugLog(`[Node] Connection to ${peer.peerId.slice(0, 12)}... state=${conn.state}`);
253
+ const mux = this._getOrCreateMux(peer.peerId, conn);
254
+ // Buyer-side: initiate lock and wait for confirmation on first request to a new peer
255
+ if (this._buyerPaymentManager && !this._buyerLockedPeers.has(peer.peerId)) {
256
+ await this._initiateBuyerLock(peer, conn);
257
+ }
258
+ const startTime = Date.now();
259
+ return new Promise((resolve, reject) => {
260
+ const timeoutMs = this._config.requestTimeoutMs ?? 30_000;
261
+ const timeout = setTimeout(() => {
262
+ debugWarn(`[Node] Request ${req.requestId.slice(0, 8)} timed out after ${timeoutMs}ms`);
263
+ mux.cancelProxyRequest(req.requestId);
264
+ reject(new Error(`Request ${req.requestId} timed out`));
265
+ }, timeoutMs);
266
+ mux.sendProxyRequest(req, (response) => {
267
+ clearTimeout(timeout);
268
+ debugLog(`[Node] Response for ${req.requestId.slice(0, 8)}: status=${response.statusCode} (${Date.now() - startTime}ms, ${response.body.length}b)`);
269
+ resolve(response);
270
+ }, (_chunk) => {
271
+ // Chunks are handled by the response handler for now
272
+ });
273
+ });
274
+ }
275
+ async *sendTask(peer, task) {
276
+ const req = {
277
+ requestId: task.taskId,
278
+ method: "POST",
279
+ path: "/v1/task",
280
+ headers: {
281
+ "content-type": "application/json",
282
+ "x-antseed-capability": "task",
283
+ },
284
+ body: new TextEncoder().encode(JSON.stringify(task)),
285
+ };
286
+ const response = await this.sendRequest(peer, req);
287
+ const bodyText = new TextDecoder().decode(response.body);
288
+ const parsed = JSON.parse(bodyText);
289
+ if (Array.isArray(parsed)) {
290
+ for (const event of parsed) {
291
+ yield event;
292
+ }
293
+ }
294
+ else {
295
+ yield parsed;
296
+ }
297
+ }
298
+ async sendSkill(peer, skill) {
299
+ const req = {
300
+ requestId: skill.skillId,
301
+ method: "POST",
302
+ path: "/v1/skill",
303
+ headers: {
304
+ "content-type": "application/json",
305
+ "x-antseed-capability": "skill",
306
+ },
307
+ body: new TextEncoder().encode(JSON.stringify(skill)),
308
+ };
309
+ const response = await this.sendRequest(peer, req);
310
+ const bodyText = new TextDecoder().decode(response.body);
311
+ return JSON.parse(bodyText);
312
+ }
313
+ _createDHTConfig(port, bootstrapNodes) {
314
+ return {
315
+ peerId: this._identity.peerId,
316
+ port,
317
+ bootstrapNodes,
318
+ reannounceIntervalMs: DEFAULT_DHT_CONFIG.reannounceIntervalMs,
319
+ operationTimeoutMs: DEFAULT_DHT_CONFIG.operationTimeoutMs,
320
+ allowPrivateIPs: this._config.allowPrivateIPs,
321
+ };
322
+ }
323
+ _wireConnection(conn, peerId) {
324
+ const decoder = new FrameDecoder();
325
+ conn.on("message", (data) => {
326
+ const frames = decoder.feed(data);
327
+ const proxyMux = this._muxes.get(peerId);
328
+ const paymentMux = this._paymentMuxes.get(peerId);
329
+ for (const frame of frames) {
330
+ if (paymentMux && PaymentMux.isPaymentMessage(frame.type)) {
331
+ paymentMux.handleFrame(frame).catch((err) => {
332
+ const message = err instanceof Error ? err.message : String(err);
333
+ debugWarn(`[Node] Failed to handle payment frame from ${peerId.slice(0, 12)}...: ${message}`);
334
+ });
335
+ }
336
+ else if (proxyMux) {
337
+ proxyMux.handleFrame(frame).catch((err) => {
338
+ const message = err instanceof Error ? err.message : String(err);
339
+ debugWarn(`[Node] Failed to handle frame from ${peerId.slice(0, 12)}...: ${message}`);
340
+ conn.fail(err instanceof Error ? err : new Error(message));
341
+ });
342
+ }
343
+ }
344
+ });
345
+ this._decoders.set(peerId, decoder);
346
+ conn.on("stateChange", (state) => {
347
+ if (state === ConnectionState.Closed || state === ConnectionState.Failed) {
348
+ this._muxes.delete(peerId);
349
+ this._paymentMuxes.delete(peerId);
350
+ this._decoders.delete(peerId);
351
+ // Handle buyer disconnect (ghost scenario)
352
+ void this._finalizeSession(peerId, "disconnect");
353
+ }
354
+ });
355
+ }
356
+ async _startSeller(bootstrapNodes) {
357
+ const identity = this._identity;
358
+ const dhtPort = this._config.dhtPort ?? 6881;
359
+ const signalingPort = this._config.signalingPort ?? 6882;
360
+ debugLog(`[Node] Starting seller — DHT port=${dhtPort}, signaling port=${signalingPort}`);
361
+ // Initialize metering storage
362
+ const dataDir = this._config.dataDir ?? join(homedir(), ".antseed");
363
+ try {
364
+ this._metering = new MeteringStorage(join(dataDir, "metering.db"));
365
+ debugLog("[Node] Metering storage initialized");
366
+ }
367
+ catch (err) {
368
+ debugWarn(`[Node] Metering storage unavailable: ${err instanceof Error ? err.message : err}`);
369
+ }
370
+ if (this._metering) {
371
+ this._receiptGenerator = new ReceiptGenerator({
372
+ peerId: identity.peerId,
373
+ sign: (message) => signUtf8Ed25519(identity.privateKey, message),
374
+ });
375
+ }
376
+ await this._initializePayments(dataDir);
377
+ // Start DHT
378
+ this._dht = new DHTNode(this._createDHTConfig(dhtPort, bootstrapNodes));
379
+ await this._dht.start();
380
+ // Create ConnectionManager and start listening
381
+ this._connectionManager = new ConnectionManager();
382
+ this._connectionManager.setLocalIdentity(identity);
383
+ await this._connectionManager.startListening({
384
+ peerId: identity.peerId,
385
+ port: signalingPort,
386
+ host: "0.0.0.0",
387
+ });
388
+ // Resolve actual bound port (important when port 0 is used for OS-assigned)
389
+ const actualSignalingPort = this._connectionManager.getListeningPort() ?? signalingPort;
390
+ const actualDhtPort = this._dht.getPort();
391
+ // NAT traversal: automatically map ports via UPnP/NAT-PMP
392
+ this._nat = new NatTraversal();
393
+ const natResult = await this._nat.mapPorts([
394
+ { port: actualSignalingPort, protocol: "TCP" },
395
+ { port: actualDhtPort, protocol: "UDP" },
396
+ ]);
397
+ if (natResult.success) {
398
+ this.emit("nat:mapped", natResult);
399
+ }
400
+ else {
401
+ debugWarn("[NAT] UPnP/NAT-PMP mapping failed — seller may not be reachable from the internet");
402
+ debugWarn("[NAT] Ensure port forwarding is configured manually, or peers on the same LAN can still connect");
403
+ this.emit("nat:failed");
404
+ }
405
+ // Set up announcer for providers
406
+ if (this._providers.length > 0) {
407
+ const announcerConfig = {
408
+ identity,
409
+ dht: this._dht,
410
+ providers: this._providers.map((p) => ({
411
+ provider: p.name,
412
+ models: p.models,
413
+ maxConcurrency: p.maxConcurrency,
414
+ })),
415
+ region: "unknown",
416
+ pricing: new Map(this._providers.map((p) => [
417
+ p.name,
418
+ {
419
+ defaults: {
420
+ inputUsdPerMillion: p.pricing.defaults.inputUsdPerMillion,
421
+ outputUsdPerMillion: p.pricing.defaults.outputUsdPerMillion,
422
+ },
423
+ ...(p.pricing.models ? { models: { ...p.pricing.models } } : {}),
424
+ },
425
+ ])),
426
+ reannounceIntervalMs: DEFAULT_DHT_CONFIG.reannounceIntervalMs,
427
+ signalingPort: actualSignalingPort,
428
+ };
429
+ this._announcer = new PeerAnnouncer(announcerConfig);
430
+ this._announcer.startPeriodicAnnounce();
431
+ // Serve metadata on the signaling port (HTTP requests are auto-detected)
432
+ this._connectionManager.setMetadataProvider(() => this._announcer?.getLatestMetadata() ?? null);
433
+ }
434
+ // Listen for incoming connections
435
+ this._connectionManager.on("connection", (conn) => {
436
+ this._handleIncomingConnection(conn);
437
+ });
438
+ debugLog(`[Node] Seller ready — announcing ${this._providers.length} provider(s)`);
439
+ }
440
+ async _startBuyer(bootstrapNodes) {
441
+ const identity = this._identity;
442
+ const dhtPort = this._config.dhtPort ?? 0;
443
+ debugLog(`[Node] Starting buyer — DHT port=${dhtPort}`);
444
+ // Start DHT with ephemeral port
445
+ this._dht = new DHTNode(this._createDHTConfig(dhtPort, bootstrapNodes));
446
+ await this._dht.start();
447
+ // Create ConnectionManager for outbound connections
448
+ this._connectionManager = new ConnectionManager();
449
+ this._connectionManager.setLocalIdentity(identity);
450
+ // Create PeerLookup with HttpMetadataResolver
451
+ const metadataResolver = new HttpMetadataResolver();
452
+ const lookupConfig = {
453
+ dht: this._dht,
454
+ metadataResolver,
455
+ requireValidSignature: DEFAULT_LOOKUP_CONFIG.requireValidSignature,
456
+ allowStaleMetadata: DEFAULT_LOOKUP_CONFIG.allowStaleMetadata,
457
+ maxAnnouncementAgeMs: DEFAULT_LOOKUP_CONFIG.maxAnnouncementAgeMs,
458
+ maxResults: DEFAULT_LOOKUP_CONFIG.maxResults,
459
+ };
460
+ this._peerLookup = new PeerLookup(lookupConfig);
461
+ // Initialize buyer-side payment manager if payments config is provided
462
+ const payments = this._config.payments;
463
+ if (payments?.enabled && payments.rpcUrl && payments.contractAddress && payments.usdcAddress) {
464
+ const buyerPaymentConfig = {
465
+ defaultLockAmountUSDC: payments.defaultEscrowAmountUSDC ?? "1000000",
466
+ rpcUrl: payments.rpcUrl,
467
+ contractAddress: payments.contractAddress,
468
+ usdcAddress: payments.usdcAddress,
469
+ };
470
+ this._buyerPaymentManager = new BuyerPaymentManager(identity, buyerPaymentConfig);
471
+ debugLog(`[Node] Buyer payment manager initialized (wallet=${identityToEvmAddress(identity).slice(0, 10)}...)`);
472
+ }
473
+ debugLog(`[Node] Buyer ready — DHT running on port ${this._dht.getPort()}`);
474
+ }
475
+ _handleIncomingConnection(conn) {
476
+ debugLog(`[Node] Incoming connection from ${conn.remotePeerId.slice(0, 12)}...`);
477
+ const buyerPeerId = conn.remotePeerId;
478
+ const mux = new ProxyMux(conn);
479
+ // Create PaymentMux alongside ProxyMux (seller-side)
480
+ const paymentMux = new PaymentMux(conn);
481
+ paymentMux.onSessionLockAuth((payload) => {
482
+ void this._handleSessionLockAuth(buyerPeerId, payload, paymentMux);
483
+ });
484
+ paymentMux.onBuyerAck((payload) => {
485
+ void this._handleBuyerAck(buyerPeerId, payload);
486
+ });
487
+ paymentMux.onSessionEnd((payload) => {
488
+ void this._handleSessionEnd(buyerPeerId, payload);
489
+ });
490
+ paymentMux.onTopUpAuth((payload) => {
491
+ void this._handleTopUpAuth(buyerPeerId, payload);
492
+ });
493
+ this._paymentMuxes.set(buyerPeerId, paymentMux);
494
+ // Register the ProxyMux request handler that routes to providers
495
+ mux.onProxyRequest(async (request) => {
496
+ debugLog(`[Node] Seller received request: ${request.method} ${request.path} (reqId=${request.requestId.slice(0, 8)})`);
497
+ // Reject with 402 if lock not committed and escrow client is configured
498
+ const session = this._sessions.get(buyerPeerId);
499
+ if (this._escrowClient && (!session || !session.lockCommitted)) {
500
+ debugWarn(`[Node] Rejecting request from ${buyerPeerId.slice(0, 12)}... — lock not committed`);
501
+ mux.sendProxyResponse({
502
+ requestId: request.requestId,
503
+ statusCode: 402,
504
+ headers: { "content-type": "text/plain" },
505
+ body: new TextEncoder().encode("Payment required: session lock not committed"),
506
+ });
507
+ return;
508
+ }
509
+ const provider = this._providers.find((p) => p.models.some((m) => request.path.includes(m)) || this._providers.length === 1);
510
+ if (!provider) {
511
+ debugWarn(`[Node] No matching provider for ${request.path}`);
512
+ mux.sendProxyResponse({
513
+ requestId: request.requestId,
514
+ statusCode: 502,
515
+ headers: { "content-type": "text/plain" },
516
+ body: new TextEncoder().encode("No matching provider"),
517
+ });
518
+ return;
519
+ }
520
+ // Track active seller session at request start so runtime state reflects
521
+ // in-flight work immediately (not only after metering persistence).
522
+ this._getOrCreateSellerSession(buyerPeerId, provider.name);
523
+ debugLog(`[Node] Routing to provider "${provider.name}"`);
524
+ const startTime = Date.now();
525
+ let statusCode = 500;
526
+ let responseBody = new Uint8Array(0);
527
+ try {
528
+ const response = await this._executeProviderRequest(provider, request);
529
+ statusCode = response.statusCode;
530
+ responseBody = response.body;
531
+ debugLog(`[Node] Provider responded: status=${statusCode} (${Date.now() - startTime}ms, ${responseBody.length}b)`);
532
+ mux.sendProxyResponse(response);
533
+ }
534
+ catch (err) {
535
+ const message = err instanceof Error ? err.message : "Internal error";
536
+ debugWarn(`[Node] Provider error after ${Date.now() - startTime}ms: ${message}`);
537
+ responseBody = new TextEncoder().encode(message);
538
+ mux.sendProxyResponse({
539
+ requestId: request.requestId,
540
+ statusCode: 500,
541
+ headers: { "content-type": "text/plain" },
542
+ body: responseBody,
543
+ });
544
+ }
545
+ // Record metering
546
+ const latencyMs = Date.now() - startTime;
547
+ const requestPricing = this._resolveProviderPricing(provider, request);
548
+ await this._recordMetering(buyerPeerId, provider.name, requestPricing, request, statusCode, latencyMs, request.body.length, responseBody.length);
549
+ // Generate bilateral receipt after each request if lock committed (Task 3)
550
+ const currentSession = this._sessions.get(buyerPeerId);
551
+ if (currentSession?.lockCommitted) {
552
+ await this._sendBilateralReceipt(buyerPeerId, currentSession, requestPricing, responseBody, paymentMux);
553
+ }
554
+ });
555
+ this._muxes.set(buyerPeerId, mux);
556
+ this._wireConnection(conn, buyerPeerId);
557
+ this.emit("connection", conn);
558
+ }
559
+ async _executeProviderRequest(provider, request) {
560
+ const capability = request.headers["x-antseed-capability"]?.toLowerCase();
561
+ const isTask = capability === "task" || request.path === "/v1/task";
562
+ const isSkill = capability === "skill" || request.path === "/v1/skill";
563
+ if (isSkill) {
564
+ if (!provider.handleSkill) {
565
+ return {
566
+ requestId: request.requestId,
567
+ statusCode: 501,
568
+ headers: { "content-type": "application/json" },
569
+ body: new TextEncoder().encode(JSON.stringify({ error: "Provider does not support skill capability" })),
570
+ };
571
+ }
572
+ const parsed = this._parseJsonBody(request.body);
573
+ if (!parsed || typeof parsed !== "object") {
574
+ return {
575
+ requestId: request.requestId,
576
+ statusCode: 400,
577
+ headers: { "content-type": "application/json" },
578
+ body: new TextEncoder().encode(JSON.stringify({ error: "Invalid skill payload" })),
579
+ };
580
+ }
581
+ const raw = parsed;
582
+ const skillReq = {
583
+ skillId: typeof raw["skillId"] === "string" ? raw["skillId"] : request.requestId,
584
+ capability: typeof raw["capability"] === "string" ? raw["capability"] : "skill",
585
+ input: raw["input"] ?? {},
586
+ inputSchema: (raw["inputSchema"] && typeof raw["inputSchema"] === "object")
587
+ ? raw["inputSchema"]
588
+ : undefined,
589
+ };
590
+ const skillResponse = await provider.handleSkill(skillReq);
591
+ return {
592
+ requestId: request.requestId,
593
+ statusCode: 200,
594
+ headers: { "content-type": "application/json" },
595
+ body: new TextEncoder().encode(JSON.stringify(skillResponse)),
596
+ };
597
+ }
598
+ if (isTask) {
599
+ if (!provider.handleTask) {
600
+ return {
601
+ requestId: request.requestId,
602
+ statusCode: 501,
603
+ headers: { "content-type": "application/json" },
604
+ body: new TextEncoder().encode(JSON.stringify({ error: "Provider does not support task capability" })),
605
+ };
606
+ }
607
+ const parsed = this._parseJsonBody(request.body);
608
+ if (!parsed || typeof parsed !== "object") {
609
+ return {
610
+ requestId: request.requestId,
611
+ statusCode: 400,
612
+ headers: { "content-type": "application/json" },
613
+ body: new TextEncoder().encode(JSON.stringify({ error: "Invalid task payload" })),
614
+ };
615
+ }
616
+ const raw = parsed;
617
+ const taskReq = {
618
+ taskId: typeof raw["taskId"] === "string" ? raw["taskId"] : request.requestId,
619
+ capability: typeof raw["capability"] === "string" ? raw["capability"] : "agent",
620
+ input: raw["input"] ?? {},
621
+ metadata: (raw["metadata"] && typeof raw["metadata"] === "object")
622
+ ? raw["metadata"]
623
+ : undefined,
624
+ };
625
+ const events = [];
626
+ for await (const event of provider.handleTask(taskReq)) {
627
+ events.push(event);
628
+ }
629
+ const payload = events.length <= 1
630
+ ? (events[0] ?? {
631
+ taskId: taskReq.taskId,
632
+ type: "final",
633
+ data: {},
634
+ timestamp: Date.now(),
635
+ })
636
+ : events;
637
+ return {
638
+ requestId: request.requestId,
639
+ statusCode: 200,
640
+ headers: { "content-type": "application/json" },
641
+ body: new TextEncoder().encode(JSON.stringify(payload)),
642
+ };
643
+ }
644
+ return provider.handleRequest(request);
645
+ }
646
+ _parseJsonBody(body) {
647
+ try {
648
+ return JSON.parse(new TextDecoder().decode(body));
649
+ }
650
+ catch {
651
+ return null;
652
+ }
653
+ }
654
+ _extractRequestedModel(request) {
655
+ const contentType = request.headers["content-type"] ?? request.headers["Content-Type"] ?? "";
656
+ if (!contentType.toLowerCase().includes("application/json")) {
657
+ return null;
658
+ }
659
+ const parsed = this._parseJsonBody(request.body);
660
+ if (!parsed || typeof parsed !== "object") {
661
+ return null;
662
+ }
663
+ const model = parsed["model"];
664
+ if (typeof model !== "string" || model.trim().length === 0) {
665
+ return null;
666
+ }
667
+ return model.trim();
668
+ }
669
+ _resolveProviderPricing(provider, request) {
670
+ const requestedModel = this._extractRequestedModel(request);
671
+ if (requestedModel) {
672
+ const modelPricing = provider.pricing.models?.[requestedModel];
673
+ if (modelPricing) {
674
+ return modelPricing;
675
+ }
676
+ }
677
+ return provider.pricing.defaults;
678
+ }
679
+ _getOrCreateSellerSession(buyerPeerId, providerName) {
680
+ if (!this._identity) {
681
+ return null;
682
+ }
683
+ let session = this._sessions.get(buyerPeerId);
684
+ if (!session) {
685
+ const now = Date.now();
686
+ const sessionId = randomUUID();
687
+ // Generate 32-byte sessionIdBytes from UUID for on-chain use
688
+ const sessionIdBytes = createHash("sha256").update(sessionId).digest();
689
+ session = {
690
+ sessionId,
691
+ sessionIdBytes: new Uint8Array(sessionIdBytes),
692
+ startedAt: now,
693
+ lastActivityAt: now,
694
+ totalRequests: 0,
695
+ totalTokens: 0,
696
+ totalLatencyMs: 0,
697
+ totalCostCents: 0,
698
+ provider: providerName,
699
+ lockCommitted: false,
700
+ lockedAmount: 0n,
701
+ runningTotal: 0n,
702
+ ackedRequestCount: 0,
703
+ lastAckedTotal: 0n,
704
+ awaitingAck: false,
705
+ buyerEvmAddress: null,
706
+ };
707
+ this._sessions.set(buyerPeerId, session);
708
+ }
709
+ session.provider = providerName;
710
+ session.lastActivityAt = Date.now();
711
+ this._emitSellerSessionUpdated(buyerPeerId, session);
712
+ return session;
713
+ }
714
+ _emitSellerSessionUpdated(buyerPeerId, session) {
715
+ this.emit("session:updated", {
716
+ buyerPeerId,
717
+ sessionId: session.sessionId,
718
+ provider: session.provider,
719
+ startedAt: session.startedAt,
720
+ lastActivityAt: session.lastActivityAt,
721
+ totalRequests: session.totalRequests,
722
+ totalTokens: session.totalTokens,
723
+ avgLatencyMs: session.totalRequests > 0 ? session.totalLatencyMs / session.totalRequests : 0,
724
+ settling: Boolean(session.settling),
725
+ });
726
+ }
727
+ /** Estimate tokens from byte lengths (rough: ~4 chars per token). */
728
+ _estimateTokens(inputBytes, outputBytes) {
729
+ const inputTokens = Math.max(1, Math.round(inputBytes / 4));
730
+ const outputTokens = Math.max(1, Math.round(outputBytes / 4));
731
+ return { inputTokens, outputTokens, totalTokens: inputTokens + outputTokens };
732
+ }
733
+ async _recordMetering(buyerPeerId, providerName, providerPricingUsdPerMillion, request, statusCode, latencyMs, inputBytes, outputBytes) {
734
+ if (!this._identity)
735
+ return;
736
+ const sellerPeerId = this._identity.peerId;
737
+ const isSSE = request.headers["accept"]?.includes("text/event-stream") ?? false;
738
+ const tokens = this._estimateTokens(inputBytes, outputBytes);
739
+ // Get or create session for this buyer
740
+ const session = this._getOrCreateSellerSession(buyerPeerId, providerName);
741
+ if (!session)
742
+ return;
743
+ session.totalRequests++;
744
+ session.totalTokens += tokens.totalTokens;
745
+ session.totalLatencyMs += latencyMs;
746
+ session.provider = providerName;
747
+ session.lastActivityAt = Date.now();
748
+ this._emitSellerSessionUpdated(buyerPeerId, session);
749
+ const metering = this._metering;
750
+ if (!metering) {
751
+ this._scheduleSettlementTimer(buyerPeerId);
752
+ return;
753
+ }
754
+ // Record metering event
755
+ const event = {
756
+ eventId: randomUUID(),
757
+ sessionId: session.sessionId,
758
+ timestamp: Date.now(),
759
+ provider: providerName,
760
+ sellerPeerId,
761
+ buyerPeerId,
762
+ tokens: { ...tokens, method: "content-length", confidence: "low" },
763
+ latencyMs,
764
+ statusCode,
765
+ wasStreaming: isSSE,
766
+ };
767
+ try {
768
+ metering.insertEvent(event);
769
+ }
770
+ catch (err) {
771
+ debugWarn(`[Node] Failed to record metering event: ${err instanceof Error ? err.message : err}`);
772
+ }
773
+ if (this._receiptGenerator) {
774
+ const estimatedCostUsd = (tokens.inputTokens * providerPricingUsdPerMillion.inputUsdPerMillion +
775
+ tokens.outputTokens * providerPricingUsdPerMillion.outputUsdPerMillion) /
776
+ 1_000_000;
777
+ const effectiveUsdPerThousandTokens = tokens.totalTokens > 0 ? (estimatedCostUsd / tokens.totalTokens) * 1000 : 0;
778
+ // Receipt unit pricing uses USD cents per 1,000 tokens.
779
+ const unitPriceCentsPerThousandTokens = Math.max(0, effectiveUsdPerThousandTokens * 100);
780
+ const receipt = this._receiptGenerator.generate(session.sessionId, event.eventId, providerName, buyerPeerId, event.tokens, unitPriceCentsPerThousandTokens);
781
+ try {
782
+ metering.insertReceipt(receipt);
783
+ session.totalCostCents += receipt.costCents;
784
+ }
785
+ catch (err) {
786
+ debugWarn(`[Node] Failed to record usage receipt: ${err instanceof Error ? err.message : err}`);
787
+ }
788
+ }
789
+ // Upsert session
790
+ const sessionMetrics = {
791
+ sessionId: session.sessionId,
792
+ sellerPeerId,
793
+ buyerPeerId,
794
+ provider: providerName,
795
+ startedAt: session.startedAt,
796
+ endedAt: null,
797
+ totalRequests: session.totalRequests,
798
+ totalTokens: session.totalTokens,
799
+ totalCostCents: session.totalCostCents,
800
+ avgLatencyMs: session.totalLatencyMs / session.totalRequests,
801
+ peerSwitches: 0,
802
+ disputedReceipts: 0,
803
+ };
804
+ try {
805
+ metering.upsertSession(sessionMetrics);
806
+ }
807
+ catch (err) {
808
+ debugWarn(`[Node] Failed to upsert session: ${err instanceof Error ? err.message : err}`);
809
+ }
810
+ this._scheduleSettlementTimer(buyerPeerId);
811
+ }
812
+ async _initializePayments(dataDir) {
813
+ const payments = this._config.payments;
814
+ if (!payments || !payments.enabled) {
815
+ return;
816
+ }
817
+ // Initialize BaseEscrowClient if Base config is provided
818
+ if (payments.rpcUrl && payments.contractAddress && payments.usdcAddress) {
819
+ this._escrowClient = new BaseEscrowClient({
820
+ rpcUrl: payments.rpcUrl,
821
+ contractAddress: payments.contractAddress,
822
+ usdcAddress: payments.usdcAddress,
823
+ });
824
+ debugLog(`[Node] BaseEscrowClient initialized (contract=${payments.contractAddress.slice(0, 10)}...)`);
825
+ }
826
+ if (!this._metering) {
827
+ debugWarn("[Node] Payments enabled but metering storage is unavailable; skipping balance manager wiring");
828
+ return;
829
+ }
830
+ const paymentsDir = join(dataDir, "payments");
831
+ this._balanceManager = new BalanceManager();
832
+ await this._balanceManager.load(paymentsDir).catch((err) => {
833
+ debugWarn(`[Node] Failed to load payment balances: ${err instanceof Error ? err.message : err}`);
834
+ });
835
+ }
836
+ _scheduleSettlementTimer(buyerPeerId) {
837
+ const existing = this._settlementTimers.get(buyerPeerId);
838
+ if (existing) {
839
+ clearTimeout(existing);
840
+ }
841
+ const idleMs = this._config.payments?.settlementIdleMs ?? 30_000;
842
+ const timer = setTimeout(() => {
843
+ void this._finalizeSession(buyerPeerId, "idle-timeout");
844
+ }, idleMs);
845
+ if (typeof timer.unref === "function") {
846
+ timer.unref();
847
+ }
848
+ this._settlementTimers.set(buyerPeerId, timer);
849
+ }
850
+ async _finalizeSession(buyerPeerId, reason) {
851
+ const session = this._sessions.get(buyerPeerId);
852
+ if (!session || session.settling) {
853
+ return;
854
+ }
855
+ session.settling = true;
856
+ const timer = this._settlementTimers.get(buyerPeerId);
857
+ if (timer) {
858
+ clearTimeout(timer);
859
+ this._settlementTimers.delete(buyerPeerId);
860
+ }
861
+ // Bilateral-aware disconnect handling (ghost scenario - Task 7)
862
+ if (session.lockCommitted && this._escrowClient && this._identity && reason === "disconnect") {
863
+ const sellerWallet = identityToEvmWallet(this._identity);
864
+ const sessionIdHex = "0x" + bytesToHex(session.sessionIdBytes);
865
+ try {
866
+ if (session.lastAckedTotal > 0n) {
867
+ // Buyer acked some work — open dispute with last acked total
868
+ debugLog(`[Node] Ghost buyer — opening dispute with lastAckedTotal=${session.lastAckedTotal}`);
869
+ await this._escrowClient.openDispute(sellerWallet, sessionIdHex, session.lastAckedTotal);
870
+ }
871
+ else if (session.runningTotal > 0n) {
872
+ // No acks but work was done — open dispute with running total
873
+ debugLog(`[Node] Ghost buyer — opening dispute with runningTotal=${session.runningTotal}`);
874
+ await this._escrowClient.openDispute(sellerWallet, sessionIdHex, session.runningTotal);
875
+ }
876
+ else {
877
+ // No work done — lock expires after 1 hour automatically
878
+ debugLog(`[Node] Ghost buyer — no work done, lock will expire`);
879
+ }
880
+ }
881
+ catch (err) {
882
+ debugWarn(`[Node] Failed to handle ghost buyer for session ${session.sessionId}: ${err instanceof Error ? err.message : err}`);
883
+ }
884
+ this._sessions.delete(buyerPeerId);
885
+ this.emit("session:finalized", {
886
+ buyerPeerId,
887
+ sessionId: session.sessionId,
888
+ reason: "ghost-disconnect",
889
+ });
890
+ return;
891
+ }
892
+ if (!this._metering || !this._identity) {
893
+ this._sessions.delete(buyerPeerId);
894
+ return;
895
+ }
896
+ const now = Date.now();
897
+ const baseMetrics = {
898
+ sessionId: session.sessionId,
899
+ sellerPeerId: this._identity.peerId,
900
+ buyerPeerId,
901
+ provider: session.provider,
902
+ startedAt: session.startedAt,
903
+ endedAt: now,
904
+ totalRequests: session.totalRequests,
905
+ totalTokens: session.totalTokens,
906
+ totalCostCents: session.totalCostCents,
907
+ avgLatencyMs: session.totalRequests > 0 ? session.totalLatencyMs / session.totalRequests : 0,
908
+ peerSwitches: 0,
909
+ disputedReceipts: 0,
910
+ };
911
+ try {
912
+ this._metering.upsertSession(baseMetrics);
913
+ this._sessions.delete(buyerPeerId);
914
+ this.emit("session:finalized", {
915
+ buyerPeerId,
916
+ sessionId: session.sessionId,
917
+ reason,
918
+ });
919
+ }
920
+ catch (err) {
921
+ session.settling = false;
922
+ debugWarn(`[Node] Failed to finalize session ${session.sessionId}: ${err instanceof Error ? err.message : err}`);
923
+ const retry = setTimeout(() => {
924
+ void this._finalizeSession(buyerPeerId, "retry");
925
+ }, 10_000);
926
+ if (typeof retry.unref === "function") {
927
+ retry.unref();
928
+ }
929
+ this._settlementTimers.set(buyerPeerId, retry);
930
+ }
931
+ }
932
+ async _finalizeAllSessions(reason) {
933
+ if (this._sessions.size === 0)
934
+ return;
935
+ const buyers = [...this._sessions.keys()];
936
+ await Promise.allSettled(buyers.map((buyerPeerId) => this._finalizeSession(buyerPeerId, reason)));
937
+ }
938
+ async _getOrCreateConnection(peer) {
939
+ if (!this._connectionManager || !this._identity) {
940
+ throw new Error("Node not started");
941
+ }
942
+ const existing = this._connectionManager.getConnection(peer.peerId);
943
+ if (existing &&
944
+ existing.state !== ConnectionState.Closed &&
945
+ existing.state !== ConnectionState.Failed) {
946
+ debugLog(`[Node] Reusing existing connection to ${peer.peerId.slice(0, 12)}... (state=${existing.state})`);
947
+ // If still connecting, wait for it to reach Open or Authenticated
948
+ if (existing.state === ConnectionState.Connecting) {
949
+ debugLog(`[Node] Waiting for connection to open...`);
950
+ await new Promise((resolve, reject) => {
951
+ const onState = (state) => {
952
+ if (state === ConnectionState.Open || state === ConnectionState.Authenticated) {
953
+ existing.off("stateChange", onState);
954
+ resolve();
955
+ }
956
+ else if (state === ConnectionState.Failed || state === ConnectionState.Closed) {
957
+ existing.off("stateChange", onState);
958
+ reject(new Error(`Connection to ${peer.peerId} failed`));
959
+ }
960
+ };
961
+ existing.on("stateChange", onState);
962
+ });
963
+ }
964
+ return existing;
965
+ }
966
+ // Register the peer endpoint so ConnectionManager can resolve it
967
+ if (peer.publicAddress) {
968
+ const parts = peer.publicAddress.split(":");
969
+ const host = parts[0];
970
+ const port = parseInt(parts[1] ?? "6882", 10);
971
+ this._connectionManager.registerPeerEndpoint(peer.peerId, { host, port });
972
+ debugLog(`[Node] Connecting to ${peer.peerId.slice(0, 12)}... at ${host}:${port}`);
973
+ }
974
+ else {
975
+ debugWarn(`[Node] Peer ${peer.peerId.slice(0, 12)}... has no public address`);
976
+ }
977
+ const connConfig = {
978
+ remotePeerId: peer.peerId,
979
+ isInitiator: true,
980
+ };
981
+ const conn = this._connectionManager.createConnection(connConfig);
982
+ // Wait for connection to open
983
+ await new Promise((resolve, reject) => {
984
+ const onState = (state) => {
985
+ debugLog(`[Node] Connection state: ${state}`);
986
+ if (state === ConnectionState.Open || state === ConnectionState.Authenticated) {
987
+ conn.off("stateChange", onState);
988
+ resolve();
989
+ }
990
+ else if (state === ConnectionState.Failed || state === ConnectionState.Closed) {
991
+ conn.off("stateChange", onState);
992
+ reject(new Error(`Connection to ${peer.peerId} failed`));
993
+ }
994
+ };
995
+ conn.on("stateChange", onState);
996
+ });
997
+ debugLog(`[Node] Connected to ${peer.peerId.slice(0, 12)}...`);
998
+ this._wireConnection(conn, peer.peerId);
999
+ return conn;
1000
+ }
1001
+ _getOrCreateMux(peerId, conn) {
1002
+ const existing = this._muxes.get(peerId);
1003
+ if (existing) {
1004
+ return existing;
1005
+ }
1006
+ const mux = new ProxyMux(conn);
1007
+ this._muxes.set(peerId, mux);
1008
+ return mux;
1009
+ }
1010
+ // ── Seller-side bilateral payment handlers ─────────────────────
1011
+ /**
1012
+ * Handle SessionLockAuth from buyer (Task 2).
1013
+ * Recovers buyer address, commits lock on-chain, initializes bilateral state.
1014
+ */
1015
+ async _handleSessionLockAuth(buyerPeerId, payload, paymentMux) {
1016
+ if (!this._identity || !this._escrowClient) {
1017
+ paymentMux.sendSessionLockReject({
1018
+ sessionId: payload.sessionId,
1019
+ reason: "Escrow client not configured",
1020
+ });
1021
+ return;
1022
+ }
1023
+ try {
1024
+ const sellerWallet = identityToEvmWallet(this._identity);
1025
+ const lockedAmount = BigInt(payload.lockedAmount);
1026
+ // Recover buyer address from ECDSA signature
1027
+ const lockMsgHash = buildLockMessageHash(payload.sessionId, sellerWallet.address, lockedAmount);
1028
+ const buyerEvmAddress = verifyMessage(getBytes(lockMsgHash), payload.buyerSig);
1029
+ // Submit commit_lock on-chain
1030
+ const txHash = await this._escrowClient.commitLock(sellerWallet, buyerEvmAddress, payload.sessionId, lockedAmount, payload.buyerSig);
1031
+ // Initialize or update bilateral session state
1032
+ let session = this._sessions.get(buyerPeerId);
1033
+ if (!session) {
1034
+ session = this._getOrCreateSellerSession(buyerPeerId, this._providers[0]?.name ?? "unknown");
1035
+ }
1036
+ if (session) {
1037
+ // Override sessionId with the one from the lock auth (buyer-chosen)
1038
+ session.sessionId = payload.sessionId;
1039
+ session.sessionIdBytes = hexToBytes(payload.sessionId.replace(/^0x/, ""));
1040
+ session.lockCommitted = true;
1041
+ session.lockedAmount = lockedAmount;
1042
+ session.runningTotal = 0n;
1043
+ session.ackedRequestCount = 0;
1044
+ session.lastAckedTotal = 0n;
1045
+ session.awaitingAck = false;
1046
+ session.buyerEvmAddress = buyerEvmAddress;
1047
+ }
1048
+ debugLog(`[Node] Lock committed for buyer ${buyerPeerId.slice(0, 12)}... amount=${lockedAmount} tx=${txHash.slice(0, 12)}...`);
1049
+ paymentMux.sendSessionLockConfirm({
1050
+ sessionId: payload.sessionId,
1051
+ txSignature: txHash,
1052
+ });
1053
+ }
1054
+ catch (err) {
1055
+ const reason = err instanceof Error ? err.message : String(err);
1056
+ debugWarn(`[Node] Failed to commit lock for ${buyerPeerId.slice(0, 12)}...: ${reason}`);
1057
+ paymentMux.sendSessionLockReject({
1058
+ sessionId: payload.sessionId,
1059
+ reason,
1060
+ });
1061
+ }
1062
+ }
1063
+ /**
1064
+ * Generate and send a bilateral receipt after processing a request (Task 3).
1065
+ */
1066
+ async _sendBilateralReceipt(_buyerPeerId, session, providerPricingUsdPerMillion, responseBody, paymentMux) {
1067
+ if (!this._identity)
1068
+ return;
1069
+ // Calculate incremental cost in USDC base units (6 decimals)
1070
+ // Estimate tokens from response body size
1071
+ const tokens = this._estimateTokens(0, responseBody.length);
1072
+ const costUSD = (tokens.inputTokens * providerPricingUsdPerMillion.inputUsdPerMillion +
1073
+ tokens.outputTokens * providerPricingUsdPerMillion.outputUsdPerMillion) /
1074
+ 1_000_000;
1075
+ const costBaseUnits = BigInt(Math.round(costUSD * 1_000_000));
1076
+ // Update running total
1077
+ session.runningTotal += costBaseUnits;
1078
+ // SHA-256 hash of response body for proof of work
1079
+ const responseHash = createHash("sha256").update(responseBody).digest();
1080
+ // Build receipt message and sign with Ed25519
1081
+ const receiptMsg = buildReceiptMessage(session.sessionIdBytes, session.runningTotal, session.totalRequests, new Uint8Array(responseHash));
1082
+ const sellerSig = await signMessageEd25519(this._identity, receiptMsg);
1083
+ paymentMux.sendSellerReceipt({
1084
+ sessionId: session.sessionId,
1085
+ runningTotal: session.runningTotal.toString(),
1086
+ requestCount: session.totalRequests,
1087
+ responseHash: bytesToHex(new Uint8Array(responseHash)),
1088
+ sellerSig: bytesToHex(sellerSig),
1089
+ });
1090
+ session.awaitingAck = true;
1091
+ // Send TopUpRequest if running total > 80% of locked amount
1092
+ if (session.lockedAmount > 0n && session.runningTotal * 100n > session.lockedAmount * 80n) {
1093
+ const additionalAmount = session.lockedAmount; // Request same amount again
1094
+ paymentMux.sendTopUpRequest({
1095
+ sessionId: session.sessionId,
1096
+ additionalAmount: additionalAmount.toString(),
1097
+ currentRunningTotal: session.runningTotal.toString(),
1098
+ currentLockedAmount: session.lockedAmount.toString(),
1099
+ });
1100
+ debugLog(`[Node] TopUpRequest sent for session ${session.sessionId.slice(0, 8)}... (running=${session.runningTotal}, locked=${session.lockedAmount})`);
1101
+ }
1102
+ }
1103
+ /**
1104
+ * Handle BuyerAck (Task 4).
1105
+ * Verifies buyer's Ed25519 ack signature and updates session state.
1106
+ */
1107
+ async _handleBuyerAck(buyerPeerId, payload) {
1108
+ const session = this._sessions.get(buyerPeerId);
1109
+ if (!session || !session.lockCommitted) {
1110
+ debugWarn(`[Node] Received BuyerAck for unknown/uncommitted session from ${buyerPeerId.slice(0, 12)}...`);
1111
+ return;
1112
+ }
1113
+ try {
1114
+ // Verify buyer's Ed25519 ack signature
1115
+ const buyerPublicKey = hexToBytes(buyerPeerId);
1116
+ const ackMsg = buildAckMessage(session.sessionIdBytes, BigInt(payload.runningTotal), payload.requestCount);
1117
+ const sigBytes = hexToBytes(payload.buyerSig);
1118
+ const valid = await verifyMessageEd25519(buyerPublicKey, sigBytes, ackMsg);
1119
+ if (!valid) {
1120
+ debugWarn(`[Node] Invalid BuyerAck signature from ${buyerPeerId.slice(0, 12)}...`);
1121
+ return;
1122
+ }
1123
+ session.ackedRequestCount = payload.requestCount;
1124
+ session.lastAckedTotal = BigInt(payload.runningTotal);
1125
+ session.awaitingAck = false;
1126
+ debugLog(`[Node] BuyerAck received: requestCount=${payload.requestCount} runningTotal=${payload.runningTotal}`);
1127
+ }
1128
+ catch (err) {
1129
+ debugWarn(`[Node] Failed to process BuyerAck: ${err instanceof Error ? err.message : err}`);
1130
+ }
1131
+ }
1132
+ /**
1133
+ * Handle SessionEnd from buyer (Task 5).
1134
+ * Submits settlement on-chain and cleans up.
1135
+ */
1136
+ async _handleSessionEnd(buyerPeerId, payload) {
1137
+ const session = this._sessions.get(buyerPeerId);
1138
+ if (!session || !session.lockCommitted) {
1139
+ debugWarn(`[Node] Received SessionEnd for unknown/uncommitted session from ${buyerPeerId.slice(0, 12)}...`);
1140
+ return;
1141
+ }
1142
+ if (!this._identity || !this._escrowClient) {
1143
+ debugWarn(`[Node] Cannot process SessionEnd — escrow client not available`);
1144
+ return;
1145
+ }
1146
+ try {
1147
+ const sellerWallet = identityToEvmWallet(this._identity);
1148
+ const sessionIdHex = "0x" + bytesToHex(session.sessionIdBytes);
1149
+ // Submit settlement on-chain with buyer's ECDSA signature and score
1150
+ const txHash = await this._escrowClient.settle(sellerWallet, sessionIdHex, BigInt(payload.runningTotal), payload.score, payload.buyerSig);
1151
+ debugLog(`[Node] Session settled on-chain: ${session.sessionId.slice(0, 8)}... tx=${txHash.slice(0, 12)}... score=${payload.score}`);
1152
+ // Clean up session
1153
+ this._sessions.delete(buyerPeerId);
1154
+ const timer = this._settlementTimers.get(buyerPeerId);
1155
+ if (timer) {
1156
+ clearTimeout(timer);
1157
+ this._settlementTimers.delete(buyerPeerId);
1158
+ }
1159
+ this.emit("session:settled", {
1160
+ buyerPeerId,
1161
+ sessionId: session.sessionId,
1162
+ runningTotal: payload.runningTotal,
1163
+ score: payload.score,
1164
+ txHash,
1165
+ });
1166
+ }
1167
+ catch (err) {
1168
+ debugWarn(`[Node] Failed to settle session ${session.sessionId}: ${err instanceof Error ? err.message : err}`);
1169
+ }
1170
+ }
1171
+ /**
1172
+ * Handle TopUpAuth from buyer (Task 6).
1173
+ * Calls extendLock on-chain and updates session.
1174
+ */
1175
+ async _handleTopUpAuth(buyerPeerId, payload) {
1176
+ const session = this._sessions.get(buyerPeerId);
1177
+ if (!session || !session.lockCommitted) {
1178
+ debugWarn(`[Node] Received TopUpAuth for unknown/uncommitted session from ${buyerPeerId.slice(0, 12)}...`);
1179
+ return;
1180
+ }
1181
+ if (!this._identity || !this._escrowClient) {
1182
+ debugWarn(`[Node] Cannot process TopUpAuth — escrow client not available`);
1183
+ return;
1184
+ }
1185
+ try {
1186
+ const sellerWallet = identityToEvmWallet(this._identity);
1187
+ const sessionIdHex = "0x" + bytesToHex(session.sessionIdBytes);
1188
+ const additionalAmount = BigInt(payload.additionalAmount);
1189
+ const txHash = await this._escrowClient.extendLock(sellerWallet, sessionIdHex, additionalAmount, payload.buyerSig);
1190
+ session.lockedAmount += additionalAmount;
1191
+ debugLog(`[Node] TopUp committed: session=${session.sessionId.slice(0, 8)}... additional=${additionalAmount} newTotal=${session.lockedAmount} tx=${txHash.slice(0, 12)}...`);
1192
+ }
1193
+ catch (err) {
1194
+ debugWarn(`[Node] Failed to extend lock for session ${session.sessionId}: ${err instanceof Error ? err.message : err}`);
1195
+ }
1196
+ }
1197
+ // ── Buyer-side payment helpers ─────────────────────────────────
1198
+ /**
1199
+ * Create a PaymentMux for a buyer-side outbound connection and register
1200
+ * buyer-side handlers (lock confirm, lock reject, seller receipt, top-up request).
1201
+ */
1202
+ _getOrCreateBuyerPaymentMux(peerId, conn) {
1203
+ const existing = this._paymentMuxes.get(peerId);
1204
+ if (existing)
1205
+ return existing;
1206
+ const pmux = new PaymentMux(conn);
1207
+ this._paymentMuxes.set(peerId, pmux);
1208
+ const bpm = this._buyerPaymentManager;
1209
+ if (!bpm)
1210
+ return pmux;
1211
+ pmux.onSessionLockConfirm((payload) => {
1212
+ bpm.handleLockConfirm(peerId, payload);
1213
+ });
1214
+ pmux.onSessionLockReject((payload) => {
1215
+ bpm.handleLockReject(peerId, payload);
1216
+ });
1217
+ pmux.onSellerReceipt((receipt) => {
1218
+ void bpm.handleSellerReceipt(peerId, receipt, pmux);
1219
+ });
1220
+ pmux.onTopUpRequest((request) => {
1221
+ void bpm.handleTopUpRequest(peerId, request, pmux);
1222
+ });
1223
+ return pmux;
1224
+ }
1225
+ /**
1226
+ * Initiate a lock with a seller peer. Creates PaymentMux, signs lock auth,
1227
+ * and waits for confirmation before returning.
1228
+ */
1229
+ async _initiateBuyerLock(peer, conn) {
1230
+ const bpm = this._buyerPaymentManager;
1231
+ if (!bpm)
1232
+ return;
1233
+ // Mark as locked so we don't re-initiate
1234
+ this._buyerLockedPeers.add(peer.peerId);
1235
+ const pmux = this._getOrCreateBuyerPaymentMux(peer.peerId, conn);
1236
+ // Determine seller EVM address — prefer from peer metadata
1237
+ const sellerEvmAddress = peer.evmAddress ?? "";
1238
+ if (!sellerEvmAddress) {
1239
+ debugWarn(`[Node] Seller ${peer.peerId.slice(0, 12)}... has no EVM address; skipping lock initiation`);
1240
+ return;
1241
+ }
1242
+ try {
1243
+ await bpm.initiateLock(peer.peerId, sellerEvmAddress, pmux);
1244
+ debugLog(`[Node] Lock initiated for seller ${peer.peerId.slice(0, 12)}..., waiting for confirmation...`);
1245
+ // Wait for lock confirmation (polls every 200ms, 30s timeout)
1246
+ await this._waitForLockConfirmation(peer.peerId);
1247
+ debugLog(`[Node] Lock confirmed for seller ${peer.peerId.slice(0, 12)}...`);
1248
+ }
1249
+ catch (err) {
1250
+ debugWarn(`[Node] Lock initiation/confirmation failed for ${peer.peerId.slice(0, 12)}...: ${err instanceof Error ? err.message : err}`);
1251
+ // Remove from locked set so next request can retry
1252
+ this._buyerLockedPeers.delete(peer.peerId);
1253
+ }
1254
+ }
1255
+ /**
1256
+ * Poll until the lock for a seller is confirmed or rejected.
1257
+ * Polls every 200ms with a 30-second timeout.
1258
+ */
1259
+ async _waitForLockConfirmation(sellerPeerId) {
1260
+ const bpm = this._buyerPaymentManager;
1261
+ if (!bpm)
1262
+ return;
1263
+ const pollIntervalMs = 200;
1264
+ const timeoutMs = 30_000;
1265
+ const deadline = Date.now() + timeoutMs;
1266
+ while (Date.now() < deadline) {
1267
+ if (bpm.isLockConfirmed(sellerPeerId)) {
1268
+ return;
1269
+ }
1270
+ if (bpm.isLockRejected(sellerPeerId)) {
1271
+ throw new Error(`Lock rejected by seller ${sellerPeerId.slice(0, 12)}...`);
1272
+ }
1273
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
1274
+ }
1275
+ throw new Error(`Lock confirmation timed out for seller ${sellerPeerId.slice(0, 12)}... (${timeoutMs}ms)`);
1276
+ }
1277
+ /**
1278
+ * End all active buyer payment sessions (called during shutdown).
1279
+ */
1280
+ async _endAllBuyerSessions() {
1281
+ const bpm = this._buyerPaymentManager;
1282
+ if (!bpm)
1283
+ return;
1284
+ const sessions = bpm.getActiveSessions();
1285
+ if (sessions.length === 0)
1286
+ return;
1287
+ debugLog(`[Node] Ending ${sessions.length} buyer payment session(s)...`);
1288
+ await Promise.allSettled(sessions.map((session) => {
1289
+ const pmux = this._paymentMuxes.get(session.sellerPeerId);
1290
+ if (pmux) {
1291
+ return bpm.endSession(session.sellerPeerId, pmux, 80);
1292
+ }
1293
+ return Promise.resolve();
1294
+ }));
1295
+ }
1296
+ _lookupResultToPeerInfo(result) {
1297
+ const providers = result.metadata.providers.map((p) => p.provider);
1298
+ const firstProvider = result.metadata.providers[0];
1299
+ const providerPricingEntries = Object.fromEntries(result.metadata.providers.map((p) => [
1300
+ p.provider,
1301
+ {
1302
+ defaults: {
1303
+ inputUsdPerMillion: p.defaultPricing.inputUsdPerMillion,
1304
+ outputUsdPerMillion: p.defaultPricing.outputUsdPerMillion,
1305
+ },
1306
+ ...(p.modelPricing ? { models: { ...p.modelPricing } } : {}),
1307
+ },
1308
+ ]));
1309
+ const hasProviderPricing = Object.keys(providerPricingEntries).length > 0;
1310
+ return {
1311
+ peerId: result.metadata.peerId,
1312
+ lastSeen: result.metadata.timestamp,
1313
+ providers,
1314
+ publicAddress: `${result.host}:${result.port}`,
1315
+ ...(hasProviderPricing ? { providerPricing: providerPricingEntries } : {}),
1316
+ defaultInputUsdPerMillion: firstProvider?.defaultPricing.inputUsdPerMillion,
1317
+ defaultOutputUsdPerMillion: firstProvider?.defaultPricing.outputUsdPerMillion,
1318
+ maxConcurrency: firstProvider?.maxConcurrency,
1319
+ currentLoad: firstProvider?.currentLoad,
1320
+ evmAddress: result.metadata.evmAddress,
1321
+ onChainReputation: result.metadata.onChainReputation,
1322
+ onChainSessionCount: result.metadata.onChainSessionCount,
1323
+ onChainDisputeCount: result.metadata.onChainDisputeCount,
1324
+ trustScore: result.metadata.onChainReputation,
1325
+ };
1326
+ }
1327
+ }
1328
+ //# sourceMappingURL=node.js.map