@bsv/sdk 2.0.0-beta.0 → 2.0.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 (487) hide show
  1. package/dist/cjs/mod.js +1 -0
  2. package/dist/cjs/mod.js.map +1 -1
  3. package/dist/cjs/package.json +1 -1
  4. package/dist/cjs/src/auth/Peer.js +149 -74
  5. package/dist/cjs/src/auth/Peer.js.map +1 -1
  6. package/dist/cjs/src/auth/SessionManager.js.map +1 -1
  7. package/dist/cjs/src/auth/certificates/Certificate.js +18 -8
  8. package/dist/cjs/src/auth/certificates/Certificate.js.map +1 -1
  9. package/dist/cjs/src/auth/certificates/MasterCertificate.js +19 -9
  10. package/dist/cjs/src/auth/certificates/MasterCertificate.js.map +1 -1
  11. package/dist/cjs/src/auth/certificates/VerifiableCertificate.js +17 -7
  12. package/dist/cjs/src/auth/certificates/VerifiableCertificate.js.map +1 -1
  13. package/dist/cjs/src/auth/certificates/__tests/CompletedProtoWallet.js.map +1 -1
  14. package/dist/cjs/src/auth/clients/AuthFetch.js +30 -10
  15. package/dist/cjs/src/auth/clients/AuthFetch.js.map +1 -1
  16. package/dist/cjs/src/auth/clients/__tests__/AuthFetch.test.js.map +1 -1
  17. package/dist/cjs/src/auth/transports/SimplifiedFetchTransport.js +17 -7
  18. package/dist/cjs/src/auth/transports/SimplifiedFetchTransport.js.map +1 -1
  19. package/dist/cjs/src/auth/transports/__tests__/SimplifiedFetchTransport.test.js +17 -7
  20. package/dist/cjs/src/auth/transports/__tests__/SimplifiedFetchTransport.test.js.map +1 -1
  21. package/dist/cjs/src/auth/utils/createNonce.js +18 -9
  22. package/dist/cjs/src/auth/utils/createNonce.js.map +1 -1
  23. package/dist/cjs/src/auth/utils/validateCertificates.js.map +1 -1
  24. package/dist/cjs/src/auth/utils/verifyNonce.js +18 -9
  25. package/dist/cjs/src/auth/utils/verifyNonce.js.map +1 -1
  26. package/dist/cjs/src/compat/BSM.js +17 -7
  27. package/dist/cjs/src/compat/BSM.js.map +1 -1
  28. package/dist/cjs/src/compat/ECIES.js +17 -7
  29. package/dist/cjs/src/compat/ECIES.js.map +1 -1
  30. package/dist/cjs/src/compat/HD.js +17 -7
  31. package/dist/cjs/src/compat/HD.js.map +1 -1
  32. package/dist/cjs/src/compat/Mnemonic.js +17 -7
  33. package/dist/cjs/src/compat/Mnemonic.js.map +1 -1
  34. package/dist/cjs/src/compat/Utxo.js +1 -1
  35. package/dist/cjs/src/compat/Utxo.js.map +1 -1
  36. package/dist/cjs/src/compat/index.js +17 -7
  37. package/dist/cjs/src/compat/index.js.map +1 -1
  38. package/dist/cjs/src/identity/ContactsManager.js +1 -1
  39. package/dist/cjs/src/identity/ContactsManager.js.map +1 -1
  40. package/dist/cjs/src/identity/IdentityClient.js.map +1 -1
  41. package/dist/cjs/src/kvstore/GlobalKVStore.js +20 -10
  42. package/dist/cjs/src/kvstore/GlobalKVStore.js.map +1 -1
  43. package/dist/cjs/src/kvstore/LocalKVStore.js +17 -7
  44. package/dist/cjs/src/kvstore/LocalKVStore.js.map +1 -1
  45. package/dist/cjs/src/kvstore/kvStoreInterpreter.js +17 -7
  46. package/dist/cjs/src/kvstore/kvStoreInterpreter.js.map +1 -1
  47. package/dist/cjs/src/messages/EncryptedMessage.js +19 -0
  48. package/dist/cjs/src/messages/EncryptedMessage.js.map +1 -1
  49. package/dist/cjs/src/messages/SignedMessage.js.map +1 -1
  50. package/dist/cjs/src/messages/index.js +17 -7
  51. package/dist/cjs/src/messages/index.js.map +1 -1
  52. package/dist/cjs/src/overlay-tools/Historian.js.map +1 -1
  53. package/dist/cjs/src/overlay-tools/HostReputationTracker.js.map +1 -1
  54. package/dist/cjs/src/overlay-tools/LookupResolver.js +17 -7
  55. package/dist/cjs/src/overlay-tools/LookupResolver.js.map +1 -1
  56. package/dist/cjs/src/overlay-tools/OverlayAdminTokenTemplate.js.map +1 -1
  57. package/dist/cjs/src/overlay-tools/SHIPBroadcaster.js +17 -7
  58. package/dist/cjs/src/overlay-tools/SHIPBroadcaster.js.map +1 -1
  59. package/dist/cjs/src/overlay-tools/withDoubleSpendRetry.js +1 -2
  60. package/dist/cjs/src/overlay-tools/withDoubleSpendRetry.js.map +1 -1
  61. package/dist/cjs/src/primitives/AESGCM.js +77 -32
  62. package/dist/cjs/src/primitives/AESGCM.js.map +1 -1
  63. package/dist/cjs/src/primitives/BigNumber.js +28 -54
  64. package/dist/cjs/src/primitives/BigNumber.js.map +1 -1
  65. package/dist/cjs/src/primitives/Curve.js.map +1 -1
  66. package/dist/cjs/src/primitives/DRBG.js.map +1 -1
  67. package/dist/cjs/src/primitives/ECDSA.js +58 -24
  68. package/dist/cjs/src/primitives/ECDSA.js.map +1 -1
  69. package/dist/cjs/src/primitives/Hash.js +6 -6
  70. package/dist/cjs/src/primitives/Hash.js.map +1 -1
  71. package/dist/cjs/src/primitives/JacobianPoint.js.map +1 -1
  72. package/dist/cjs/src/primitives/K256.js.map +1 -1
  73. package/dist/cjs/src/primitives/Mersenne.js.map +1 -1
  74. package/dist/cjs/src/primitives/MontgomoryMethod.js.map +1 -1
  75. package/dist/cjs/src/primitives/Point.js +63 -6
  76. package/dist/cjs/src/primitives/Point.js.map +1 -1
  77. package/dist/cjs/src/primitives/Polynomial.js.map +1 -1
  78. package/dist/cjs/src/primitives/PrivateKey.js +46 -9
  79. package/dist/cjs/src/primitives/PrivateKey.js.map +1 -1
  80. package/dist/cjs/src/primitives/PublicKey.js +1 -1
  81. package/dist/cjs/src/primitives/PublicKey.js.map +1 -1
  82. package/dist/cjs/src/primitives/Random.js.map +1 -1
  83. package/dist/cjs/src/primitives/ReaderUint8Array.js +180 -0
  84. package/dist/cjs/src/primitives/ReaderUint8Array.js.map +1 -0
  85. package/dist/cjs/src/primitives/ReductionContext.js +35 -46
  86. package/dist/cjs/src/primitives/ReductionContext.js.map +1 -1
  87. package/dist/cjs/src/primitives/Schnorr.js.map +1 -1
  88. package/dist/cjs/src/primitives/Secp256r1.js.map +1 -1
  89. package/dist/cjs/src/primitives/Signature.js.map +1 -1
  90. package/dist/cjs/src/primitives/SymmetricKey.js.map +1 -1
  91. package/dist/cjs/src/primitives/TransactionSignature.js +132 -17
  92. package/dist/cjs/src/primitives/TransactionSignature.js.map +1 -1
  93. package/dist/cjs/src/primitives/WriterUint8Array.js +173 -0
  94. package/dist/cjs/src/primitives/WriterUint8Array.js.map +1 -0
  95. package/dist/cjs/src/primitives/hex.js +2 -3
  96. package/dist/cjs/src/primitives/hex.js.map +1 -1
  97. package/dist/cjs/src/primitives/index.js +17 -7
  98. package/dist/cjs/src/primitives/index.js.map +1 -1
  99. package/dist/cjs/src/primitives/utils.js +43 -16
  100. package/dist/cjs/src/primitives/utils.js.map +1 -1
  101. package/dist/cjs/src/registry/RegistryClient.js +2 -2
  102. package/dist/cjs/src/registry/RegistryClient.js.map +1 -1
  103. package/dist/cjs/src/remittance/CommsLayer.js +3 -0
  104. package/dist/cjs/src/remittance/CommsLayer.js.map +1 -0
  105. package/dist/cjs/src/remittance/IdentityLayer.js +3 -0
  106. package/dist/cjs/src/remittance/IdentityLayer.js.map +1 -0
  107. package/dist/cjs/src/remittance/RemittanceManager.js +1245 -0
  108. package/dist/cjs/src/remittance/RemittanceManager.js.map +1 -0
  109. package/dist/cjs/src/remittance/RemittanceModule.js +3 -0
  110. package/dist/cjs/src/remittance/RemittanceModule.js.map +1 -0
  111. package/dist/cjs/src/remittance/index.js +23 -0
  112. package/dist/cjs/src/remittance/index.js.map +1 -0
  113. package/dist/cjs/src/remittance/modules/BasicBRC29.js +225 -0
  114. package/dist/cjs/src/remittance/modules/BasicBRC29.js.map +1 -0
  115. package/dist/cjs/src/remittance/modules/index.js +18 -0
  116. package/dist/cjs/src/remittance/modules/index.js.map +1 -0
  117. package/dist/cjs/src/remittance/types.js +22 -0
  118. package/dist/cjs/src/remittance/types.js.map +1 -0
  119. package/dist/cjs/src/script/OP.js +15 -13
  120. package/dist/cjs/src/script/OP.js.map +1 -1
  121. package/dist/cjs/src/script/Script.js +4 -1
  122. package/dist/cjs/src/script/Script.js.map +1 -1
  123. package/dist/cjs/src/script/Spend.js +145 -53
  124. package/dist/cjs/src/script/Spend.js.map +1 -1
  125. package/dist/cjs/src/script/templates/P2PKH.js.map +1 -1
  126. package/dist/cjs/src/script/templates/PushDrop.js +21 -7
  127. package/dist/cjs/src/script/templates/PushDrop.js.map +1 -1
  128. package/dist/cjs/src/script/templates/RPuzzle.js.map +1 -1
  129. package/dist/cjs/src/storage/StorageDownloader.js.map +1 -1
  130. package/dist/cjs/src/storage/StorageUploader.js +17 -7
  131. package/dist/cjs/src/storage/StorageUploader.js.map +1 -1
  132. package/dist/cjs/src/storage/StorageUtils.js.map +1 -1
  133. package/dist/cjs/src/storage/index.js +17 -7
  134. package/dist/cjs/src/storage/index.js.map +1 -1
  135. package/dist/cjs/src/totp/totp.js.map +1 -1
  136. package/dist/cjs/src/transaction/Beef.js +85 -27
  137. package/dist/cjs/src/transaction/Beef.js.map +1 -1
  138. package/dist/cjs/src/transaction/BeefParty.js.map +1 -1
  139. package/dist/cjs/src/transaction/BeefTx.js +32 -14
  140. package/dist/cjs/src/transaction/BeefTx.js.map +1 -1
  141. package/dist/cjs/src/transaction/Broadcaster.js +2 -3
  142. package/dist/cjs/src/transaction/Broadcaster.js.map +1 -1
  143. package/dist/cjs/src/transaction/MerklePath.js +25 -6
  144. package/dist/cjs/src/transaction/MerklePath.js.map +1 -1
  145. package/dist/cjs/src/transaction/Transaction.js +238 -27
  146. package/dist/cjs/src/transaction/Transaction.js.map +1 -1
  147. package/dist/cjs/src/transaction/broadcasters/ARC.js +23 -0
  148. package/dist/cjs/src/transaction/broadcasters/ARC.js.map +1 -1
  149. package/dist/cjs/src/transaction/broadcasters/DefaultBroadcaster.js +1 -2
  150. package/dist/cjs/src/transaction/broadcasters/DefaultBroadcaster.js.map +1 -1
  151. package/dist/cjs/src/transaction/broadcasters/Teranode.js.map +1 -1
  152. package/dist/cjs/src/transaction/broadcasters/WhatsOnChainBroadcaster.js.map +1 -1
  153. package/dist/cjs/src/transaction/chaintrackers/BlockHeadersService.js.map +1 -1
  154. package/dist/cjs/src/transaction/chaintrackers/DefaultChainTracker.js +1 -2
  155. package/dist/cjs/src/transaction/chaintrackers/DefaultChainTracker.js.map +1 -1
  156. package/dist/cjs/src/transaction/chaintrackers/WhatsOnChain.js.map +1 -1
  157. package/dist/cjs/src/transaction/fee-models/LivePolicy.js.map +1 -1
  158. package/dist/cjs/src/transaction/fee-models/SatoshisPerKilobyte.js.map +1 -1
  159. package/dist/cjs/src/transaction/http/BinaryFetchClient.js +2 -2
  160. package/dist/cjs/src/transaction/http/BinaryFetchClient.js.map +1 -1
  161. package/dist/cjs/src/transaction/http/DefaultHttpClient.js +1 -2
  162. package/dist/cjs/src/transaction/http/DefaultHttpClient.js.map +1 -1
  163. package/dist/cjs/src/transaction/http/NodejsHttpClient.js.map +1 -1
  164. package/dist/cjs/src/wallet/CachedKeyDeriver.js.map +1 -1
  165. package/dist/cjs/src/wallet/KeyDeriver.js.map +1 -1
  166. package/dist/cjs/src/wallet/ProtoWallet.js +1 -1
  167. package/dist/cjs/src/wallet/ProtoWallet.js.map +1 -1
  168. package/dist/cjs/src/wallet/WalletClient.js.map +1 -1
  169. package/dist/cjs/src/wallet/WalletError.js.map +1 -1
  170. package/dist/cjs/src/wallet/index.js +17 -7
  171. package/dist/cjs/src/wallet/index.js.map +1 -1
  172. package/dist/cjs/src/wallet/substrates/HTTPWalletJSON.js.map +1 -1
  173. package/dist/cjs/src/wallet/substrates/HTTPWalletWire.js +17 -7
  174. package/dist/cjs/src/wallet/substrates/HTTPWalletWire.js.map +1 -1
  175. package/dist/cjs/src/wallet/substrates/ReactNativeWebView.js +17 -7
  176. package/dist/cjs/src/wallet/substrates/ReactNativeWebView.js.map +1 -1
  177. package/dist/cjs/src/wallet/substrates/WalletWireProcessor.js +17 -7
  178. package/dist/cjs/src/wallet/substrates/WalletWireProcessor.js.map +1 -1
  179. package/dist/cjs/src/wallet/substrates/WalletWireTransceiver.js +17 -7
  180. package/dist/cjs/src/wallet/substrates/WalletWireTransceiver.js.map +1 -1
  181. package/dist/cjs/src/wallet/substrates/XDM.js +17 -7
  182. package/dist/cjs/src/wallet/substrates/XDM.js.map +1 -1
  183. package/dist/cjs/src/wallet/substrates/utils/toOriginHeader.js +1 -2
  184. package/dist/cjs/src/wallet/substrates/utils/toOriginHeader.js.map +1 -1
  185. package/dist/cjs/src/wallet/substrates/window.CWI.js.map +1 -1
  186. package/dist/cjs/src/wallet/validationHelpers.js +51 -41
  187. package/dist/cjs/src/wallet/validationHelpers.js.map +1 -1
  188. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  189. package/dist/esm/mod.js +1 -0
  190. package/dist/esm/mod.js.map +1 -1
  191. package/dist/esm/src/auth/Peer.js +132 -67
  192. package/dist/esm/src/auth/Peer.js.map +1 -1
  193. package/dist/esm/src/auth/SessionManager.js.map +1 -1
  194. package/dist/esm/src/auth/certificates/Certificate.js +1 -1
  195. package/dist/esm/src/auth/certificates/Certificate.js.map +1 -1
  196. package/dist/esm/src/auth/certificates/MasterCertificate.js +2 -2
  197. package/dist/esm/src/auth/certificates/MasterCertificate.js.map +1 -1
  198. package/dist/esm/src/auth/certificates/VerifiableCertificate.js.map +1 -1
  199. package/dist/esm/src/auth/certificates/__tests/CompletedProtoWallet.js.map +1 -1
  200. package/dist/esm/src/auth/clients/AuthFetch.js +13 -3
  201. package/dist/esm/src/auth/clients/AuthFetch.js.map +1 -1
  202. package/dist/esm/src/auth/clients/__tests__/AuthFetch.test.js.map +1 -1
  203. package/dist/esm/src/auth/transports/SimplifiedFetchTransport.js.map +1 -1
  204. package/dist/esm/src/auth/transports/__tests__/SimplifiedFetchTransport.test.js.map +1 -1
  205. package/dist/esm/src/auth/utils/validateCertificates.js.map +1 -1
  206. package/dist/esm/src/compat/BSM.js.map +1 -1
  207. package/dist/esm/src/compat/ECIES.js.map +1 -1
  208. package/dist/esm/src/compat/HD.js.map +1 -1
  209. package/dist/esm/src/compat/Mnemonic.js.map +1 -1
  210. package/dist/esm/src/identity/ContactsManager.js +1 -1
  211. package/dist/esm/src/identity/ContactsManager.js.map +1 -1
  212. package/dist/esm/src/identity/IdentityClient.js.map +1 -1
  213. package/dist/esm/src/kvstore/GlobalKVStore.js +3 -3
  214. package/dist/esm/src/kvstore/GlobalKVStore.js.map +1 -1
  215. package/dist/esm/src/kvstore/LocalKVStore.js.map +1 -1
  216. package/dist/esm/src/kvstore/kvStoreInterpreter.js.map +1 -1
  217. package/dist/esm/src/messages/EncryptedMessage.js +19 -0
  218. package/dist/esm/src/messages/EncryptedMessage.js.map +1 -1
  219. package/dist/esm/src/messages/SignedMessage.js.map +1 -1
  220. package/dist/esm/src/overlay-tools/Historian.js.map +1 -1
  221. package/dist/esm/src/overlay-tools/HostReputationTracker.js.map +1 -1
  222. package/dist/esm/src/overlay-tools/LookupResolver.js.map +1 -1
  223. package/dist/esm/src/overlay-tools/OverlayAdminTokenTemplate.js.map +1 -1
  224. package/dist/esm/src/overlay-tools/SHIPBroadcaster.js.map +1 -1
  225. package/dist/esm/src/overlay-tools/withDoubleSpendRetry.js.map +1 -1
  226. package/dist/esm/src/primitives/AESGCM.js +71 -26
  227. package/dist/esm/src/primitives/AESGCM.js.map +1 -1
  228. package/dist/esm/src/primitives/BigNumber.js +28 -54
  229. package/dist/esm/src/primitives/BigNumber.js.map +1 -1
  230. package/dist/esm/src/primitives/Curve.js.map +1 -1
  231. package/dist/esm/src/primitives/DRBG.js.map +1 -1
  232. package/dist/esm/src/primitives/ECDSA.js +58 -24
  233. package/dist/esm/src/primitives/ECDSA.js.map +1 -1
  234. package/dist/esm/src/primitives/Hash.js.map +1 -1
  235. package/dist/esm/src/primitives/JacobianPoint.js.map +1 -1
  236. package/dist/esm/src/primitives/K256.js.map +1 -1
  237. package/dist/esm/src/primitives/Mersenne.js.map +1 -1
  238. package/dist/esm/src/primitives/MontgomoryMethod.js.map +1 -1
  239. package/dist/esm/src/primitives/Point.js +61 -4
  240. package/dist/esm/src/primitives/Point.js.map +1 -1
  241. package/dist/esm/src/primitives/Polynomial.js.map +1 -1
  242. package/dist/esm/src/primitives/PrivateKey.js +29 -2
  243. package/dist/esm/src/primitives/PrivateKey.js.map +1 -1
  244. package/dist/esm/src/primitives/PublicKey.js +1 -1
  245. package/dist/esm/src/primitives/PublicKey.js.map +1 -1
  246. package/dist/esm/src/primitives/Random.js.map +1 -1
  247. package/dist/esm/src/primitives/ReaderUint8Array.js +176 -0
  248. package/dist/esm/src/primitives/ReaderUint8Array.js.map +1 -0
  249. package/dist/esm/src/primitives/ReductionContext.js +35 -46
  250. package/dist/esm/src/primitives/ReductionContext.js.map +1 -1
  251. package/dist/esm/src/primitives/Schnorr.js.map +1 -1
  252. package/dist/esm/src/primitives/Secp256r1.js.map +1 -1
  253. package/dist/esm/src/primitives/Signature.js.map +1 -1
  254. package/dist/esm/src/primitives/SymmetricKey.js.map +1 -1
  255. package/dist/esm/src/primitives/TransactionSignature.js +115 -10
  256. package/dist/esm/src/primitives/TransactionSignature.js.map +1 -1
  257. package/dist/esm/src/primitives/WriterUint8Array.js +169 -0
  258. package/dist/esm/src/primitives/WriterUint8Array.js.map +1 -0
  259. package/dist/esm/src/primitives/hex.js.map +1 -1
  260. package/dist/esm/src/primitives/utils.js +37 -11
  261. package/dist/esm/src/primitives/utils.js.map +1 -1
  262. package/dist/esm/src/registry/RegistryClient.js.map +1 -1
  263. package/dist/esm/src/remittance/CommsLayer.js +2 -0
  264. package/dist/esm/src/remittance/CommsLayer.js.map +1 -0
  265. package/dist/esm/src/remittance/IdentityLayer.js +2 -0
  266. package/dist/esm/src/remittance/IdentityLayer.js.map +1 -0
  267. package/dist/esm/src/remittance/RemittanceManager.js +1254 -0
  268. package/dist/esm/src/remittance/RemittanceManager.js.map +1 -0
  269. package/dist/esm/src/remittance/RemittanceModule.js +2 -0
  270. package/dist/esm/src/remittance/RemittanceModule.js.map +1 -0
  271. package/dist/esm/src/remittance/index.js +7 -0
  272. package/dist/esm/src/remittance/index.js.map +1 -0
  273. package/dist/esm/src/remittance/modules/BasicBRC29.js +227 -0
  274. package/dist/esm/src/remittance/modules/BasicBRC29.js.map +1 -0
  275. package/dist/esm/src/remittance/modules/index.js +2 -0
  276. package/dist/esm/src/remittance/modules/index.js.map +1 -0
  277. package/dist/esm/src/remittance/types.js +19 -0
  278. package/dist/esm/src/remittance/types.js.map +1 -0
  279. package/dist/esm/src/script/OP.js +15 -13
  280. package/dist/esm/src/script/OP.js.map +1 -1
  281. package/dist/esm/src/script/Script.js +4 -1
  282. package/dist/esm/src/script/Script.js.map +1 -1
  283. package/dist/esm/src/script/Spend.js +129 -46
  284. package/dist/esm/src/script/Spend.js.map +1 -1
  285. package/dist/esm/src/script/templates/P2PKH.js.map +1 -1
  286. package/dist/esm/src/script/templates/PushDrop.js +21 -7
  287. package/dist/esm/src/script/templates/PushDrop.js.map +1 -1
  288. package/dist/esm/src/script/templates/RPuzzle.js.map +1 -1
  289. package/dist/esm/src/storage/StorageDownloader.js.map +1 -1
  290. package/dist/esm/src/storage/StorageUploader.js.map +1 -1
  291. package/dist/esm/src/storage/StorageUtils.js.map +1 -1
  292. package/dist/esm/src/totp/totp.js.map +1 -1
  293. package/dist/esm/src/transaction/Beef.js +86 -28
  294. package/dist/esm/src/transaction/Beef.js.map +1 -1
  295. package/dist/esm/src/transaction/BeefParty.js.map +1 -1
  296. package/dist/esm/src/transaction/BeefTx.js +32 -14
  297. package/dist/esm/src/transaction/BeefTx.js.map +1 -1
  298. package/dist/esm/src/transaction/MerklePath.js +26 -7
  299. package/dist/esm/src/transaction/MerklePath.js.map +1 -1
  300. package/dist/esm/src/transaction/Transaction.js +239 -28
  301. package/dist/esm/src/transaction/Transaction.js.map +1 -1
  302. package/dist/esm/src/transaction/broadcasters/ARC.js +23 -0
  303. package/dist/esm/src/transaction/broadcasters/ARC.js.map +1 -1
  304. package/dist/esm/src/transaction/broadcasters/Teranode.js.map +1 -1
  305. package/dist/esm/src/transaction/broadcasters/WhatsOnChainBroadcaster.js.map +1 -1
  306. package/dist/esm/src/transaction/chaintrackers/BlockHeadersService.js.map +1 -1
  307. package/dist/esm/src/transaction/chaintrackers/WhatsOnChain.js.map +1 -1
  308. package/dist/esm/src/transaction/fee-models/LivePolicy.js.map +1 -1
  309. package/dist/esm/src/transaction/fee-models/SatoshisPerKilobyte.js.map +1 -1
  310. package/dist/esm/src/transaction/http/BinaryFetchClient.js.map +1 -1
  311. package/dist/esm/src/transaction/http/DefaultHttpClient.js.map +1 -1
  312. package/dist/esm/src/transaction/http/NodejsHttpClient.js.map +1 -1
  313. package/dist/esm/src/wallet/CachedKeyDeriver.js.map +1 -1
  314. package/dist/esm/src/wallet/KeyDeriver.js.map +1 -1
  315. package/dist/esm/src/wallet/ProtoWallet.js +1 -1
  316. package/dist/esm/src/wallet/ProtoWallet.js.map +1 -1
  317. package/dist/esm/src/wallet/WalletClient.js.map +1 -1
  318. package/dist/esm/src/wallet/WalletError.js.map +1 -1
  319. package/dist/esm/src/wallet/substrates/HTTPWalletJSON.js.map +1 -1
  320. package/dist/esm/src/wallet/substrates/HTTPWalletWire.js.map +1 -1
  321. package/dist/esm/src/wallet/substrates/ReactNativeWebView.js.map +1 -1
  322. package/dist/esm/src/wallet/substrates/WalletWireProcessor.js.map +1 -1
  323. package/dist/esm/src/wallet/substrates/WalletWireTransceiver.js.map +1 -1
  324. package/dist/esm/src/wallet/substrates/XDM.js.map +1 -1
  325. package/dist/esm/src/wallet/substrates/utils/toOriginHeader.js.map +1 -1
  326. package/dist/esm/src/wallet/substrates/window.CWI.js.map +1 -1
  327. package/dist/esm/src/wallet/validationHelpers.js +1 -1
  328. package/dist/esm/src/wallet/validationHelpers.js.map +1 -1
  329. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  330. package/dist/types/mod.d.ts +1 -0
  331. package/dist/types/mod.d.ts.map +1 -1
  332. package/dist/types/src/auth/Peer.d.ts +12 -7
  333. package/dist/types/src/auth/Peer.d.ts.map +1 -1
  334. package/dist/types/src/auth/certificates/MasterCertificate.d.ts.map +1 -1
  335. package/dist/types/src/auth/clients/AuthFetch.d.ts.map +1 -1
  336. package/dist/types/src/auth/types.d.ts +2 -0
  337. package/dist/types/src/auth/types.d.ts.map +1 -1
  338. package/dist/types/src/auth/utils/getVerifiableCertificates.d.ts.map +1 -1
  339. package/dist/types/src/auth/utils/validateCertificates.d.ts.map +1 -1
  340. package/dist/types/src/compat/BSM.d.ts +1 -1
  341. package/dist/types/src/compat/BSM.d.ts.map +1 -1
  342. package/dist/types/src/messages/EncryptedMessage.d.ts +19 -0
  343. package/dist/types/src/messages/EncryptedMessage.d.ts.map +1 -1
  344. package/dist/types/src/messages/SignedMessage.d.ts.map +1 -1
  345. package/dist/types/src/primitives/AESGCM.d.ts +18 -0
  346. package/dist/types/src/primitives/AESGCM.d.ts.map +1 -1
  347. package/dist/types/src/primitives/BigNumber.d.ts +8 -0
  348. package/dist/types/src/primitives/BigNumber.d.ts.map +1 -1
  349. package/dist/types/src/primitives/ECDSA.d.ts +24 -0
  350. package/dist/types/src/primitives/ECDSA.d.ts.map +1 -1
  351. package/dist/types/src/primitives/Hash.d.ts +8 -8
  352. package/dist/types/src/primitives/Hash.d.ts.map +1 -1
  353. package/dist/types/src/primitives/Point.d.ts +1 -0
  354. package/dist/types/src/primitives/Point.d.ts.map +1 -1
  355. package/dist/types/src/primitives/PrivateKey.d.ts +27 -0
  356. package/dist/types/src/primitives/PrivateKey.d.ts.map +1 -1
  357. package/dist/types/src/primitives/Random.d.ts.map +1 -1
  358. package/dist/types/src/primitives/ReaderUint8Array.d.ts +32 -0
  359. package/dist/types/src/primitives/ReaderUint8Array.d.ts.map +1 -0
  360. package/dist/types/src/primitives/ReductionContext.d.ts +9 -0
  361. package/dist/types/src/primitives/ReductionContext.d.ts.map +1 -1
  362. package/dist/types/src/primitives/Secp256r1.d.ts.map +1 -1
  363. package/dist/types/src/primitives/TransactionSignature.d.ts +16 -4
  364. package/dist/types/src/primitives/TransactionSignature.d.ts.map +1 -1
  365. package/dist/types/src/primitives/WriterUint8Array.d.ts +54 -0
  366. package/dist/types/src/primitives/WriterUint8Array.d.ts.map +1 -0
  367. package/dist/types/src/primitives/utils.d.ts +19 -6
  368. package/dist/types/src/primitives/utils.d.ts.map +1 -1
  369. package/dist/types/src/remittance/CommsLayer.d.ts +50 -0
  370. package/dist/types/src/remittance/CommsLayer.d.ts.map +1 -0
  371. package/dist/types/src/remittance/IdentityLayer.d.ts +35 -0
  372. package/dist/types/src/remittance/IdentityLayer.d.ts.map +1 -0
  373. package/dist/types/src/remittance/RemittanceManager.d.ts +452 -0
  374. package/dist/types/src/remittance/RemittanceManager.d.ts.map +1 -0
  375. package/dist/types/src/remittance/RemittanceModule.d.ts +106 -0
  376. package/dist/types/src/remittance/RemittanceModule.d.ts.map +1 -0
  377. package/dist/types/src/remittance/index.d.ts +7 -0
  378. package/dist/types/src/remittance/index.d.ts.map +1 -0
  379. package/dist/types/src/remittance/modules/BasicBRC29.d.ts +133 -0
  380. package/dist/types/src/remittance/modules/BasicBRC29.d.ts.map +1 -0
  381. package/dist/types/src/remittance/modules/index.d.ts +2 -0
  382. package/dist/types/src/remittance/modules/index.d.ts.map +1 -0
  383. package/dist/types/src/remittance/types.d.ts +238 -0
  384. package/dist/types/src/remittance/types.d.ts.map +1 -0
  385. package/dist/types/src/script/OP.d.ts +5 -3
  386. package/dist/types/src/script/OP.d.ts.map +1 -1
  387. package/dist/types/src/script/Script.d.ts.map +1 -1
  388. package/dist/types/src/script/Spend.d.ts +7 -0
  389. package/dist/types/src/script/Spend.d.ts.map +1 -1
  390. package/dist/types/src/script/templates/PushDrop.d.ts +3 -4
  391. package/dist/types/src/script/templates/PushDrop.d.ts.map +1 -1
  392. package/dist/types/src/storage/StorageUtils.d.ts.map +1 -1
  393. package/dist/types/src/transaction/Beef.d.ts +26 -9
  394. package/dist/types/src/transaction/Beef.d.ts.map +1 -1
  395. package/dist/types/src/transaction/BeefTx.d.ts +13 -6
  396. package/dist/types/src/transaction/BeefTx.d.ts.map +1 -1
  397. package/dist/types/src/transaction/MerklePath.d.ts +16 -3
  398. package/dist/types/src/transaction/MerklePath.d.ts.map +1 -1
  399. package/dist/types/src/transaction/Transaction.d.ts +58 -7
  400. package/dist/types/src/transaction/Transaction.d.ts.map +1 -1
  401. package/dist/types/src/transaction/broadcasters/ARC.d.ts.map +1 -1
  402. package/dist/types/src/transaction/http/BinaryFetchClient.d.ts +0 -2
  403. package/dist/types/src/transaction/http/BinaryFetchClient.d.ts.map +1 -1
  404. package/dist/types/src/wallet/Wallet.interfaces.d.ts +5 -5
  405. package/dist/types/src/wallet/Wallet.interfaces.d.ts.map +1 -1
  406. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  407. package/dist/umd/bundle.js +13 -13
  408. package/dist/umd/bundle.js.map +1 -1
  409. package/docs/index.md +3 -1
  410. package/docs/reference/auth.md +8 -12
  411. package/docs/reference/primitives.md +260 -55
  412. package/docs/reference/remittance.md +2166 -0
  413. package/docs/reference/script.md +12 -4
  414. package/docs/reference/transaction.md +203 -34
  415. package/docs/reference/wallet.md +6 -5
  416. package/docs/remittance-getting-started.md +138 -0
  417. package/mod.ts +1 -0
  418. package/package.json +11 -1
  419. package/src/auth/Peer.ts +196 -85
  420. package/src/auth/__tests/Peer.test.ts +424 -257
  421. package/src/auth/clients/AuthFetch.ts +18 -3
  422. package/src/auth/types.ts +2 -0
  423. package/src/messages/EncryptedMessage.ts +19 -0
  424. package/src/overlay-tools/__tests/SHIPBroadcaster.test.ts +7 -0
  425. package/src/primitives/AESGCM.ts +75 -34
  426. package/src/primitives/BigNumber.ts +27 -31
  427. package/src/primitives/ECDSA.ts +41 -2
  428. package/src/primitives/PrivateKey.ts +27 -0
  429. package/src/primitives/ReaderUint8Array.ts +196 -0
  430. package/src/primitives/ReductionContext.ts +44 -48
  431. package/src/primitives/TransactionSignature.ts +129 -10
  432. package/src/primitives/WriterUint8Array.ts +195 -0
  433. package/src/primitives/__tests/AESGCM.test.ts +31 -0
  434. package/src/primitives/__tests/ECDSA.test.ts +16 -0
  435. package/src/primitives/__tests/ReaderUint8Array.test.ts +317 -0
  436. package/src/primitives/__tests/WriterUint8Array.test.ts +208 -0
  437. package/src/primitives/utils.ts +31 -4
  438. package/src/remittance/CommsLayer.ts +41 -0
  439. package/src/remittance/IdentityLayer.ts +32 -0
  440. package/src/remittance/RemittanceManager.ts +1672 -0
  441. package/src/remittance/RemittanceModule.ts +92 -0
  442. package/src/remittance/__tests/BasicBRC29.test.ts +188 -0
  443. package/src/remittance/__tests/RemittanceManager.test.ts +493 -0
  444. package/src/remittance/__tests/examples.ts +130 -0
  445. package/src/remittance/index.ts +6 -0
  446. package/src/remittance/modules/BasicBRC29.ts +361 -0
  447. package/src/remittance/modules/index.ts +1 -0
  448. package/src/remittance/types.ts +284 -0
  449. package/src/script/OP.ts +15 -13
  450. package/src/script/Script.ts +3 -1
  451. package/src/script/Spend.ts +128 -52
  452. package/src/script/__tests/Chronicle.test.ts +186 -0
  453. package/src/script/__tests/Spend.test.ts +1 -1
  454. package/src/script/__tests/SpendValildVectors.test.ts +63 -0
  455. package/src/script/__tests/lrshiftnum.test.ts +185 -0
  456. package/src/script/__tests/sighashTestData.ts +1031 -0
  457. package/src/script/__tests/spend.valid.vectors.ts +9 -16
  458. package/src/script/templates/PushDrop.ts +32 -17
  459. package/src/script/templates/__tests/PushDrop.test.ts +28 -0
  460. package/src/storage/__tests/StorageUploader.test.ts +1 -1
  461. package/src/transaction/Beef.ts +103 -40
  462. package/src/transaction/BeefTx.ts +38 -19
  463. package/src/transaction/MerklePath.ts +30 -9
  464. package/src/transaction/Transaction.ts +277 -38
  465. package/src/transaction/__tests/Beef.test.ts +77 -0
  466. package/src/transaction/__tests/Transaction.test.ts +641 -3
  467. package/src/transaction/broadcasters/ARC.ts +34 -7
  468. package/src/transaction/broadcasters/__tests/ARC.test.ts +98 -0
  469. package/src/wallet/Wallet.interfaces.ts +5 -5
  470. package/dist/cjs/src/storage/__test/StorageDownloader.test.js +0 -171
  471. package/dist/cjs/src/storage/__test/StorageDownloader.test.js.map +0 -1
  472. package/dist/cjs/src/storage/__test/StorageUploader.test.js +0 -163
  473. package/dist/cjs/src/storage/__test/StorageUploader.test.js.map +0 -1
  474. package/dist/cjs/src/storage/__test/StorageUtils.test.js +0 -97
  475. package/dist/cjs/src/storage/__test/StorageUtils.test.js.map +0 -1
  476. package/dist/esm/src/storage/__test/StorageDownloader.test.js +0 -166
  477. package/dist/esm/src/storage/__test/StorageDownloader.test.js.map +0 -1
  478. package/dist/esm/src/storage/__test/StorageUploader.test.js +0 -135
  479. package/dist/esm/src/storage/__test/StorageUploader.test.js.map +0 -1
  480. package/dist/esm/src/storage/__test/StorageUtils.test.js +0 -72
  481. package/dist/esm/src/storage/__test/StorageUtils.test.js.map +0 -1
  482. package/dist/types/src/storage/__test/StorageDownloader.test.d.ts +0 -2
  483. package/dist/types/src/storage/__test/StorageDownloader.test.d.ts.map +0 -1
  484. package/dist/types/src/storage/__test/StorageUploader.test.d.ts +0 -2
  485. package/dist/types/src/storage/__test/StorageUploader.test.d.ts.map +0 -1
  486. package/dist/types/src/storage/__test/StorageUtils.test.d.ts +0 -2
  487. package/dist/types/src/storage/__test/StorageUtils.test.d.ts.map +0 -1
@@ -0,0 +1,1672 @@
1
+ import type {
2
+ Invoice,
3
+ IdentityVerificationRequest,
4
+ IdentityVerificationResponse,
5
+ IdentityVerificationAcknowledgment,
6
+ Settlement,
7
+ Receipt,
8
+ Termination,
9
+ RemittanceEnvelope,
10
+ PeerMessage,
11
+ ThreadId,
12
+ UnixMillis,
13
+ LoggerLike,
14
+ ModuleContext,
15
+ RemittanceKind,
16
+ RemittanceOptionId,
17
+ RemittanceThreadState
18
+ } from './types.js'
19
+ import { REMITTANCE_STATE_TRANSITIONS } from './types.js'
20
+ import type { CommsLayer } from './CommsLayer.js'
21
+ import type { IdentityLayer } from './IdentityLayer.js'
22
+ import type { RemittanceModule } from './RemittanceModule.js'
23
+ import { OriginatorDomainNameStringUnder250Bytes, PubKeyHex, WalletInterface } from '../wallet/Wallet.interfaces.js'
24
+ import { toBase64 } from '../primitives/utils.js'
25
+ import Random from '../primitives/Random.js'
26
+
27
+ export const DEFAULT_REMITTANCE_MESSAGEBOX = 'remittance_inbox'
28
+
29
+ export interface RemittanceManagerRuntimeOptions {
30
+ /** Identity verification options. */
31
+ identityOptions?: {
32
+ /** At what point should a maker request identity verification? */
33
+ makerRequestIdentity?: 'never' | 'beforeInvoicing' | 'beforeSettlement'
34
+ /** At what point should a taker request identity verification? */
35
+ takerRequestIdentity?: 'never' | 'beforeInvoicing' | 'beforeSettlement'
36
+ }
37
+ /** If true, payees are expected to send receipts. */
38
+ receiptProvided: boolean
39
+ /** If true, manager auto-sends receipts as soon as a settlement is processed. */
40
+ autoIssueReceipt: boolean
41
+ /** Invoice expiry in seconds, or -1 for no expiry. */
42
+ invoiceExpirySeconds: number
43
+ /** Identity verification timeout in milliseconds. */
44
+ identityTimeoutMs: number
45
+ /** Identity verification poll interval in milliseconds. */
46
+ identityPollIntervalMs: number
47
+ }
48
+
49
+ export interface RemittanceManagerConfig {
50
+ /** Optional message box name to use for communication. */
51
+ messageBox?: string
52
+ /** Optional originator forwarded to wallet APIs. */
53
+ originator?: OriginatorDomainNameStringUnder250Bytes
54
+ /**
55
+ * Provide a logger. If omitted, RemittanceManager stays quiet.
56
+ *
57
+ * The manager itself never throws on network/message parsing errors; it will mark threads as errored.
58
+ */
59
+ logger?: LoggerLike
60
+
61
+ /** Runtime options that influence core behavior. */
62
+ options?: Partial<RemittanceManagerRuntimeOptions>
63
+
64
+ /** Modules (remittance options) available to this manager. */
65
+ remittanceModules: Array<RemittanceModule<any, any, any>>
66
+
67
+ /** Optional identity layer for exchanging certificates before transacting. */
68
+ identityLayer?: IdentityLayer
69
+
70
+ /** Optional event callback for remittance lifecycle events. */
71
+ onEvent?: (event: RemittanceEvent) => void
72
+ /** Optional event callbacks keyed by process. */
73
+ events?: RemittanceEventHandlers
74
+
75
+ /** Persist manager state (threads). */
76
+ stateSaver?: (state: RemittanceManagerState) => Promise<void> | void
77
+ /** Load manager state (threads). */
78
+ stateLoader?: () => Promise<RemittanceManagerState | undefined> | RemittanceManagerState | undefined
79
+
80
+ /** Injectable clock for tests. */
81
+ now?: () => UnixMillis
82
+ /** Injectable thread id factory for tests. */
83
+ threadIdFactory?: () => ThreadId
84
+ }
85
+
86
+ export type RemittanceEvent =
87
+ | {
88
+ type: 'threadCreated'
89
+ threadId: ThreadId
90
+ thread: Thread
91
+ }
92
+ | {
93
+ type: 'stateChanged'
94
+ threadId: ThreadId
95
+ previous: RemittanceThreadState
96
+ next: RemittanceThreadState
97
+ reason?: string
98
+ }
99
+ | {
100
+ type: 'envelopeSent'
101
+ threadId: ThreadId
102
+ envelope: RemittanceEnvelope
103
+ transportMessageId: string
104
+ }
105
+ | {
106
+ type: 'envelopeReceived'
107
+ threadId: ThreadId
108
+ envelope: RemittanceEnvelope
109
+ transportMessageId: string
110
+ }
111
+ | {
112
+ type: 'identityRequested'
113
+ threadId: ThreadId
114
+ direction: 'in' | 'out'
115
+ request: IdentityVerificationRequest
116
+ }
117
+ | {
118
+ type: 'identityResponded'
119
+ threadId: ThreadId
120
+ direction: 'in' | 'out'
121
+ response: IdentityVerificationResponse
122
+ }
123
+ | {
124
+ type: 'identityAcknowledged'
125
+ threadId: ThreadId
126
+ direction: 'in' | 'out'
127
+ acknowledgment: IdentityVerificationAcknowledgment
128
+ }
129
+ | {
130
+ type: 'invoiceSent'
131
+ threadId: ThreadId
132
+ invoice: Invoice
133
+ }
134
+ | {
135
+ type: 'invoiceReceived'
136
+ threadId: ThreadId
137
+ invoice: Invoice
138
+ }
139
+ | {
140
+ type: 'settlementSent'
141
+ threadId: ThreadId
142
+ settlement: Settlement
143
+ }
144
+ | {
145
+ type: 'settlementReceived'
146
+ threadId: ThreadId
147
+ settlement: Settlement
148
+ }
149
+ | {
150
+ type: 'receiptSent'
151
+ threadId: ThreadId
152
+ receipt: Receipt
153
+ }
154
+ | {
155
+ type: 'receiptReceived'
156
+ threadId: ThreadId
157
+ receipt: Receipt
158
+ }
159
+ | {
160
+ type: 'terminationSent'
161
+ threadId: ThreadId
162
+ termination: Termination
163
+ }
164
+ | {
165
+ type: 'terminationReceived'
166
+ threadId: ThreadId
167
+ termination: Termination
168
+ }
169
+ | {
170
+ type: 'error'
171
+ threadId: ThreadId
172
+ error: string
173
+ }
174
+
175
+ export interface RemittanceEventHandlers {
176
+ onThreadCreated?: (event: Extract<RemittanceEvent, { type: 'threadCreated' }>) => void
177
+ onStateChanged?: (event: Extract<RemittanceEvent, { type: 'stateChanged' }>) => void
178
+ onEnvelopeSent?: (event: Extract<RemittanceEvent, { type: 'envelopeSent' }>) => void
179
+ onEnvelopeReceived?: (event: Extract<RemittanceEvent, { type: 'envelopeReceived' }>) => void
180
+ onIdentityRequested?: (event: Extract<RemittanceEvent, { type: 'identityRequested' }>) => void
181
+ onIdentityResponded?: (event: Extract<RemittanceEvent, { type: 'identityResponded' }>) => void
182
+ onIdentityAcknowledged?: (event: Extract<RemittanceEvent, { type: 'identityAcknowledged' }>) => void
183
+ onInvoiceSent?: (event: Extract<RemittanceEvent, { type: 'invoiceSent' }>) => void
184
+ onInvoiceReceived?: (event: Extract<RemittanceEvent, { type: 'invoiceReceived' }>) => void
185
+ onSettlementSent?: (event: Extract<RemittanceEvent, { type: 'settlementSent' }>) => void
186
+ onSettlementReceived?: (event: Extract<RemittanceEvent, { type: 'settlementReceived' }>) => void
187
+ onReceiptSent?: (event: Extract<RemittanceEvent, { type: 'receiptSent' }>) => void
188
+ onReceiptReceived?: (event: Extract<RemittanceEvent, { type: 'receiptReceived' }>) => void
189
+ onTerminationSent?: (event: Extract<RemittanceEvent, { type: 'terminationSent' }>) => void
190
+ onTerminationReceived?: (event: Extract<RemittanceEvent, { type: 'terminationReceived' }>) => void
191
+ onError?: (event: Extract<RemittanceEvent, { type: 'error' }>) => void
192
+ }
193
+
194
+ export interface Thread {
195
+ threadId: ThreadId
196
+ counterparty: PubKeyHex
197
+ myRole: 'maker' | 'taker'
198
+ theirRole: 'maker' | 'taker'
199
+ createdAt: UnixMillis
200
+ updatedAt: UnixMillis
201
+ state: RemittanceThreadState
202
+ /** State transition log for audit purposes. */
203
+ stateLog: Array<{ at: UnixMillis, from: RemittanceThreadState, to: RemittanceThreadState, reason?: string }>
204
+
205
+ /** Transport messageIds processed for this thread (dedupe across retries). */
206
+ processedMessageIds: string[]
207
+
208
+ /** Protocol envelopes received/sent (for debugging/audit). */
209
+ protocolLog: Array<{
210
+ direction: 'in' | 'out'
211
+ envelope: RemittanceEnvelope
212
+ transportMessageId: string
213
+ }>
214
+
215
+ identity: {
216
+ certsSent: IdentityVerificationResponse['certificates']
217
+ certsReceived: IdentityVerificationResponse['certificates']
218
+ requestSent: boolean
219
+ responseSent: boolean
220
+ acknowledgmentSent: boolean
221
+ acknowledgmentReceived: boolean
222
+ }
223
+
224
+ invoice?: Invoice
225
+ settlement?: Settlement
226
+ receipt?: Receipt
227
+ termination?: Termination
228
+
229
+ flags: {
230
+ hasIdentified: boolean
231
+ hasInvoiced: boolean
232
+ hasPaid: boolean
233
+ hasReceipted: boolean
234
+ error: boolean
235
+ }
236
+
237
+ lastError?: { message: string, at: UnixMillis }
238
+ }
239
+
240
+ export interface RemittanceManagerState {
241
+ v: 1
242
+ threads: Thread[]
243
+ defaultPaymentOptionId?: string
244
+ }
245
+
246
+ export interface ComposeInvoiceInput {
247
+ /** Human note/memo. */
248
+ note?: string
249
+ /** Line items. */
250
+ lineItems: Invoice['lineItems']
251
+ /** Total amount. */
252
+ total: Invoice['total']
253
+ invoiceNumber?: string
254
+ arbitrary?: Record<string, unknown>
255
+ }
256
+
257
+ /**
258
+ * RemittanceManager.
259
+ *
260
+ * Responsibilities:
261
+ * - message transport via CommsLayer
262
+ * - thread lifecycle and persistence (via stateSaver/stateLoader)
263
+ * - invoice creation and transmission (when invoices are used)
264
+ * - settlement and settlement routing to the appropriate module
265
+ * - receipt issuance and receipt routing to the appropriate module
266
+ * - identity and identity certificate exchange (when identity layer is used)
267
+ *
268
+ * Non-responsibilities (left to modules):
269
+ * - transaction structure (whether UTXO “offer” formats, token logic, BRC-98/99 specifics, etc.)
270
+ * - validation rules for settlement (e.g. partial tx templates, UTXO validity, etc.)
271
+ * - on-chain broadcasting strategy or non-chain settlement specifics (like legacy payment protocols)
272
+ * - Providing option terms for invoices
273
+ * - Building settlement artifacts
274
+ * - Accepting/rejecting settlements
275
+ * - Deciding which identity certificates to request
276
+ * - Deciding about sufficiency of identity certificates
277
+ * - Preparing/processing specific receipt formats
278
+ * - Internal business logic like order fulfillment, refunds, etc.
279
+ */
280
+ export class RemittanceManager {
281
+ readonly wallet: WalletInterface
282
+ readonly comms: CommsLayer
283
+ readonly cfg: RemittanceManagerConfig
284
+
285
+ private readonly messageBox: string
286
+ private readonly now: () => UnixMillis
287
+ private readonly threadIdFactory: () => ThreadId
288
+
289
+ private readonly moduleRegistry: Map<string, RemittanceModule<any, any, any>>
290
+ private readonly runtime: RemittanceManagerRuntimeOptions
291
+ private readonly eventListeners: Set<(event: RemittanceEvent) => void>
292
+ private readonly stateWaiters: Map<ThreadId, Array<{ state: RemittanceThreadState, resolve: () => void, reject: (err: Error) => void }>>
293
+ private readonly eventHandlers?: RemittanceEventHandlers
294
+
295
+ /** Default option id used when paying an invoice, if not overridden per-call. */
296
+ private defaultPaymentOptionId?: string
297
+
298
+ /** Mutable threads list (persisted via stateSaver). */
299
+ threads: Thread[]
300
+
301
+ /** Cached identity key if wallet provides it. */
302
+ private myIdentityKey?: PubKeyHex
303
+
304
+ constructor (cfg: RemittanceManagerConfig, wallet: WalletInterface, commsLayer: CommsLayer, threads: Thread[] = []) {
305
+ this.cfg = cfg
306
+ this.wallet = wallet
307
+ this.comms = commsLayer
308
+ this.messageBox = cfg.messageBox ?? DEFAULT_REMITTANCE_MESSAGEBOX
309
+
310
+ this.now = cfg.now ?? (() => Date.now())
311
+ this.threadIdFactory = cfg.threadIdFactory ?? defaultThreadIdFactory
312
+
313
+ this.moduleRegistry = new Map(cfg.remittanceModules.map((m) => [m.id, m]))
314
+ this.eventListeners = new Set()
315
+ this.stateWaiters = new Map()
316
+ this.eventHandlers = cfg.events
317
+ if (typeof cfg.onEvent === 'function') {
318
+ this.eventListeners.add(cfg.onEvent)
319
+ }
320
+
321
+ this.runtime = {
322
+ identityOptions: cfg.options?.identityOptions ?? {
323
+ makerRequestIdentity: 'never',
324
+ takerRequestIdentity: 'never'
325
+ },
326
+ receiptProvided: cfg.options?.receiptProvided ?? true,
327
+ autoIssueReceipt: cfg.options?.autoIssueReceipt ?? true,
328
+ invoiceExpirySeconds: cfg.options?.invoiceExpirySeconds ?? 3600,
329
+ identityTimeoutMs: cfg.options?.identityTimeoutMs ?? 30_000,
330
+ identityPollIntervalMs: cfg.options?.identityPollIntervalMs ?? 500
331
+ }
332
+
333
+ this.threads = threads.map((thread) => this.ensureThreadState(thread))
334
+ }
335
+
336
+ /**
337
+ * Loads persisted state from cfg.stateLoader (if provided).
338
+ *
339
+ * Safe to call multiple times.
340
+ */
341
+ async init (): Promise<void> {
342
+ if (typeof this.cfg.stateLoader !== 'function') return
343
+
344
+ const loaded = await this.cfg.stateLoader()
345
+ if (typeof loaded !== 'object') return
346
+
347
+ this.loadState(loaded)
348
+
349
+ if (typeof loaded.defaultPaymentOptionId === 'string') {
350
+ this.defaultPaymentOptionId = loaded.defaultPaymentOptionId
351
+ }
352
+
353
+ await this.refreshMyIdentityKey()
354
+ }
355
+
356
+ /**
357
+ * Registers a remittance event listener.
358
+ */
359
+ onEvent (listener: (event: RemittanceEvent) => void): () => void {
360
+ this.eventListeners.add(listener)
361
+ return () => {
362
+ this.eventListeners.delete(listener)
363
+ }
364
+ }
365
+
366
+ /**
367
+ * Sets a default payment option (module id) to use when paying invoices.
368
+ */
369
+ preselectPaymentOption (optionId: string): void {
370
+ this.defaultPaymentOptionId = optionId
371
+ }
372
+
373
+ /**
374
+ * Returns an immutable snapshot of current manager state suitable for persistence.
375
+ */
376
+ saveState (): RemittanceManagerState {
377
+ return {
378
+ v: 1,
379
+ threads: JSON.parse(JSON.stringify(this.threads)) as Thread[],
380
+ defaultPaymentOptionId: this.defaultPaymentOptionId
381
+ }
382
+ }
383
+
384
+ /**
385
+ * Loads state from an object previously produced by saveState().
386
+ */
387
+ loadState (state: RemittanceManagerState): void {
388
+ if (state.v !== 1) throw new Error('Unsupported RemittanceManagerState version')
389
+ this.threads = (state.threads ?? []).map((thread) => this.ensureThreadState(thread))
390
+ this.defaultPaymentOptionId = state.defaultPaymentOptionId
391
+ }
392
+
393
+ /**
394
+ * Persists current state via cfg.stateSaver (if provided).
395
+ */
396
+ async persistState (): Promise<void> {
397
+ if (this.cfg.stateSaver == null) return
398
+ await this.cfg.stateSaver(this.saveState())
399
+ }
400
+
401
+ /**
402
+ * Syncs threads by fetching pending messages from the comms layer and processing them.
403
+ *
404
+ * Processing is idempotent using transport messageIds tracked per thread.
405
+ * Messages are acknowledged after they are successfully applied to local state.
406
+ */
407
+ async syncThreads (hostOverride?: string): Promise<void> {
408
+ await this.refreshMyIdentityKey()
409
+
410
+ const msgs = await this.comms.listMessages({ messageBox: this.messageBox, host: hostOverride })
411
+
412
+ for (const msg of msgs) {
413
+ await this.handleInboundMessage(msg)
414
+ }
415
+ }
416
+
417
+ /**
418
+ * Starts listening for live messages (if the CommsLayer supports it).
419
+ */
420
+ async startListening (hostOverride?: string): Promise<void> {
421
+ if (typeof this.comms.listenForLiveMessages !== 'function') {
422
+ throw new Error('CommsLayer does not support live message listening')
423
+ }
424
+
425
+ await this.comms.listenForLiveMessages({
426
+ messageBox: this.messageBox,
427
+ overrideHost: hostOverride,
428
+ onMessage: (msg) => {
429
+ void this.handleInboundMessage(msg)
430
+ }
431
+ })
432
+ }
433
+
434
+ /**
435
+ * Creates, records, and sends an invoice to a counterparty.
436
+ *
437
+ * Returns a handle you can use to wait for payment/receipt.
438
+ */
439
+ async sendInvoice (to: PubKeyHex, input: ComposeInvoiceInput, hostOverride?: string): Promise<InvoiceHandle> {
440
+ await this.refreshMyIdentityKey()
441
+ const threadId = this.threadIdFactory()
442
+ const createdAt = this.now()
443
+
444
+ const myKey = this.requireMyIdentityKey('sendInvoice requires the wallet to provide an identity key')
445
+
446
+ const thread: Thread = {
447
+ threadId,
448
+ counterparty: to,
449
+ myRole: 'maker',
450
+ theirRole: 'taker',
451
+ createdAt,
452
+ updatedAt: createdAt,
453
+ state: 'new',
454
+ stateLog: [],
455
+ processedMessageIds: [],
456
+ protocolLog: [],
457
+ identity: {
458
+ certsSent: [],
459
+ certsReceived: [],
460
+ requestSent: false,
461
+ responseSent: false,
462
+ acknowledgmentSent: false,
463
+ acknowledgmentReceived: false
464
+ },
465
+ flags: {
466
+ hasIdentified: false,
467
+ hasInvoiced: false,
468
+ hasPaid: false,
469
+ hasReceipted: false,
470
+ error: false
471
+ }
472
+ }
473
+
474
+ this.threads.push(thread)
475
+ this.emitEvent({ type: 'threadCreated', threadId: thread.threadId, thread })
476
+
477
+ if (thread.identity.responseSent && !thread.flags.hasIdentified) {
478
+ await this.waitForIdentityAcknowledgment(threadId, {
479
+ timeoutMs: this.runtime.identityTimeoutMs,
480
+ pollIntervalMs: this.runtime.identityPollIntervalMs
481
+ })
482
+ }
483
+
484
+ if (this.shouldRequestIdentity(thread, 'beforeInvoicing')) {
485
+ await this.ensureIdentityExchange(thread, hostOverride)
486
+ }
487
+
488
+ const invoice = await this.composeInvoice(threadId, myKey, to, input)
489
+ thread.invoice = invoice
490
+ thread.flags.hasInvoiced = true
491
+ this.transitionThreadState(thread, 'invoiced', 'invoice created')
492
+
493
+ // Generate option terms for each configured module.
494
+ for (const mod of this.moduleRegistry.values()) {
495
+ if (typeof mod.createOption !== 'function') continue
496
+ const option = await mod.createOption({ threadId, invoice }, this.moduleContext())
497
+ invoice.options[mod.id] = option
498
+ }
499
+
500
+ const env = this.makeEnvelope('invoice', threadId, invoice)
501
+ const mid = await this.sendEnvelope(to, env, hostOverride)
502
+ thread.protocolLog.push({ direction: 'out', envelope: env, transportMessageId: mid })
503
+ this.emitEvent({ type: 'invoiceSent', threadId: thread.threadId, invoice })
504
+ thread.updatedAt = this.now()
505
+ await this.persistState()
506
+
507
+ return new InvoiceHandle(this, threadId)
508
+ }
509
+
510
+ /**
511
+ * Sends an invoice for an existing thread, e.g. after an identity request was received.
512
+ */
513
+ async sendInvoiceForThread (threadId: ThreadId, input: ComposeInvoiceInput, hostOverride?: string): Promise<InvoiceHandle> {
514
+ await this.refreshMyIdentityKey()
515
+ const thread = this.getThreadOrThrow(threadId)
516
+
517
+ if (thread.flags.error) throw new Error('Thread is in error state')
518
+ if (thread.myRole !== 'maker') throw new Error('Only makers can send invoices')
519
+ if (thread.invoice != null) throw new Error('Thread already has an invoice')
520
+
521
+ if (thread.identity.responseSent && !thread.flags.hasIdentified) {
522
+ await this.waitForIdentityAcknowledgment(threadId, {
523
+ timeoutMs: this.runtime.identityTimeoutMs,
524
+ pollIntervalMs: this.runtime.identityPollIntervalMs
525
+ })
526
+ }
527
+
528
+ if (this.shouldRequestIdentity(thread, 'beforeInvoicing')) {
529
+ await this.ensureIdentityExchange(thread, hostOverride)
530
+ }
531
+
532
+ const myKey = this.requireMyIdentityKey('sendInvoice requires the wallet to provide an identity key')
533
+ const invoice = await this.composeInvoice(threadId, myKey, thread.counterparty, input)
534
+ thread.invoice = invoice
535
+ thread.flags.hasInvoiced = true
536
+ this.transitionThreadState(thread, 'invoiced', 'invoice created')
537
+
538
+ for (const mod of this.moduleRegistry.values()) {
539
+ if (typeof mod.createOption !== 'function') continue
540
+ const option = await mod.createOption({ threadId, invoice }, this.moduleContext())
541
+ invoice.options[mod.id] = option
542
+ }
543
+
544
+ const env = this.makeEnvelope('invoice', threadId, invoice)
545
+ const mid = await this.sendEnvelope(thread.counterparty, env, hostOverride)
546
+ thread.protocolLog.push({ direction: 'out', envelope: env, transportMessageId: mid })
547
+ this.emitEvent({ type: 'invoiceSent', threadId: thread.threadId, invoice })
548
+ thread.updatedAt = this.now()
549
+ await this.persistState()
550
+
551
+ return new InvoiceHandle(this, threadId)
552
+ }
553
+
554
+ /**
555
+ * Returns invoice handles that this manager can pay (we are the taker/payer).
556
+ */
557
+ findInvoicesPayable (counterparty?: PubKeyHex): InvoiceHandle[] {
558
+ const hasCounterparty = typeof counterparty === 'string' && counterparty.length > 0
559
+ return this.threads
560
+ .filter((t) => t.myRole === 'taker' && (t.invoice != null) && (t.settlement == null) && !t.flags.error)
561
+ .filter((t) => (hasCounterparty ? t.counterparty === counterparty : true))
562
+ .map((t) => new InvoiceHandle(this, t.threadId))
563
+ }
564
+
565
+ /**
566
+ * Returns invoice handles that we issued and are waiting to receive settlement for.
567
+ */
568
+ findReceivableInvoices (counterparty?: PubKeyHex): InvoiceHandle[] {
569
+ const hasCounterparty = typeof counterparty === 'string' && counterparty.length > 0
570
+ return this.threads
571
+ .filter((t) => t.myRole === 'maker' && (t.invoice != null) && (t.settlement == null) && !t.flags.error)
572
+ .filter((t) => (hasCounterparty ? t.counterparty === counterparty : true))
573
+ .map((t) => new InvoiceHandle(this, t.threadId))
574
+ }
575
+
576
+ /**
577
+ * Pays an invoice by selecting a remittance option and sending a settlement message.
578
+ *
579
+ * If receipts are enabled (receiptProvided), this method will optionally wait for a receipt.
580
+ */
581
+ async pay (threadId: ThreadId, optionId?: string, hostOverride?: string): Promise<Receipt | Termination | undefined> {
582
+ await this.refreshMyIdentityKey()
583
+
584
+ const thread = this.getThreadOrThrow(threadId)
585
+ if (thread.invoice == null) throw new Error('Thread has no invoice to pay')
586
+
587
+ if (thread.flags.error) throw new Error('Thread is in error state')
588
+ if (thread.settlement != null) throw new Error('Invoice already paid (settlement exists)')
589
+
590
+ if (thread.identity.responseSent && !thread.flags.hasIdentified) {
591
+ await this.waitForIdentityAcknowledgment(threadId, {
592
+ timeoutMs: this.runtime.identityTimeoutMs,
593
+ pollIntervalMs: this.runtime.identityPollIntervalMs
594
+ })
595
+ }
596
+
597
+ if (this.shouldRequestIdentity(thread, 'beforeSettlement')) {
598
+ await this.ensureIdentityExchange(thread, hostOverride)
599
+ }
600
+
601
+ // Check expiry.
602
+ const expiresAt = thread.invoice.expiresAt
603
+ if (typeof expiresAt === 'number' && this.now() > expiresAt) {
604
+ throw new Error('Invoice is expired')
605
+ }
606
+
607
+ const chosenOptionId = optionId ?? this.defaultPaymentOptionId ?? Object.keys(thread.invoice.options)[0]
608
+ if (chosenOptionId == null || chosenOptionId === '') {
609
+ throw new Error('No remittance options available on invoice')
610
+ }
611
+
612
+ const module = this.moduleRegistry.get(chosenOptionId)
613
+ if (module == null) {
614
+ throw new Error(`No configured remittance module for option: ${chosenOptionId}`)
615
+ }
616
+
617
+ const option = thread.invoice.options[chosenOptionId]
618
+ const myKey = this.requireMyIdentityKey('pay() requires the wallet to provide an identity key')
619
+
620
+ const buildResult = await module.buildSettlement(
621
+ { threadId, invoice: thread.invoice, option, note: thread.invoice.note },
622
+ this.moduleContext()
623
+ )
624
+
625
+ if (buildResult.action === 'terminate') {
626
+ const termination = buildResult.termination
627
+ await this.sendTermination(thread, thread.counterparty, termination.message, termination.details, termination.code)
628
+ await this.persistState()
629
+ return termination
630
+ }
631
+
632
+ const settlement: Settlement = {
633
+ kind: 'settlement',
634
+ threadId,
635
+ moduleId: module.id,
636
+ optionId: chosenOptionId,
637
+ sender: myKey,
638
+ createdAt: this.now(),
639
+ artifact: buildResult.artifact,
640
+ note: thread.invoice.note
641
+ }
642
+
643
+ const env = this.makeEnvelope('settlement', threadId, settlement)
644
+
645
+ // Send settlement to payee (invoice.payee).
646
+ const mid = await this.sendEnvelope(thread.invoice.payee, env, hostOverride)
647
+ thread.protocolLog.push({ direction: 'out', envelope: env, transportMessageId: mid })
648
+ this.emitEvent({ type: 'settlementSent', threadId: thread.threadId, settlement })
649
+
650
+ thread.settlement = settlement
651
+ thread.flags.hasPaid = true
652
+ this.transitionThreadState(thread, 'settled', 'settlement sent')
653
+ thread.updatedAt = this.now()
654
+ await this.persistState()
655
+
656
+ if (!this.runtime.receiptProvided) {
657
+ return undefined
658
+ }
659
+
660
+ // Wait for receipt (polling + syncThreads) up to a default timeout.
661
+ return await this.waitForReceipt(threadId)
662
+ }
663
+
664
+ /**
665
+ * Waits for a receipt to arrive for a thread.
666
+ *
667
+ * Uses polling via syncThreads because live listeners are optional.
668
+ */
669
+ async waitForReceipt (threadId: ThreadId, opts: { timeoutMs?: number, pollIntervalMs?: number } = {}): Promise<Receipt | Termination> {
670
+ const timeoutMs = opts.timeoutMs ?? 30_000
671
+ const pollIntervalMs = opts.pollIntervalMs ?? 500
672
+
673
+ const start = this.now()
674
+ while (this.now() - start < timeoutMs) {
675
+ const t = this.getThreadOrThrow(threadId)
676
+ if (typeof t.receipt === 'object') return t.receipt
677
+ if (typeof t.termination === 'object') return t.termination
678
+
679
+ await this.syncThreads()
680
+ await sleep(pollIntervalMs)
681
+ }
682
+
683
+ throw new Error('Timed out waiting for receipt')
684
+ }
685
+
686
+ /**
687
+ * Waits for a thread to reach a specific state.
688
+ */
689
+ async waitForState (
690
+ threadId: ThreadId,
691
+ state: RemittanceThreadState,
692
+ opts: { timeoutMs?: number, pollIntervalMs?: number } = {}
693
+ ): Promise<Thread> {
694
+ const timeoutMs = opts.timeoutMs ?? 30_000
695
+ const pollIntervalMs = opts.pollIntervalMs ?? 500
696
+ const start = this.now()
697
+
698
+ const t = this.getThreadOrThrow(threadId)
699
+ if (t.state === state) return t
700
+ if (t.state === 'terminated' || t.state === 'errored') {
701
+ throw new Error(`Thread entered terminal state: ${t.state}`)
702
+ }
703
+
704
+ let settled = false
705
+ let timedOut = false
706
+
707
+ let resolvePromise: () => void
708
+ let rejectPromise: (err: Error) => void
709
+
710
+ const entry = {
711
+ state,
712
+ resolve: () => {
713
+ if (settled || timedOut) return
714
+ settled = true
715
+ resolvePromise()
716
+ },
717
+ reject: (err: Error) => {
718
+ if (settled || timedOut) return
719
+ settled = true
720
+ rejectPromise(err)
721
+ }
722
+ }
723
+
724
+ const waiter = new Promise<void>((resolve, reject) => {
725
+ resolvePromise = resolve
726
+ rejectPromise = reject
727
+ const waiters = this.stateWaiters.get(threadId) ?? []
728
+ waiters.push(entry)
729
+ this.stateWaiters.set(threadId, waiters)
730
+ })
731
+
732
+ const removeEntry = (): void => {
733
+ const waiters = this.stateWaiters.get(threadId)
734
+ if (waiters == null) return
735
+ const remaining = waiters.filter((item) => item !== entry)
736
+ if (remaining.length === 0) {
737
+ this.stateWaiters.delete(threadId)
738
+ } else {
739
+ this.stateWaiters.set(threadId, remaining)
740
+ }
741
+ }
742
+
743
+ const poller = (async () => {
744
+ while (this.now() - start < timeoutMs) {
745
+ if (settled) return
746
+ const current = this.getThreadOrThrow(threadId)
747
+ if (current.state === state) {
748
+ this.resolveStateWaiters(threadId, state)
749
+ return
750
+ }
751
+ if (current.state === 'terminated' || current.state === 'errored') {
752
+ throw new Error(`Thread entered terminal state: ${current.state}`)
753
+ }
754
+ await this.syncThreads()
755
+ await sleep(pollIntervalMs)
756
+ }
757
+ })()
758
+
759
+ await Promise.race([waiter, poller]).catch((err) => {
760
+ removeEntry()
761
+ throw err
762
+ })
763
+
764
+ if (this.now() - start >= timeoutMs && !settled) {
765
+ timedOut = true
766
+ removeEntry()
767
+ throw new Error(`Timed out waiting for state: ${state}`)
768
+ }
769
+
770
+ removeEntry()
771
+ return this.getThreadOrThrow(threadId)
772
+ }
773
+
774
+ /**
775
+ * Waits for identity exchange to complete for a thread.
776
+ */
777
+ async waitForIdentity (threadId: ThreadId, opts?: { timeoutMs?: number, pollIntervalMs?: number }): Promise<Thread> {
778
+ return await this.waitForState(threadId, 'identityAcknowledged', opts)
779
+ }
780
+
781
+ /**
782
+ * Waits for a settlement to arrive for a thread.
783
+ */
784
+ async waitForSettlement (
785
+ threadId: ThreadId,
786
+ opts: { timeoutMs?: number, pollIntervalMs?: number } = {}
787
+ ): Promise<Settlement | Termination> {
788
+ const timeoutMs = opts.timeoutMs ?? 30_000
789
+ const pollIntervalMs = opts.pollIntervalMs ?? 500
790
+
791
+ const start = this.now()
792
+ while (this.now() - start < timeoutMs) {
793
+ const t = this.getThreadOrThrow(threadId)
794
+ if (typeof t.settlement === 'object') return t.settlement
795
+ if (typeof t.termination === 'object') return t.termination
796
+
797
+ await this.syncThreads()
798
+ await sleep(pollIntervalMs)
799
+ }
800
+
801
+ throw new Error('Timed out waiting for settlement')
802
+ }
803
+
804
+ /**
805
+ * Sends an unsolicited settlement to a counterparty.
806
+ */
807
+ async sendUnsolicitedSettlement (
808
+ to: PubKeyHex,
809
+ args: { moduleId: RemittanceOptionId, option: unknown, optionId?: RemittanceOptionId, note?: string },
810
+ hostOverride?: string
811
+ ): Promise<ThreadHandle> {
812
+ await this.refreshMyIdentityKey()
813
+
814
+ const module = this.moduleRegistry.get(args.moduleId)
815
+ if (module == null) throw new Error(`No configured remittance module for option: ${args.moduleId}`)
816
+ if (!module.allowUnsolicitedSettlements) {
817
+ throw new Error(`Remittance module ${args.moduleId} does not allow unsolicited settlements`)
818
+ }
819
+
820
+ const threadId = this.threadIdFactory()
821
+ const createdAt = this.now()
822
+ const myKey = this.requireMyIdentityKey('sendUnsolicitedSettlement requires the wallet to provide an identity key')
823
+
824
+ const thread: Thread = {
825
+ threadId,
826
+ counterparty: to,
827
+ myRole: 'taker',
828
+ theirRole: 'maker',
829
+ createdAt,
830
+ updatedAt: createdAt,
831
+ state: 'new',
832
+ stateLog: [],
833
+ processedMessageIds: [],
834
+ protocolLog: [],
835
+ identity: {
836
+ certsSent: [],
837
+ certsReceived: [],
838
+ requestSent: false,
839
+ responseSent: false,
840
+ acknowledgmentSent: false,
841
+ acknowledgmentReceived: false
842
+ },
843
+ flags: {
844
+ hasIdentified: false,
845
+ hasInvoiced: false,
846
+ hasPaid: false,
847
+ hasReceipted: false,
848
+ error: false
849
+ }
850
+ }
851
+
852
+ this.threads.push(thread)
853
+ this.emitEvent({ type: 'threadCreated', threadId: thread.threadId, thread })
854
+
855
+ if (this.shouldRequestIdentity(thread, 'beforeSettlement')) {
856
+ await this.ensureIdentityExchange(thread, hostOverride)
857
+ }
858
+
859
+ const buildResult = await module.buildSettlement(
860
+ { threadId, option: args.option, note: args.note },
861
+ this.moduleContext()
862
+ )
863
+
864
+ if (buildResult.action === 'terminate') {
865
+ await this.sendTermination(thread, to, buildResult.termination.message, buildResult.termination.details, buildResult.termination.code)
866
+ await this.persistState()
867
+ return new ThreadHandle(this, threadId)
868
+ }
869
+
870
+ const settlement: Settlement = {
871
+ kind: 'settlement',
872
+ threadId,
873
+ moduleId: module.id,
874
+ optionId: args.optionId ?? module.id,
875
+ sender: myKey,
876
+ createdAt: this.now(),
877
+ artifact: buildResult.artifact,
878
+ note: args.note
879
+ }
880
+
881
+ const env = this.makeEnvelope('settlement', threadId, settlement)
882
+ const mid = await this.sendEnvelope(to, env, hostOverride)
883
+ thread.protocolLog.push({ direction: 'out', envelope: env, transportMessageId: mid })
884
+ this.emitEvent({ type: 'settlementSent', threadId: thread.threadId, settlement })
885
+ thread.settlement = settlement
886
+ thread.flags.hasPaid = true
887
+ this.transitionThreadState(thread, 'settled', 'settlement sent')
888
+ thread.updatedAt = this.now()
889
+ await this.persistState()
890
+
891
+ return new ThreadHandle(this, threadId)
892
+ }
893
+
894
+ /**
895
+ * Returns a thread by id (if present).
896
+ */
897
+ getThread (threadId: ThreadId): Thread | undefined {
898
+ return this.threads.find((t) => t.threadId === threadId)
899
+ }
900
+
901
+ /**
902
+ * Returns a thread handle by id, or throws if the thread does not exist.
903
+ */
904
+ getThreadHandle (threadId: ThreadId): ThreadHandle {
905
+ this.getThreadOrThrow(threadId)
906
+ return new ThreadHandle(this, threadId)
907
+ }
908
+
909
+ /**
910
+ * Returns a thread by id or throws.
911
+ *
912
+ * Public so helper handles (e.g. InvoiceHandle) can call it.
913
+ */
914
+ getThreadOrThrow (threadId: ThreadId): Thread {
915
+ const t = this.getThread(threadId)
916
+ if (typeof t !== 'object') throw new Error(`Unknown thread: ${threadId}`)
917
+ return this.ensureThreadState(t)
918
+ }
919
+
920
+ // ----------------------------
921
+ // Internal helpers
922
+ // ----------------------------
923
+
924
+ private moduleContext (): ModuleContext {
925
+ return {
926
+ wallet: this.wallet,
927
+ originator: this.cfg.originator,
928
+ now: this.now,
929
+ logger: this.cfg.logger
930
+ }
931
+ }
932
+
933
+ private makeEnvelope<K extends RemittanceKind, P>(kind: K, threadId: ThreadId, payload: P): RemittanceEnvelope<K, P> {
934
+ return {
935
+ v: 1,
936
+ id: this.threadIdFactory(),
937
+ kind,
938
+ threadId,
939
+ createdAt: this.now(),
940
+ payload
941
+ }
942
+ }
943
+
944
+ private async sendEnvelope (recipient: PubKeyHex, env: RemittanceEnvelope, hostOverride?: string): Promise<string> {
945
+ const body = JSON.stringify(env)
946
+
947
+ // Prefer live if available.
948
+ if (typeof this.comms.sendLiveMessage === 'function') {
949
+ try {
950
+ const mid = await this.comms.sendLiveMessage({ recipient, messageBox: this.messageBox, body }, hostOverride)
951
+ this.emitEvent({ type: 'envelopeSent', threadId: env.threadId, envelope: env, transportMessageId: mid })
952
+ return mid
953
+ } catch (e) {
954
+ this.cfg.logger?.warn?.('[RemittanceManager] sendLiveMessage failed, falling back to non-live', e)
955
+ }
956
+ }
957
+
958
+ const mid = await this.comms.sendMessage({ recipient, messageBox: this.messageBox, body }, hostOverride)
959
+ this.emitEvent({ type: 'envelopeSent', threadId: env.threadId, envelope: env, transportMessageId: mid })
960
+ return mid
961
+ }
962
+
963
+ private getOrCreateThreadFromInboundEnvelope (env: RemittanceEnvelope, msg: PeerMessage): Thread {
964
+ const existing = this.getThread(env.threadId)
965
+ if (typeof existing === 'object') return existing
966
+
967
+ // If we didn't create the thread, infer roles from the first message kind:
968
+ // - Receiving identity verification request/response/acknowledgment -> we are either maker or taker depending on config
969
+ // - Receiving an invoice -> we are taker (payer)
970
+ // - Receiving a settlement -> we are maker (payee)
971
+ // - Receiving a receipt -> we are taker
972
+ // - Receiving a termination -> assume we are taker
973
+ const createdAt = this.now()
974
+
975
+ const inferredMyRole: Thread['myRole'] = (() => {
976
+ if (env.kind === 'invoice') return 'taker'
977
+ if (env.kind === 'settlement') return 'maker'
978
+ if (env.kind === 'receipt') return 'taker'
979
+ if (env.kind === 'termination') return 'taker'
980
+
981
+ if (
982
+ env.kind === 'identityVerificationRequest' ||
983
+ env.kind === 'identityVerificationResponse' ||
984
+ env.kind === 'identityVerificationAcknowledgment'
985
+ ) {
986
+ const makerRequest = this.runtime.identityOptions?.makerRequestIdentity ?? 'never'
987
+ const takerRequest = this.runtime.identityOptions?.takerRequestIdentity ?? 'never'
988
+ const makerRequests = makerRequest !== 'never'
989
+ const takerRequests = takerRequest !== 'never'
990
+
991
+ let requesterRole: Thread['myRole'] | undefined
992
+ if (makerRequests && !takerRequests) {
993
+ requesterRole = 'maker'
994
+ } else if (takerRequests && !makerRequests) {
995
+ requesterRole = 'taker'
996
+ } else if (makerRequests && takerRequests && makerRequest !== takerRequest) {
997
+ requesterRole =
998
+ makerRequest === 'beforeInvoicing' && takerRequest === 'beforeSettlement'
999
+ ? 'maker'
1000
+ : makerRequest === 'beforeSettlement' && takerRequest === 'beforeInvoicing'
1001
+ ? 'taker'
1002
+ : undefined
1003
+ }
1004
+
1005
+ if (typeof requesterRole !== 'string') return 'taker'
1006
+
1007
+ if (env.kind === 'identityVerificationResponse') {
1008
+ return requesterRole
1009
+ }
1010
+
1011
+ return requesterRole === 'maker' ? 'taker' : 'maker'
1012
+ }
1013
+
1014
+ return 'taker'
1015
+ })()
1016
+ const inferredTheirRole: Thread['theirRole'] = inferredMyRole === 'maker' ? 'taker' : 'maker'
1017
+
1018
+ const t: Thread = {
1019
+ threadId: env.threadId,
1020
+ counterparty: msg.sender,
1021
+ myRole: inferredMyRole,
1022
+ theirRole: inferredTheirRole,
1023
+ createdAt,
1024
+ updatedAt: createdAt,
1025
+ state: 'new',
1026
+ stateLog: [],
1027
+ processedMessageIds: [],
1028
+ protocolLog: [],
1029
+ identity: {
1030
+ certsSent: [],
1031
+ certsReceived: [],
1032
+ requestSent: false,
1033
+ responseSent: false,
1034
+ acknowledgmentSent: false,
1035
+ acknowledgmentReceived: false
1036
+ },
1037
+ flags: {
1038
+ hasIdentified: false,
1039
+ hasInvoiced: false,
1040
+ hasPaid: false,
1041
+ hasReceipted: false,
1042
+ error: false
1043
+ }
1044
+ }
1045
+
1046
+ this.threads.push(t)
1047
+ this.emitEvent({ type: 'threadCreated', threadId: t.threadId, thread: t })
1048
+ return t
1049
+ }
1050
+
1051
+ private async handleInboundMessage (msg: PeerMessage): Promise<void> {
1052
+ const parsed = safeParseEnvelope(msg.body)
1053
+ if (parsed == null) {
1054
+ // Not our protocol message; leave it for the application or acknowledge? Here we leave it.
1055
+ return
1056
+ }
1057
+
1058
+ const thread = this.getOrCreateThreadFromInboundEnvelope(parsed, msg)
1059
+ if (thread.processedMessageIds.includes(msg.messageId)) {
1060
+ // Already applied; ack and continue.
1061
+ await this.safeAck([msg.messageId])
1062
+ return
1063
+ }
1064
+
1065
+ try {
1066
+ await this.applyInboundEnvelope(thread, parsed, msg)
1067
+ thread.processedMessageIds.push(msg.messageId)
1068
+ thread.updatedAt = this.now()
1069
+ await this.persistState()
1070
+ await this.safeAck([msg.messageId])
1071
+ } catch (e: any) {
1072
+ this.markThreadError(thread, e)
1073
+ await this.persistState()
1074
+ // Do not acknowledge so it can be retried.
1075
+ }
1076
+ }
1077
+
1078
+ private async applyInboundEnvelope (thread: Thread, env: RemittanceEnvelope, msg: PeerMessage): Promise<void> {
1079
+ thread.protocolLog.push({ direction: 'in', envelope: env, transportMessageId: msg.messageId })
1080
+ this.emitEvent({ type: 'envelopeReceived', threadId: thread.threadId, envelope: env, transportMessageId: msg.messageId })
1081
+
1082
+ switch (env.kind) {
1083
+ case 'identityVerificationRequest': {
1084
+ const payload = env.payload as IdentityVerificationRequest
1085
+ if (typeof payload !== 'object') {
1086
+ throw new Error('Identity verification request payload missing data')
1087
+ }
1088
+
1089
+ if (this.cfg.identityLayer == null) {
1090
+ await this.sendTermination(thread, msg.sender, 'Identity verification requested but no identity layer is configured')
1091
+ return
1092
+ }
1093
+
1094
+ this.transitionThreadState(thread, 'identityRequested', 'identity request received')
1095
+ this.emitEvent({ type: 'identityRequested', threadId: thread.threadId, direction: 'in', request: payload })
1096
+
1097
+ const response = await this.cfg.identityLayer.respondToRequest(
1098
+ { counterparty: msg.sender, threadId: thread.threadId, request: payload },
1099
+ this.moduleContext()
1100
+ )
1101
+
1102
+ if (response.action === 'terminate') {
1103
+ await this.sendTermination(thread, msg.sender, response.termination.message, response.termination.details, response.termination.code)
1104
+ return
1105
+ }
1106
+
1107
+ const responseEnv = this.makeEnvelope('identityVerificationResponse', thread.threadId, response.response)
1108
+ const mid = await this.sendEnvelope(msg.sender, responseEnv)
1109
+ thread.protocolLog.push({ direction: 'out', envelope: responseEnv, transportMessageId: mid })
1110
+ thread.identity.certsSent = response.response.certificates
1111
+ thread.identity.responseSent = true
1112
+ this.transitionThreadState(thread, 'identityResponded', 'identity response sent')
1113
+ this.emitEvent({ type: 'identityResponded', threadId: thread.threadId, direction: 'out', response: response.response })
1114
+ return
1115
+ }
1116
+
1117
+ case 'identityVerificationResponse': {
1118
+ const payload = env.payload as IdentityVerificationResponse
1119
+ if (typeof payload !== 'object') {
1120
+ throw new Error('Identity verification response payload missing data')
1121
+ }
1122
+
1123
+ if (this.cfg.identityLayer == null) {
1124
+ await this.sendTermination(thread, msg.sender, 'Identity verification response received but no identity layer is configured')
1125
+ return
1126
+ }
1127
+
1128
+ thread.identity.certsReceived = payload.certificates
1129
+ this.transitionThreadState(thread, 'identityResponded', 'identity response received')
1130
+ this.emitEvent({ type: 'identityResponded', threadId: thread.threadId, direction: 'in', response: payload })
1131
+ const decision = await this.cfg.identityLayer.assessReceivedCertificateSufficiency(
1132
+ msg.sender,
1133
+ payload,
1134
+ thread.threadId
1135
+ )
1136
+
1137
+ if ('message' in decision) {
1138
+ await this.sendTermination(thread, msg.sender, decision.message, decision.details, decision.code)
1139
+ return
1140
+ }
1141
+
1142
+ if (decision.kind === 'identityVerificationAcknowledgment') {
1143
+ const ackEnv = this.makeEnvelope('identityVerificationAcknowledgment', thread.threadId, decision)
1144
+ const mid = await this.sendEnvelope(msg.sender, ackEnv)
1145
+ thread.protocolLog.push({ direction: 'out', envelope: ackEnv, transportMessageId: mid })
1146
+ thread.identity.acknowledgmentSent = true
1147
+ thread.flags.hasIdentified = true
1148
+ this.transitionThreadState(thread, 'identityAcknowledged', 'identity acknowledgment sent')
1149
+ this.emitEvent({ type: 'identityAcknowledged', threadId: thread.threadId, direction: 'out', acknowledgment: decision })
1150
+ return
1151
+ }
1152
+ throw new Error('Unknown identity verification decision')
1153
+ }
1154
+
1155
+ case 'identityVerificationAcknowledgment': {
1156
+ const payload = env.payload as IdentityVerificationAcknowledgment
1157
+ if (typeof payload !== 'object') {
1158
+ throw new Error('Identity verification acknowledgment payload missing data')
1159
+ }
1160
+
1161
+ thread.identity.acknowledgmentReceived = true
1162
+ thread.flags.hasIdentified = true
1163
+ this.transitionThreadState(thread, 'identityAcknowledged', 'identity acknowledgment received')
1164
+ this.emitEvent({ type: 'identityAcknowledged', threadId: thread.threadId, direction: 'in', acknowledgment: payload })
1165
+ return
1166
+ }
1167
+
1168
+ case 'invoice': {
1169
+ const invoice = env.payload as Invoice
1170
+ if (typeof invoice !== 'object') {
1171
+ throw new Error('Invoice payload missing invoice data')
1172
+ }
1173
+
1174
+ thread.invoice = invoice
1175
+ thread.flags.hasInvoiced = true
1176
+ this.transitionThreadState(thread, 'invoiced', 'invoice received')
1177
+ this.emitEvent({ type: 'invoiceReceived', threadId: thread.threadId, invoice })
1178
+ return
1179
+ }
1180
+
1181
+ case 'settlement': {
1182
+ const settlement = env.payload as Settlement
1183
+ if (typeof settlement !== 'object') {
1184
+ throw new Error('Settlement payload missing settlement data')
1185
+ }
1186
+
1187
+ if (this.shouldRequireIdentityBeforeSettlement(thread) && !thread.flags.hasIdentified) {
1188
+ await this.sendTermination(thread, msg.sender, 'Identity verification is required before settlement')
1189
+ return
1190
+ }
1191
+
1192
+ // Persist settlement immediately (even if we later reject); it is part of the audit trail.
1193
+ thread.settlement = settlement
1194
+ thread.flags.hasPaid = true
1195
+ this.transitionThreadState(thread, 'settled', 'settlement received')
1196
+ this.emitEvent({ type: 'settlementReceived', threadId: thread.threadId, settlement })
1197
+
1198
+ const module = this.moduleRegistry.get(settlement.moduleId)
1199
+ if (typeof module !== 'object') {
1200
+ await this.maybeSendTermination(thread, settlement, msg.sender, `Unsupported module: ${settlement.moduleId}`)
1201
+ return
1202
+ }
1203
+
1204
+ if ((thread.invoice == null) && !module.allowUnsolicitedSettlements) {
1205
+ await this.maybeSendTermination(thread, settlement, msg.sender, 'Unsolicited settlement not supported')
1206
+ return
1207
+ }
1208
+
1209
+ const result = await module.acceptSettlement({
1210
+ threadId: thread.threadId,
1211
+ invoice: thread.invoice,
1212
+ settlement: settlement.artifact,
1213
+ sender: msg.sender
1214
+ }, this.moduleContext()).catch(async (e) => {
1215
+ const errMsg = e instanceof Error ? e.message : String(e)
1216
+ await this.maybeSendTermination(thread, settlement, msg.sender, `Settlement processing failed: ${errMsg}`)
1217
+ throw e // re-throw to stop further processing
1218
+ })
1219
+
1220
+ if (result.action === 'accept') {
1221
+ const myKey = this.requireMyIdentityKey('Receiving settlement requires identity key')
1222
+ const payerKey = msg.sender
1223
+
1224
+ const receipt: Receipt = {
1225
+ kind: 'receipt',
1226
+ threadId: thread.threadId,
1227
+ moduleId: settlement.moduleId,
1228
+ optionId: settlement.optionId,
1229
+ payee: myKey,
1230
+ payer: payerKey,
1231
+ createdAt: this.now(),
1232
+ receiptData: result.receiptData
1233
+ }
1234
+
1235
+ thread.receipt = receipt
1236
+ thread.flags.hasReceipted = true
1237
+ this.transitionThreadState(thread, 'receipted', 'receipt issued')
1238
+
1239
+ if (this.runtime.receiptProvided && this.runtime.autoIssueReceipt) {
1240
+ const receiptEnv = this.makeEnvelope('receipt', thread.threadId, receipt)
1241
+ const mid = await this.sendEnvelope(msg.sender, receiptEnv)
1242
+ thread.protocolLog.push({ direction: 'out', envelope: receiptEnv, transportMessageId: mid })
1243
+ this.emitEvent({ type: 'receiptSent', threadId: thread.threadId, receipt })
1244
+ }
1245
+ } else if (result.action === 'terminate') {
1246
+ await this.maybeSendTermination(thread, settlement, msg.sender, result.termination.message, result.termination.details)
1247
+ } else {
1248
+ throw new Error('Unknown settlement acceptance action')
1249
+ }
1250
+
1251
+ return
1252
+ }
1253
+ case 'receipt': {
1254
+ const receipt = env.payload as Receipt
1255
+ if (typeof receipt !== 'object') {
1256
+ throw new Error('Receipt payload missing receipt data')
1257
+ }
1258
+
1259
+ thread.receipt = receipt
1260
+ thread.flags.hasReceipted = true
1261
+ this.transitionThreadState(thread, 'receipted', 'receipt received')
1262
+ this.emitEvent({ type: 'receiptReceived', threadId: thread.threadId, receipt })
1263
+
1264
+ const module = this.moduleRegistry.get(receipt.moduleId)
1265
+ if (module?.processReceipt != null) {
1266
+ await module.processReceipt(
1267
+ { threadId: thread.threadId, invoice: thread.invoice, receiptData: receipt.receiptData, sender: msg.sender },
1268
+ this.moduleContext()
1269
+ )
1270
+ }
1271
+
1272
+ return
1273
+ }
1274
+
1275
+ case 'termination': {
1276
+ const payload = env.payload as Termination
1277
+ if (typeof payload !== 'object') {
1278
+ throw new Error('Termination payload missing data')
1279
+ }
1280
+ thread.termination = payload
1281
+ thread.lastError = { message: payload.message, at: this.now() }
1282
+ thread.flags.error = true
1283
+ this.transitionThreadState(thread, 'terminated', 'termination received')
1284
+ this.emitEvent({ type: 'terminationReceived', threadId: thread.threadId, termination: payload })
1285
+ if (thread.settlement != null) {
1286
+ const module = this.moduleRegistry.get(thread.settlement.moduleId)
1287
+ if ((module?.processTermination) != null) {
1288
+ await module.processTermination(
1289
+ { threadId: thread.threadId, invoice: thread.invoice, settlement: thread.settlement, termination: payload, sender: msg.sender },
1290
+ this.moduleContext()
1291
+ )
1292
+ }
1293
+ }
1294
+ return
1295
+ }
1296
+
1297
+ default: {
1298
+ const kind = (env as { kind?: unknown }).kind
1299
+ throw new Error(`Unknown envelope kind: ${String(kind)}`)
1300
+ }
1301
+ }
1302
+ }
1303
+
1304
+ private async maybeSendTermination (thread: Thread, settlement: Settlement, payer: PubKeyHex, message: string, details?: any): Promise<void> {
1305
+ const t: Termination = {
1306
+ code: 'error',
1307
+ message,
1308
+ details
1309
+ }
1310
+
1311
+ const env = this.makeEnvelope('termination', thread.threadId, t)
1312
+ const mid = await this.sendEnvelope(payer, env)
1313
+ thread.protocolLog.push({ direction: 'out', envelope: env, transportMessageId: mid })
1314
+ this.emitEvent({ type: 'terminationSent', threadId: thread.threadId, termination: t })
1315
+
1316
+ thread.termination = t
1317
+ thread.lastError = {
1318
+ message: `Sent termination: ${message}`,
1319
+ at: this.now()
1320
+ }
1321
+ thread.flags.error = true
1322
+ this.transitionThreadState(thread, 'terminated', 'termination sent')
1323
+ }
1324
+
1325
+ private async sendTermination (
1326
+ thread: Thread,
1327
+ recipient: PubKeyHex,
1328
+ message: string,
1329
+ details?: unknown,
1330
+ code = 'error'
1331
+ ): Promise<void> {
1332
+ const t: Termination = { code, message, details }
1333
+ const env = this.makeEnvelope('termination', thread.threadId, t)
1334
+ const mid = await this.sendEnvelope(recipient, env)
1335
+ thread.protocolLog.push({ direction: 'out', envelope: env, transportMessageId: mid })
1336
+ this.emitEvent({ type: 'terminationSent', threadId: thread.threadId, termination: t })
1337
+ thread.termination = t
1338
+ thread.lastError = { message: `Sent termination: ${message}`, at: this.now() }
1339
+ thread.flags.error = true
1340
+ this.transitionThreadState(thread, 'terminated', 'termination sent')
1341
+ }
1342
+
1343
+ private shouldRequestIdentity (thread: Thread, phase: 'beforeInvoicing' | 'beforeSettlement'): boolean {
1344
+ const { makerRequestIdentity = 'never', takerRequestIdentity = 'never' } = this.runtime.identityOptions ?? {}
1345
+ const requiresIdentity = thread.myRole === 'maker' ? makerRequestIdentity === phase : takerRequestIdentity === phase
1346
+ if (!requiresIdentity) return false
1347
+ if (this.cfg.identityLayer == null) {
1348
+ throw new Error('Identity layer is required by runtime options but is not configured')
1349
+ }
1350
+ return true
1351
+ }
1352
+
1353
+ private shouldRequireIdentityBeforeSettlement (thread: Thread): boolean {
1354
+ if (thread.myRole !== 'maker') return false
1355
+ return (this.runtime.identityOptions?.makerRequestIdentity ?? 'never') === 'beforeSettlement'
1356
+ }
1357
+
1358
+ private async ensureIdentityExchange (thread: Thread, hostOverride?: string): Promise<void> {
1359
+ if (this.cfg.identityLayer == null) return
1360
+ if (thread.flags.hasIdentified) return
1361
+
1362
+ if (!thread.identity.requestSent) {
1363
+ const request = await this.cfg.identityLayer.determineCertificatesToRequest(
1364
+ { counterparty: thread.counterparty, threadId: thread.threadId },
1365
+ this.moduleContext()
1366
+ )
1367
+ const env = this.makeEnvelope('identityVerificationRequest', thread.threadId, request)
1368
+ const mid = await this.sendEnvelope(thread.counterparty, env, hostOverride)
1369
+ thread.protocolLog.push({ direction: 'out', envelope: env, transportMessageId: mid })
1370
+ thread.identity.requestSent = true
1371
+ this.transitionThreadState(thread, 'identityRequested', 'identity request sent')
1372
+ this.emitEvent({ type: 'identityRequested', threadId: thread.threadId, direction: 'out', request })
1373
+ thread.updatedAt = this.now()
1374
+ await this.persistState()
1375
+ }
1376
+
1377
+ await this.waitForIdentityAcknowledgment(thread.threadId, {
1378
+ timeoutMs: this.runtime.identityTimeoutMs,
1379
+ pollIntervalMs: this.runtime.identityPollIntervalMs
1380
+ })
1381
+ }
1382
+
1383
+ private async waitForIdentityAcknowledgment (
1384
+ threadId: ThreadId,
1385
+ opts: { timeoutMs?: number, pollIntervalMs?: number } = {}
1386
+ ): Promise<void> {
1387
+ await this.waitForState(threadId, 'identityAcknowledged', opts)
1388
+ }
1389
+
1390
+ private async safeAck (messageIds: string[]): Promise<void> {
1391
+ try {
1392
+ await this.comms.acknowledgeMessage({ messageIds })
1393
+ } catch (e) {
1394
+ this.cfg.logger?.warn?.('[RemittanceManager] Failed to acknowledge message(s)', e)
1395
+ }
1396
+ }
1397
+
1398
+ private markThreadError (thread: Thread, e: any): void {
1399
+ thread.flags.error = true
1400
+ this.transitionThreadState(thread, 'errored', 'thread error')
1401
+ thread.lastError = { message: String(e?.message ?? e), at: this.now() }
1402
+ this.cfg.logger?.error?.('[RemittanceManager] Thread error', thread.threadId, e)
1403
+ this.emitEvent({ type: 'error', threadId: thread.threadId, error: String(e?.message ?? e) })
1404
+ }
1405
+
1406
+ private ensureThreadState (thread: Thread): Thread {
1407
+ thread.identity = thread.identity ?? {
1408
+ certsSent: [],
1409
+ certsReceived: [],
1410
+ requestSent: false,
1411
+ responseSent: false,
1412
+ acknowledgmentSent: false,
1413
+ acknowledgmentReceived: false
1414
+ }
1415
+ thread.identity.certsSent ??= []
1416
+ thread.identity.certsReceived ??= []
1417
+ thread.identity.requestSent ??= false
1418
+ thread.identity.responseSent ??= false
1419
+ thread.identity.acknowledgmentSent ??= false
1420
+ thread.identity.acknowledgmentReceived ??= false
1421
+
1422
+ thread.flags = thread.flags ?? {
1423
+ hasIdentified: false,
1424
+ hasInvoiced: false,
1425
+ hasPaid: false,
1426
+ hasReceipted: false,
1427
+ error: false
1428
+ }
1429
+ thread.processedMessageIds ??= []
1430
+ thread.protocolLog ??= []
1431
+ thread.stateLog ??= []
1432
+
1433
+ if (thread.state == null) {
1434
+ thread.state = this.deriveThreadState(thread)
1435
+ }
1436
+ return thread
1437
+ }
1438
+
1439
+ private deriveThreadState (thread: Thread): RemittanceThreadState {
1440
+ if (thread.flags.error) return 'errored'
1441
+ if (thread.termination != null) return 'terminated'
1442
+ if (thread.receipt != null) return 'receipted'
1443
+ if (thread.settlement != null) return 'settled'
1444
+ if (thread.invoice != null) return 'invoiced'
1445
+ if (thread.identity.acknowledgmentReceived || thread.identity.acknowledgmentSent || thread.flags.hasIdentified) {
1446
+ return 'identityAcknowledged'
1447
+ }
1448
+ if (thread.identity.responseSent || thread.identity.certsSent.length > 0) return 'identityResponded'
1449
+ if (thread.identity.requestSent || thread.identity.certsReceived.length > 0) return 'identityRequested'
1450
+ return 'new'
1451
+ }
1452
+
1453
+ private transitionThreadState (thread: Thread, next: RemittanceThreadState, reason?: string): void {
1454
+ const current = thread.state
1455
+ if (current === next) return
1456
+ const allowed = REMITTANCE_STATE_TRANSITIONS[current] ?? []
1457
+ if (!allowed.includes(next)) {
1458
+ throw new Error(`Invalid remittance state transition: ${current} -> ${next}`)
1459
+ }
1460
+
1461
+ thread.state = next
1462
+ thread.updatedAt = this.now()
1463
+ thread.stateLog.push({ at: this.now(), from: current, to: next, reason })
1464
+ this.emitEvent({ type: 'stateChanged', threadId: thread.threadId, previous: current, next, reason })
1465
+ this.resolveStateWaiters(thread.threadId, next)
1466
+ if (next === 'terminated' || next === 'errored') {
1467
+ this.rejectStateWaiters(thread.threadId, new Error(`Thread entered terminal state: ${next}`))
1468
+ }
1469
+ }
1470
+
1471
+ private resolveStateWaiters (threadId: ThreadId, state: RemittanceThreadState): void {
1472
+ const waiters = this.stateWaiters.get(threadId)
1473
+ if (waiters == null) return
1474
+
1475
+ const remaining: Array<{ state: RemittanceThreadState, resolve: () => void, reject: (err: Error) => void }> = []
1476
+ for (const waiter of waiters) {
1477
+ if (waiter.state === state) {
1478
+ waiter.resolve()
1479
+ } else {
1480
+ remaining.push(waiter)
1481
+ }
1482
+ }
1483
+ if (remaining.length === 0) {
1484
+ this.stateWaiters.delete(threadId)
1485
+ } else {
1486
+ this.stateWaiters.set(threadId, remaining)
1487
+ }
1488
+ }
1489
+
1490
+ private rejectStateWaiters (threadId: ThreadId, err: Error): void {
1491
+ const waiters = this.stateWaiters.get(threadId)
1492
+ if (waiters == null) return
1493
+ for (const waiter of waiters) {
1494
+ waiter.reject(err)
1495
+ }
1496
+ this.stateWaiters.delete(threadId)
1497
+ }
1498
+
1499
+ private emitEvent (event: RemittanceEvent): void {
1500
+ const handlers = this.eventHandlers
1501
+ if (handlers != null) {
1502
+ try {
1503
+ switch (event.type) {
1504
+ case 'threadCreated':
1505
+ handlers.onThreadCreated?.(event)
1506
+ break
1507
+ case 'stateChanged':
1508
+ handlers.onStateChanged?.(event)
1509
+ break
1510
+ case 'envelopeSent':
1511
+ handlers.onEnvelopeSent?.(event)
1512
+ break
1513
+ case 'envelopeReceived':
1514
+ handlers.onEnvelopeReceived?.(event)
1515
+ break
1516
+ case 'identityRequested':
1517
+ handlers.onIdentityRequested?.(event)
1518
+ break
1519
+ case 'identityResponded':
1520
+ handlers.onIdentityResponded?.(event)
1521
+ break
1522
+ case 'identityAcknowledged':
1523
+ handlers.onIdentityAcknowledged?.(event)
1524
+ break
1525
+ case 'invoiceSent':
1526
+ handlers.onInvoiceSent?.(event)
1527
+ break
1528
+ case 'invoiceReceived':
1529
+ handlers.onInvoiceReceived?.(event)
1530
+ break
1531
+ case 'settlementSent':
1532
+ handlers.onSettlementSent?.(event)
1533
+ break
1534
+ case 'settlementReceived':
1535
+ handlers.onSettlementReceived?.(event)
1536
+ break
1537
+ case 'receiptSent':
1538
+ handlers.onReceiptSent?.(event)
1539
+ break
1540
+ case 'receiptReceived':
1541
+ handlers.onReceiptReceived?.(event)
1542
+ break
1543
+ case 'terminationSent':
1544
+ handlers.onTerminationSent?.(event)
1545
+ break
1546
+ case 'terminationReceived':
1547
+ handlers.onTerminationReceived?.(event)
1548
+ break
1549
+ case 'error':
1550
+ handlers.onError?.(event)
1551
+ break
1552
+ }
1553
+ } catch (e) {
1554
+ this.cfg.logger?.warn?.('[RemittanceManager] Event handler error', e)
1555
+ }
1556
+ }
1557
+ for (const listener of this.eventListeners) {
1558
+ try {
1559
+ listener(event)
1560
+ } catch (e) {
1561
+ this.cfg.logger?.warn?.('[RemittanceManager] Event listener error', e)
1562
+ }
1563
+ }
1564
+ }
1565
+
1566
+ private async refreshMyIdentityKey (): Promise<void> {
1567
+ if (typeof this.myIdentityKey === 'string') return
1568
+ if (typeof this.wallet !== 'object') return
1569
+
1570
+ const { publicKey: k } = await this.wallet.getPublicKey({ identityKey: true }, this.cfg.originator)
1571
+ if (typeof k === 'string' && k.trim() !== '') {
1572
+ this.myIdentityKey = k
1573
+ }
1574
+ }
1575
+
1576
+ private requireMyIdentityKey (errMsg: string): PubKeyHex {
1577
+ if (typeof this.myIdentityKey !== 'string') {
1578
+ throw new Error(errMsg)
1579
+ }
1580
+ return this.myIdentityKey
1581
+ }
1582
+
1583
+ private async composeInvoice (
1584
+ threadId: ThreadId,
1585
+ payee: PubKeyHex,
1586
+ payer: PubKeyHex,
1587
+ input: ComposeInvoiceInput
1588
+ ): Promise<Invoice> {
1589
+ const createdAt = this.now()
1590
+ const expiresAt =
1591
+ this.runtime.invoiceExpirySeconds >= 0 ? createdAt + this.runtime.invoiceExpirySeconds * 1000 : undefined
1592
+
1593
+ return {
1594
+ kind: 'invoice',
1595
+ threadId,
1596
+ payee,
1597
+ payer,
1598
+ note: input.note,
1599
+ lineItems: input.lineItems,
1600
+ total: input.total,
1601
+ invoiceNumber: input.invoiceNumber ?? threadId,
1602
+ createdAt,
1603
+ expiresAt,
1604
+ arbitrary: input.arbitrary,
1605
+ options: {}
1606
+ }
1607
+ }
1608
+ }
1609
+
1610
+ /**
1611
+ * A lightweight wrapper around a thread's invoice, with convenience methods.
1612
+ */
1613
+ export class ThreadHandle {
1614
+ constructor (protected readonly manager: RemittanceManager, public readonly threadId: ThreadId) {}
1615
+
1616
+ get thread (): Thread {
1617
+ return this.manager.getThreadOrThrow(this.threadId)
1618
+ }
1619
+
1620
+ async waitForState (state: RemittanceThreadState, opts?: { timeoutMs?: number, pollIntervalMs?: number }): Promise<Thread> {
1621
+ return await this.manager.waitForState(this.threadId, state, opts)
1622
+ }
1623
+
1624
+ async waitForIdentity (opts?: { timeoutMs?: number, pollIntervalMs?: number }): Promise<Thread> {
1625
+ return await this.manager.waitForIdentity(this.threadId, opts)
1626
+ }
1627
+
1628
+ async waitForSettlement (opts?: { timeoutMs?: number, pollIntervalMs?: number }): Promise<Settlement | Termination> {
1629
+ return await this.manager.waitForSettlement(this.threadId, opts)
1630
+ }
1631
+
1632
+ async waitForReceipt (opts?: { timeoutMs?: number, pollIntervalMs?: number }): Promise<Receipt | Termination> {
1633
+ return await this.manager.waitForReceipt(this.threadId, opts)
1634
+ }
1635
+ }
1636
+
1637
+ export class InvoiceHandle extends ThreadHandle {
1638
+ get invoice (): Invoice {
1639
+ const inv = this.thread.invoice
1640
+ if (typeof inv !== 'object') throw new Error('Thread has no invoice')
1641
+ return inv
1642
+ }
1643
+
1644
+ /**
1645
+ * Pays the invoice using the selected remittance option.
1646
+ */
1647
+ async pay (optionId?: string): Promise<Receipt | Termination | undefined> {
1648
+ return await this.manager.pay(this.threadId, optionId)
1649
+ }
1650
+ }
1651
+
1652
+ function safeParseEnvelope (body: string): RemittanceEnvelope | undefined {
1653
+ try {
1654
+ const parsed = JSON.parse(body)
1655
+ if (typeof parsed !== 'object') return undefined
1656
+ if (parsed.v !== 1) return undefined
1657
+ if (typeof parsed.kind !== 'string') return undefined
1658
+ if (typeof parsed.threadId !== 'string') return undefined
1659
+ if (typeof parsed.id !== 'string') return undefined
1660
+ return parsed as RemittanceEnvelope
1661
+ } catch {
1662
+ return undefined
1663
+ }
1664
+ }
1665
+
1666
+ function defaultThreadIdFactory (): ThreadId {
1667
+ return toBase64(Random(32))
1668
+ }
1669
+
1670
+ async function sleep (ms: number): Promise<void> {
1671
+ return await new Promise((resolve) => setTimeout(resolve, ms))
1672
+ }