@bsv/overlay 2.0.3 → 2.0.4

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 (86) hide show
  1. package/README.md +226 -106
  2. package/dist/cjs/mod.js +6 -1
  3. package/dist/cjs/mod.js.map +1 -1
  4. package/dist/cjs/package.json +13 -11
  5. package/dist/cjs/src/BASM.js +92 -0
  6. package/dist/cjs/src/BASM.js.map +1 -0
  7. package/dist/cjs/src/BASMRemote.js +43 -0
  8. package/dist/cjs/src/BASMRemote.js.map +1 -0
  9. package/dist/cjs/src/Engine.js +649 -47
  10. package/dist/cjs/src/Engine.js.map +1 -1
  11. package/dist/cjs/src/GASP/OverlayGASPRemote.js +2 -2
  12. package/dist/cjs/src/GASP/OverlayGASPRemote.js.map +1 -1
  13. package/dist/cjs/src/GASP/OverlayGASPStorage.js +18 -25
  14. package/dist/cjs/src/GASP/OverlayGASPStorage.js.map +1 -1
  15. package/dist/cjs/src/storage/knex/KnexStorage.js +329 -12
  16. package/dist/cjs/src/storage/knex/KnexStorage.js.map +1 -1
  17. package/dist/cjs/src/storage/knex/all-migrations.js +3 -1
  18. package/dist/cjs/src/storage/knex/all-migrations.js.map +1 -1
  19. package/dist/cjs/src/storage/knex/migrations/2025-05-28-001-enlarge.js +2 -24
  20. package/dist/cjs/src/storage/knex/migrations/2025-05-28-001-enlarge.js.map +1 -1
  21. package/dist/cjs/src/storage/knex/migrations/2026-05-29-001-brc136-basm.js +114 -0
  22. package/dist/cjs/src/storage/knex/migrations/2026-05-29-001-brc136-basm.js.map +1 -0
  23. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  24. package/dist/esm/mod.js +1 -0
  25. package/dist/esm/mod.js.map +1 -1
  26. package/dist/esm/src/BASM.js +85 -0
  27. package/dist/esm/src/BASM.js.map +1 -0
  28. package/dist/esm/src/BASMRemote.js +42 -0
  29. package/dist/esm/src/BASMRemote.js.map +1 -0
  30. package/dist/esm/src/Engine.js +637 -46
  31. package/dist/esm/src/Engine.js.map +1 -1
  32. package/dist/esm/src/GASP/OverlayGASPRemote.js +2 -2
  33. package/dist/esm/src/GASP/OverlayGASPRemote.js.map +1 -1
  34. package/dist/esm/src/GASP/OverlayGASPStorage.js +18 -25
  35. package/dist/esm/src/GASP/OverlayGASPStorage.js.map +1 -1
  36. package/dist/esm/src/storage/knex/KnexStorage.js +323 -12
  37. package/dist/esm/src/storage/knex/KnexStorage.js.map +1 -1
  38. package/dist/esm/src/storage/knex/all-migrations.js +3 -1
  39. package/dist/esm/src/storage/knex/all-migrations.js.map +1 -1
  40. package/dist/esm/src/storage/knex/migrations/2025-05-28-001-enlarge.js +2 -24
  41. package/dist/esm/src/storage/knex/migrations/2025-05-28-001-enlarge.js.map +1 -1
  42. package/dist/esm/src/storage/knex/migrations/2026-05-29-001-brc136-basm.js +109 -0
  43. package/dist/esm/src/storage/knex/migrations/2026-05-29-001-brc136-basm.js.map +1 -0
  44. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  45. package/dist/types/mod.d.ts +2 -0
  46. package/dist/types/mod.d.ts.map +1 -1
  47. package/dist/types/src/BASM.d.ts +110 -0
  48. package/dist/types/src/BASM.d.ts.map +1 -0
  49. package/dist/types/src/BASMRemote.d.ts +14 -0
  50. package/dist/types/src/BASMRemote.d.ts.map +1 -0
  51. package/dist/types/src/Engine.d.ts +78 -1
  52. package/dist/types/src/Engine.d.ts.map +1 -1
  53. package/dist/types/src/GASP/OverlayGASPStorage.d.ts +1 -1
  54. package/dist/types/src/GASP/OverlayGASPStorage.d.ts.map +1 -1
  55. package/dist/types/src/LookupService.d.ts.map +1 -1
  56. package/dist/types/src/storage/Storage.d.ts +118 -0
  57. package/dist/types/src/storage/Storage.d.ts.map +1 -1
  58. package/dist/types/src/storage/knex/KnexStorage.d.ts +38 -6
  59. package/dist/types/src/storage/knex/KnexStorage.d.ts.map +1 -1
  60. package/dist/types/src/storage/knex/all-migrations.d.ts.map +1 -1
  61. package/dist/types/src/storage/knex/migrations/2025-05-28-001-enlarge.d.ts.map +1 -1
  62. package/dist/types/src/storage/knex/migrations/2026-05-29-001-brc136-basm.d.ts +4 -0
  63. package/dist/types/src/storage/knex/migrations/2026-05-29-001-brc136-basm.d.ts.map +1 -0
  64. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  65. package/docs/API.md +72 -580
  66. package/docs/BRC-136-BASM.md +285 -0
  67. package/docs/README.md +2 -0
  68. package/docs/Synchronization.md +10 -1
  69. package/docs/examples/README.md +2 -2
  70. package/docs/examples/gs-wip.md +68 -76
  71. package/mod.ts +25 -1
  72. package/package.json +25 -23
  73. package/src/BASM.ts +208 -0
  74. package/src/BASMRemote.ts +54 -0
  75. package/src/Engine.ts +762 -47
  76. package/src/GASP/OverlayGASPRemote.ts +2 -2
  77. package/src/GASP/OverlayGASPStorage.ts +17 -22
  78. package/src/LookupService.ts +2 -1
  79. package/src/__tests/BASM.test.ts +44 -0
  80. package/src/__tests/BASMChain.test.ts +290 -0
  81. package/src/__tests/Engine.test.ts +3 -4
  82. package/src/storage/Storage.ts +127 -0
  83. package/src/storage/knex/KnexStorage.ts +376 -14
  84. package/src/storage/knex/all-migrations.ts +3 -1
  85. package/src/storage/knex/migrations/2025-05-28-001-enlarge.ts +2 -22
  86. package/src/storage/knex/migrations/2026-05-29-001-brc136-basm.ts +123 -0
package/README.md CHANGED
@@ -23,123 +23,243 @@ The Overlay Services Engine enables dynamic tracking and management of UTXO-base
23
23
 
24
24
  ## Getting Started
25
25
 
26
- ### Choose the Right Package
27
-
28
- Most application developers should start with the higher-level deployment tools:
29
-
30
- - [`@bsv/overlay-express`](https://github.com/bsv-blockchain/overlay-express): opinionated Express wrapper for running overlay nodes with HTTP routes, health checks, storage configuration, SHIP/SLAP discovery, and GASP synchronization.
31
- - [`@bsv/lars`](https://github.com/bsv-blockchain/lars): local runtime for BSV application projects that use `deployment-info.json`.
32
- - [`@bsv/cars-cli`](https://github.com/bsv-blockchain/cars-cli): cloud deployment workflow for the same `deployment-info.json` project structure.
33
-
34
- Use this package, `@bsv/overlay`, when you need direct control over the Overlay Services Engine itself, such as custom storage, custom broadcasting, or a non-Express host.
35
-
36
26
  ### Installation
37
27
 
38
- For direct engine integration, install the engine with the current BSV TypeScript SDK:
28
+ You'll usually want to wrap the Engine within an HTTP server. To get set up with Express, create a new project and install everything you'll need:
39
29
 
40
30
  ```
41
- npm i @bsv/overlay @bsv/sdk knex
31
+ npm i express body-parser @bsv/sdk @bsv/overlay hello-services knex
42
32
  ```
43
33
 
44
34
  ### Basic Usage
45
35
 
46
- Create an `Engine` with topic managers, lookup services, a storage implementation, and a chain tracker. The example below uses minimal in-memory stubs so the shape is clear. Production services should use durable storage, a real chain tracker, and a broadcaster appropriate for the target network.
47
-
48
- ```ts
49
- import {
50
- Engine,
51
- type AdmittanceInstructions,
52
- type LookupFormula,
53
- type LookupQuestion,
54
- type Output,
55
- type Storage,
56
- type TopicManager,
57
- type LookupService
58
- } from '@bsv/overlay'
59
-
60
- const topicManager: TopicManager = {
61
- async identifyAdmissibleOutputs (): Promise<AdmittanceInstructions> {
62
- return {
63
- outputsToAdmit: [],
64
- coinsToRetain: []
65
- }
66
- },
67
- async getDocumentation () {
68
- return 'Admits outputs for the example topic.'
69
- },
70
- async getMetaData () {
71
- return {
72
- name: 'Example Topic Manager',
73
- shortDescription: 'Example topic manager',
74
- iconURL: '',
75
- version: '1.0.0',
76
- informationURL: ''
36
+ In your server's main file, you can set everything up. Create a new Engine to run the overlay services you want, then expose some routes over HTTP. For example:
37
+
38
+ ```js
39
+ const express = require('express')
40
+ const bodyparser = require('body-parser')
41
+ const { Engine, KnexStorage, HelloTopicManager, HelloLookupService, HelloStorageEngine } = require('@bsv/overlay')
42
+ import { WhatsOnChain, NodejsHttpClient, ARC, ArcConfig, MerklePath } from '@bsv/sdk'
43
+ // Populate a Knexfile with your database credentials
44
+ const knex = require('knex')(require('../knexfile.js'))
45
+ const app = express()
46
+ app.use(bodyparser.json({ limit: '1gb', type: 'application/json' }))
47
+
48
+ const engine = new Engine(
49
+ {
50
+ hello: new HelloTopicManager(),
51
+ },
52
+ {
53
+ hello: new HelloLookupService({
54
+ storageEngine: new HelloStorageEngine({
55
+ knex
56
+ })
57
+ }),
58
+ },
59
+ new KnexStorageEngine({
60
+ knex
61
+ }),
62
+ new CombinatorialChainTracker([
63
+ new WhatsOnChain(
64
+ NODE_ENV === 'production' ? 'main' : 'test',
65
+ {
66
+ httpClient: new NodejsHttpClient(https)
67
+ })
68
+ ]),
69
+ HOSTING_DOMAIN as string,
70
+ SHIP_TRACKERS,
71
+ SLAP_TRACKERS,
72
+ new ARC('https://arc.taal.com', arcConfig)
73
+ )
74
+
75
+ // This allows the API to be used everywhere when CORS is enforced
76
+ app.use((req, res, next) => {
77
+ res.header('Access-Control-Allow-Origin', '*')
78
+ res.header('Access-Control-Allow-Headers', '*')
79
+ res.header('Access-Control-Allow-Methods', '*')
80
+ res.header('Access-Control-Expose-Headers', '*')
81
+ res.header('Access-Control-Allow-Private-Network', 'true')
82
+ if (req.method === 'OPTIONS') {
83
+ res.sendStatus(200)
84
+ } else {
85
+ next()
86
+ }
87
+ })
88
+
89
+ // Serve a static documentstion site, if you have one.
90
+ app.use(express.static('public'))
91
+
92
+ // List hosted topic managers and lookup services
93
+ app.get(`/listTopicManagers`, async (req, res) => {
94
+ try {
95
+ const result = await engine.listTopicManagers()
96
+ return res.status(200).json(result)
97
+ } catch (error) {
98
+ return res.status(400).json({
99
+ status: 'error',
100
+ code: error.code,
101
+ description: error.message
102
+ })
103
+ }
104
+ })
105
+ app.get(`/listLookupServiceProviders`, async (req, res) => {
106
+ try {
107
+ const result = await engine.listLookupServiceProviders()
108
+ return res.status(200).json(result)
109
+ } catch (error) {
110
+ return res.status(400).json({
111
+ status: 'error',
112
+ code: error.code,
113
+ description: error.message
114
+ })
115
+ }
116
+ })
117
+
118
+ // Host documentation for the services
119
+ app.get(`/getDocumentationForTopicManager`, async (req, res) => {
120
+ try {
121
+ const result = await engine.getDocumentationForTopicManger(req.query.manager)
122
+ return res.status(200).json(result)
123
+ } catch (error) {
124
+ return res.status(400).json({
125
+ status: 'error',
126
+ code: error.code,
127
+ description: error.message
128
+ })
129
+ }
130
+ })
131
+ app.get(`/getDocumentationForLookupServiceProvider`, async (req, res) => {
132
+ try {
133
+ const result = await engine.getDocumentationForLookupServiceProvider(req.query.lookupServices)
134
+ return res.status(200).json(result)
135
+ } catch (error) {
136
+ return res.status(400).json({
137
+ status: 'error',
138
+ code: error.code,
139
+ description: error.message
140
+ })
141
+ }
142
+ })
143
+
144
+ // Submit transactions and facilitate lookup requests
145
+ app.post(`/submit`, async (req, res) => {
146
+ try {
147
+ // Parse out the topics and construct the tagged BEEF
148
+ const topics = JSON.parse(req.headers['x-topics'] as string)
149
+ const taggedBEEF: TaggedBEEF = {
150
+ beef: Array.from(req.body as number[]),
151
+ topics
77
152
  }
153
+
154
+ // Using a callback function, we can just return once our steak is ready
155
+ // instead of having to wait for all the broadcasts to occur.
156
+ await engine.submit(taggedBEEF, (steak: STEAK) => {
157
+ return res.status(200).json(steak)
158
+ })
159
+ } catch (error) {
160
+ return res.status(400).json({
161
+ status: 'error',
162
+ code: error.code,
163
+ description: error.message
164
+ })
165
+ }
166
+ })
167
+ app.post(`/lookup`, async (req, res) => {
168
+ try {
169
+ const result = await engine.lookup(req.body)
170
+ return res.status(200).json(result)
171
+ } catch (error) {
172
+ return res.status(400).json({
173
+ status: 'error',
174
+ code: error.code,
175
+ description: error.message
176
+ })
78
177
  }
79
- }
80
-
81
- const lookupService: LookupService = {
82
- async outputAdmittedByTopic (): Promise<void> {},
83
- async outputSpent (): Promise<void> {},
84
- async outputEvicted (): Promise<void> {},
85
- async lookup (_question: LookupQuestion): Promise<LookupFormula> {
86
- return {
87
- type: 'formula',
88
- outpoints: []
178
+ })
179
+
180
+ app.post('/arc-ingest', (req, res) => {
181
+ (async () => {
182
+ try {
183
+ const merklePath = MerklePath.fromHex(req.body.merklePath)
184
+ await engine.handleNewMerkleProof(req.body.txid, merklePath, req.body.blockHeight)
185
+ return res.status(200).json({ status: 'success', message: 'transaction status updated' })
186
+ } catch (error) {
187
+ console.error(error)
188
+ return res.status(400).json({
189
+ status: 'error',
190
+ message: error instanceof Error ? error.message : 'An unknown error occurred'
191
+ })
89
192
  }
90
- },
91
- async getDocumentation () {
92
- return 'Looks up outputs admitted by the example topic.'
93
- },
94
- async getMetaData () {
95
- return {
96
- name: 'Example Lookup Service',
97
- shortDescription: 'Example lookup service',
98
- iconURL: '',
99
- version: '1.0.0',
100
- informationURL: ''
193
+ })().catch(() => {
194
+ res.status(500).json({
195
+ status: 'error',
196
+ message: 'Unexpected error'
197
+ })
198
+ })
199
+ })
200
+
201
+ app.post('/requestSyncResponse', (req, res) => {
202
+ (async () => {
203
+ try {
204
+ const topic = req.headers['x-bsv-topic'] as string
205
+ const response = await engine.provideForeignSyncResponse(req.body, topic)
206
+ return res.status(200).json(response)
207
+ } catch (error) {
208
+ console.error(error)
209
+ return res.status(400).json({
210
+ status: 'error',
211
+ message: error instanceof Error ? error.message : 'An unknown error occurred'
212
+ })
101
213
  }
102
- }
103
- }
104
-
105
- const storage: Storage = {
106
- async findOutput (): Promise<Output | null> { return null },
107
- async findOutputsForTransaction (): Promise<Output[]> { return [] },
108
- async findOutputsForTopic (): Promise<Output[]> { return [] },
109
- async findUTXOHistory (): Promise<Output[]> { return [] },
110
- async insertOutput (): Promise<void> {},
111
- async updateConsumedBy (): Promise<void> {},
112
- async updateTransactionBEEF (): Promise<void> {},
113
- async deleteOutput (): Promise<void> {},
114
- async startGASPSync (): Promise<void> {},
115
- async updateLastInteraction (): Promise<void> {},
116
- async getLastInteraction (): Promise<Date | null> { return null },
117
- async getSyncState (): Promise<unknown> { return undefined },
118
- async setSyncState (): Promise<void> {}
119
- }
120
-
121
- const engine = new Engine(
122
- { tm_example: topicManager },
123
- { ls_example: lookupService },
124
- storage,
125
- 'scripts only',
126
- 'https://example-overlay.example',
127
- [],
128
- [],
129
- undefined,
130
- undefined,
131
- { tm_example: false }
132
- )
133
-
134
- const topics = await engine.listTopicManagers()
135
- const services = await engine.listLookupServiceProviders()
214
+ })().catch(() => {
215
+ res.status(500).json({
216
+ status: 'error',
217
+ message: 'Unexpected error'
218
+ })
219
+ })
220
+ })
221
+
222
+ app.post('/requestForeignGASPNode', (req, res) => {
223
+ (async () => {
224
+ try {
225
+ console.log(req.body)
226
+ const { graphID, txid, outputIndex, metadata } = req.body
227
+ const response = await engine.provideForeignGASPNode(graphID, txid, outputIndex)
228
+ return res.status(200).json(response)
229
+ } catch (error) {
230
+ console.error(error)
231
+ return res.status(400).json({
232
+ status: 'error',
233
+ message: error instanceof Error ? error.message : 'An unknown error occurred'
234
+ })
235
+ }
236
+ })().catch(() => {
237
+ res.status(500).json({
238
+ status: 'error',
239
+ message: 'Unexpected error'
240
+ })
241
+ })
242
+ })
243
+
244
+ // 404, all other routes are not found.
245
+ app.use((req, res) => {
246
+ console.log('404', req.url)
247
+ res.status(404).json({
248
+ status: 'error',
249
+ code: 'ERR_ROUTE_NOT_FOUND',
250
+ description: 'Route not found.'
251
+ })
252
+ })
253
+
254
+ // Start your Engines!
255
+ app.listen(8080, () => {
256
+ console.log('BSV Overlay Services Engine is listening on port', 8080)
257
+ })
136
258
  ```
137
259
 
138
- For deployable HTTP examples, use [`@bsv/overlay-express`](https://github.com/bsv-blockchain/overlay-express) and [`overlay-express-examples`](https://github.com/bsv-blockchain/overlay-express-examples). For local and cloud application runtime workflows, use LARS and CARS with the BRC-102 `deployment-info.json` project structure.
139
-
140
- For lower-level engine examples, check out the [full documentation](#documentation).
260
+ For more detailed tutorials and examples, check out the [full documentation](#documentation).
141
261
 
142
- The Overlay Services Engine is also richly documented with code-level annotations. This should show up well within editors like VSCode.
262
+ The Overlay Services Engine is also richly documented with code-level annotations. This should show up well within editors like VSCode.
143
263
 
144
264
  <!-- ## Documentation
145
265
 
@@ -151,10 +271,10 @@ The Overlay Services Engine is also richly documented with code-level annotation
151
271
  - History management and state tracking
152
272
  - Lookup Services
153
273
  - Storage engine abstractions
154
- - Examples, HTTP wrapper, and docs through `@bsv/overlay-express`
155
- - ARC callback handling through overlay hosts
156
- - Distributed overlay availability advertisements with SHIP and SLAP
157
- - Federated transaction synchronization with GASP
274
+ - [WIP] Examples, HTTP wrapper and Docs
275
+ - [WIP] Arc Proof Acquisition
276
+ - [WIP] Distributed Overlay Availability Advertisements
277
+ - [WIP] Federated Transaction Synchronization
158
278
 
159
279
  ## Contribution Guidelines
160
280
 
package/dist/cjs/mod.js CHANGED
@@ -33,10 +33,15 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.KnexStorageMigrations = exports.KnexStorage = exports.Engine = void 0;
36
+ exports.KnexStorageMigrations = exports.KnexStorage = exports.extractMerkleProofMetadata = exports.computeTac = exports.computeBasmRoot = exports.BASM_ZERO_HASH = exports.Engine = void 0;
37
37
  // Fundamentals
38
38
  var Engine_js_1 = require("./src/Engine.js");
39
39
  Object.defineProperty(exports, "Engine", { enumerable: true, get: function () { return Engine_js_1.Engine; } });
40
+ var BASM_js_1 = require("./src/BASM.js");
41
+ Object.defineProperty(exports, "BASM_ZERO_HASH", { enumerable: true, get: function () { return BASM_js_1.BASM_ZERO_HASH; } });
42
+ Object.defineProperty(exports, "computeBasmRoot", { enumerable: true, get: function () { return BASM_js_1.computeBasmRoot; } });
43
+ Object.defineProperty(exports, "computeTac", { enumerable: true, get: function () { return BASM_js_1.computeTac; } });
44
+ Object.defineProperty(exports, "extractMerkleProofMetadata", { enumerable: true, get: function () { return BASM_js_1.extractMerkleProofMetadata; } });
40
45
  // The Knex storage system
41
46
  var KnexStorage_js_1 = require("./src/storage/knex/KnexStorage.js");
42
47
  Object.defineProperty(exports, "KnexStorage", { enumerable: true, get: function () { return KnexStorage_js_1.KnexStorage; } });
@@ -1 +1 @@
1
- {"version":3,"file":"mod.js","sourceRoot":"","sources":["../../mod.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,eAAe;AACf,6CAAwC;AAA/B,mGAAA,MAAM,OAAA;AAYf,0BAA0B;AAC1B,oEAA+D;AAAtD,6GAAA,WAAW,OAAA;AACpB,8FAA6E"}
1
+ {"version":3,"file":"mod.js","sourceRoot":"","sources":["../../mod.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,eAAe;AACf,6CAAwC;AAA/B,mGAAA,MAAM,OAAA;AAWf,yCAKsB;AAJpB,yGAAA,cAAc,OAAA;AACd,0GAAA,eAAe,OAAA;AACf,qGAAA,UAAU,OAAA;AACV,qHAAA,0BAA0B,OAAA;AAqB5B,0BAA0B;AAC1B,oEAA+D;AAAtD,6GAAA,WAAW,OAAA;AACpB,8FAA6E"}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsv/overlay",
3
- "version": "2.0.3",
3
+ "version": "2.0.4",
4
4
  "type": "commonjs",
5
5
  "description": "BSV Blockchain Overlay Services Engine",
6
6
  "files": [
@@ -23,7 +23,8 @@
23
23
  },
24
24
  "repository": {
25
25
  "type": "git",
26
- "url": "git+https://github.com/bsv-blockchain/overlay-services.git"
26
+ "url": "git+https://github.com/bsv-blockchain/ts-stack.git",
27
+ "directory": "packages/overlays/overlay"
27
28
  },
28
29
  "keywords": [
29
30
  "BSV",
@@ -35,21 +36,22 @@
35
36
  "author": "BSV Association",
36
37
  "license": "SEE LICENSE IN LICENSE.txt",
37
38
  "bugs": {
38
- "url": "https://github.com/bitcoin-sv/overlay-services/issues"
39
+ "url": "https://github.com/bsv-blockchain/ts-stack/issues"
39
40
  },
40
- "homepage": "https://github.com/bitcoin-sv/overlay-services#readme",
41
+ "homepage": "https://github.com/bsv-blockchain/ts-stack/tree/main/packages/overlays/overlay#readme",
41
42
  "devDependencies": {
42
- "@types/jest": "^29.5.12",
43
- "jest": "^29.7.0",
44
- "ts-jest": "^29.1.1",
43
+ "@types/jest": "^30.0.0",
44
+ "@types/node": "^25.9.3",
45
+ "jest": "^30.4.2",
46
+ "ts-jest": "^29.4.11",
45
47
  "ts-standard": "^12.0.2",
46
48
  "ts2md": "^0.2.0",
47
49
  "tsconfig-to-dual-package": "^1.2.0",
48
- "typescript": "^5.2.2"
50
+ "typescript": "^6.0.3"
49
51
  },
50
52
  "dependencies": {
51
- "@bsv/gasp": "^1.2.2",
52
- "@bsv/sdk": "^2.0.14",
53
- "knex": "^3.1.0"
53
+ "@bsv/gasp": "^1.2.3",
54
+ "@bsv/sdk": "^2.1.4",
55
+ "knex": "^3.2.10"
54
56
  }
55
57
  }
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BASM_ZERO_HASH = void 0;
4
+ exports.computeBasmRoot = computeBasmRoot;
5
+ exports.computeTac = computeTac;
6
+ exports.extractMerkleProofMetadata = extractMerkleProofMetadata;
7
+ const node_crypto_1 = require("node:crypto");
8
+ exports.BASM_ZERO_HASH = '0000000000000000000000000000000000000000000000000000000000000000';
9
+ function sha256d(buffer) {
10
+ const first = (0, node_crypto_1.createHash)('sha256').update(buffer).digest();
11
+ return (0, node_crypto_1.createHash)('sha256').update(first).digest();
12
+ }
13
+ function assertHashHex(hash, label) {
14
+ if (!/^[0-9a-fA-F]{64}$/.test(hash)) {
15
+ throw new Error(`${label} must be 32 bytes of hex`);
16
+ }
17
+ }
18
+ function displayHexToInternal(hash) {
19
+ assertHashHex(hash, 'hash');
20
+ return Buffer.from(hash, 'hex').reverse();
21
+ }
22
+ function internalToDisplayHex(hash) {
23
+ return Buffer.from(hash).reverse().toString('hex');
24
+ }
25
+ function normalizeAdmittedTxids(admitted) {
26
+ return admitted
27
+ .map((item, originalIndex) => {
28
+ if (typeof item === 'string') {
29
+ return { txid: item, blockIndex: originalIndex };
30
+ }
31
+ return item;
32
+ })
33
+ .sort((a, b) => a.blockIndex - b.blockIndex)
34
+ .map(item => item.txid.toLowerCase());
35
+ }
36
+ /**
37
+ * Computes a BRC-136 BASM root from admitted topic txids.
38
+ *
39
+ * The API accepts normal display-order txid hex because that is what the BSV
40
+ * TypeScript stack exposes at its public boundaries. Hashing is performed on
41
+ * internal byte order as required by the BRC, and the returned root is display
42
+ * order for JSON/wire compatibility.
43
+ */
44
+ function computeBasmRoot(admitted) {
45
+ const txids = normalizeAdmittedTxids(admitted);
46
+ if (txids.length === 0) {
47
+ return exports.BASM_ZERO_HASH;
48
+ }
49
+ let layer = txids.map(txid => displayHexToInternal(txid));
50
+ if (layer.length === 1) {
51
+ return internalToDisplayHex(layer[0]);
52
+ }
53
+ while (layer.length > 1) {
54
+ const next = [];
55
+ for (let i = 0; i < layer.length; i += 2) {
56
+ const left = layer[i];
57
+ const right = i + 1 < layer.length ? layer[i + 1] : left;
58
+ next.push(sha256d(Buffer.concat([left, right])));
59
+ }
60
+ layer = next;
61
+ }
62
+ return internalToDisplayHex(layer[0]);
63
+ }
64
+ /**
65
+ * Computes the BRC-136 cumulative Topic Anchor Chain hash:
66
+ * SHA256d(prevTac || blockHash || basmRoot), with all inputs reversed to
67
+ * internal byte order before hashing and the output returned as display hex.
68
+ */
69
+ function computeTac(prevTac, blockHash, basmRoot) {
70
+ const input = Buffer.concat([
71
+ displayHexToInternal(prevTac.toLowerCase()),
72
+ displayHexToInternal(blockHash.toLowerCase()),
73
+ displayHexToInternal(basmRoot.toLowerCase())
74
+ ]);
75
+ return internalToDisplayHex(sha256d(input));
76
+ }
77
+ function extractMerkleProofMetadata(txid, proof) {
78
+ var _a;
79
+ if (proof === undefined) {
80
+ return undefined;
81
+ }
82
+ const leaf = (_a = proof.path[0]) === null || _a === void 0 ? void 0 : _a.find(candidate => candidate.hash === txid);
83
+ if (leaf === undefined) {
84
+ return undefined;
85
+ }
86
+ return {
87
+ blockHeight: proof.blockHeight,
88
+ blockIndex: leaf.offset,
89
+ merkleRoot: proof.computeRoot(txid)
90
+ };
91
+ }
92
+ //# sourceMappingURL=BASM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BASM.js","sourceRoot":"","sources":["../../../src/BASM.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BASMRemote = void 0;
4
+ class BASMRemote {
5
+ constructor(endpoint, topic, fetchImpl = fetch.bind(globalThis)) {
6
+ this.endpoint = endpoint;
7
+ this.topic = topic;
8
+ this.fetchImpl = fetchImpl;
9
+ }
10
+ async requestTopicAnchorTip() {
11
+ return await this.post('/requestTopicAnchorTip', {});
12
+ }
13
+ async requestTopicAnchorRange(fromHeight, toHeight) {
14
+ return await this.post('/requestTopicAnchorRange', { fromHeight, toHeight });
15
+ }
16
+ async requestAdmittedList(blockHeight, blockHash) {
17
+ return await this.post('/requestAdmittedList', { blockHeight, blockHash });
18
+ }
19
+ async requestCompoundMerklePath(blockHeight, txids) {
20
+ return await this.post('/requestCompoundMerklePath', { blockHeight, txids });
21
+ }
22
+ async requestRawTransactions(txids) {
23
+ return await this.post('/requestRawTransactions', { txids });
24
+ }
25
+ async post(path, body) {
26
+ const response = await this.fetchImpl(new URL(path, this.endpoint).toString(), {
27
+ method: 'POST',
28
+ headers: {
29
+ Accept: 'application/json',
30
+ 'Content-Type': 'application/json',
31
+ 'x-bsv-topic': this.topic
32
+ },
33
+ body: JSON.stringify(body)
34
+ });
35
+ const text = await response.text();
36
+ if (!response.ok) {
37
+ throw new Error(`BASM peer ${this.endpoint} returned ${response.status}: ${text}`);
38
+ }
39
+ return (text.length === 0 ? {} : JSON.parse(text));
40
+ }
41
+ }
42
+ exports.BASMRemote = BASMRemote;
43
+ //# sourceMappingURL=BASMRemote.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BASMRemote.js","sourceRoot":"","sources":["../../../src/BASMRemote.ts"],"names":[],"mappings":";;;AAQA,MAAa,UAAU;IACrB,YACmB,QAAgB,EAChB,KAAa,EACb,YAA0B,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC;QAFhD,aAAQ,GAAR,QAAQ,CAAQ;QAChB,UAAK,GAAL,KAAK,CAAQ;QACb,cAAS,GAAT,SAAS,CAAuC;IAC/D,CAAC;IAEL,KAAK,CAAC,qBAAqB;QACzB,OAAO,MAAM,IAAI,CAAC,IAAI,CAAiB,wBAAwB,EAAE,EAAE,CAAC,CAAA;IACtE,CAAC;IAED,KAAK,CAAC,uBAAuB,CAAC,UAAkB,EAAE,QAAgB;QAChE,OAAO,MAAM,IAAI,CAAC,IAAI,CAA2B,0BAA0B,EAAE,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,CAAA;IACxG,CAAC;IAED,KAAK,CAAC,mBAAmB,CAAC,WAAmB,EAAE,SAAkB;QAC/D,OAAO,MAAM,IAAI,CAAC,IAAI,CAAuB,sBAAsB,EAAE,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC,CAAA;IAClG,CAAC;IAED,KAAK,CAAC,yBAAyB,CAAC,WAAmB,EAAE,KAAe;QAClE,OAAO,MAAM,IAAI,CAAC,IAAI,CAA6B,4BAA4B,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,CAAA;IAC1G,CAAC;IAED,KAAK,CAAC,sBAAsB,CAAC,KAAe;QAC1C,OAAO,MAAM,IAAI,CAAC,IAAI,CAAyB,yBAAyB,EAAE,EAAE,KAAK,EAAE,CAAC,CAAA;IACtF,CAAC;IAEO,KAAK,CAAC,IAAI,CAAI,IAAY,EAAE,IAAa;QAC/C,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,IAAI,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,QAAQ,EAAE,EAAE;YAC7E,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,MAAM,EAAE,kBAAkB;gBAC1B,cAAc,EAAE,kBAAkB;gBAClC,aAAa,EAAE,IAAI,CAAC,KAAK;aAC1B;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;SAC3B,CAAC,CAAA;QAEF,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAA;QAClC,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,aAAa,IAAI,CAAC,QAAQ,aAAa,QAAQ,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC,CAAA;QACpF,CAAC;QAED,OAAO,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAM,CAAA;IACzD,CAAC;CACF;AA7CD,gCA6CC"}