@bsv/sdk 2.1.0 → 2.1.2

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 (392) hide show
  1. package/README.md +7 -7
  2. package/dist/cjs/package.json +1 -1
  3. package/dist/cjs/src/auth/Peer.js +8 -13
  4. package/dist/cjs/src/auth/Peer.js.map +1 -1
  5. package/dist/cjs/src/auth/SessionManager.js +4 -7
  6. package/dist/cjs/src/auth/SessionManager.js.map +1 -1
  7. package/dist/cjs/src/auth/certificates/MasterCertificate.js +1 -1
  8. package/dist/cjs/src/auth/certificates/MasterCertificate.js.map +1 -1
  9. package/dist/cjs/src/auth/certificates/__tests/CompletedProtoWallet.js +1 -1
  10. package/dist/cjs/src/auth/certificates/__tests/CompletedProtoWallet.js.map +1 -1
  11. package/dist/cjs/src/auth/clients/AuthFetch.js +32 -32
  12. package/dist/cjs/src/auth/clients/AuthFetch.js.map +1 -1
  13. package/dist/cjs/src/auth/transports/SimplifiedFetchTransport.js +4 -4
  14. package/dist/cjs/src/auth/transports/SimplifiedFetchTransport.js.map +1 -1
  15. package/dist/cjs/src/compat/ECIES.js +29 -34
  16. package/dist/cjs/src/compat/ECIES.js.map +1 -1
  17. package/dist/cjs/src/compat/HD.js +9 -4
  18. package/dist/cjs/src/compat/HD.js.map +1 -1
  19. package/dist/cjs/src/compat/Mnemonic.js +12 -12
  20. package/dist/cjs/src/compat/Mnemonic.js.map +1 -1
  21. package/dist/cjs/src/identity/ContactsManager.js +212 -234
  22. package/dist/cjs/src/identity/ContactsManager.js.map +1 -1
  23. package/dist/cjs/src/identity/IdentityClient.js +199 -63
  24. package/dist/cjs/src/identity/IdentityClient.js.map +1 -1
  25. package/dist/cjs/src/kvstore/GlobalKVStore.js +30 -31
  26. package/dist/cjs/src/kvstore/GlobalKVStore.js.map +1 -1
  27. package/dist/cjs/src/kvstore/LocalKVStore.js +9 -9
  28. package/dist/cjs/src/kvstore/LocalKVStore.js.map +1 -1
  29. package/dist/cjs/src/kvstore/kvStoreInterpreter.js +2 -2
  30. package/dist/cjs/src/kvstore/kvStoreInterpreter.js.map +1 -1
  31. package/dist/cjs/src/messages/SignedMessage.js +1 -1
  32. package/dist/cjs/src/messages/SignedMessage.js.map +1 -1
  33. package/dist/cjs/src/overlay-tools/Historian.js +1 -1
  34. package/dist/cjs/src/overlay-tools/Historian.js.map +1 -1
  35. package/dist/cjs/src/overlay-tools/LookupResolver.js +213 -93
  36. package/dist/cjs/src/overlay-tools/LookupResolver.js.map +1 -1
  37. package/dist/cjs/src/overlay-tools/SHIPBroadcaster.js +75 -146
  38. package/dist/cjs/src/overlay-tools/SHIPBroadcaster.js.map +1 -1
  39. package/dist/cjs/src/primitives/AESGCM.js +2 -2
  40. package/dist/cjs/src/primitives/AESGCM.js.map +1 -1
  41. package/dist/cjs/src/primitives/BigNumber.js +164 -148
  42. package/dist/cjs/src/primitives/BigNumber.js.map +1 -1
  43. package/dist/cjs/src/primitives/Curve.js +17 -15
  44. package/dist/cjs/src/primitives/Curve.js.map +1 -1
  45. package/dist/cjs/src/primitives/ECDSA.js +12 -7
  46. package/dist/cjs/src/primitives/ECDSA.js.map +1 -1
  47. package/dist/cjs/src/primitives/Hash.js +312 -105
  48. package/dist/cjs/src/primitives/Hash.js.map +1 -1
  49. package/dist/cjs/src/primitives/JacobianPoint.js +8 -8
  50. package/dist/cjs/src/primitives/JacobianPoint.js.map +1 -1
  51. package/dist/cjs/src/primitives/K256.js +3 -3
  52. package/dist/cjs/src/primitives/K256.js.map +1 -1
  53. package/dist/cjs/src/primitives/Point.js +36 -40
  54. package/dist/cjs/src/primitives/Point.js.map +1 -1
  55. package/dist/cjs/src/primitives/PrivateKey.js +4 -4
  56. package/dist/cjs/src/primitives/PrivateKey.js.map +1 -1
  57. package/dist/cjs/src/primitives/PublicKey.js +4 -4
  58. package/dist/cjs/src/primitives/PublicKey.js.map +1 -1
  59. package/dist/cjs/src/primitives/Random.js +10 -14
  60. package/dist/cjs/src/primitives/Random.js.map +1 -1
  61. package/dist/cjs/src/primitives/ReaderUint8Array.js +6 -6
  62. package/dist/cjs/src/primitives/ReaderUint8Array.js.map +1 -1
  63. package/dist/cjs/src/primitives/Schnorr.js +2 -2
  64. package/dist/cjs/src/primitives/Schnorr.js.map +1 -1
  65. package/dist/cjs/src/primitives/Secp256r1.js +2 -1
  66. package/dist/cjs/src/primitives/Secp256r1.js.map +1 -1
  67. package/dist/cjs/src/primitives/Signature.js +8 -8
  68. package/dist/cjs/src/primitives/Signature.js.map +1 -1
  69. package/dist/cjs/src/primitives/SymmetricKey.js +123 -1
  70. package/dist/cjs/src/primitives/SymmetricKey.js.map +1 -1
  71. package/dist/cjs/src/primitives/TransactionSignature.js +20 -21
  72. package/dist/cjs/src/primitives/TransactionSignature.js.map +1 -1
  73. package/dist/cjs/src/primitives/utils.js +39 -46
  74. package/dist/cjs/src/primitives/utils.js.map +1 -1
  75. package/dist/cjs/src/registry/RegistryClient.js +31 -23
  76. package/dist/cjs/src/registry/RegistryClient.js.map +1 -1
  77. package/dist/cjs/src/remittance/RemittanceManager.js +19 -18
  78. package/dist/cjs/src/remittance/RemittanceManager.js.map +1 -1
  79. package/dist/cjs/src/remittance/modules/BasicBRC29.js.map +1 -1
  80. package/dist/cjs/src/script/Script.js +93 -170
  81. package/dist/cjs/src/script/Script.js.map +1 -1
  82. package/dist/cjs/src/script/ScriptEvaluationError.js +2 -2
  83. package/dist/cjs/src/script/ScriptEvaluationError.js.map +1 -1
  84. package/dist/cjs/src/script/Spend.js +14 -12
  85. package/dist/cjs/src/script/Spend.js.map +1 -1
  86. package/dist/cjs/src/script/templates/PushDrop.js +22 -18
  87. package/dist/cjs/src/script/templates/PushDrop.js.map +1 -1
  88. package/dist/cjs/src/script/templates/RPuzzle.js +2 -4
  89. package/dist/cjs/src/script/templates/RPuzzle.js.map +1 -1
  90. package/dist/cjs/src/storage/StorageDownloader.js +42 -9
  91. package/dist/cjs/src/storage/StorageDownloader.js.map +1 -1
  92. package/dist/cjs/src/totp/totp.js +1 -1
  93. package/dist/cjs/src/totp/totp.js.map +1 -1
  94. package/dist/cjs/src/transaction/Beef.js +239 -192
  95. package/dist/cjs/src/transaction/Beef.js.map +1 -1
  96. package/dist/cjs/src/transaction/BeefConstants.js +19 -0
  97. package/dist/cjs/src/transaction/BeefConstants.js.map +1 -0
  98. package/dist/cjs/src/transaction/BeefTx.js +12 -12
  99. package/dist/cjs/src/transaction/BeefTx.js.map +1 -1
  100. package/dist/cjs/src/transaction/MerklePath.js +4 -4
  101. package/dist/cjs/src/transaction/MerklePath.js.map +1 -1
  102. package/dist/cjs/src/transaction/Transaction.js +49 -52
  103. package/dist/cjs/src/transaction/Transaction.js.map +1 -1
  104. package/dist/cjs/src/transaction/fee-models/SatoshisPerKilobyte.js +1 -1
  105. package/dist/cjs/src/transaction/fee-models/SatoshisPerKilobyte.js.map +1 -1
  106. package/dist/cjs/src/transaction/http/BinaryFetchClient.js +9 -9
  107. package/dist/cjs/src/transaction/http/BinaryFetchClient.js.map +1 -1
  108. package/dist/cjs/src/transaction/http/DefaultHttpClient.js +9 -9
  109. package/dist/cjs/src/transaction/http/DefaultHttpClient.js.map +1 -1
  110. package/dist/cjs/src/wallet/CachedKeyDeriver.js +1 -1
  111. package/dist/cjs/src/wallet/CachedKeyDeriver.js.map +1 -1
  112. package/dist/cjs/src/wallet/WalletClient.js.map +1 -1
  113. package/dist/cjs/src/wallet/WalletError.js.map +1 -1
  114. package/dist/cjs/src/wallet/substrates/HTTPWalletJSON.js +5 -4
  115. package/dist/cjs/src/wallet/substrates/HTTPWalletJSON.js.map +1 -1
  116. package/dist/cjs/src/wallet/substrates/ReactNativeWebView.js +9 -9
  117. package/dist/cjs/src/wallet/substrates/ReactNativeWebView.js.map +1 -1
  118. package/dist/cjs/src/wallet/substrates/WalletWireProcessor.js +92 -92
  119. package/dist/cjs/src/wallet/substrates/WalletWireProcessor.js.map +1 -1
  120. package/dist/cjs/src/wallet/substrates/WalletWireTransceiver.js +387 -711
  121. package/dist/cjs/src/wallet/substrates/WalletWireTransceiver.js.map +1 -1
  122. package/dist/cjs/src/wallet/substrates/XDM.js +4 -4
  123. package/dist/cjs/src/wallet/substrates/XDM.js.map +1 -1
  124. package/dist/cjs/src/wallet/substrates/window.CWI.js +2 -2
  125. package/dist/cjs/src/wallet/substrates/window.CWI.js.map +1 -1
  126. package/dist/cjs/src/wallet/validationHelpers.js +9 -9
  127. package/dist/cjs/src/wallet/validationHelpers.js.map +1 -1
  128. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  129. package/dist/esm/src/auth/Peer.js +25 -13
  130. package/dist/esm/src/auth/Peer.js.map +1 -1
  131. package/dist/esm/src/auth/SessionManager.js +4 -7
  132. package/dist/esm/src/auth/SessionManager.js.map +1 -1
  133. package/dist/esm/src/auth/certificates/MasterCertificate.js +1 -1
  134. package/dist/esm/src/auth/certificates/MasterCertificate.js.map +1 -1
  135. package/dist/esm/src/auth/certificates/__tests/CompletedProtoWallet.js +1 -1
  136. package/dist/esm/src/auth/certificates/__tests/CompletedProtoWallet.js.map +1 -1
  137. package/dist/esm/src/auth/clients/AuthFetch.js +32 -32
  138. package/dist/esm/src/auth/clients/AuthFetch.js.map +1 -1
  139. package/dist/esm/src/auth/transports/SimplifiedFetchTransport.js +4 -4
  140. package/dist/esm/src/auth/transports/SimplifiedFetchTransport.js.map +1 -1
  141. package/dist/esm/src/compat/ECIES.js +29 -34
  142. package/dist/esm/src/compat/ECIES.js.map +1 -1
  143. package/dist/esm/src/compat/HD.js +9 -4
  144. package/dist/esm/src/compat/HD.js.map +1 -1
  145. package/dist/esm/src/compat/Mnemonic.js +12 -12
  146. package/dist/esm/src/compat/Mnemonic.js.map +1 -1
  147. package/dist/esm/src/identity/ContactsManager.js +212 -234
  148. package/dist/esm/src/identity/ContactsManager.js.map +1 -1
  149. package/dist/esm/src/identity/IdentityClient.js +199 -63
  150. package/dist/esm/src/identity/IdentityClient.js.map +1 -1
  151. package/dist/esm/src/kvstore/GlobalKVStore.js +30 -31
  152. package/dist/esm/src/kvstore/GlobalKVStore.js.map +1 -1
  153. package/dist/esm/src/kvstore/LocalKVStore.js +9 -9
  154. package/dist/esm/src/kvstore/LocalKVStore.js.map +1 -1
  155. package/dist/esm/src/kvstore/kvStoreInterpreter.js +2 -2
  156. package/dist/esm/src/kvstore/kvStoreInterpreter.js.map +1 -1
  157. package/dist/esm/src/messages/SignedMessage.js +1 -1
  158. package/dist/esm/src/messages/SignedMessage.js.map +1 -1
  159. package/dist/esm/src/overlay-tools/Historian.js +1 -1
  160. package/dist/esm/src/overlay-tools/Historian.js.map +1 -1
  161. package/dist/esm/src/overlay-tools/LookupResolver.js +213 -93
  162. package/dist/esm/src/overlay-tools/LookupResolver.js.map +1 -1
  163. package/dist/esm/src/overlay-tools/SHIPBroadcaster.js +74 -146
  164. package/dist/esm/src/overlay-tools/SHIPBroadcaster.js.map +1 -1
  165. package/dist/esm/src/primitives/AESGCM.js +2 -2
  166. package/dist/esm/src/primitives/AESGCM.js.map +1 -1
  167. package/dist/esm/src/primitives/BigNumber.js +167 -154
  168. package/dist/esm/src/primitives/BigNumber.js.map +1 -1
  169. package/dist/esm/src/primitives/Curve.js +17 -15
  170. package/dist/esm/src/primitives/Curve.js.map +1 -1
  171. package/dist/esm/src/primitives/ECDSA.js +12 -7
  172. package/dist/esm/src/primitives/ECDSA.js.map +1 -1
  173. package/dist/esm/src/primitives/Hash.js +316 -105
  174. package/dist/esm/src/primitives/Hash.js.map +1 -1
  175. package/dist/esm/src/primitives/JacobianPoint.js +8 -8
  176. package/dist/esm/src/primitives/JacobianPoint.js.map +1 -1
  177. package/dist/esm/src/primitives/K256.js +3 -3
  178. package/dist/esm/src/primitives/K256.js.map +1 -1
  179. package/dist/esm/src/primitives/Point.js +36 -40
  180. package/dist/esm/src/primitives/Point.js.map +1 -1
  181. package/dist/esm/src/primitives/PrivateKey.js +4 -4
  182. package/dist/esm/src/primitives/PrivateKey.js.map +1 -1
  183. package/dist/esm/src/primitives/PublicKey.js +4 -4
  184. package/dist/esm/src/primitives/PublicKey.js.map +1 -1
  185. package/dist/esm/src/primitives/Random.js +10 -14
  186. package/dist/esm/src/primitives/Random.js.map +1 -1
  187. package/dist/esm/src/primitives/ReaderUint8Array.js +6 -6
  188. package/dist/esm/src/primitives/ReaderUint8Array.js.map +1 -1
  189. package/dist/esm/src/primitives/Schnorr.js +1 -1
  190. package/dist/esm/src/primitives/Schnorr.js.map +1 -1
  191. package/dist/esm/src/primitives/Secp256r1.js +2 -1
  192. package/dist/esm/src/primitives/Secp256r1.js.map +1 -1
  193. package/dist/esm/src/primitives/Signature.js +8 -8
  194. package/dist/esm/src/primitives/Signature.js.map +1 -1
  195. package/dist/esm/src/primitives/SymmetricKey.js +123 -1
  196. package/dist/esm/src/primitives/SymmetricKey.js.map +1 -1
  197. package/dist/esm/src/primitives/TransactionSignature.js +20 -21
  198. package/dist/esm/src/primitives/TransactionSignature.js.map +1 -1
  199. package/dist/esm/src/primitives/utils.js +39 -48
  200. package/dist/esm/src/primitives/utils.js.map +1 -1
  201. package/dist/esm/src/registry/RegistryClient.js +31 -23
  202. package/dist/esm/src/registry/RegistryClient.js.map +1 -1
  203. package/dist/esm/src/remittance/RemittanceManager.js +19 -18
  204. package/dist/esm/src/remittance/RemittanceManager.js.map +1 -1
  205. package/dist/esm/src/remittance/modules/BasicBRC29.js.map +1 -1
  206. package/dist/esm/src/script/Script.js +93 -170
  207. package/dist/esm/src/script/Script.js.map +1 -1
  208. package/dist/esm/src/script/ScriptEvaluationError.js +2 -2
  209. package/dist/esm/src/script/ScriptEvaluationError.js.map +1 -1
  210. package/dist/esm/src/script/Spend.js +14 -12
  211. package/dist/esm/src/script/Spend.js.map +1 -1
  212. package/dist/esm/src/script/templates/PushDrop.js +4 -3
  213. package/dist/esm/src/script/templates/PushDrop.js.map +1 -1
  214. package/dist/esm/src/script/templates/RPuzzle.js +2 -4
  215. package/dist/esm/src/script/templates/RPuzzle.js.map +1 -1
  216. package/dist/esm/src/storage/StorageDownloader.js +1 -1
  217. package/dist/esm/src/storage/StorageDownloader.js.map +1 -1
  218. package/dist/esm/src/totp/totp.js +1 -1
  219. package/dist/esm/src/totp/totp.js.map +1 -1
  220. package/dist/esm/src/transaction/Beef.js +229 -186
  221. package/dist/esm/src/transaction/Beef.js.map +1 -1
  222. package/dist/esm/src/transaction/BeefConstants.js +16 -0
  223. package/dist/esm/src/transaction/BeefConstants.js.map +1 -0
  224. package/dist/esm/src/transaction/BeefTx.js +3 -3
  225. package/dist/esm/src/transaction/BeefTx.js.map +1 -1
  226. package/dist/esm/src/transaction/MerklePath.js +4 -4
  227. package/dist/esm/src/transaction/MerklePath.js.map +1 -1
  228. package/dist/esm/src/transaction/Transaction.js +49 -52
  229. package/dist/esm/src/transaction/Transaction.js.map +1 -1
  230. package/dist/esm/src/transaction/fee-models/SatoshisPerKilobyte.js +1 -1
  231. package/dist/esm/src/transaction/fee-models/SatoshisPerKilobyte.js.map +1 -1
  232. package/dist/esm/src/transaction/http/BinaryFetchClient.js +9 -9
  233. package/dist/esm/src/transaction/http/BinaryFetchClient.js.map +1 -1
  234. package/dist/esm/src/transaction/http/DefaultHttpClient.js +9 -9
  235. package/dist/esm/src/transaction/http/DefaultHttpClient.js.map +1 -1
  236. package/dist/esm/src/wallet/CachedKeyDeriver.js +1 -1
  237. package/dist/esm/src/wallet/CachedKeyDeriver.js.map +1 -1
  238. package/dist/esm/src/wallet/WalletClient.js.map +1 -1
  239. package/dist/esm/src/wallet/WalletError.js.map +1 -1
  240. package/dist/esm/src/wallet/substrates/HTTPWalletJSON.js +5 -4
  241. package/dist/esm/src/wallet/substrates/HTTPWalletJSON.js.map +1 -1
  242. package/dist/esm/src/wallet/substrates/ReactNativeWebView.js +9 -9
  243. package/dist/esm/src/wallet/substrates/ReactNativeWebView.js.map +1 -1
  244. package/dist/esm/src/wallet/substrates/WalletWireProcessor.js +92 -92
  245. package/dist/esm/src/wallet/substrates/WalletWireProcessor.js.map +1 -1
  246. package/dist/esm/src/wallet/substrates/WalletWireTransceiver.js +387 -711
  247. package/dist/esm/src/wallet/substrates/WalletWireTransceiver.js.map +1 -1
  248. package/dist/esm/src/wallet/substrates/XDM.js +4 -4
  249. package/dist/esm/src/wallet/substrates/XDM.js.map +1 -1
  250. package/dist/esm/src/wallet/substrates/window.CWI.js +2 -2
  251. package/dist/esm/src/wallet/substrates/window.CWI.js.map +1 -1
  252. package/dist/esm/src/wallet/validationHelpers.js +9 -9
  253. package/dist/esm/src/wallet/validationHelpers.js.map +1 -1
  254. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  255. package/dist/types/src/auth/Peer.d.ts +13 -0
  256. package/dist/types/src/auth/Peer.d.ts.map +1 -1
  257. package/dist/types/src/auth/SessionManager.d.ts.map +1 -1
  258. package/dist/types/src/auth/clients/AuthFetch.d.ts.map +1 -1
  259. package/dist/types/src/compat/ECIES.d.ts.map +1 -1
  260. package/dist/types/src/compat/HD.d.ts.map +1 -1
  261. package/dist/types/src/identity/ContactsManager.d.ts +31 -2
  262. package/dist/types/src/identity/ContactsManager.d.ts.map +1 -1
  263. package/dist/types/src/identity/IdentityClient.d.ts +75 -10
  264. package/dist/types/src/identity/IdentityClient.d.ts.map +1 -1
  265. package/dist/types/src/kvstore/GlobalKVStore.d.ts.map +1 -1
  266. package/dist/types/src/overlay-tools/LookupResolver.d.ts +73 -2
  267. package/dist/types/src/overlay-tools/LookupResolver.d.ts.map +1 -1
  268. package/dist/types/src/overlay-tools/SHIPBroadcaster.d.ts +18 -3
  269. package/dist/types/src/overlay-tools/SHIPBroadcaster.d.ts.map +1 -1
  270. package/dist/types/src/primitives/BigNumber.d.ts +13 -3
  271. package/dist/types/src/primitives/BigNumber.d.ts.map +1 -1
  272. package/dist/types/src/primitives/Curve.d.ts.map +1 -1
  273. package/dist/types/src/primitives/ECDSA.d.ts.map +1 -1
  274. package/dist/types/src/primitives/Hash.d.ts +22 -17
  275. package/dist/types/src/primitives/Hash.d.ts.map +1 -1
  276. package/dist/types/src/primitives/JacobianPoint.d.ts +3 -1
  277. package/dist/types/src/primitives/JacobianPoint.d.ts.map +1 -1
  278. package/dist/types/src/primitives/Point.d.ts.map +1 -1
  279. package/dist/types/src/primitives/Random.d.ts +2 -2
  280. package/dist/types/src/primitives/Random.d.ts.map +1 -1
  281. package/dist/types/src/primitives/ReaderUint8Array.d.ts.map +1 -1
  282. package/dist/types/src/primitives/Schnorr.d.ts +2 -1
  283. package/dist/types/src/primitives/Schnorr.d.ts.map +1 -1
  284. package/dist/types/src/primitives/Secp256r1.d.ts.map +1 -1
  285. package/dist/types/src/primitives/SymmetricKey.d.ts.map +1 -1
  286. package/dist/types/src/primitives/utils.d.ts +2 -4
  287. package/dist/types/src/primitives/utils.d.ts.map +1 -1
  288. package/dist/types/src/registry/RegistryClient.d.ts.map +1 -1
  289. package/dist/types/src/remittance/RemittanceManager.d.ts.map +1 -1
  290. package/dist/types/src/remittance/modules/BasicBRC29.d.ts.map +1 -1
  291. package/dist/types/src/script/Script.d.ts +15 -8
  292. package/dist/types/src/script/Script.d.ts.map +1 -1
  293. package/dist/types/src/script/Spend.d.ts.map +1 -1
  294. package/dist/types/src/script/templates/PushDrop.d.ts +3 -1
  295. package/dist/types/src/script/templates/PushDrop.d.ts.map +1 -1
  296. package/dist/types/src/script/templates/RPuzzle.d.ts.map +1 -1
  297. package/dist/types/src/transaction/Beef.d.ts +46 -8
  298. package/dist/types/src/transaction/Beef.d.ts.map +1 -1
  299. package/dist/types/src/transaction/BeefConstants.d.ts +15 -0
  300. package/dist/types/src/transaction/BeefConstants.d.ts.map +1 -0
  301. package/dist/types/src/transaction/Transaction.d.ts.map +1 -1
  302. package/dist/types/src/wallet/CachedKeyDeriver.d.ts.map +1 -1
  303. package/dist/types/src/wallet/KeyDeriver.d.ts +1 -1
  304. package/dist/types/src/wallet/KeyDeriver.d.ts.map +1 -1
  305. package/dist/types/src/wallet/Wallet.interfaces.d.ts +18 -3
  306. package/dist/types/src/wallet/Wallet.interfaces.d.ts.map +1 -1
  307. package/dist/types/src/wallet/WalletClient.d.ts +8 -8
  308. package/dist/types/src/wallet/WalletClient.d.ts.map +1 -1
  309. package/dist/types/src/wallet/substrates/HTTPWalletJSON.d.ts +7 -7
  310. package/dist/types/src/wallet/substrates/HTTPWalletJSON.d.ts.map +1 -1
  311. package/dist/types/src/wallet/substrates/WalletWireTransceiver.d.ts +36 -7
  312. package/dist/types/src/wallet/substrates/WalletWireTransceiver.d.ts.map +1 -1
  313. package/dist/types/src/wallet/substrates/window.CWI.d.ts +9 -9
  314. package/dist/types/src/wallet/substrates/window.CWI.d.ts.map +1 -1
  315. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  316. package/dist/umd/bundle.js +3 -3
  317. package/package.json +1 -1
  318. package/src/auth/Peer.ts +26 -13
  319. package/src/auth/SessionManager.ts +4 -7
  320. package/src/auth/certificates/MasterCertificate.ts +1 -1
  321. package/src/auth/certificates/__tests/CompletedProtoWallet.ts +1 -1
  322. package/src/auth/clients/AuthFetch.ts +41 -41
  323. package/src/auth/transports/SimplifiedFetchTransport.ts +4 -4
  324. package/src/compat/ECIES.ts +29 -34
  325. package/src/compat/HD.ts +10 -5
  326. package/src/compat/Mnemonic.ts +11 -11
  327. package/src/compat/__tests/HD.test.ts +19 -0
  328. package/src/identity/ContactsManager.ts +236 -258
  329. package/src/identity/IdentityClient.ts +244 -71
  330. package/src/identity/__tests/IdentityClient.additional.test.ts +150 -1
  331. package/src/identity/__tests/IdentityClient.test.ts +27 -3
  332. package/src/kvstore/GlobalKVStore.ts +31 -32
  333. package/src/kvstore/LocalKVStore.ts +8 -8
  334. package/src/kvstore/kvStoreInterpreter.ts +2 -2
  335. package/src/messages/SignedMessage.ts +1 -1
  336. package/src/overlay-tools/Historian.ts +1 -1
  337. package/src/overlay-tools/LookupResolver.ts +264 -90
  338. package/src/overlay-tools/SHIPBroadcaster.ts +92 -168
  339. package/src/primitives/AESGCM.ts +2 -2
  340. package/src/primitives/BigNumber.ts +122 -113
  341. package/src/primitives/Curve.ts +16 -15
  342. package/src/primitives/ECDSA.ts +10 -8
  343. package/src/primitives/Hash.ts +381 -146
  344. package/src/primitives/JacobianPoint.ts +13 -11
  345. package/src/primitives/K256.ts +3 -3
  346. package/src/primitives/Point.ts +35 -38
  347. package/src/primitives/PrivateKey.ts +3 -3
  348. package/src/primitives/PublicKey.ts +3 -3
  349. package/src/primitives/Random.ts +11 -14
  350. package/src/primitives/ReaderUint8Array.ts +7 -7
  351. package/src/primitives/Schnorr.ts +2 -1
  352. package/src/primitives/Secp256r1.ts +2 -1
  353. package/src/primitives/Signature.ts +8 -8
  354. package/src/primitives/SymmetricKey.ts +145 -1
  355. package/src/primitives/TransactionSignature.ts +16 -16
  356. package/src/primitives/__tests/Hash.additional.test.ts +65 -0
  357. package/src/primitives/__tests/Hash.test.ts +6 -1
  358. package/src/primitives/utils.ts +37 -47
  359. package/src/registry/RegistryClient.ts +25 -25
  360. package/src/remittance/RemittanceManager.ts +17 -18
  361. package/src/remittance/modules/BasicBRC29.ts +2 -5
  362. package/src/script/Script.ts +114 -170
  363. package/src/script/ScriptEvaluationError.ts +2 -2
  364. package/src/script/Spend.ts +14 -15
  365. package/src/script/templates/PushDrop.ts +5 -3
  366. package/src/script/templates/RPuzzle.ts +2 -4
  367. package/src/storage/StorageDownloader.ts +1 -1
  368. package/src/totp/totp.ts +1 -1
  369. package/src/transaction/Beef.ts +241 -203
  370. package/src/transaction/BeefConstants.ts +16 -0
  371. package/src/transaction/BeefTx.ts +3 -3
  372. package/src/transaction/MerklePath.ts +4 -4
  373. package/src/transaction/Transaction.ts +48 -51
  374. package/src/transaction/fee-models/SatoshisPerKilobyte.ts +1 -1
  375. package/src/transaction/http/BinaryFetchClient.ts +8 -8
  376. package/src/transaction/http/DefaultHttpClient.ts +8 -8
  377. package/src/wallet/CachedKeyDeriver.ts +8 -6
  378. package/src/wallet/KeyDeriver.ts +1 -1
  379. package/src/wallet/Wallet.interfaces.ts +18 -5
  380. package/src/wallet/WalletClient.ts +9 -9
  381. package/src/wallet/WalletError.ts +1 -1
  382. package/src/wallet/__tests/WalletClient.substrate.test.ts +10 -6
  383. package/src/wallet/substrates/HTTPWalletJSON.ts +22 -21
  384. package/src/wallet/substrates/ReactNativeWebView.ts +9 -9
  385. package/src/wallet/substrates/WalletWireProcessor.ts +83 -83
  386. package/src/wallet/substrates/WalletWireTransceiver.ts +528 -938
  387. package/src/wallet/substrates/XDM.ts +4 -4
  388. package/src/wallet/substrates/__tests/HTTPWalletJSON.test.ts +38 -25
  389. package/src/wallet/substrates/__tests/ReactNativeWebView.test.ts +174 -0
  390. package/src/wallet/substrates/__tests/window.CWI.test.ts +256 -0
  391. package/src/wallet/substrates/window.CWI.ts +11 -11
  392. package/src/wallet/validationHelpers.ts +9 -9
@@ -394,6 +394,25 @@ describe('HD', () => {
394
394
  })
395
395
  })
396
396
 
397
+ describe('#derive path validation', () => {
398
+ it('should derive mixed hardened and non-hardened path segments', () => {
399
+ const bip32 = HD.fromString(vector1mPrivate)
400
+ expect(() => bip32.derive("m/0'/0'/2/3/4'/4")).not.toThrow()
401
+ })
402
+
403
+ it('should reject apostrophes that are not trailing hardening markers', () => {
404
+ const bip32 = HD.fromString(vector1mPrivate)
405
+ expect(() => bip32.derive("m/1'2")).toThrow('invalid path')
406
+ expect(() => bip32.derive("m/1''")).toThrow('invalid path')
407
+ })
408
+
409
+ it('should reject child indexes outside the non-hardened range', () => {
410
+ const bip32 = HD.fromString(vector1mPrivate)
411
+ expect(() => bip32.derive('m/2147483648')).toThrow('invalid path')
412
+ expect(() => bip32.derive("m/2147483648'")).toThrow('invalid path')
413
+ })
414
+ })
415
+
397
416
  describe('#toString', () => {
398
417
  const bip32 = new HD()
399
418
  bip32.fromRandom()
@@ -34,68 +34,110 @@ export class ContactsManager {
34
34
  private readonly CONTACTS_CACHE_KEY = 'metanet-contacts'
35
35
  private readonly originator?: string
36
36
 
37
+ // Performance state — prevents thundering herd of concurrent contact loads and
38
+ // short-circuits the overlay path entirely when we've previously observed an
39
+ // empty contacts basket. Both are invalidated by saveContact/removeContact and
40
+ // by an explicit forceRefresh.
41
+ private inFlightLoad: Promise<Contact[]> | null = null
42
+ private knownEmpty = false
43
+
37
44
  constructor (wallet?: WalletInterface, originator?: string) {
38
45
  this.wallet = wallet ?? new WalletClient()
39
46
  this.originator = originator
40
47
  }
41
48
 
42
49
  /**
43
- * Load all records from the contacts basket
50
+ * Load all records from the contacts basket.
51
+ *
52
+ * Concurrent calls share a single in-flight load (no thundering herd). After
53
+ * the basket has been observed empty once, subsequent calls return `[]`
54
+ * synchronously without hitting the wallet — until `forceRefresh` is passed
55
+ * or a contact is saved/removed.
56
+ *
44
57
  * @param identityKey Optional specific identity key to fetch
45
58
  * @param forceRefresh Whether to force a check for new contact data
46
59
  * @param limit Maximum number of contacts to return
47
- * @returns A promise that resolves with an array of contacts
48
60
  */
49
61
  async getContacts (identityKey?: PubKeyHex, forceRefresh = false, limit = 1000): Promise<Contact[]> {
50
- // Check in-memory cache first unless forcing refresh
62
+ if (forceRefresh) this.invalidate()
63
+
64
+ if (this.knownEmpty) return []
65
+
51
66
  if (!forceRefresh) {
52
- const cached = this.cache.getItem(this.CONTACTS_CACHE_KEY)
53
- if (cached != null && cached !== '') {
54
- try {
55
- const cachedContacts: Contact[] = JSON.parse(cached)
56
- return identityKey != null
57
- ? cachedContacts.filter(c => c.identityKey === identityKey)
58
- : cachedContacts
59
- } catch (e) {
60
- console.warn('Invalid cached contacts JSON; will reload from chain', e)
61
- }
62
- }
67
+ const fromCache = this.loadCachedContacts(identityKey)
68
+ if (fromCache !== null) return fromCache
63
69
  }
64
70
 
65
- const tags: string[] = []
66
- if (identityKey != null) {
67
- // Hash the identity key to use as a tag for quick lookup
68
- const { hmac: hashedIdentityKey } = await this.wallet.createHmac({
69
- protocolID: CONTACT_PROTOCOL_ID,
70
- keyID: identityKey,
71
- counterparty: 'self',
72
- data: Utils.toArray(identityKey, 'utf8')
73
- }, this.originator)
74
- tags.push(`identityKey ${Utils.toHex(hashedIdentityKey)}`)
75
- }
71
+ // Coalesce concurrent loads onto a single Promise so a fan-out of N
72
+ // identity calls produces ONE listOutputs + decrypt batch, not N.
73
+ this.inFlightLoad ??= this.loadContactsFromWallet(limit).finally(() => {
74
+ this.inFlightLoad = null
75
+ })
76
+ const all = await this.inFlightLoad
77
+ return identityKey == null ? all : all.filter(c => c.identityKey === identityKey)
78
+ }
76
79
 
77
- // Get all contact outputs from the contacts basket
78
- const outputs = await this.wallet.listOutputs({
79
- basket: 'contacts',
80
- include: 'locking scripts',
81
- includeCustomInstructions: true,
82
- tags,
83
- limit
84
- }, this.originator)
80
+ /** Reset cached state. Call after writes. */
81
+ private invalidate (): void {
82
+ this.cache.removeItem(this.CONTACTS_CACHE_KEY)
83
+ this.knownEmpty = false
84
+ this.inFlightLoad = null
85
+ }
86
+
87
+ /** Underlying wallet load — invoked at most once concurrently via `inFlightLoad`. */
88
+ private async loadContactsFromWallet (limit: number): Promise<Contact[]> {
89
+ // Always load the full basket so subsequent filters (by identityKey) hit cache.
90
+ // Tag filtering is reserved for explicit per-key write paths.
91
+ const outputs = await this.wallet.listOutputs(
92
+ { basket: 'contacts', include: 'locking scripts', includeCustomInstructions: true, tags: [], limit },
93
+ this.originator
94
+ )
85
95
 
86
96
  if (outputs.outputs == null || outputs.outputs.length === 0) {
87
97
  this.cache.setItem(this.CONTACTS_CACHE_KEY, JSON.stringify([]))
98
+ this.knownEmpty = true
88
99
  return []
89
100
  }
90
101
 
91
- // Pre-process outputs synchronously to extract decode data, then decrypt in parallel
92
- const decryptTasks: Array<{ keyID: string, ciphertext: number[] }> = []
102
+ const contacts = await this.decryptContactOutputs(outputs.outputs)
103
+ this.cache.setItem(this.CONTACTS_CACHE_KEY, JSON.stringify(contacts))
104
+ return contacts
105
+ }
93
106
 
94
- for (const output of outputs.outputs) {
107
+ /** Returns cached contacts (optionally filtered) or null if cache is missing/invalid. */
108
+ private loadCachedContacts (identityKey?: PubKeyHex): Contact[] | null {
109
+ const cached = this.cache.getItem(this.CONTACTS_CACHE_KEY)
110
+ if (cached == null || cached === '') return null
111
+ try {
112
+ const cachedContacts: Contact[] = JSON.parse(cached)
113
+ return identityKey != null ? cachedContacts.filter(c => c.identityKey === identityKey) : cachedContacts
114
+ } catch (e) {
115
+ console.warn('Invalid cached contacts JSON; will reload from chain', e)
116
+ return null
117
+ }
118
+ }
119
+
120
+ /** Builds the HMAC-based identity-key tag array; empty array if no identity key is given. */
121
+ private async buildIdentityKeyTags (identityKey?: PubKeyHex): Promise<string[]> {
122
+ if (identityKey == null) return []
123
+ const { hmac: hashedIdentityKey } = await this.wallet.createHmac({
124
+ protocolID: CONTACT_PROTOCOL_ID,
125
+ keyID: identityKey,
126
+ counterparty: 'self',
127
+ data: Utils.toArray(identityKey, 'utf8')
128
+ }, this.originator)
129
+ return [`identityKey ${Utils.toHex(hashedIdentityKey)}`]
130
+ }
131
+
132
+ /** Decodes and decrypts all contact outputs in parallel, returning valid Contact objects. */
133
+ private async decryptContactOutputs (
134
+ rawOutputs: Awaited<ReturnType<WalletInterface['listOutputs']>>['outputs']
135
+ ): Promise<Contact[]> {
136
+ const decryptTasks: Array<{ keyID: string, ciphertext: number[] }> = []
137
+ for (const output of rawOutputs) {
95
138
  try {
96
- if (output.lockingScript == null) continue
139
+ if (output.lockingScript == null || output.customInstructions == null) continue
97
140
  const decoded = PushDrop.decode(LockingScript.fromHex(output.lockingScript))
98
- if (output.customInstructions == null) continue
99
141
  const keyID = JSON.parse(output.customInstructions).keyID
100
142
  decryptTasks.push({ keyID, ciphertext: decoded.fields[0] })
101
143
  } catch (error) {
@@ -103,25 +145,17 @@ export class ContactsManager {
103
145
  }
104
146
  }
105
147
 
106
- // Decrypt all contacts in parallel — each call is a network round-trip over localhost
107
148
  const decryptResults = await Promise.allSettled(
108
149
  decryptTasks.map(async task =>
109
- await this.wallet.decrypt({
110
- ciphertext: task.ciphertext,
111
- protocolID: CONTACT_PROTOCOL_ID,
112
- keyID: task.keyID,
113
- counterparty: 'self'
114
- }, this.originator)
150
+ await this.wallet.decrypt({ ciphertext: task.ciphertext, protocolID: CONTACT_PROTOCOL_ID, keyID: task.keyID, counterparty: 'self' }, this.originator)
115
151
  )
116
152
  )
117
153
 
118
154
  const contacts: Contact[] = []
119
- for (let i = 0; i < decryptResults.length; i++) {
120
- const result = decryptResults[i]
155
+ for (const result of decryptResults) {
121
156
  if (result.status === 'fulfilled') {
122
157
  try {
123
- const contactData: Contact = JSON.parse(Utils.toUTF8(result.value.plaintext))
124
- contacts.push(contactData)
158
+ contacts.push(JSON.parse(Utils.toUTF8(result.value.plaintext)) as Contact)
125
159
  } catch (error) {
126
160
  console.warn('ContactsManager: Failed to parse contact data:', error)
127
161
  }
@@ -129,13 +163,7 @@ export class ContactsManager {
129
163
  console.warn('ContactsManager: Failed to decrypt contact output:', result.reason)
130
164
  }
131
165
  }
132
-
133
- // Cache the loaded contacts
134
- this.cache.setItem(this.CONTACTS_CACHE_KEY, JSON.stringify(contacts))
135
- const filteredContacts = identityKey != null
136
- ? contacts.filter(c => c.identityKey === identityKey)
137
- : contacts
138
- return filteredContacts
166
+ return contacts
139
167
  }
140
168
 
141
169
  /**
@@ -144,152 +172,125 @@ export class ContactsManager {
144
172
  * @param metadata Optional metadata to store with the contact (ex. notes, aliases, etc)
145
173
  */
146
174
  async saveContact (contact: DisplayableIdentity, metadata?: Record<string, any>): Promise<void> {
147
- // Get current contacts from cache or blockchain
148
175
  const cached = this.cache.getItem(this.CONTACTS_CACHE_KEY)
149
- let contacts: Contact[]
150
- if (cached != null && cached !== '') {
151
- contacts = JSON.parse(cached)
152
- } else {
153
- // If cache is empty, get current data from blockchain
154
- contacts = await this.getContacts()
155
- }
156
-
176
+ const contacts: Contact[] = (cached != null && cached !== '') ? JSON.parse(cached) : await this.getContacts()
177
+ const contactToStore: Contact = { ...contact, metadata }
157
178
  const existingIndex = contacts.findIndex(c => c.identityKey === contact.identityKey)
158
- const contactToStore: Contact = {
159
- ...contact,
160
- metadata
161
- }
179
+ if (existingIndex >= 0) contacts[existingIndex] = contactToStore
180
+ else contacts.push(contactToStore)
181
+
182
+ const hashedIdentityKey = await this.hashIdentityKey(contact.identityKey)
183
+ const outputs = await this.wallet.listOutputs({
184
+ basket: 'contacts', include: 'entire transactions', includeCustomInstructions: true,
185
+ tags: [`identityKey ${Utils.toHex(hashedIdentityKey)}`], limit: 100
186
+ }, this.originator)
162
187
 
163
- if (existingIndex >= 0) {
164
- contacts[existingIndex] = contactToStore
188
+ const { existingOutput, keyID } = await this.findExistingOutput(outputs, contact.identityKey)
189
+ const lockingScript = await this.encryptAndLock(contactToStore, keyID)
190
+
191
+ if (existingOutput != null) {
192
+ await this.updateContactOutput(outputs, existingOutput, lockingScript, keyID, hashedIdentityKey, contact)
165
193
  } else {
166
- contacts.push(contactToStore)
194
+ await this.createContactOutput(lockingScript, keyID, hashedIdentityKey, contact)
167
195
  }
196
+ this.cache.setItem(this.CONTACTS_CACHE_KEY, JSON.stringify(contacts))
197
+ this.knownEmpty = false
198
+ this.inFlightLoad = null
199
+ }
168
200
 
169
- const { hmac: hashedIdentityKey } = await this.wallet.createHmac({
170
- protocolID: CONTACT_PROTOCOL_ID,
171
- keyID: contact.identityKey,
172
- counterparty: 'self',
173
- data: Utils.toArray(contact.identityKey, 'utf8')
174
- }, this.originator)
175
-
176
- // Check if this contact already exists (to update it)
177
- const outputs = await this.wallet.listOutputs({
178
- basket: 'contacts',
179
- include: 'entire transactions',
180
- includeCustomInstructions: true,
181
- tags: [`identityKey ${Utils.toHex(hashedIdentityKey)}`],
182
- limit: 100 // Should only be one contact!
201
+ /** Computes the HMAC-based hash of an identity key for tag indexing. */
202
+ private async hashIdentityKey (identityKey: string): Promise<number[]> {
203
+ const { hmac } = await this.wallet.createHmac({
204
+ protocolID: CONTACT_PROTOCOL_ID, keyID: identityKey, counterparty: 'self',
205
+ data: Utils.toArray(identityKey, 'utf8')
183
206
  }, this.originator)
207
+ return hmac
208
+ }
184
209
 
210
+ /** Scans existing outputs to find the one matching the given identity key; returns output + keyID. */
211
+ private async findExistingOutput (
212
+ outputs: Awaited<ReturnType<WalletInterface['listOutputs']>>,
213
+ identityKey: string
214
+ ): Promise<{ existingOutput: any, keyID: string }> {
185
215
  let existingOutput: any = null
186
216
  let keyID = Utils.toBase64(Random(32))
187
- if (outputs.outputs != null) {
188
- // Find output by trying to decrypt and checking identityKey in payload
189
- for (const output of outputs.outputs) {
190
- try {
191
- const [txid, outputIndex] = output.outpoint.split('.')
192
- const tx = Transaction.fromBEEF(outputs.BEEF as number[], txid)
193
- const decoded = PushDrop.decode(tx.outputs[Number(outputIndex)].lockingScript)
194
- if (output.customInstructions == null) continue
195
- keyID = JSON.parse(output.customInstructions).keyID
196
-
197
- const { plaintext } = await this.wallet.decrypt({
198
- ciphertext: decoded.fields[0],
199
- protocolID: CONTACT_PROTOCOL_ID,
200
- keyID,
201
- counterparty: 'self'
202
- }, this.originator)
203
-
204
- const storedContact: Contact = JSON.parse(Utils.toUTF8(plaintext))
205
- if (storedContact.identityKey === contact.identityKey) {
206
- // Found the right output
207
- existingOutput = output
208
- break
209
- }
210
- } catch (e) {
211
- // Skip malformed or undecryptable outputs
212
- }
213
- }
217
+ if (outputs.outputs == null) return { existingOutput, keyID }
218
+ for (const output of outputs.outputs) {
219
+ try {
220
+ const [txid, outputIndex] = output.outpoint.split('.')
221
+ const tx = Transaction.fromBEEF(outputs.BEEF as number[], txid)
222
+ const decoded = PushDrop.decode(tx.outputs[Number(outputIndex)].lockingScript)
223
+ if (output.customInstructions == null) continue
224
+ keyID = JSON.parse(output.customInstructions).keyID
225
+ const { plaintext } = await this.wallet.decrypt(
226
+ { ciphertext: decoded.fields[0], protocolID: CONTACT_PROTOCOL_ID, keyID, counterparty: 'self' }, this.originator
227
+ )
228
+ const storedContact: Contact = JSON.parse(Utils.toUTF8(plaintext))
229
+ if (storedContact.identityKey === identityKey) { existingOutput = output; break }
230
+ } catch (_malformedOrUndecryptableOutput) { /* skip */ }
214
231
  }
232
+ return { existingOutput, keyID }
233
+ }
215
234
 
216
- // Encrypt the contact data directly
217
- const contactWithMetadata: Contact = {
218
- ...contact,
219
- metadata
220
- }
235
+ /** Encrypts a contact and produces its PushDrop locking script. */
236
+ private async encryptAndLock (contactData: Contact, keyID: string): Promise<LockingScript> {
221
237
  const { ciphertext } = await this.wallet.encrypt({
222
- plaintext: Utils.toArray(JSON.stringify(contactWithMetadata), 'utf8'),
223
- protocolID: CONTACT_PROTOCOL_ID,
224
- keyID,
225
- counterparty: 'self'
238
+ plaintext: Utils.toArray(JSON.stringify(contactData), 'utf8'),
239
+ protocolID: CONTACT_PROTOCOL_ID, keyID, counterparty: 'self'
226
240
  }, this.originator)
241
+ return await new PushDrop(this.wallet, this.originator).lock([ciphertext], CONTACT_PROTOCOL_ID, keyID, 'self')
242
+ }
227
243
 
228
- // Create locking script for the new contact token
229
- const lockingScript = await new PushDrop(this.wallet, this.originator).lock(
230
- [ciphertext],
231
- CONTACT_PROTOCOL_ID,
232
- keyID,
233
- 'self'
234
- )
235
-
236
- if (existingOutput != null) {
237
- // Update existing contact by spending its output
238
- const [txid, outputIndex] = String(existingOutput.outpoint).split('.')
239
- const prevOutpoint = `${txid}.${outputIndex}` as const
240
-
241
- const pushdrop = new PushDrop(this.wallet, this.originator)
242
- const { signableTransaction } = await this.wallet.createAction({
243
- description: 'Update Contact',
244
- inputBEEF: outputs.BEEF as number[],
245
- inputs: [{
246
- outpoint: prevOutpoint,
247
- unlockingScriptLength: 74,
248
- inputDescription: 'Spend previous contact output'
249
- }],
250
- outputs: [{
251
- basket: 'contacts',
252
- satoshis: 1,
253
- lockingScript: lockingScript.toHex(),
254
- outputDescription: `Updated Contact: ${contact.name ?? contact.identityKey.slice(0, 10)}`,
255
- tags: [`identityKey ${Utils.toHex(hashedIdentityKey)}`],
256
- customInstructions: JSON.stringify({ keyID })
257
- }],
258
- options: { acceptDelayedBroadcast: false, randomizeOutputs: false } // TODO: Support custom config as needed.
259
- }, this.originator)
260
-
261
- if (signableTransaction == null) throw new Error('Unable to update contact')
262
-
263
- const unlocker = pushdrop.unlock(CONTACT_PROTOCOL_ID, keyID, 'self')
264
- const unlockingScript = await unlocker.sign(
265
- Transaction.fromBEEF(signableTransaction.tx),
266
- 0
267
- )
268
-
269
- const { tx } = await this.wallet.signAction({
270
- reference: signableTransaction.reference,
271
- spends: { 0: { unlockingScript: unlockingScript.toHex() } }
272
- }, this.originator)
244
+ /** Spends an existing contact output and creates a replacement with updated data. */
245
+ private async updateContactOutput (
246
+ outputs: Awaited<ReturnType<WalletInterface['listOutputs']>>,
247
+ existingOutput: any,
248
+ lockingScript: LockingScript,
249
+ keyID: string,
250
+ hashedIdentityKey: number[],
251
+ contact: DisplayableIdentity
252
+ ): Promise<void> {
253
+ const [txid, outputIndex] = String(existingOutput.outpoint).split('.')
254
+ const prevOutpoint = `${txid}.${outputIndex}` as const
255
+ const pushdrop = new PushDrop(this.wallet, this.originator)
256
+ const { signableTransaction } = await this.wallet.createAction({
257
+ description: 'Update Contact',
258
+ inputBEEF: outputs.BEEF as number[],
259
+ inputs: [{ outpoint: prevOutpoint, unlockingScriptLength: 74, inputDescription: 'Spend previous contact output' }],
260
+ outputs: [{
261
+ basket: 'contacts', satoshis: 1, lockingScript: lockingScript.toHex(),
262
+ outputDescription: `Updated Contact: ${contact.name ?? contact.identityKey.slice(0, 10)}`,
263
+ tags: [`identityKey ${Utils.toHex(hashedIdentityKey)}`], customInstructions: JSON.stringify({ keyID })
264
+ }],
265
+ options: { acceptDelayedBroadcast: false, randomizeOutputs: false }
266
+ }, this.originator)
267
+ if (signableTransaction == null) throw new Error('Unable to update contact')
268
+ const unlockingScript = await pushdrop.unlock(CONTACT_PROTOCOL_ID, keyID, 'self')
269
+ .sign(Transaction.fromBEEF(signableTransaction.tx), 0)
270
+ const { tx } = await this.wallet.signAction({
271
+ reference: signableTransaction.reference,
272
+ spends: { 0: { unlockingScript: unlockingScript.toHex() } }
273
+ }, this.originator)
274
+ if (tx == null) throw new Error('Failed to update contact output')
275
+ }
273
276
 
274
- if (tx == null) throw new Error('Failed to update contact output')
275
- } else {
276
- // Create new contact output
277
- const { tx } = await this.wallet.createAction({
278
- description: 'Add Contact',
279
- outputs: [{
280
- basket: 'contacts',
281
- satoshis: 1,
282
- lockingScript: lockingScript.toHex(),
283
- outputDescription: `Contact: ${contact.name ?? contact.identityKey.slice(0, 10)}`,
284
- tags: [`identityKey ${Utils.toHex(hashedIdentityKey)}`],
285
- customInstructions: JSON.stringify({ keyID })
286
- }],
287
- options: { acceptDelayedBroadcast: false, randomizeOutputs: false } // TODO: Support custom config as needed.
288
- }, this.originator)
289
-
290
- if (tx == null) throw new Error('Failed to create contact output')
291
- }
292
- this.cache.setItem(this.CONTACTS_CACHE_KEY, JSON.stringify(contacts))
277
+ /** Creates a new on-chain contact output. */
278
+ private async createContactOutput (
279
+ lockingScript: LockingScript,
280
+ keyID: string,
281
+ hashedIdentityKey: number[],
282
+ contact: DisplayableIdentity
283
+ ): Promise<void> {
284
+ const { tx } = await this.wallet.createAction({
285
+ description: 'Add Contact',
286
+ outputs: [{
287
+ basket: 'contacts', satoshis: 1, lockingScript: lockingScript.toHex(),
288
+ outputDescription: `Contact: ${contact.name ?? contact.identityKey.slice(0, 10)}`,
289
+ tags: [`identityKey ${Utils.toHex(hashedIdentityKey)}`], customInstructions: JSON.stringify({ keyID })
290
+ }],
291
+ options: { acceptDelayedBroadcast: false, randomizeOutputs: false }
292
+ }, this.originator)
293
+ if (tx == null) throw new Error('Failed to create contact output')
293
294
  }
294
295
 
295
296
  /**
@@ -302,87 +303,64 @@ export class ContactsManager {
302
303
  if (cached != null && cached !== '') {
303
304
  try {
304
305
  const contacts: Contact[] = JSON.parse(cached)
305
- const filteredContacts = contacts.filter(c => c.identityKey !== identityKey)
306
- this.cache.setItem(this.CONTACTS_CACHE_KEY, JSON.stringify(filteredContacts))
306
+ const remaining = contacts.filter(c => c.identityKey !== identityKey)
307
+ this.cache.setItem(this.CONTACTS_CACHE_KEY, JSON.stringify(remaining))
308
+ this.knownEmpty = remaining.length === 0
307
309
  } catch (e) {
308
310
  console.warn('Failed to update cache after contact removal:', e)
309
311
  }
310
312
  }
313
+ this.inFlightLoad = null
311
314
 
312
- // Hash the identity key to use as a tag for quick lookup
313
- const tags: string[] = []
314
- const { hmac: hashedIdentityKey } = await this.wallet.createHmac({
315
- protocolID: CONTACT_PROTOCOL_ID,
316
- keyID: identityKey,
317
- counterparty: 'self',
318
- data: Utils.toArray(identityKey, 'utf8')
319
- }, this.originator)
320
- tags.push(`identityKey ${Utils.toHex(hashedIdentityKey)}`)
321
-
322
- // Find and spend the contact's output
323
- const outputs = await this.wallet.listOutputs({
324
- basket: 'contacts',
325
- include: 'entire transactions',
326
- includeCustomInstructions: true,
327
- tags,
328
- limit: 100 // Should only be one contact!
329
- }, this.originator)
330
-
315
+ const tags = await this.buildIdentityKeyTags(identityKey)
316
+ const outputs = await this.wallet.listOutputs(
317
+ { basket: 'contacts', include: 'entire transactions', includeCustomInstructions: true, tags, limit: 100 },
318
+ this.originator
319
+ )
331
320
  if (outputs.outputs == null) return
332
321
 
333
- // Find the output for this specific contact by decrypting and checking identityKey
334
322
  for (const output of outputs.outputs) {
335
323
  try {
336
- const [txid, outputIndex] = String(output.outpoint).split('.')
337
- const tx = Transaction.fromBEEF(outputs.BEEF as number[], txid)
338
- const decoded = PushDrop.decode(tx.outputs[Number(outputIndex)].lockingScript)
339
- if (output.customInstructions == null) continue
340
- const keyID = JSON.parse(output.customInstructions).keyID
341
-
342
- const { plaintext } = await this.wallet.decrypt({
343
- ciphertext: decoded.fields[0],
344
- protocolID: CONTACT_PROTOCOL_ID,
345
- keyID,
346
- counterparty: 'self'
347
- }, this.originator)
348
-
349
- const storedContact: Contact = JSON.parse(Utils.toUTF8(plaintext))
350
- if (storedContact.identityKey === identityKey) {
351
- // Found the contact's output, spend it without creating a new one
352
- const prevOutpoint = `${txid}.${outputIndex}` as const
353
-
354
- const pushdrop = new PushDrop(this.wallet, this.originator)
355
- const { signableTransaction } = await this.wallet.createAction({
356
- description: 'Delete Contact',
357
- inputBEEF: outputs.BEEF as number[],
358
- inputs: [{
359
- outpoint: prevOutpoint,
360
- unlockingScriptLength: 74,
361
- inputDescription: 'Spend contact output to delete'
362
- }],
363
- outputs: [], // No outputs = deletion
364
- options: { acceptDelayedBroadcast: false, randomizeOutputs: false } // TODO: Support custom config as needed.
365
- }, this.originator)
366
-
367
- if (signableTransaction == null) throw new Error('Unable to delete contact')
368
-
369
- const unlocker = pushdrop.unlock(CONTACT_PROTOCOL_ID, keyID, 'self')
370
- const unlockingScript = await unlocker.sign(
371
- Transaction.fromBEEF(signableTransaction.tx),
372
- 0
373
- )
374
-
375
- const { tx: deleteTx } = await this.wallet.signAction({
376
- reference: signableTransaction.reference,
377
- spends: { 0: { unlockingScript: unlockingScript.toHex() } }
378
- }, this.originator)
379
-
380
- if (deleteTx == null) throw new Error('Failed to delete contact output')
381
- return
382
- }
383
- } catch (e) {
384
- // Skip malformed or undecryptable outputs
385
- }
324
+ const spent = await this.trySpendContactOutput(output, outputs, identityKey)
325
+ if (spent) return
326
+ } catch (_malformedOrUndecryptableOutput) { /* skip */ }
386
327
  }
387
328
  }
329
+
330
+ /** Attempts to decrypt and spend a single output if it matches the given identity key. Returns true if spent. */
331
+ private async trySpendContactOutput (
332
+ output: Awaited<ReturnType<WalletInterface['listOutputs']>>['outputs'][number],
333
+ outputs: Awaited<ReturnType<WalletInterface['listOutputs']>>,
334
+ identityKey: string
335
+ ): Promise<boolean> {
336
+ const [txid, outputIndex] = String(output.outpoint).split('.')
337
+ const tx = Transaction.fromBEEF(outputs.BEEF as number[], txid)
338
+ const decoded = PushDrop.decode(tx.outputs[Number(outputIndex)].lockingScript)
339
+ if (output.customInstructions == null) return false
340
+ const keyID = JSON.parse(output.customInstructions).keyID
341
+ const { plaintext } = await this.wallet.decrypt(
342
+ { ciphertext: decoded.fields[0], protocolID: CONTACT_PROTOCOL_ID, keyID, counterparty: 'self' }, this.originator
343
+ )
344
+ const storedContact: Contact = JSON.parse(Utils.toUTF8(plaintext))
345
+ if (storedContact.identityKey !== identityKey) return false
346
+
347
+ const prevOutpoint = `${txid}.${outputIndex}` as const
348
+ const pushdrop = new PushDrop(this.wallet, this.originator)
349
+ const { signableTransaction } = await this.wallet.createAction({
350
+ description: 'Delete Contact',
351
+ inputBEEF: outputs.BEEF as number[],
352
+ inputs: [{ outpoint: prevOutpoint, unlockingScriptLength: 74, inputDescription: 'Spend contact output to delete' }],
353
+ outputs: [],
354
+ options: { acceptDelayedBroadcast: false, randomizeOutputs: false }
355
+ }, this.originator)
356
+ if (signableTransaction == null) throw new Error('Unable to delete contact')
357
+ const unlockingScript = await pushdrop.unlock(CONTACT_PROTOCOL_ID, keyID, 'self')
358
+ .sign(Transaction.fromBEEF(signableTransaction.tx), 0)
359
+ const { tx: deleteTx } = await this.wallet.signAction({
360
+ reference: signableTransaction.reference,
361
+ spends: { 0: { unlockingScript: unlockingScript.toHex() } }
362
+ }, this.originator)
363
+ if (deleteTx == null) throw new Error('Failed to delete contact output')
364
+ return true
365
+ }
388
366
  }