@originals/sdk 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (403) hide show
  1. package/.eslintrc.json +33 -0
  2. package/.turbo/turbo-build.log +1 -0
  3. package/.turbo/turbo-test.log +68353 -0
  4. package/dist/adapters/FeeOracleMock.d.ts +6 -0
  5. package/dist/adapters/FeeOracleMock.js +8 -0
  6. package/dist/adapters/index.d.ts +4 -0
  7. package/dist/adapters/index.js +4 -0
  8. package/dist/adapters/providers/OrdHttpProvider.d.ts +56 -0
  9. package/dist/adapters/providers/OrdHttpProvider.js +110 -0
  10. package/dist/adapters/providers/OrdMockProvider.d.ts +70 -0
  11. package/dist/adapters/providers/OrdMockProvider.js +75 -0
  12. package/dist/adapters/types.d.ts +71 -0
  13. package/dist/adapters/types.js +1 -0
  14. package/dist/bitcoin/BitcoinManager.d.ts +15 -0
  15. package/dist/bitcoin/BitcoinManager.js +262 -0
  16. package/dist/bitcoin/BroadcastClient.d.ts +30 -0
  17. package/dist/bitcoin/BroadcastClient.js +35 -0
  18. package/dist/bitcoin/OrdinalsClient.d.ts +21 -0
  19. package/dist/bitcoin/OrdinalsClient.js +105 -0
  20. package/dist/bitcoin/PSBTBuilder.d.ts +24 -0
  21. package/dist/bitcoin/PSBTBuilder.js +80 -0
  22. package/dist/bitcoin/fee-calculation.d.ts +14 -0
  23. package/dist/bitcoin/fee-calculation.js +31 -0
  24. package/dist/bitcoin/providers/OrdNodeProvider.d.ts +38 -0
  25. package/dist/bitcoin/providers/OrdNodeProvider.js +67 -0
  26. package/dist/bitcoin/providers/OrdinalsProvider.d.ts +33 -0
  27. package/dist/bitcoin/providers/OrdinalsProvider.js +50 -0
  28. package/dist/bitcoin/providers/types.d.ts +63 -0
  29. package/dist/bitcoin/providers/types.js +1 -0
  30. package/dist/bitcoin/transactions/commit.d.ts +89 -0
  31. package/dist/bitcoin/transactions/commit.js +311 -0
  32. package/dist/bitcoin/transactions/index.d.ts +7 -0
  33. package/dist/bitcoin/transactions/index.js +8 -0
  34. package/dist/bitcoin/transfer.d.ts +9 -0
  35. package/dist/bitcoin/transfer.js +26 -0
  36. package/dist/bitcoin/utxo-selection.d.ts +78 -0
  37. package/dist/bitcoin/utxo-selection.js +237 -0
  38. package/dist/bitcoin/utxo.d.ts +26 -0
  39. package/dist/bitcoin/utxo.js +78 -0
  40. package/dist/contexts/credentials-v1.json +195 -0
  41. package/dist/contexts/credentials-v2-examples.json +5 -0
  42. package/dist/contexts/credentials-v2.json +301 -0
  43. package/dist/contexts/credentials.json +195 -0
  44. package/dist/contexts/data-integrity-v2.json +81 -0
  45. package/dist/contexts/dids.json +57 -0
  46. package/dist/contexts/ed255192020.json +93 -0
  47. package/dist/contexts/ordinals-plus.json +23 -0
  48. package/dist/contexts/originals.json +22 -0
  49. package/dist/core/OriginalsSDK.d.ts +158 -0
  50. package/dist/core/OriginalsSDK.js +274 -0
  51. package/dist/crypto/Multikey.d.ts +30 -0
  52. package/dist/crypto/Multikey.js +149 -0
  53. package/dist/crypto/Signer.d.ts +21 -0
  54. package/dist/crypto/Signer.js +196 -0
  55. package/dist/crypto/noble-init.d.ts +18 -0
  56. package/dist/crypto/noble-init.js +106 -0
  57. package/dist/did/BtcoDidResolver.d.ts +57 -0
  58. package/dist/did/BtcoDidResolver.js +166 -0
  59. package/dist/did/DIDManager.d.ts +101 -0
  60. package/dist/did/DIDManager.js +493 -0
  61. package/dist/did/Ed25519Verifier.d.ts +30 -0
  62. package/dist/did/Ed25519Verifier.js +59 -0
  63. package/dist/did/KeyManager.d.ts +17 -0
  64. package/dist/did/KeyManager.js +207 -0
  65. package/dist/did/WebVHManager.d.ts +100 -0
  66. package/dist/did/WebVHManager.js +304 -0
  67. package/dist/did/createBtcoDidDocument.d.ts +10 -0
  68. package/dist/did/createBtcoDidDocument.js +42 -0
  69. package/dist/did/providers/OrdinalsClientProviderAdapter.d.ts +23 -0
  70. package/dist/did/providers/OrdinalsClientProviderAdapter.js +51 -0
  71. package/dist/events/EventEmitter.d.ts +115 -0
  72. package/dist/events/EventEmitter.js +198 -0
  73. package/dist/events/index.d.ts +7 -0
  74. package/dist/events/index.js +6 -0
  75. package/dist/events/types.d.ts +286 -0
  76. package/dist/events/types.js +9 -0
  77. package/dist/examples/basic-usage.d.ts +3 -0
  78. package/dist/examples/basic-usage.js +62 -0
  79. package/dist/examples/run.d.ts +1 -0
  80. package/dist/examples/run.js +4 -0
  81. package/dist/index.d.ts +39 -0
  82. package/dist/index.js +47 -0
  83. package/dist/lifecycle/BatchOperations.d.ts +147 -0
  84. package/dist/lifecycle/BatchOperations.js +251 -0
  85. package/dist/lifecycle/LifecycleManager.d.ts +116 -0
  86. package/dist/lifecycle/LifecycleManager.js +971 -0
  87. package/dist/lifecycle/OriginalsAsset.d.ts +164 -0
  88. package/dist/lifecycle/OriginalsAsset.js +380 -0
  89. package/dist/lifecycle/ProvenanceQuery.d.ts +126 -0
  90. package/dist/lifecycle/ProvenanceQuery.js +220 -0
  91. package/dist/lifecycle/ResourceVersioning.d.ts +73 -0
  92. package/dist/lifecycle/ResourceVersioning.js +127 -0
  93. package/dist/migration/MigrationManager.d.ts +86 -0
  94. package/dist/migration/MigrationManager.js +412 -0
  95. package/dist/migration/audit/AuditLogger.d.ts +51 -0
  96. package/dist/migration/audit/AuditLogger.js +156 -0
  97. package/dist/migration/checkpoint/CheckpointManager.d.ts +31 -0
  98. package/dist/migration/checkpoint/CheckpointManager.js +96 -0
  99. package/dist/migration/checkpoint/CheckpointStorage.d.ts +26 -0
  100. package/dist/migration/checkpoint/CheckpointStorage.js +89 -0
  101. package/dist/migration/index.d.ts +22 -0
  102. package/dist/migration/index.js +27 -0
  103. package/dist/migration/operations/BaseMigration.d.ts +48 -0
  104. package/dist/migration/operations/BaseMigration.js +83 -0
  105. package/dist/migration/operations/PeerToBtcoMigration.d.ts +25 -0
  106. package/dist/migration/operations/PeerToBtcoMigration.js +67 -0
  107. package/dist/migration/operations/PeerToWebvhMigration.d.ts +19 -0
  108. package/dist/migration/operations/PeerToWebvhMigration.js +46 -0
  109. package/dist/migration/operations/WebvhToBtcoMigration.d.ts +25 -0
  110. package/dist/migration/operations/WebvhToBtcoMigration.js +67 -0
  111. package/dist/migration/rollback/RollbackManager.d.ts +29 -0
  112. package/dist/migration/rollback/RollbackManager.js +146 -0
  113. package/dist/migration/state/StateMachine.d.ts +25 -0
  114. package/dist/migration/state/StateMachine.js +76 -0
  115. package/dist/migration/state/StateTracker.d.ts +36 -0
  116. package/dist/migration/state/StateTracker.js +123 -0
  117. package/dist/migration/types.d.ts +306 -0
  118. package/dist/migration/types.js +33 -0
  119. package/dist/migration/validation/BitcoinValidator.d.ts +13 -0
  120. package/dist/migration/validation/BitcoinValidator.js +83 -0
  121. package/dist/migration/validation/CredentialValidator.d.ts +13 -0
  122. package/dist/migration/validation/CredentialValidator.js +46 -0
  123. package/dist/migration/validation/DIDCompatibilityValidator.d.ts +16 -0
  124. package/dist/migration/validation/DIDCompatibilityValidator.js +127 -0
  125. package/dist/migration/validation/LifecycleValidator.d.ts +10 -0
  126. package/dist/migration/validation/LifecycleValidator.js +52 -0
  127. package/dist/migration/validation/StorageValidator.d.ts +10 -0
  128. package/dist/migration/validation/StorageValidator.js +65 -0
  129. package/dist/migration/validation/ValidationPipeline.d.ts +29 -0
  130. package/dist/migration/validation/ValidationPipeline.js +180 -0
  131. package/dist/storage/LocalStorageAdapter.d.ts +11 -0
  132. package/dist/storage/LocalStorageAdapter.js +53 -0
  133. package/dist/storage/MemoryStorageAdapter.d.ts +6 -0
  134. package/dist/storage/MemoryStorageAdapter.js +21 -0
  135. package/dist/storage/StorageAdapter.d.ts +16 -0
  136. package/dist/storage/StorageAdapter.js +1 -0
  137. package/dist/storage/index.d.ts +2 -0
  138. package/dist/storage/index.js +2 -0
  139. package/dist/types/bitcoin.d.ts +84 -0
  140. package/dist/types/bitcoin.js +1 -0
  141. package/dist/types/common.d.ts +82 -0
  142. package/dist/types/common.js +1 -0
  143. package/dist/types/credentials.d.ts +75 -0
  144. package/dist/types/credentials.js +1 -0
  145. package/dist/types/did.d.ts +26 -0
  146. package/dist/types/did.js +1 -0
  147. package/dist/types/index.d.ts +5 -0
  148. package/dist/types/index.js +5 -0
  149. package/dist/types/network.d.ts +78 -0
  150. package/dist/types/network.js +145 -0
  151. package/dist/utils/EventLogger.d.ts +71 -0
  152. package/dist/utils/EventLogger.js +232 -0
  153. package/dist/utils/Logger.d.ts +106 -0
  154. package/dist/utils/Logger.js +257 -0
  155. package/dist/utils/MetricsCollector.d.ts +110 -0
  156. package/dist/utils/MetricsCollector.js +264 -0
  157. package/dist/utils/bitcoin-address.d.ts +38 -0
  158. package/dist/utils/bitcoin-address.js +113 -0
  159. package/dist/utils/cbor.d.ts +2 -0
  160. package/dist/utils/cbor.js +9 -0
  161. package/dist/utils/encoding.d.ts +37 -0
  162. package/dist/utils/encoding.js +120 -0
  163. package/dist/utils/hash.d.ts +1 -0
  164. package/dist/utils/hash.js +5 -0
  165. package/dist/utils/retry.d.ts +10 -0
  166. package/dist/utils/retry.js +35 -0
  167. package/dist/utils/satoshi-validation.d.ts +60 -0
  168. package/dist/utils/satoshi-validation.js +156 -0
  169. package/dist/utils/serialization.d.ts +14 -0
  170. package/dist/utils/serialization.js +76 -0
  171. package/dist/utils/telemetry.d.ts +17 -0
  172. package/dist/utils/telemetry.js +24 -0
  173. package/dist/utils/validation.d.ts +5 -0
  174. package/dist/utils/validation.js +98 -0
  175. package/dist/vc/CredentialManager.d.ts +22 -0
  176. package/dist/vc/CredentialManager.js +227 -0
  177. package/dist/vc/Issuer.d.ts +27 -0
  178. package/dist/vc/Issuer.js +70 -0
  179. package/dist/vc/Verifier.d.ts +16 -0
  180. package/dist/vc/Verifier.js +50 -0
  181. package/dist/vc/cryptosuites/bbs.d.ts +44 -0
  182. package/dist/vc/cryptosuites/bbs.js +213 -0
  183. package/dist/vc/cryptosuites/bbsSimple.d.ts +9 -0
  184. package/dist/vc/cryptosuites/bbsSimple.js +12 -0
  185. package/dist/vc/cryptosuites/eddsa.d.ts +30 -0
  186. package/dist/vc/cryptosuites/eddsa.js +81 -0
  187. package/dist/vc/documentLoader.d.ts +16 -0
  188. package/dist/vc/documentLoader.js +59 -0
  189. package/dist/vc/proofs/data-integrity.d.ts +21 -0
  190. package/dist/vc/proofs/data-integrity.js +15 -0
  191. package/dist/vc/utils/jsonld.d.ts +2 -0
  192. package/dist/vc/utils/jsonld.js +15 -0
  193. package/package.json +79 -0
  194. package/src/adapters/FeeOracleMock.ts +9 -0
  195. package/src/adapters/index.ts +5 -0
  196. package/src/adapters/providers/OrdHttpProvider.ts +126 -0
  197. package/src/adapters/providers/OrdMockProvider.ts +101 -0
  198. package/src/adapters/types.ts +66 -0
  199. package/src/bitcoin/BitcoinManager.ts +330 -0
  200. package/src/bitcoin/BroadcastClient.ts +54 -0
  201. package/src/bitcoin/OrdinalsClient.ts +119 -0
  202. package/src/bitcoin/PSBTBuilder.ts +106 -0
  203. package/src/bitcoin/fee-calculation.ts +38 -0
  204. package/src/bitcoin/providers/OrdNodeProvider.ts +92 -0
  205. package/src/bitcoin/providers/OrdinalsProvider.ts +56 -0
  206. package/src/bitcoin/providers/types.ts +59 -0
  207. package/src/bitcoin/transactions/commit.ts +465 -0
  208. package/src/bitcoin/transactions/index.ts +13 -0
  209. package/src/bitcoin/transfer.ts +43 -0
  210. package/src/bitcoin/utxo-selection.ts +322 -0
  211. package/src/bitcoin/utxo.ts +113 -0
  212. package/src/contexts/credentials-v1.json +237 -0
  213. package/src/contexts/credentials-v2-examples.json +5 -0
  214. package/src/contexts/credentials-v2.json +340 -0
  215. package/src/contexts/credentials.json +237 -0
  216. package/src/contexts/data-integrity-v2.json +81 -0
  217. package/src/contexts/dids.json +58 -0
  218. package/src/contexts/ed255192020.json +93 -0
  219. package/src/contexts/ordinals-plus.json +23 -0
  220. package/src/contexts/originals.json +22 -0
  221. package/src/core/OriginalsSDK.ts +416 -0
  222. package/src/crypto/Multikey.ts +194 -0
  223. package/src/crypto/Signer.ts +254 -0
  224. package/src/crypto/noble-init.ts +121 -0
  225. package/src/did/BtcoDidResolver.ts +227 -0
  226. package/src/did/DIDManager.ts +694 -0
  227. package/src/did/Ed25519Verifier.ts +68 -0
  228. package/src/did/KeyManager.ts +236 -0
  229. package/src/did/WebVHManager.ts +489 -0
  230. package/src/did/createBtcoDidDocument.ts +59 -0
  231. package/src/did/providers/OrdinalsClientProviderAdapter.ts +68 -0
  232. package/src/events/EventEmitter.ts +222 -0
  233. package/src/events/index.ts +19 -0
  234. package/src/events/types.ts +331 -0
  235. package/src/examples/basic-usage.ts +78 -0
  236. package/src/examples/run.ts +5 -0
  237. package/src/index.ts +84 -0
  238. package/src/lifecycle/BatchOperations.ts +373 -0
  239. package/src/lifecycle/LifecycleManager.ts +1218 -0
  240. package/src/lifecycle/OriginalsAsset.ts +524 -0
  241. package/src/lifecycle/ProvenanceQuery.ts +280 -0
  242. package/src/lifecycle/ResourceVersioning.ts +163 -0
  243. package/src/migration/MigrationManager.ts +527 -0
  244. package/src/migration/audit/AuditLogger.ts +176 -0
  245. package/src/migration/checkpoint/CheckpointManager.ts +112 -0
  246. package/src/migration/checkpoint/CheckpointStorage.ts +101 -0
  247. package/src/migration/index.ts +33 -0
  248. package/src/migration/operations/BaseMigration.ts +126 -0
  249. package/src/migration/operations/PeerToBtcoMigration.ts +105 -0
  250. package/src/migration/operations/PeerToWebvhMigration.ts +62 -0
  251. package/src/migration/operations/WebvhToBtcoMigration.ts +105 -0
  252. package/src/migration/rollback/RollbackManager.ts +170 -0
  253. package/src/migration/state/StateMachine.ts +92 -0
  254. package/src/migration/state/StateTracker.ts +156 -0
  255. package/src/migration/types.ts +344 -0
  256. package/src/migration/validation/BitcoinValidator.ts +107 -0
  257. package/src/migration/validation/CredentialValidator.ts +62 -0
  258. package/src/migration/validation/DIDCompatibilityValidator.ts +151 -0
  259. package/src/migration/validation/LifecycleValidator.ts +64 -0
  260. package/src/migration/validation/StorageValidator.ts +79 -0
  261. package/src/migration/validation/ValidationPipeline.ts +213 -0
  262. package/src/storage/LocalStorageAdapter.ts +61 -0
  263. package/src/storage/MemoryStorageAdapter.ts +29 -0
  264. package/src/storage/StorageAdapter.ts +25 -0
  265. package/src/storage/index.ts +3 -0
  266. package/src/types/bitcoin.ts +98 -0
  267. package/src/types/common.ts +92 -0
  268. package/src/types/credentials.ts +88 -0
  269. package/src/types/did.ts +31 -0
  270. package/src/types/external-shims.d.ts +53 -0
  271. package/src/types/index.ts +7 -0
  272. package/src/types/network.ts +175 -0
  273. package/src/utils/EventLogger.ts +298 -0
  274. package/src/utils/Logger.ts +322 -0
  275. package/src/utils/MetricsCollector.ts +358 -0
  276. package/src/utils/bitcoin-address.ts +130 -0
  277. package/src/utils/cbor.ts +12 -0
  278. package/src/utils/encoding.ts +127 -0
  279. package/src/utils/hash.ts +6 -0
  280. package/src/utils/retry.ts +46 -0
  281. package/src/utils/satoshi-validation.ts +196 -0
  282. package/src/utils/serialization.ts +96 -0
  283. package/src/utils/telemetry.ts +40 -0
  284. package/src/utils/validation.ts +119 -0
  285. package/src/vc/CredentialManager.ts +273 -0
  286. package/src/vc/Issuer.ts +100 -0
  287. package/src/vc/Verifier.ts +47 -0
  288. package/src/vc/cryptosuites/bbs.ts +253 -0
  289. package/src/vc/cryptosuites/bbsSimple.ts +21 -0
  290. package/src/vc/cryptosuites/eddsa.ts +99 -0
  291. package/src/vc/documentLoader.ts +67 -0
  292. package/src/vc/proofs/data-integrity.ts +33 -0
  293. package/src/vc/utils/jsonld.ts +18 -0
  294. package/test/logs/did_webvh_QmQsRNhXxPSCSeLjpbKYcNMZj8b1kBQAoC6cZmkFAgmpHt_example_com.jsonl +1 -0
  295. package/test/logs/did_webvh_QmSQkpD58qxcqMWHYcEmDUn3wk7hHvJwzYTrZmhh6zjPQ8_example_com_users_alice123_profile.jsonl +1 -0
  296. package/test/logs/did_webvh_QmTMda6VW3cUPdKk5Yc3onnv1vdgEumvWWdP2noAYFSjeG_example_com.jsonl +1 -0
  297. package/test/logs/did_webvh_QmTkb8KnCYcsnKKDCY4eUQuKQdKJLrCinvhw13v3zETxpE_example_com_users_etc_passwd.jsonl +1 -0
  298. package/test/logs/did_webvh_QmTn9FdCfpXFDrxHH52pwB4iNrDFVvNDjJ5FQTcDbmM3Fg_example_com.jsonl +1 -0
  299. package/test/logs/did_webvh_QmUCQUi1xjtJjnSQ1XJZgKqcWgErx1v7E2dz4DAPraAyJP_example_com_etc_passwd.jsonl +1 -0
  300. package/test/logs/did_webvh_QmUENQJCDKBJVRS5BkL6zjaUvcRjkb9xHmy7foCgRjmv3W_example_com.jsonl +1 -0
  301. package/test/logs/did_webvh_QmUPdGyjYBEnQ3aQUkmqyyBKTyjvCP5RZQGiaEDeTtf6dc_example_com.jsonl +1 -0
  302. package/test/logs/did_webvh_QmUoHTuHMWzQM29ZFrE9VLtMxkZ5u869yqee8LwcCLN39M_example_com.jsonl +1 -0
  303. package/test/logs/did_webvh_QmUrnms8G65ggVKsr9oQeWrLUBuGChwQPPb2LCFvaoNxaw_example_com_users_alice.jsonl +1 -0
  304. package/test/logs/did_webvh_QmUwiw3eSXdHG1hPvoAGu3cuK5jF4aXRYDLBAjPXfv1qzb_example_com_level1_level2.jsonl +1 -0
  305. package/test/logs/did_webvh_QmW7bzKh6yFEKNAtmVsrPGvvsMHTUQdzJSNsTZkbuGFpbj_example_com_secret.jsonl +1 -0
  306. package/test/logs/did_webvh_QmXbFTFBBJ8zpjdz9WE1DNN44A2wprFmdvAubjSffeyoAG_example_com.jsonl +1 -0
  307. package/test/logs/did_webvh_QmXyVXFPCTffGb2mTUFDeMCsScjnpLWkyUkVkB6q6QoeBf_example_com_C_Windows_System32.jsonl +1 -0
  308. package/test/logs/did_webvh_QmZK9B81gxZtvo5fYHYKDtKt8zZfZZPhmCMhbujBJuRRzE_example_com_etc_passwd.jsonl +1 -0
  309. package/test/logs/did_webvh_QmbNLCVSdXSVLrwFBvCBQPAabjtRb1SGHjkGVyw3QUbfBL_example_com_users_etc_passwd.jsonl +1 -0
  310. package/test/logs/did_webvh_QmbeaicmGW3Q7Yzbqmftc8a9jLBngokveb5A2KVKfVGZRb_example_com_my_org_user_name_test_123.jsonl +1 -0
  311. package/test/logs/did_webvh_Qmdv7c7AjUreUfoKyvkN2UpAWTozxKsv99srQetPJMJEnp_example_com_users_etc_passwd.jsonl +1 -0
  312. package/test/logs/did_webvh_QmeioWY3uypYLkYpCXe9eCYnn4xBVruP9C1d79azMrTEHG_example_com.jsonl +1 -0
  313. package/test/logs/did_webvh_Qmf4QH5dsA6Ecr5HJ6KaJL9uJRyY8RxrQdqoRCM25DzvPi_example_com_users_alice.jsonl +1 -0
  314. package/tests/__mocks__/bbs-signatures.js +17 -0
  315. package/tests/__mocks__/mf-base58.js +24 -0
  316. package/tests/e2e/README.md +97 -0
  317. package/tests/e2e/example.spec.ts +78 -0
  318. package/tests/fixtures/did-documents.ts +247 -0
  319. package/tests/index.test.ts +21 -0
  320. package/tests/integration/BatchOperations.test.ts +531 -0
  321. package/tests/integration/CompleteLifecycle.e2e.test.ts +735 -0
  322. package/tests/integration/CredentialManager.test.ts +42 -0
  323. package/tests/integration/DIDManager.test.ts +41 -0
  324. package/tests/integration/DidPeerToWebVhFlow.test.ts +351 -0
  325. package/tests/integration/Events.test.ts +435 -0
  326. package/tests/integration/Lifecycle.transfer.btco.integration.test.ts +25 -0
  327. package/tests/integration/LifecycleManager.test.ts +21 -0
  328. package/tests/integration/MultikeyFlow.test.ts +52 -0
  329. package/tests/integration/TelemetryIntegration.test.ts +395 -0
  330. package/tests/integration/WebVhPublish.test.ts +48 -0
  331. package/tests/integration/migration/peer-to-webvh.test.ts +172 -0
  332. package/tests/manual/test-commit-creation.ts +323 -0
  333. package/tests/mocks/MockKeyStore.ts +38 -0
  334. package/tests/mocks/adapters/MemoryStorageAdapter.ts +24 -0
  335. package/tests/mocks/adapters/MockFeeOracle.ts +11 -0
  336. package/tests/mocks/adapters/MockOrdinalsProvider.ts +76 -0
  337. package/tests/mocks/adapters/OrdMockProvider.test.ts +176 -0
  338. package/tests/mocks/adapters/index.ts +6 -0
  339. package/tests/performance/BatchOperations.perf.test.ts +403 -0
  340. package/tests/performance/logging.perf.test.ts +336 -0
  341. package/tests/sdk.test.ts +43 -0
  342. package/tests/security/bitcoin-penetration-tests.test.ts +622 -0
  343. package/tests/setup.bun.ts +69 -0
  344. package/tests/setup.jest.ts +23 -0
  345. package/tests/stress/batch-operations-stress.test.ts +571 -0
  346. package/tests/unit/adapters/FeeOracleMock.test.ts +40 -0
  347. package/tests/unit/bitcoin/BitcoinManager.test.ts +293 -0
  348. package/tests/unit/bitcoin/BroadcastClient.test.ts +52 -0
  349. package/tests/unit/bitcoin/OrdNodeProvider.test.ts +53 -0
  350. package/tests/unit/bitcoin/OrdinalsClient.test.ts +381 -0
  351. package/tests/unit/bitcoin/OrdinalsClientProvider.test.ts +102 -0
  352. package/tests/unit/bitcoin/PSBTBuilder.test.ts +84 -0
  353. package/tests/unit/bitcoin/fee-calculation.test.ts +261 -0
  354. package/tests/unit/bitcoin/transactions/commit.test.ts +649 -0
  355. package/tests/unit/bitcoin/transfer.test.ts +31 -0
  356. package/tests/unit/bitcoin/utxo-selection-new.test.ts +502 -0
  357. package/tests/unit/bitcoin/utxo.more.test.ts +39 -0
  358. package/tests/unit/bitcoin/utxo.selection.test.ts +38 -0
  359. package/tests/unit/core/OriginalsSDK.test.ts +152 -0
  360. package/tests/unit/crypto/Multikey.test.ts +206 -0
  361. package/tests/unit/crypto/Signer.test.ts +408 -0
  362. package/tests/unit/did/BtcoDidResolver.test.ts +611 -0
  363. package/tests/unit/did/DIDManager.more.test.ts +43 -0
  364. package/tests/unit/did/DIDManager.test.ts +185 -0
  365. package/tests/unit/did/Ed25519Verifier.test.ts +160 -0
  366. package/tests/unit/did/KeyManager.test.ts +452 -0
  367. package/tests/unit/did/OrdinalsClientProviderAdapter.test.ts +45 -0
  368. package/tests/unit/did/WebVHManager.test.ts +435 -0
  369. package/tests/unit/did/createBtcoDidDocument.test.ts +67 -0
  370. package/tests/unit/did/providers/OrdinalsClientProviderAdapter.test.ts +159 -0
  371. package/tests/unit/events/EventEmitter.test.ts +407 -0
  372. package/tests/unit/lifecycle/BatchOperations.test.ts +527 -0
  373. package/tests/unit/lifecycle/LifecycleManager.keymanagement.test.ts +312 -0
  374. package/tests/unit/lifecycle/LifecycleManager.prov.test.ts +18 -0
  375. package/tests/unit/lifecycle/LifecycleManager.test.ts +213 -0
  376. package/tests/unit/lifecycle/LifecycleManager.transfer.unit.test.ts +30 -0
  377. package/tests/unit/lifecycle/OriginalsAsset.test.ts +176 -0
  378. package/tests/unit/lifecycle/ProvenanceQuery.test.ts +577 -0
  379. package/tests/unit/lifecycle/ResourceVersioning.test.ts +651 -0
  380. package/tests/unit/storage/MemoryStorageAdapter.test.ts +93 -0
  381. package/tests/unit/types/network.test.ts +255 -0
  382. package/tests/unit/utils/EventIntegration.test.ts +384 -0
  383. package/tests/unit/utils/Logger.test.ts +473 -0
  384. package/tests/unit/utils/MetricsCollector.test.ts +358 -0
  385. package/tests/unit/utils/bitcoin-address.test.ts +250 -0
  386. package/tests/unit/utils/cbor.test.ts +35 -0
  387. package/tests/unit/utils/encoding.test.ts +318 -0
  388. package/tests/unit/utils/hash.test.ts +12 -0
  389. package/tests/unit/utils/retry.test.ts +100 -0
  390. package/tests/unit/utils/satoshi-validation.test.ts +354 -0
  391. package/tests/unit/utils/serialization.test.ts +124 -0
  392. package/tests/unit/utils/telemetry.test.ts +52 -0
  393. package/tests/unit/utils/validation.test.ts +141 -0
  394. package/tests/unit/vc/CredentialManager.test.ts +487 -0
  395. package/tests/unit/vc/Issuer.test.ts +107 -0
  396. package/tests/unit/vc/Verifier.test.ts +525 -0
  397. package/tests/unit/vc/bbs.test.ts +282 -0
  398. package/tests/unit/vc/cryptosuites/eddsa.test.ts +398 -0
  399. package/tests/unit/vc/documentLoader.test.ts +121 -0
  400. package/tests/unit/vc/proofs/data-integrity.test.ts +24 -0
  401. package/tsconfig.json +32 -0
  402. package/tsconfig.test.json +15 -0
  403. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,971 @@
1
+ import { BitcoinManager } from '../bitcoin/BitcoinManager';
2
+ import { OriginalsAsset } from './OriginalsAsset';
3
+ import { MemoryStorageAdapter } from '../storage/MemoryStorageAdapter';
4
+ import { encodeBase64UrlMultibase, hexToBytes } from '../utils/encoding';
5
+ import { validateBitcoinAddress } from '../utils/bitcoin-address';
6
+ import { multikey } from '../crypto/Multikey';
7
+ import { EventEmitter } from '../events/EventEmitter';
8
+ import { Logger } from '../utils/Logger';
9
+ import { MetricsCollector } from '../utils/MetricsCollector';
10
+ import { BatchOperationExecutor, BatchValidator, BatchError, } from './BatchOperations';
11
+ export class LifecycleManager {
12
+ constructor(config, didManager, credentialManager, deps, keyStore) {
13
+ this.config = config;
14
+ this.didManager = didManager;
15
+ this.credentialManager = credentialManager;
16
+ this.deps = deps;
17
+ this.keyStore = keyStore;
18
+ this.eventEmitter = new EventEmitter();
19
+ this.batchExecutor = new BatchOperationExecutor();
20
+ this.batchValidator = new BatchValidator();
21
+ this.logger = new Logger('LifecycleManager', config);
22
+ this.metrics = new MetricsCollector();
23
+ }
24
+ /**
25
+ * Subscribe to a lifecycle event
26
+ * @param eventType - The type of event to subscribe to
27
+ * @param handler - The handler function to call when the event is emitted
28
+ * @returns A function to unsubscribe from the event
29
+ */
30
+ on(eventType, handler) {
31
+ return this.eventEmitter.on(eventType, handler);
32
+ }
33
+ /**
34
+ * Subscribe to a lifecycle event once
35
+ * @param eventType - The type of event to subscribe to
36
+ * @param handler - The handler function to call when the event is emitted (will only fire once)
37
+ * @returns A function to unsubscribe from the event
38
+ */
39
+ once(eventType, handler) {
40
+ return this.eventEmitter.once(eventType, handler);
41
+ }
42
+ /**
43
+ * Unsubscribe from a lifecycle event
44
+ * @param eventType - The type of event to unsubscribe from
45
+ * @param handler - The handler function to remove
46
+ */
47
+ off(eventType, handler) {
48
+ this.eventEmitter.off(eventType, handler);
49
+ }
50
+ async registerKey(verificationMethodId, privateKey) {
51
+ if (!this.keyStore) {
52
+ throw new Error('KeyStore not configured. Provide keyStore to LifecycleManager constructor.');
53
+ }
54
+ // Validate verification method ID format
55
+ if (!verificationMethodId || typeof verificationMethodId !== 'string') {
56
+ throw new Error('Invalid verificationMethodId: must be a non-empty string');
57
+ }
58
+ // Validate private key format (should be multibase encoded)
59
+ if (!privateKey || typeof privateKey !== 'string') {
60
+ throw new Error('Invalid privateKey: must be a non-empty string');
61
+ }
62
+ // Validate that it's a valid multibase-encoded private key
63
+ try {
64
+ multikey.decodePrivateKey(privateKey);
65
+ }
66
+ catch (err) {
67
+ throw new Error('Invalid privateKey format: must be a valid multibase-encoded private key');
68
+ }
69
+ await this.keyStore.setPrivateKey(verificationMethodId, privateKey);
70
+ }
71
+ async createAsset(resources) {
72
+ const stopTimer = this.logger.startTimer('createAsset');
73
+ this.logger.info('Creating asset', { resourceCount: resources.length });
74
+ try {
75
+ // Input validation
76
+ if (!Array.isArray(resources)) {
77
+ throw new Error('Resources must be an array');
78
+ }
79
+ if (resources.length === 0) {
80
+ throw new Error('At least one resource is required');
81
+ }
82
+ // Validate each resource
83
+ for (const resource of resources) {
84
+ if (!resource || typeof resource !== 'object') {
85
+ throw new Error('Invalid resource: must be an object');
86
+ }
87
+ if (!resource.id || typeof resource.id !== 'string') {
88
+ throw new Error('Invalid resource: missing or invalid id');
89
+ }
90
+ if (!resource.type || typeof resource.type !== 'string') {
91
+ throw new Error('Invalid resource: missing or invalid type');
92
+ }
93
+ if (!resource.contentType || typeof resource.contentType !== 'string') {
94
+ throw new Error('Invalid resource: missing or invalid contentType');
95
+ }
96
+ if (!resource.hash || typeof resource.hash !== 'string' || !/^[0-9a-fA-F]+$/.test(resource.hash)) {
97
+ throw new Error('Invalid resource: missing or invalid hash (must be hex string)');
98
+ }
99
+ // Validate contentType is a valid MIME type
100
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9!#$&^_.+-]{0,126}\/[a-zA-Z0-9][a-zA-Z0-9!#$&^_.+-]{0,126}$/.test(resource.contentType)) {
101
+ throw new Error(`Invalid resource: invalid contentType MIME format: ${resource.contentType}`);
102
+ }
103
+ }
104
+ // Create a proper DID:peer document with verification methods
105
+ // If keyStore is provided, request the key pair to be returned
106
+ if (this.keyStore) {
107
+ const result = await this.didManager.createDIDPeer(resources, true);
108
+ const didDoc = result.didDocument;
109
+ const keyPair = result.keyPair;
110
+ // Register the private key in the keyStore
111
+ if (didDoc.verificationMethod && didDoc.verificationMethod.length > 0) {
112
+ let verificationMethodId = didDoc.verificationMethod[0].id;
113
+ // Ensure VM ID is absolute (not just a fragment like #key-0)
114
+ if (verificationMethodId.startsWith('#')) {
115
+ verificationMethodId = `${didDoc.id}${verificationMethodId}`;
116
+ }
117
+ await this.keyStore.setPrivateKey(verificationMethodId, keyPair.privateKey);
118
+ }
119
+ const asset = new OriginalsAsset(resources, didDoc, []);
120
+ // Defer asset:created event emission to next microtask so callers can subscribe first
121
+ queueMicrotask(() => {
122
+ const event = {
123
+ type: 'asset:created',
124
+ timestamp: new Date().toISOString(),
125
+ asset: {
126
+ id: asset.id,
127
+ layer: asset.currentLayer,
128
+ resourceCount: resources.length,
129
+ createdAt: asset.getProvenance().createdAt
130
+ }
131
+ };
132
+ // Emit from both LifecycleManager and asset emitters
133
+ this.eventEmitter.emit(event);
134
+ asset.eventEmitter.emit(event);
135
+ });
136
+ stopTimer();
137
+ this.logger.info('Asset created successfully', { assetId: asset.id });
138
+ this.metrics.recordAssetCreated();
139
+ return asset;
140
+ }
141
+ else {
142
+ // No keyStore, just create the DID document
143
+ const didDoc = await this.didManager.createDIDPeer(resources);
144
+ const asset = new OriginalsAsset(resources, didDoc, []);
145
+ // Defer asset:created event emission to next microtask so callers can subscribe first
146
+ queueMicrotask(() => {
147
+ const event = {
148
+ type: 'asset:created',
149
+ timestamp: new Date().toISOString(),
150
+ asset: {
151
+ id: asset.id,
152
+ layer: asset.currentLayer,
153
+ resourceCount: resources.length,
154
+ createdAt: asset.getProvenance().createdAt
155
+ }
156
+ };
157
+ // Emit from both LifecycleManager and asset emitters
158
+ this.eventEmitter.emit(event);
159
+ asset.eventEmitter.emit(event);
160
+ });
161
+ stopTimer();
162
+ this.logger.info('Asset created successfully', { assetId: asset.id });
163
+ this.metrics.recordAssetCreated();
164
+ return asset;
165
+ }
166
+ }
167
+ catch (error) {
168
+ stopTimer();
169
+ this.logger.error('Asset creation failed', error, { resourceCount: resources.length });
170
+ this.metrics.recordError('ASSET_CREATION_FAILED', 'createAsset');
171
+ throw error;
172
+ }
173
+ }
174
+ async publishToWeb(asset, publisherDidOrSigner) {
175
+ const stopTimer = this.logger.startTimer('publishToWeb');
176
+ try {
177
+ if (asset.currentLayer !== 'did:peer') {
178
+ throw new Error('Asset must be in did:peer layer to publish to web');
179
+ }
180
+ const { publisherDid, signer } = await this.extractPublisherInfo(publisherDidOrSigner);
181
+ const { domain, userPath } = this.parseWebVHDid(publisherDid);
182
+ this.logger.info('Publishing asset to web', { assetId: asset.id, publisherDid });
183
+ // Publish resources to storage
184
+ await this.publishResources(asset, publisherDid, domain, userPath);
185
+ // Store the original did:peer ID before migration
186
+ const originalPeerDid = asset.id;
187
+ // Migrate asset to did:webvh layer
188
+ await asset.migrate('did:webvh');
189
+ asset.bindings = { ...asset.bindings, 'did:peer': originalPeerDid, 'did:webvh': publisherDid };
190
+ // Issue publication credential (best-effort)
191
+ await this.issuePublicationCredential(asset, publisherDid, signer);
192
+ stopTimer();
193
+ this.logger.info('Asset published to web successfully', {
194
+ assetId: asset.id,
195
+ publisherDid,
196
+ resourceCount: asset.resources.length
197
+ });
198
+ this.metrics.recordMigration('did:peer', 'did:webvh');
199
+ return asset;
200
+ }
201
+ catch (error) {
202
+ stopTimer();
203
+ this.logger.error('Publish to web failed', error, { assetId: asset.id });
204
+ this.metrics.recordError('PUBLISH_FAILED', 'publishToWeb');
205
+ throw error;
206
+ }
207
+ }
208
+ async extractPublisherInfo(publisherDidOrSigner) {
209
+ if (typeof publisherDidOrSigner === 'string') {
210
+ // If it's already a did:webvh DID, use it as-is
211
+ if (publisherDidOrSigner.startsWith('did:webvh:')) {
212
+ return { publisherDid: publisherDidOrSigner };
213
+ }
214
+ // Otherwise, treat it as a domain and construct a did:webvh DID
215
+ // Format: did:webvh:domain:user (use 'user' as default user path)
216
+ // Encode the domain to handle ports (e.g., localhost:5000 -> localhost%3A5000)
217
+ const domain = publisherDidOrSigner;
218
+ const encodedDomain = encodeURIComponent(domain);
219
+ const publisherDid = `did:webvh:${encodedDomain}:user`;
220
+ return { publisherDid };
221
+ }
222
+ const signer = publisherDidOrSigner;
223
+ const vmId = await signer.getVerificationMethodId();
224
+ const publisherDid = vmId.includes('#') ? vmId.split('#')[0] : vmId;
225
+ if (!publisherDid.startsWith('did:webvh:')) {
226
+ throw new Error('Signer must be associated with a did:webvh identifier');
227
+ }
228
+ return { publisherDid, signer };
229
+ }
230
+ parseWebVHDid(did) {
231
+ const parts = did.split(':');
232
+ if (parts.length < 4) {
233
+ throw new Error('Invalid did:webvh format: must include domain and user path');
234
+ }
235
+ const domain = decodeURIComponent(parts[2]);
236
+ const userPath = parts.slice(3).join('/');
237
+ return { domain, userPath };
238
+ }
239
+ async publishResources(asset, publisherDid, domain, userPath) {
240
+ const storage = this.config.storageAdapter || new MemoryStorageAdapter();
241
+ for (const resource of asset.resources) {
242
+ const hashBytes = hexToBytes(resource.hash);
243
+ const multibase = encodeBase64UrlMultibase(hashBytes);
244
+ const resourceUrl = `${publisherDid}/resources/${multibase}`;
245
+ const relativePath = `${userPath}/resources/${multibase}`;
246
+ // Store resource content
247
+ const data = resource.content
248
+ ? Buffer.from(resource.content)
249
+ : Buffer.from(resource.hash);
250
+ if (typeof storage.put === 'function') {
251
+ await storage.put(`${domain}/${relativePath}`, data, { contentType: resource.contentType });
252
+ }
253
+ else {
254
+ const encoded = new TextEncoder().encode(resource.content || resource.hash);
255
+ await storage.putObject(domain, relativePath, encoded);
256
+ }
257
+ resource.url = resourceUrl;
258
+ await this.emitResourcePublishedEvent(asset, resource, resourceUrl, publisherDid, domain);
259
+ }
260
+ }
261
+ async emitResourcePublishedEvent(asset, resource, resourceUrl, publisherDid, domain) {
262
+ const event = {
263
+ type: 'resource:published',
264
+ timestamp: new Date().toISOString(),
265
+ asset: { id: asset.id },
266
+ resource: {
267
+ id: resource.id,
268
+ url: resourceUrl,
269
+ contentType: resource.contentType,
270
+ hash: resource.hash
271
+ },
272
+ publisherDid,
273
+ domain
274
+ };
275
+ try {
276
+ // Emit from both LifecycleManager and asset emitters
277
+ await this.eventEmitter.emit(event);
278
+ await asset.eventEmitter.emit(event);
279
+ }
280
+ catch (err) {
281
+ this.logger.error('Event handler error', err, { event: event.type });
282
+ }
283
+ }
284
+ async issuePublicationCredential(asset, publisherDid, signer) {
285
+ try {
286
+ const subject = {
287
+ id: asset.id,
288
+ publishedAs: publisherDid,
289
+ resourceId: asset.resources[0]?.id,
290
+ fromLayer: 'did:peer',
291
+ toLayer: 'did:webvh',
292
+ migratedAt: new Date().toISOString()
293
+ };
294
+ const unsigned = await this.credentialManager.createResourceCredential('ResourceMigrated', subject, publisherDid);
295
+ const signed = signer
296
+ ? await this.credentialManager.signCredentialWithExternalSigner(unsigned, signer)
297
+ : await this.signWithKeyStore(unsigned, publisherDid);
298
+ asset.credentials.push(signed);
299
+ const event = {
300
+ type: 'credential:issued',
301
+ timestamp: new Date().toISOString(),
302
+ asset: { id: asset.id },
303
+ credential: {
304
+ type: signed.type,
305
+ issuer: typeof signed.issuer === 'string' ? signed.issuer : signed.issuer.id
306
+ }
307
+ };
308
+ // Emit from both LifecycleManager and asset emitters
309
+ await this.eventEmitter.emit(event);
310
+ await asset.eventEmitter.emit(event);
311
+ }
312
+ catch (err) {
313
+ this.logger.error('Failed to issue credential during publish', err);
314
+ }
315
+ }
316
+ async signWithKeyStore(credential, issuer) {
317
+ if (!this.keyStore) {
318
+ throw new Error('KeyStore required for signing. Provide keyStore or external signer.');
319
+ }
320
+ // Try to find a key in the keyStore for this DID
321
+ // First try common verification method patterns: #key-0, #keys-1, etc.
322
+ const commonVmIds = [
323
+ `${issuer}#key-0`,
324
+ `${issuer}#keys-1`,
325
+ `${issuer}#authentication`,
326
+ ];
327
+ let privateKey = null;
328
+ let vmId = null;
329
+ for (const testVmId of commonVmIds) {
330
+ const key = await this.keyStore.getPrivateKey(testVmId);
331
+ if (key) {
332
+ privateKey = key;
333
+ vmId = testVmId;
334
+ break;
335
+ }
336
+ }
337
+ // If not found, try to find ANY key that starts with the issuer DID
338
+ if (!privateKey && typeof this.keyStore.getAllVerificationMethodIds === 'function') {
339
+ const allVmIds = this.keyStore.getAllVerificationMethodIds();
340
+ for (const testVmId of allVmIds) {
341
+ if (testVmId.startsWith(issuer)) {
342
+ const key = await this.keyStore.getPrivateKey(testVmId);
343
+ if (key) {
344
+ privateKey = key;
345
+ vmId = testVmId;
346
+ break;
347
+ }
348
+ }
349
+ }
350
+ }
351
+ // If no key found in common patterns, try resolving the DID
352
+ if (!privateKey) {
353
+ const didDoc = await this.didManager.resolveDID(issuer);
354
+ if (!didDoc?.verificationMethod?.[0]) {
355
+ throw new Error('No verification method found in publisher DID document');
356
+ }
357
+ vmId = didDoc.verificationMethod[0].id;
358
+ if (vmId.startsWith('#')) {
359
+ vmId = `${issuer}${vmId}`;
360
+ }
361
+ privateKey = await this.keyStore.getPrivateKey(vmId);
362
+ if (!privateKey) {
363
+ throw new Error('Private key not found in keyStore');
364
+ }
365
+ }
366
+ return this.credentialManager.signCredential(credential, privateKey, vmId);
367
+ }
368
+ async inscribeOnBitcoin(asset, feeRate) {
369
+ const stopTimer = this.logger.startTimer('inscribeOnBitcoin');
370
+ this.logger.info('Inscribing asset on Bitcoin', { assetId: asset.id, feeRate });
371
+ try {
372
+ // Input validation
373
+ if (!asset || typeof asset !== 'object') {
374
+ throw new Error('Invalid asset: must be a valid OriginalsAsset');
375
+ }
376
+ if (feeRate !== undefined) {
377
+ if (typeof feeRate !== 'number' || feeRate <= 0 || !Number.isFinite(feeRate)) {
378
+ throw new Error('Invalid feeRate: must be a positive number');
379
+ }
380
+ if (feeRate < 1 || feeRate > 1000000) {
381
+ throw new Error('Invalid feeRate: must be between 1 and 1000000 sat/vB');
382
+ }
383
+ }
384
+ if (typeof asset.migrate !== 'function') {
385
+ throw new Error('Not implemented');
386
+ }
387
+ if (asset.currentLayer !== 'did:webvh' && asset.currentLayer !== 'did:peer') {
388
+ throw new Error('Not implemented');
389
+ }
390
+ const bitcoinManager = this.deps?.bitcoinManager ?? new BitcoinManager(this.config);
391
+ const manifest = {
392
+ assetId: asset.id,
393
+ resources: asset.resources.map(res => ({ id: res.id, hash: res.hash, contentType: res.contentType, url: res.url })),
394
+ timestamp: new Date().toISOString()
395
+ };
396
+ const payload = Buffer.from(JSON.stringify(manifest));
397
+ const inscription = await bitcoinManager.inscribeData(payload, 'application/json', feeRate);
398
+ const revealTxId = inscription.revealTxId ?? inscription.txid;
399
+ const commitTxId = inscription.commitTxId;
400
+ const usedFeeRate = typeof inscription.feeRate === 'number' ? inscription.feeRate : feeRate;
401
+ // Capture the layer before migration for accurate metrics
402
+ const fromLayer = asset.currentLayer;
403
+ await asset.migrate('did:btco', {
404
+ transactionId: revealTxId,
405
+ inscriptionId: inscription.inscriptionId,
406
+ satoshi: inscription.satoshi,
407
+ commitTxId,
408
+ revealTxId,
409
+ feeRate: usedFeeRate
410
+ });
411
+ const bindingValue = inscription.satoshi
412
+ ? `did:btco:${inscription.satoshi}`
413
+ : `did:btco:${inscription.inscriptionId}`;
414
+ asset.bindings = Object.assign({}, asset.bindings, { 'did:btco': bindingValue });
415
+ stopTimer();
416
+ this.logger.info('Asset inscribed on Bitcoin successfully', {
417
+ assetId: asset.id,
418
+ inscriptionId: inscription.inscriptionId,
419
+ transactionId: revealTxId
420
+ });
421
+ this.metrics.recordMigration(fromLayer, 'did:btco');
422
+ return asset;
423
+ }
424
+ catch (error) {
425
+ stopTimer();
426
+ this.logger.error('Bitcoin inscription failed', error, { assetId: asset.id, feeRate });
427
+ this.metrics.recordError('INSCRIPTION_FAILED', 'inscribeOnBitcoin');
428
+ throw error;
429
+ }
430
+ }
431
+ async transferOwnership(asset, newOwner) {
432
+ const stopTimer = this.logger.startTimer('transferOwnership');
433
+ this.logger.info('Transferring asset ownership', { assetId: asset.id, newOwner });
434
+ try {
435
+ // Input validation
436
+ if (!asset || typeof asset !== 'object') {
437
+ throw new Error('Invalid asset: must be a valid OriginalsAsset');
438
+ }
439
+ if (!newOwner || typeof newOwner !== 'string') {
440
+ throw new Error('Invalid newOwner: must be a non-empty string');
441
+ }
442
+ // Validate Bitcoin address format and checksum
443
+ try {
444
+ validateBitcoinAddress(newOwner, this.config.network);
445
+ }
446
+ catch (error) {
447
+ const message = error instanceof Error ? error.message : 'Invalid Bitcoin address';
448
+ throw new Error(`Invalid Bitcoin address for ownership transfer: ${message}`);
449
+ }
450
+ // Transfer Bitcoin-anchored asset ownership
451
+ // Only works for assets in did:btco layer
452
+ if (asset.currentLayer !== 'did:btco') {
453
+ throw new Error('Asset must be inscribed on Bitcoin before transfer');
454
+ }
455
+ const bm = this.deps?.bitcoinManager ?? new BitcoinManager(this.config);
456
+ const provenance = asset.getProvenance();
457
+ const latestMigration = provenance.migrations[provenance.migrations.length - 1];
458
+ const satoshi = latestMigration?.satoshi ?? (asset.id.startsWith('did:btco:') ? asset.id.split(':')[2] : '');
459
+ const inscription = {
460
+ satoshi,
461
+ inscriptionId: latestMigration?.inscriptionId ?? `insc-${satoshi || 'unknown'}`,
462
+ content: Buffer.alloc(0),
463
+ contentType: 'application/octet-stream',
464
+ txid: latestMigration?.transactionId ?? 'unknown-tx',
465
+ vout: 0
466
+ };
467
+ const tx = await bm.transferInscription(inscription, newOwner);
468
+ await asset.recordTransfer(asset.id, newOwner, tx.txid);
469
+ stopTimer();
470
+ this.logger.info('Asset ownership transferred successfully', {
471
+ assetId: asset.id,
472
+ newOwner,
473
+ transactionId: tx.txid
474
+ });
475
+ this.metrics.recordTransfer();
476
+ return tx;
477
+ }
478
+ catch (error) {
479
+ stopTimer();
480
+ this.logger.error('Ownership transfer failed', error, { assetId: asset.id, newOwner });
481
+ this.metrics.recordError('TRANSFER_FAILED', 'transferOwnership');
482
+ throw error;
483
+ }
484
+ }
485
+ /**
486
+ * Create multiple assets in batch
487
+ *
488
+ * @param resourcesList - Array of resource arrays, one per asset to create
489
+ * @param options - Batch operation options
490
+ * @returns BatchResult with created assets
491
+ */
492
+ async batchCreateAssets(resourcesList, options) {
493
+ const batchId = this.batchExecutor.generateBatchId();
494
+ // Validate first if requested
495
+ if (options?.validateFirst !== false) {
496
+ const validationResults = this.batchValidator.validateBatchCreate(resourcesList);
497
+ const invalid = validationResults.filter(r => !r.isValid);
498
+ if (invalid.length > 0) {
499
+ const errors = invalid.flatMap(r => r.errors).join('; ');
500
+ throw new Error(`Batch validation failed: ${errors}`);
501
+ }
502
+ }
503
+ // Emit batch:started event
504
+ await this.eventEmitter.emit({
505
+ type: 'batch:started',
506
+ timestamp: new Date().toISOString(),
507
+ operation: 'create',
508
+ batchId,
509
+ itemCount: resourcesList.length
510
+ });
511
+ try {
512
+ // Use batch executor to process all asset creations
513
+ const result = await this.batchExecutor.execute(resourcesList, async (resources, index) => {
514
+ const asset = await this.createAsset(resources);
515
+ return asset;
516
+ }, options, batchId // Pass the pre-generated batchId for event correlation
517
+ );
518
+ // Emit batch:completed event
519
+ await this.eventEmitter.emit({
520
+ type: 'batch:completed',
521
+ timestamp: new Date().toISOString(),
522
+ batchId,
523
+ operation: 'create',
524
+ results: {
525
+ successful: result.successful.length,
526
+ failed: result.failed.length,
527
+ totalDuration: result.totalDuration
528
+ }
529
+ });
530
+ return result;
531
+ }
532
+ catch (error) {
533
+ // Emit batch:failed event
534
+ await this.eventEmitter.emit({
535
+ type: 'batch:failed',
536
+ timestamp: new Date().toISOString(),
537
+ batchId,
538
+ operation: 'create',
539
+ error: error instanceof Error ? error.message : String(error)
540
+ });
541
+ throw error;
542
+ }
543
+ }
544
+ /**
545
+ * Publish multiple assets to web storage in batch
546
+ *
547
+ * @param assets - Array of assets to publish
548
+ * @param domain - Domain to publish to
549
+ * @param options - Batch operation options
550
+ * @returns BatchResult with published assets
551
+ */
552
+ async batchPublishToWeb(assets, domain, options) {
553
+ const batchId = this.batchExecutor.generateBatchId();
554
+ // Validate domain once
555
+ if (!domain || typeof domain !== 'string') {
556
+ throw new Error('Invalid domain: must be a non-empty string');
557
+ }
558
+ const normalized = domain.trim().toLowerCase();
559
+ // Split domain and port if present
560
+ const [domainPart, portPart] = normalized.split(':');
561
+ // Validate port if present
562
+ if (portPart && (!/^\d+$/.test(portPart) || parseInt(portPart) < 1 || parseInt(portPart) > 65535)) {
563
+ throw new Error(`Invalid domain format: ${domain} - invalid port`);
564
+ }
565
+ // Allow localhost and IP addresses for development
566
+ const isLocalhost = domainPart === 'localhost';
567
+ const isIP = /^(\d{1,3}\.){3}\d{1,3}$/.test(domainPart);
568
+ if (!isLocalhost && !isIP) {
569
+ // For non-localhost domains, require proper domain format
570
+ const label = '[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?';
571
+ const domainRegex = new RegExp(`^(?=.{1,253}$)(?:${label})(?:\\.(?:${label}))+?$`, 'i');
572
+ if (!domainRegex.test(domainPart)) {
573
+ throw new Error(`Invalid domain format: ${domain}`);
574
+ }
575
+ }
576
+ // Emit batch:started event
577
+ await this.eventEmitter.emit({
578
+ type: 'batch:started',
579
+ timestamp: new Date().toISOString(),
580
+ operation: 'publish',
581
+ batchId,
582
+ itemCount: assets.length
583
+ });
584
+ try {
585
+ const result = await this.batchExecutor.execute(assets, async (asset, index) => {
586
+ return await this.publishToWeb(asset, domain);
587
+ }, options, batchId // Pass the pre-generated batchId for event correlation
588
+ );
589
+ // Emit batch:completed event
590
+ await this.eventEmitter.emit({
591
+ type: 'batch:completed',
592
+ timestamp: new Date().toISOString(),
593
+ batchId,
594
+ operation: 'publish',
595
+ results: {
596
+ successful: result.successful.length,
597
+ failed: result.failed.length,
598
+ totalDuration: result.totalDuration
599
+ }
600
+ });
601
+ return result;
602
+ }
603
+ catch (error) {
604
+ // Emit batch:failed event
605
+ await this.eventEmitter.emit({
606
+ type: 'batch:failed',
607
+ timestamp: new Date().toISOString(),
608
+ batchId,
609
+ operation: 'publish',
610
+ error: error instanceof Error ? error.message : String(error)
611
+ });
612
+ throw error;
613
+ }
614
+ }
615
+ /**
616
+ * Inscribe multiple assets on Bitcoin with cost optimization
617
+ * KEY FEATURE: singleTransaction option for 30%+ cost savings
618
+ *
619
+ * @param assets - Array of assets to inscribe
620
+ * @param options - Batch inscription options
621
+ * @returns BatchResult with inscribed assets
622
+ */
623
+ async batchInscribeOnBitcoin(assets, options) {
624
+ // Validate first if requested
625
+ if (options?.validateFirst !== false) {
626
+ const validationResults = this.batchValidator.validateBatchInscription(assets);
627
+ const invalid = validationResults.filter(r => !r.isValid);
628
+ if (invalid.length > 0) {
629
+ const errors = invalid.flatMap(r => r.errors).join('; ');
630
+ throw new Error(`Batch validation failed: ${errors}`);
631
+ }
632
+ }
633
+ if (options?.singleTransaction) {
634
+ return this.batchInscribeSingleTransaction(assets, options);
635
+ }
636
+ else {
637
+ return this.batchInscribeIndividualTransactions(assets, options);
638
+ }
639
+ }
640
+ /**
641
+ * CORE INNOVATION: Single-transaction batch inscription
642
+ * Combines multiple assets into one Bitcoin transaction for 30%+ cost savings
643
+ *
644
+ * @param assets - Array of assets to inscribe
645
+ * @param options - Batch inscription options
646
+ * @returns BatchResult with inscribed assets and cost savings data
647
+ */
648
+ async batchInscribeSingleTransaction(assets, options) {
649
+ const batchId = this.batchExecutor.generateBatchId();
650
+ const startTime = Date.now();
651
+ const startedAt = new Date().toISOString();
652
+ // Emit batch:started event
653
+ await this.eventEmitter.emit({
654
+ type: 'batch:started',
655
+ timestamp: startedAt,
656
+ operation: 'inscribe',
657
+ batchId,
658
+ itemCount: assets.length
659
+ });
660
+ try {
661
+ // Calculate total data size for all assets
662
+ const totalDataSize = this.calculateTotalDataSize(assets);
663
+ // Estimate savings from batch inscription
664
+ const estimatedSavings = await this.estimateBatchSavings(assets, options?.feeRate);
665
+ // Create manifests for all assets
666
+ const manifests = assets.map(asset => ({
667
+ assetId: asset.id,
668
+ resources: asset.resources.map(res => ({
669
+ id: res.id,
670
+ hash: res.hash,
671
+ contentType: res.contentType,
672
+ url: res.url
673
+ })),
674
+ timestamp: new Date().toISOString()
675
+ }));
676
+ // Combine all manifests into a single batch payload
677
+ const batchManifest = {
678
+ batchId,
679
+ assets: manifests,
680
+ timestamp: new Date().toISOString()
681
+ };
682
+ const payload = Buffer.from(JSON.stringify(batchManifest));
683
+ // Inscribe the batch manifest as a single transaction
684
+ const bitcoinManager = this.deps?.bitcoinManager ?? new BitcoinManager(this.config);
685
+ const inscription = await bitcoinManager.inscribeData(payload, 'application/json', options?.feeRate);
686
+ const revealTxId = inscription.revealTxId ?? inscription.txid;
687
+ const commitTxId = inscription.commitTxId;
688
+ const usedFeeRate = typeof inscription.feeRate === 'number' ? inscription.feeRate : options?.feeRate;
689
+ // Calculate fee per asset (split proportionally by data size)
690
+ // Include both metadata and resource content size for accurate fee distribution
691
+ const assetSizes = assets.map(asset => {
692
+ // Calculate metadata size
693
+ const metadataSize = JSON.stringify({
694
+ assetId: asset.id,
695
+ resources: asset.resources.map(r => ({
696
+ id: r.id,
697
+ hash: r.hash,
698
+ contentType: r.contentType,
699
+ url: r.url
700
+ }))
701
+ }).length;
702
+ // Add resource content sizes
703
+ const contentSize = asset.resources.reduce((sum, r) => {
704
+ const content = r.content;
705
+ if (content) {
706
+ return sum + (typeof content === 'string' ? Buffer.byteLength(content) : content.length || 0);
707
+ }
708
+ return sum;
709
+ }, 0);
710
+ return metadataSize + contentSize;
711
+ });
712
+ const totalSize = assetSizes.reduce((sum, size) => sum + size, 0);
713
+ // Calculate total fee from batch transaction size and fee rate
714
+ // Estimate transaction size: base overhead (200 bytes) + batch payload size
715
+ const batchTxSize = 200 + totalDataSize;
716
+ const effectiveFeeRate = usedFeeRate ?? 10;
717
+ const totalFee = batchTxSize * effectiveFeeRate;
718
+ // Split fees proportionally by asset data size
719
+ const feePerAsset = assetSizes.map(size => Math.floor(totalFee * (size / totalSize)));
720
+ // Update all assets with batch inscription data
721
+ const individualInscriptionIds = [];
722
+ const successful = [];
723
+ for (let i = 0; i < assets.length; i++) {
724
+ const asset = assets[i];
725
+ // For batch inscriptions, use the base inscription ID for all assets
726
+ // The batch index is stored as metadata, not in the ID
727
+ const individualInscriptionId = inscription.inscriptionId;
728
+ individualInscriptionIds.push(individualInscriptionId);
729
+ await asset.migrate('did:btco', {
730
+ transactionId: revealTxId,
731
+ inscriptionId: individualInscriptionId,
732
+ satoshi: inscription.satoshi,
733
+ commitTxId,
734
+ revealTxId,
735
+ feeRate: usedFeeRate
736
+ });
737
+ // Add batch metadata to provenance
738
+ const provenance = asset.getProvenance();
739
+ const latestMigration = provenance.migrations[provenance.migrations.length - 1];
740
+ latestMigration.batchId = batchId;
741
+ latestMigration.batchInscription = true;
742
+ latestMigration.batchIndex = i; // Store index as metadata
743
+ latestMigration.feePaid = feePerAsset[i];
744
+ const bindingValue = inscription.satoshi
745
+ ? `did:btco:${inscription.satoshi}`
746
+ : `did:btco:${individualInscriptionId}`;
747
+ asset.bindings = Object.assign({}, asset.bindings, { 'did:btco': bindingValue });
748
+ successful.push({
749
+ index: i,
750
+ result: asset,
751
+ duration: Date.now() - startTime
752
+ });
753
+ }
754
+ const totalDuration = Date.now() - startTime;
755
+ const completedAt = new Date().toISOString();
756
+ // Emit batch:completed event with cost savings
757
+ await this.eventEmitter.emit({
758
+ type: 'batch:completed',
759
+ timestamp: completedAt,
760
+ batchId,
761
+ operation: 'inscribe',
762
+ results: {
763
+ successful: successful.length,
764
+ failed: 0,
765
+ totalDuration,
766
+ costSavings: {
767
+ amount: estimatedSavings.savings,
768
+ percentage: estimatedSavings.savingsPercentage
769
+ }
770
+ }
771
+ });
772
+ return {
773
+ successful,
774
+ failed: [],
775
+ totalProcessed: assets.length,
776
+ totalDuration,
777
+ batchId,
778
+ startedAt,
779
+ completedAt
780
+ };
781
+ }
782
+ catch (error) {
783
+ // Emit batch:failed event
784
+ await this.eventEmitter.emit({
785
+ type: 'batch:failed',
786
+ timestamp: new Date().toISOString(),
787
+ batchId,
788
+ operation: 'inscribe',
789
+ error: error instanceof Error ? error.message : String(error)
790
+ });
791
+ throw new BatchError(batchId, 'inscribe', { successful: 0, failed: assets.length }, error instanceof Error ? error.message : String(error));
792
+ }
793
+ }
794
+ /**
795
+ * Individual transaction batch inscription (fallback mode)
796
+ * Each asset is inscribed in its own transaction
797
+ *
798
+ * @param assets - Array of assets to inscribe
799
+ * @param options - Batch inscription options
800
+ * @returns BatchResult with inscribed assets
801
+ */
802
+ async batchInscribeIndividualTransactions(assets, options) {
803
+ const batchId = this.batchExecutor.generateBatchId();
804
+ // Emit batch:started event
805
+ await this.eventEmitter.emit({
806
+ type: 'batch:started',
807
+ timestamp: new Date().toISOString(),
808
+ operation: 'inscribe',
809
+ batchId,
810
+ itemCount: assets.length
811
+ });
812
+ try {
813
+ const result = await this.batchExecutor.execute(assets, async (asset, index) => {
814
+ return await this.inscribeOnBitcoin(asset, options?.feeRate);
815
+ }, options, batchId // Pass the pre-generated batchId for event correlation
816
+ );
817
+ // Emit batch:completed event
818
+ await this.eventEmitter.emit({
819
+ type: 'batch:completed',
820
+ timestamp: new Date().toISOString(),
821
+ batchId,
822
+ operation: 'inscribe',
823
+ results: {
824
+ successful: result.successful.length,
825
+ failed: result.failed.length,
826
+ totalDuration: result.totalDuration
827
+ }
828
+ });
829
+ return result;
830
+ }
831
+ catch (error) {
832
+ // Emit batch:failed event
833
+ await this.eventEmitter.emit({
834
+ type: 'batch:failed',
835
+ timestamp: new Date().toISOString(),
836
+ batchId,
837
+ operation: 'inscribe',
838
+ error: error instanceof Error ? error.message : String(error)
839
+ });
840
+ throw error;
841
+ }
842
+ }
843
+ /**
844
+ * Transfer ownership of multiple assets in batch
845
+ *
846
+ * @param transfers - Array of transfer operations
847
+ * @param options - Batch operation options
848
+ * @returns BatchResult with transaction results
849
+ */
850
+ async batchTransferOwnership(transfers, options) {
851
+ const batchId = this.batchExecutor.generateBatchId();
852
+ // Validate first if requested
853
+ if (options?.validateFirst !== false) {
854
+ const validationResults = this.batchValidator.validateBatchTransfer(transfers);
855
+ const invalid = validationResults.filter(r => !r.isValid);
856
+ if (invalid.length > 0) {
857
+ const errors = invalid.flatMap(r => r.errors).join('; ');
858
+ throw new Error(`Batch validation failed: ${errors}`);
859
+ }
860
+ // Validate all Bitcoin addresses
861
+ for (let i = 0; i < transfers.length; i++) {
862
+ try {
863
+ validateBitcoinAddress(transfers[i].to, this.config.network);
864
+ }
865
+ catch (error) {
866
+ const message = error instanceof Error ? error.message : 'Invalid Bitcoin address';
867
+ throw new Error(`Transfer ${i}: Invalid Bitcoin address: ${message}`);
868
+ }
869
+ }
870
+ }
871
+ // Emit batch:started event
872
+ await this.eventEmitter.emit({
873
+ type: 'batch:started',
874
+ timestamp: new Date().toISOString(),
875
+ operation: 'transfer',
876
+ batchId,
877
+ itemCount: transfers.length
878
+ });
879
+ try {
880
+ const result = await this.batchExecutor.execute(transfers, async (transfer, index) => {
881
+ return await this.transferOwnership(transfer.asset, transfer.to);
882
+ }, options, batchId // Pass the pre-generated batchId for event correlation
883
+ );
884
+ // Emit batch:completed event
885
+ await this.eventEmitter.emit({
886
+ type: 'batch:completed',
887
+ timestamp: new Date().toISOString(),
888
+ batchId,
889
+ operation: 'transfer',
890
+ results: {
891
+ successful: result.successful.length,
892
+ failed: result.failed.length,
893
+ totalDuration: result.totalDuration
894
+ }
895
+ });
896
+ return result;
897
+ }
898
+ catch (error) {
899
+ // Emit batch:failed event
900
+ await this.eventEmitter.emit({
901
+ type: 'batch:failed',
902
+ timestamp: new Date().toISOString(),
903
+ batchId,
904
+ operation: 'transfer',
905
+ error: error instanceof Error ? error.message : String(error)
906
+ });
907
+ throw error;
908
+ }
909
+ }
910
+ /**
911
+ * Calculate total data size for all assets in a batch
912
+ */
913
+ calculateTotalDataSize(assets) {
914
+ return assets.reduce((total, asset) => {
915
+ const manifest = {
916
+ assetId: asset.id,
917
+ resources: asset.resources.map(res => ({
918
+ id: res.id,
919
+ hash: res.hash,
920
+ contentType: res.contentType,
921
+ url: res.url
922
+ })),
923
+ timestamp: new Date().toISOString()
924
+ };
925
+ return total + JSON.stringify(manifest).length;
926
+ }, 0);
927
+ }
928
+ /**
929
+ * Estimate cost savings from batch inscription vs individual inscriptions
930
+ */
931
+ async estimateBatchSavings(assets, feeRate) {
932
+ // Calculate total size for batch
933
+ const batchSize = this.calculateTotalDataSize(assets);
934
+ // Estimate individual sizes
935
+ const individualSizes = assets.map(asset => JSON.stringify({
936
+ assetId: asset.id,
937
+ resources: asset.resources.map(r => ({
938
+ id: r.id,
939
+ hash: r.hash,
940
+ contentType: r.contentType,
941
+ url: r.url
942
+ }))
943
+ }).length);
944
+ // Realistic fee estimation based on Bitcoin transaction structure
945
+ // Base transaction overhead: ~200 bytes (inputs, outputs, etc.)
946
+ // Per inscription witness overhead: ~120 bytes (script, envelope, etc.)
947
+ // In batch mode: shared transaction overhead + minimal per-asset overhead
948
+ const effectiveFeeRate = feeRate ?? 10; // default 10 sat/vB
949
+ // Batch: one transaction overhead + batch data + minimal per-asset overhead
950
+ // The batch manifest is more efficient as it shares structure
951
+ const batchTxSize = 200 + batchSize + (assets.length * 5); // 5 bytes per asset for array/object overhead
952
+ const batchFee = batchTxSize * effectiveFeeRate;
953
+ // Individual: each inscription needs full transaction overhead + witness overhead + data
954
+ const individualFees = individualSizes.reduce((total, size) => {
955
+ // Each individual inscription has:
956
+ // - Full transaction overhead: 200 bytes
957
+ // - Witness/inscription overhead: 122 bytes
958
+ // - Asset data: size bytes
959
+ const txSize = 200 + 122 + size;
960
+ return total + (txSize * effectiveFeeRate);
961
+ }, 0);
962
+ const savings = individualFees - batchFee;
963
+ const savingsPercentage = (savings / individualFees) * 100;
964
+ return {
965
+ batchFee,
966
+ individualFees,
967
+ savings,
968
+ savingsPercentage
969
+ };
970
+ }
971
+ }