@bsv/overlay 0.1.27 → 0.2.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 (54) hide show
  1. package/dist/cjs/package.json +3 -3
  2. package/dist/cjs/src/Engine.js +95 -13
  3. package/dist/cjs/src/Engine.js.map +1 -1
  4. package/dist/cjs/src/GASP/OverlayGASPRemote.js.map +1 -1
  5. package/dist/cjs/src/GASP/OverlayGASPStorage.js +1 -1
  6. package/dist/cjs/src/GASP/OverlayGASPStorage.js.map +1 -1
  7. package/dist/cjs/src/storage/knex/KnexStorage.js.map +1 -1
  8. package/dist/cjs/src/storage/knex/migrations/2024-05-18-001-initial.js.map +1 -1
  9. package/dist/cjs/src/storage/knex/migrations/2024-07-10-001-block-height.js.map +1 -1
  10. package/dist/cjs/src/storage/knex/migrations/2024-07-17-001-transactions.js.map +1 -1
  11. package/dist/cjs/src/storage/knex/migrations/2024-07-18-001-indexes.js.map +1 -1
  12. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  13. package/dist/esm/src/Engine.js +93 -12
  14. package/dist/esm/src/Engine.js.map +1 -1
  15. package/dist/esm/src/GASP/OverlayGASPRemote.js.map +1 -1
  16. package/dist/esm/src/GASP/OverlayGASPStorage.js +1 -1
  17. package/dist/esm/src/GASP/OverlayGASPStorage.js.map +1 -1
  18. package/dist/esm/src/storage/knex/KnexStorage.js.map +1 -1
  19. package/dist/esm/src/storage/knex/migrations/2024-05-18-001-initial.js.map +1 -1
  20. package/dist/esm/src/storage/knex/migrations/2024-07-10-001-block-height.js.map +1 -1
  21. package/dist/esm/src/storage/knex/migrations/2024-07-17-001-transactions.js.map +1 -1
  22. package/dist/esm/src/storage/knex/migrations/2024-07-18-001-indexes.js.map +1 -1
  23. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  24. package/dist/types/src/Engine.d.ts.map +1 -1
  25. package/dist/types/src/GASP/OverlayGASPRemote.d.ts +1 -1
  26. package/dist/types/src/GASP/OverlayGASPRemote.d.ts.map +1 -1
  27. package/dist/types/src/GASP/OverlayGASPStorage.d.ts.map +1 -1
  28. package/dist/types/src/LookupService.d.ts +67 -48
  29. package/dist/types/src/LookupService.d.ts.map +1 -1
  30. package/dist/types/src/Output.d.ts +2 -2
  31. package/dist/types/src/Output.d.ts.map +1 -1
  32. package/dist/types/src/storage/Storage.d.ts.map +1 -1
  33. package/dist/types/src/storage/knex/KnexStorage.d.ts.map +1 -1
  34. package/dist/types/src/storage/knex/all-migrations.d.ts +2 -2
  35. package/dist/types/src/storage/knex/all-migrations.d.ts.map +1 -1
  36. package/dist/types/src/storage/knex/migrations/2024-05-18-001-initial.d.ts.map +1 -1
  37. package/dist/types/src/storage/knex/migrations/2024-07-10-001-block-height.d.ts.map +1 -1
  38. package/dist/types/src/storage/knex/migrations/2024-07-17-001-transactions.d.ts.map +1 -1
  39. package/dist/types/src/storage/knex/migrations/2024-07-18-001-indexes.d.ts.map +1 -1
  40. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  41. package/package.json +3 -3
  42. package/src/Engine.ts +139 -61
  43. package/src/GASP/OverlayGASPRemote.ts +6 -6
  44. package/src/GASP/OverlayGASPStorage.ts +17 -17
  45. package/src/LookupService.ts +112 -49
  46. package/src/Output.ts +1 -1
  47. package/src/__tests/Engine.test.ts +38 -29
  48. package/src/storage/Storage.ts +0 -1
  49. package/src/storage/knex/KnexStorage.ts +12 -12
  50. package/src/storage/knex/all-migrations.ts +1 -1
  51. package/src/storage/knex/migrations/2024-05-18-001-initial.ts +2 -2
  52. package/src/storage/knex/migrations/2024-07-10-001-block-height.ts +2 -2
  53. package/src/storage/knex/migrations/2024-07-17-001-transactions.ts +2 -2
  54. package/src/storage/knex/migrations/2024-07-18-001-indexes.ts +2 -2
package/src/Engine.ts CHANGED
@@ -47,7 +47,7 @@ export class Engine {
47
47
  * @param {OverlayBroadcastFacilitator} overlayBroadcastFacilitator - Facilitator for propagation to other Overlay Services.
48
48
  * @param {typeof console} logger - The place where log entries are written.
49
49
  */
50
- constructor(
50
+ constructor (
51
51
  public managers: { [key: string]: TopicManager },
52
52
  public lookupServices: { [key: string]: LookupService },
53
53
  public storage: Storage,
@@ -76,14 +76,14 @@ export class Engine {
76
76
  if (managerName === 'tm_ship' && this.shipTrackers !== undefined && this.syncConfiguration[managerName] !== false) {
77
77
  // Combine tm_ship trackers with preexisting entries if any
78
78
  const combinedSet = new Set([
79
- ...(Array.isArray(this.syncConfiguration[managerName]) ? this.syncConfiguration[managerName] as string[] : []),
79
+ ...(Array.isArray(this.syncConfiguration[managerName]) ? this.syncConfiguration[managerName] : []),
80
80
  ...this.shipTrackers
81
81
  ])
82
82
  this.syncConfiguration[managerName] = Array.from(combinedSet)
83
83
  } else if (managerName === 'tm_slap' && this.slapTrackers !== undefined && this.syncConfiguration[managerName] !== false) {
84
84
  // Combine tm_slap trackers with preexisting entries if any
85
85
  const combinedSet = new Set([
86
- ...(Array.isArray(this.syncConfiguration[managerName]) ? this.syncConfiguration[managerName] as string[] : []),
86
+ ...(Array.isArray(this.syncConfiguration[managerName]) ? this.syncConfiguration[managerName] : []),
87
87
  ...this.slapTrackers
88
88
  ])
89
89
  this.syncConfiguration[managerName] = Array.from(combinedSet)
@@ -97,13 +97,13 @@ export class Engine {
97
97
  }
98
98
 
99
99
  // Helper functions for logging timings
100
- private startTime(label: string): void {
100
+ private startTime (label: string): void {
101
101
  if (this.logTime) {
102
102
  this.logger.time(`${this.logPrefix} ${label}`)
103
103
  }
104
104
  }
105
105
 
106
- private endTime(label: string): void {
106
+ private endTime (label: string): void {
107
107
  if (this.logTime) {
108
108
  this.logger.timeEnd(`${this.logPrefix} ${label}`)
109
109
  }
@@ -114,12 +114,12 @@ export class Engine {
114
114
  * @param {TaggedBEEF} taggedBEEF - The transaction to process
115
115
  * @param {function(STEAK): void} [onSTEAKReady] - Optional callback function invoked when the STEAK is ready.
116
116
  * @param {string} mode — Indicates the submission behavior, whether historical or current. Historical transactions are not broadcast or propagated.
117
- *
117
+ *
118
118
  * The optional callback function should be used to get STEAK when ready, and avoid waiting for broadcast and transaction propagation to complete.
119
- *
119
+ *
120
120
  * @returns {Promise<STEAK>} The submitted transaction execution acknowledgement
121
121
  */
122
- async submit(taggedBEEF: TaggedBEEF, onSteakReady?: (steak: STEAK) => void, mode: 'historical-tx' | 'current-tx' = 'current-tx'): Promise<STEAK> {
122
+ async submit (taggedBEEF: TaggedBEEF, onSteakReady?: (steak: STEAK) => void, mode: 'historical-tx' | 'current-tx' = 'current-tx'): Promise<STEAK> {
123
123
  for (const t of taggedBEEF.topics) {
124
124
  if (this.managers[t] === undefined || this.managers[t] === null) {
125
125
  throw new Error(`This server does not support this topic: ${t}`)
@@ -181,7 +181,53 @@ export class Engine {
181
181
  await this.storage.markUTXOAsSpent(output.txid, output.outputIndex, topic)
182
182
  await Promise.all(Object.values(this.lookupServices).map(async l => {
183
183
  try {
184
- await l.outputSpent?.(output.txid, output.outputIndex, topic)
184
+ if (typeof l.outputSpent === 'function') {
185
+ if (l.spendNotificationMode === 'txid') {
186
+ await l.outputSpent({
187
+ mode: 'txid',
188
+ spendingTxid: txid,
189
+ txid: output.txid,
190
+ outputIndex: output.outputIndex,
191
+ topic
192
+ })
193
+ } else if (l.spendNotificationMode === 'script') {
194
+ const inputIndex = tx.inputs.findIndex(i => {
195
+ let realSource = i.sourceTXID
196
+ if (!realSource) {
197
+ realSource = i.sourceTransaction?.id('hex')
198
+ }
199
+ return realSource === output.txid && i.sourceOutputIndex === output.outputIndex
200
+ })
201
+ if (!inputIndex) {
202
+ throw new Error('Could not find input index')
203
+ }
204
+ await l.outputSpent({
205
+ mode: 'script',
206
+ spendingTxid: txid,
207
+ inputIndex,
208
+ sequenceNumber: tx.inputs[inputIndex].sequence ?? 0xffffffff,
209
+ unlockingScript: tx.inputs[inputIndex].unlockingScript!,
210
+ txid: output.txid,
211
+ outputIndex: output.outputIndex,
212
+ topic
213
+ })
214
+ } else if (l.spendNotificationMode === 'whole-tx') {
215
+ await l.outputSpent({
216
+ mode: 'whole-tx',
217
+ spendingAtomicBEEF: tx.toAtomicBEEF(),
218
+ txid: output.txid,
219
+ outputIndex: output.outputIndex,
220
+ topic
221
+ })
222
+ } else { // none
223
+ await l.outputSpent({
224
+ mode: 'none',
225
+ txid: output.txid,
226
+ outputIndex: output.outputIndex,
227
+ topic
228
+ })
229
+ }
230
+ }
185
231
  } catch (error) {
186
232
  this.logger.error('Error in lookup service for outputSpent:', error)
187
233
  }
@@ -244,7 +290,7 @@ export class Engine {
244
290
 
245
291
  const outputsToMarkStale: Array<{
246
292
  txid: string
247
- previousOutputIndex: number,
293
+ previousOutputIndex: number
248
294
  inputIndex: number
249
295
  }> = []
250
296
 
@@ -262,12 +308,13 @@ export class Engine {
262
308
 
263
309
  // For each of the previous UTXOs for this topic, if the UTXO was not included in the list of UTXOs identified for retention, then it will be marked as stale.
264
310
  for (const inputIndex of previousCoins) {
265
- const previousTXID = tx.inputs[inputIndex].sourceTXID || tx.inputs[inputIndex].sourceTransaction?.id('hex') as string
311
+ const previousTXID = tx.inputs[inputIndex].sourceTXID ?? tx.inputs[inputIndex].sourceTransaction?.id('hex')
312
+ if (typeof previousTXID !== 'string') continue
266
313
  const previousOutputIndex = tx.inputs[inputIndex].sourceOutputIndex
267
314
  if (!admissibleOutputs.coinsToRetain.includes(inputIndex)) {
268
315
  outputsToMarkStale.push({
269
316
  txid: previousTXID,
270
- previousOutputIndex: previousOutputIndex,
317
+ previousOutputIndex,
271
318
  inputIndex
272
319
  })
273
320
  } else {
@@ -294,12 +341,13 @@ export class Engine {
294
341
  // Handle admittance and notification of incoming UTXOs
295
342
  const newUTXOs: Array<{ txid: string, outputIndex: number }> = []
296
343
  await Promise.all(outputsToAdmit.map(async outputIndex => {
344
+ if (typeof tx.outputs[outputIndex].satoshis !== 'number') return
297
345
  this.startTime(`insertNewOutput_${txid.substring(0, 10)}`)
298
346
  await this.storage.insertOutput({
299
347
  txid,
300
348
  outputIndex,
301
349
  outputScript: tx.outputs[outputIndex].lockingScript.toBinary(),
302
- satoshis: tx.outputs[outputIndex].satoshis as number,
350
+ satoshis: tx.outputs[outputIndex].satoshis,
303
351
  topic,
304
352
  spent: false,
305
353
  beef: taggedBEEF.beef,
@@ -310,7 +358,31 @@ export class Engine {
310
358
  newUTXOs.push({ txid, outputIndex })
311
359
 
312
360
  this.startTime(`notifyLookupService${txid.substring(0, 10)}`)
313
- await Promise.all(Object.values(this.lookupServices).map(async l => await l.outputAdded?.(txid, outputIndex, tx.outputs[outputIndex].lockingScript, topic)))
361
+ await Promise.all(Object.values(this.lookupServices).map(async l => {
362
+ if (l.admissionMode === 'locking-script') {
363
+ if (
364
+ typeof tx.outputs[outputIndex].lockingScript !== 'object' ||
365
+ typeof tx.outputs[outputIndex].satoshis !== 'number'
366
+ ) {
367
+ return
368
+ }
369
+ await l.outputAdmittedByTopic({
370
+ mode: 'locking-script',
371
+ txid,
372
+ outputIndex,
373
+ lockingScript: tx.outputs[outputIndex].lockingScript,
374
+ satoshis: tx.outputs[outputIndex].satoshis,
375
+ topic
376
+ })
377
+ } else {
378
+ await l.outputAdmittedByTopic({
379
+ mode: 'whole-tx',
380
+ atomicBEEF: tx.toAtomicBEEF(),
381
+ outputIndex,
382
+ topic
383
+ })
384
+ }
385
+ }))
314
386
  this.endTime(`notifyLookupService${txid.substring(0, 10)}`)
315
387
  }))
316
388
 
@@ -376,7 +448,7 @@ export class Engine {
376
448
  * @param LookupQuestion — The question to ask the Overlay Services Engine
377
449
  * @returns The answer to the question
378
450
  */
379
- async lookup(lookupQuestion: LookupQuestion): Promise<LookupAnswer> {
451
+ async lookup (lookupQuestion: LookupQuestion): Promise<LookupAnswer> {
380
452
  // Validate a lookup service for the provider is found
381
453
  const lookupService = this.lookupServices[lookupQuestion.service]
382
454
  if (lookupService === undefined || lookupService === null) throw new Error(`Lookup service not found for provider: ${lookupQuestion.service} `)
@@ -417,7 +489,7 @@ export class Engine {
417
489
  }
418
490
 
419
491
  /**
420
- * Ensures alignment between the current SHIP/SLAP advertisements and the
492
+ * Ensures alignment between the current SHIP/SLAP advertisements and the
421
493
  * configured Topic Managers and Lookup Services in the engine.
422
494
  *
423
495
  * This method performs the following actions:
@@ -435,8 +507,13 @@ export class Engine {
435
507
  * @throws Will throw an error if there are issues during the advertisement synchronization process.
436
508
  * @returns {Promise<void>} A promise that resolves when the synchronization process is complete.
437
509
  */
438
- async syncAdvertisements(): Promise<void> {
439
- if (this.advertiser === undefined || !this.hostingURL || !this.isValidUrl(this.hostingURL)) {
510
+ async syncAdvertisements (): Promise<void> {
511
+ if (
512
+ this.advertiser === undefined ||
513
+ typeof this.hostingURL !== 'string' ||
514
+ this.hostingURL.length < 1 ||
515
+ !this.isValidUrl(this.hostingURL)
516
+ ) {
440
517
  return
441
518
  }
442
519
  const advertiser = this.advertiser
@@ -496,7 +573,7 @@ export class Engine {
496
573
  *
497
574
  * @throws Error if the overlay service engine is not configured for topical synchronization.
498
575
  */
499
- async startGASPSync(): Promise<void> {
576
+ async startGASPSync (): Promise<void> {
500
577
  if (this.syncConfiguration === undefined) {
501
578
  throw new Error('Overlay Service Engine not configured for topical synchronization!')
502
579
  }
@@ -590,7 +667,7 @@ export class Engine {
590
667
  * @param topic - The topic for which UTXOs are being requested.
591
668
  * @returns A promise that resolves to a GASPInitialResponse containing the list of UTXOs and the provided min block height.
592
669
  */
593
- async provideForeignSyncResponse(initialRequest: GASPInitialRequest, topic: string): Promise<GASPInitialResponse> {
670
+ async provideForeignSyncResponse (initialRequest: GASPInitialRequest, topic: string): Promise<GASPInitialResponse> {
594
671
  const UTXOs = await this.storage.findUTXOsForTopic(topic, initialRequest.since)
595
672
 
596
673
  return {
@@ -613,7 +690,7 @@ export class Engine {
613
690
  * @returns A promise that resolves to a GASPNode containing the raw transaction and other optional data.
614
691
  * @throws An error if no output is found for the given transaction ID and output index.
615
692
  */
616
- async provideForeignGASPNode(graphID: string, txid: string, outputIndex: number): Promise<GASPNode> {
693
+ async provideForeignGASPNode (graphID: string, txid: string, outputIndex: number): Promise<GASPNode> {
617
694
  const hydrator = async (output: Output | null): Promise<GASPNode> => {
618
695
  if (output?.beef === undefined) {
619
696
  throw new Error('No matching output found!')
@@ -691,7 +768,7 @@ export class Engine {
691
768
  *
692
769
  * @returns {Promise<Output | undefined>} - A promise that resolves to the output history if found, or undefined if not.
693
770
  */
694
- async getUTXOHistory(
771
+ async getUTXOHistory (
695
772
  output: Output,
696
773
  historySelector?: ((beef: number[], outputIndex: number, currentDepth: number) => Promise<boolean>) | number,
697
774
  currentDepth = 0
@@ -772,7 +849,7 @@ export class Engine {
772
849
  * @param output - The UTXO to be deleted.
773
850
  * @returns {Promise<void>} - A promise that resolves when the deletion process is complete.
774
851
  */
775
- private async deleteUTXODeep(output: Output): Promise<void> {
852
+ private async deleteUTXODeep (output: Output): Promise<void> {
776
853
  try {
777
854
  // Delete the current output IFF there are no references to it
778
855
  if (output.consumedBy.length === 0) {
@@ -781,7 +858,7 @@ export class Engine {
781
858
  // Notify the lookup services of the UTXO being deleted
782
859
  for (const l of Object.values(this.lookupServices)) {
783
860
  try {
784
- await l.outputDeleted?.(
861
+ await l.outputNoLongerRetainedInHistory?.(
785
862
  output.txid,
786
863
  output.outputIndex,
787
864
  output.topic
@@ -828,7 +905,7 @@ export class Engine {
828
905
  * @param txid BE hex string double hash of transaction proven by proof.
829
906
  * @param proof for txid
830
907
  */
831
- private updateInputProofs(tx: Transaction, txid: string, proof: MerklePath): void {
908
+ private updateInputProofs (tx: Transaction, txid: string, proof: MerklePath): void {
832
909
  if (tx.merklePath !== undefined) {
833
910
  // Update the merkle path to handle potential reorgs
834
911
  tx.merklePath = proof
@@ -839,7 +916,8 @@ export class Engine {
839
916
  } else {
840
917
  for (const input of tx.inputs) {
841
918
  // All inputs must have sourceTransactions
842
- const stx = input.sourceTransaction!
919
+ const stx = input.sourceTransaction
920
+ if (typeof stx !== 'object') continue
843
921
  this.updateInputProofs(stx, txid, proof)
844
922
  }
845
923
  }
@@ -852,7 +930,7 @@ export class Engine {
852
930
  * @param txid - The txid for which proof is a valid merkle path.
853
931
  * @param proof - The merklePath proving txid is a mined transaction hash
854
932
  */
855
- private async updateMerkleProof(output: Output, txid: string, proof: MerklePath): Promise<void> {
933
+ private async updateMerkleProof (output: Output, txid: string, proof: MerklePath): Promise<void> {
856
934
  if (output.beef === undefined) {
857
935
  throw new Error('Output must have associated transaction BEEF!')
858
936
  }
@@ -886,7 +964,7 @@ export class Engine {
886
964
  * @param proof - Merkle proof containing the Merkle path and other relevant data to verify the transaction.
887
965
  * @param blockHeight - The block height associated with the incoming merkle proof.
888
966
  */
889
- async handleNewMerkleProof(txid: string, proof: MerklePath, blockHeight?: number): Promise<void> {
967
+ async handleNewMerkleProof (txid: string, proof: MerklePath, blockHeight?: number): Promise<void> {
890
968
  const outputs = await this.storage.findOutputsForTransaction(txid, true)
891
969
 
892
970
  if (outputs === undefined || outputs.length === 0) {
@@ -909,21 +987,21 @@ export class Engine {
909
987
  * @public
910
988
  * @returns {Promise<Record<string, { name: string; shortDescription: string; iconURL?: string; version?: string; informationURL?: string; }>>} - Supported topic managers and their metadata
911
989
  */
912
- async listTopicManagers(): Promise<Record<string, {
913
- name: string;
914
- shortDescription: string;
915
- iconURL?: string;
916
- version?: string;
917
- informationURL?: string;
990
+ async listTopicManagers (): Promise<Record<string, {
991
+ name: string
992
+ shortDescription: string
993
+ iconURL?: string
994
+ version?: string
995
+ informationURL?: string
918
996
  }>> {
919
- let result: Record<string, {
920
- name: string;
921
- shortDescription: string;
922
- iconURL?: string;
923
- version?: string;
924
- informationURL?: string;
997
+ const result: Record<string, {
998
+ name: string
999
+ shortDescription: string
1000
+ iconURL?: string
1001
+ version?: string
1002
+ informationURL?: string
925
1003
  }> = {}
926
- for (let t in this.managers) {
1004
+ for (const t in this.managers) {
927
1005
  try {
928
1006
  result[t] = await this.managers[t].getMetaData()
929
1007
  } catch (e) {
@@ -942,21 +1020,21 @@ export class Engine {
942
1020
  * @public
943
1021
  * @returns {Promise<Record<string, { name: string; shortDescription: string; iconURL?: string; version?: string; informationURL?: string; }>>} - Supported lookup services and their metadata
944
1022
  */
945
- async listLookupServiceProviders(): Promise<Record<string, {
946
- name: string;
947
- shortDescription: string;
948
- iconURL?: string;
949
- version?: string;
950
- informationURL?: string;
1023
+ async listLookupServiceProviders (): Promise<Record<string, {
1024
+ name: string
1025
+ shortDescription: string
1026
+ iconURL?: string
1027
+ version?: string
1028
+ informationURL?: string
951
1029
  }>> {
952
- let result: Record<string, {
953
- name: string;
954
- shortDescription: string;
955
- iconURL?: string;
956
- version?: string;
957
- informationURL?: string;
1030
+ const result: Record<string, {
1031
+ name: string
1032
+ shortDescription: string
1033
+ iconURL?: string
1034
+ version?: string
1035
+ informationURL?: string
958
1036
  }> = {}
959
- for (let ls in this.lookupServices) {
1037
+ for (const ls in this.lookupServices) {
960
1038
  try {
961
1039
  result[ls] = await this.lookupServices[ls].getMetaData()
962
1040
  } catch (e) {
@@ -975,7 +1053,7 @@ export class Engine {
975
1053
  * @public
976
1054
  * @returns {Promise<string>} - the documentation for the topic manager
977
1055
  */
978
- async getDocumentationForTopicManager(manager: any): Promise<string> {
1056
+ async getDocumentationForTopicManager (manager: any): Promise<string> {
979
1057
  const documentation = await this.managers[manager]?.getDocumentation?.()
980
1058
  return documentation !== undefined ? documentation : 'No documentation found!'
981
1059
  }
@@ -985,7 +1063,7 @@ export class Engine {
985
1063
  * @public
986
1064
  * @returns {Promise<string>} - the documentation for the lookup service
987
1065
  */
988
- async getDocumentationForLookupServiceProvider(provider: any): Promise<string> {
1066
+ async getDocumentationForLookupServiceProvider (provider: any): Promise<string> {
989
1067
  const documentation = await this.lookupServices[provider]?.getDocumentation?.()
990
1068
  return documentation !== undefined ? documentation : 'No documentation found!'
991
1069
  }
@@ -1000,12 +1078,12 @@ export class Engine {
1000
1078
  * @param url - The URL string to validate
1001
1079
  * @returns {boolean} - Returns `false` if the URL violates any of the conditions `true` otherwise
1002
1080
  */
1003
- private isValidUrl(url: string): boolean {
1081
+ private isValidUrl (url: string): boolean {
1004
1082
  try {
1005
1083
  const parsedUrl = new URL(url)
1006
1084
 
1007
1085
  // Disallow http:
1008
- if (parsedUrl.protocol === "http:") {
1086
+ if (parsedUrl.protocol === 'http:') {
1009
1087
  return false
1010
1088
  }
1011
1089
 
@@ -1021,9 +1099,9 @@ export class Engine {
1021
1099
  const nonRoutableIpv4Patterns = [
1022
1100
  /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/, // Loopback IPs
1023
1101
  /^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$/, // 10.x.x.x private IPs
1024
- /^192\.168\.\d{1,3}\.\d{1,3}$/, // 192.168.x.x private IPs
1102
+ /^192\.168\.\d{1,3}\.\d{1,3}$/, // 192.168.x.x private IPs
1025
1103
  /^172\.(1[6-9]|2[0-9]|3[0-1])\.\d{1,3}\.\d{1,3}$/, // 172.16.x.x to 172.31.x.x private IPs
1026
- /^0\.0\.0\.0$/ // Non-routable address
1104
+ /^0\.0\.0\.0$/ // Non-routable address
1027
1105
  ]
1028
1106
 
1029
1107
  // Check for IPv4 matches
@@ -1032,7 +1110,7 @@ export class Engine {
1032
1110
  }
1033
1111
 
1034
1112
  // Check for non-routable IPv6 addresses explicitly
1035
- if (ipAddress === "[::1]") {
1113
+ if (ipAddress === '[::1]') {
1036
1114
  return false
1037
1115
  }
1038
1116
 
@@ -1,14 +1,14 @@
1
- import { GASPInitialReply, GASPInitialRequest, GASPInitialResponse, GASPNode, GASPNodeResponse, GASPRemote } from "@bsv/gasp"
1
+ import { GASPInitialReply, GASPInitialRequest, GASPInitialResponse, GASPNode, GASPNodeResponse, GASPRemote } from '@bsv/gasp'
2
2
 
3
3
  export class OverlayGASPRemote implements GASPRemote {
4
- constructor(public endpointURL: string, public topic: string) { }
4
+ constructor (public endpointURL: string, public topic: string) { }
5
5
 
6
6
  /**
7
7
  * Given an outgoing initial request, sends the request to the foreign instance and obtains their initial response.
8
8
  * @param request
9
9
  * @returns
10
10
  */
11
- async getInitialResponse(request: GASPInitialRequest): Promise<GASPInitialResponse> {
11
+ async getInitialResponse (request: GASPInitialRequest): Promise<GASPInitialResponse> {
12
12
  // Send out an HTTP request to the URL (current host for topic)
13
13
  // Include the topic in the request
14
14
  // Parse out response and return correct format
@@ -50,7 +50,7 @@ export class OverlayGASPRemote implements GASPRemote {
50
50
  * @param metadata
51
51
  * @returns
52
52
  */
53
- async requestNode(graphID: string, txid: string, outputIndex: number, metadata: boolean): Promise<GASPNode> {
53
+ async requestNode (graphID: string, txid: string, outputIndex: number, metadata: boolean): Promise<GASPNode> {
54
54
  // Send an HTTP request with the provided info and get back a gaspNode
55
55
  const url = `${this.endpointURL}/requestForeignGASPNode`
56
56
  const body = {
@@ -95,13 +95,13 @@ export class OverlayGASPRemote implements GASPRemote {
95
95
  // ---- Now optional methods ----
96
96
 
97
97
  // When are only syncing to them
98
- async getInitialReply(response: GASPInitialResponse): Promise<GASPInitialReply> {
98
+ async getInitialReply (response: GASPInitialResponse): Promise<GASPInitialReply> {
99
99
  throw new Error('Function not supported!')
100
100
  }
101
101
 
102
102
  // Only used when supporting bidirectional sync.
103
103
  // Overlay services does not support this.
104
- async submitNode(node: GASPNode): Promise<void | GASPNodeResponse> {
104
+ async submitNode (node: GASPNode): Promise<void | GASPNodeResponse> {
105
105
  throw new Error('Node submission not supported!')
106
106
  }
107
107
  }
@@ -22,14 +22,14 @@ export interface GraphNode {
22
22
  export class OverlayGASPStorage implements GASPStorage {
23
23
  readonly temporaryGraphNodeRefs: Record<string, GraphNode> = {}
24
24
 
25
- constructor(public topic: string, public engine: Engine, public maxNodesInGraph?: number) { }
25
+ constructor (public topic: string, public engine: Engine, public maxNodesInGraph?: number) { }
26
26
 
27
27
  /**
28
28
  *
29
29
  * @param since
30
30
  * @returns
31
31
  */
32
- async findKnownUTXOs(since: number): Promise<Array<{ txid: string, outputIndex: number }>> {
32
+ async findKnownUTXOs (since: number): Promise<Array<{ txid: string, outputIndex: number }>> {
33
33
  const UTXOs = await this.engine.storage.findUTXOsForTopic(this.topic, since)
34
34
  return UTXOs.map(output => ({
35
35
  txid: output.txid,
@@ -39,13 +39,13 @@ export class OverlayGASPStorage implements GASPStorage {
39
39
 
40
40
  /**
41
41
  * For a given txid and output index, returns the associated transaction, a merkle proof if the transaction is in a block, and metadata if if requested. If no metadata is requested, metadata hashes on inputs are not returned.
42
- * @param graphID
43
- * @param txid
44
- * @param outputIndex
45
- * @param metadata
46
- * @returns
42
+ * @param graphID
43
+ * @param txid
44
+ * @param outputIndex
45
+ * @param metadata
46
+ * @returns
47
47
  */
48
- async hydrateGASPNode(graphID: string, txid: string, outputIndex: number, metadata: boolean): Promise<GASPNode> {
48
+ async hydrateGASPNode (graphID: string, txid: string, outputIndex: number, metadata: boolean): Promise<GASPNode> {
49
49
  const output = await this.engine.storage.findOutput(txid, outputIndex, undefined, undefined, true)
50
50
 
51
51
  if (output?.beef === undefined) {
@@ -72,7 +72,7 @@ export class OverlayGASPStorage implements GASPStorage {
72
72
  * @param tx The node for which needed inputs should be found.
73
73
  * @returns A promise for a mapping of requested input transactions and whether metadata should be provided for each.
74
74
  */
75
- async findNeededInputs(tx: GASPNode): Promise<GASPNodeResponse | undefined> {
75
+ async findNeededInputs (tx: GASPNode): Promise<GASPNodeResponse | undefined> {
76
76
  // If there is no Merkle proof, we always need the inputs
77
77
  const response: GASPNodeResponse = {
78
78
  requestedInputs: {}
@@ -120,7 +120,7 @@ export class OverlayGASPStorage implements GASPStorage {
120
120
  * Ensures that no inputs are requested from foreign nodes before sending any GASP response
121
121
  * Also terminates graphs if the response would be empty.
122
122
  */
123
- private async stripAlreadyKnownInputs(response: GASPNodeResponse | undefined): Promise<GASPNodeResponse | undefined> {
123
+ private async stripAlreadyKnownInputs (response: GASPNodeResponse | undefined): Promise<GASPNodeResponse | undefined> {
124
124
  if (typeof response === 'undefined') {
125
125
  return response
126
126
  }
@@ -144,7 +144,7 @@ export class OverlayGASPStorage implements GASPStorage {
144
144
  * @param spentBy Unless this is the same node identified by the graph ID, denotes the TXID and input index for the node which spent this one, in 36-byte format.
145
145
  * @throws If the node cannot be appended to the graph, either because the graph ID is for a graph the recipient does not want or because the graph has grown to be too large before being finalized.
146
146
  */
147
- async appendToGraph(tx: GASPNode, spentBy?: string | undefined): Promise<void> {
147
+ async appendToGraph (tx: GASPNode, spentBy?: string | undefined): Promise<void> {
148
148
  if (this.maxNodesInGraph !== undefined && Object.keys(this.temporaryGraphNodeRefs).length >= this.maxNodesInGraph) {
149
149
  throw new Error('The max number of nodes in transaction graph has been reached!')
150
150
  }
@@ -194,7 +194,7 @@ export class OverlayGASPStorage implements GASPStorage {
194
194
  * @param graphID The TXID and output index (in 36-byte format) for the UTXO at the tip of this graph.
195
195
  * @throws If the graph is not well-anchored, according to the rules of Bitcoin or the rules of the Overlay Topic Manager.
196
196
  */
197
- async validateGraphAnchor(graphID: string): Promise<void> {
197
+ async validateGraphAnchor (graphID: string): Promise<void> {
198
198
  const rootNode = this.temporaryGraphNodeRefs[graphID]
199
199
  if (rootNode === undefined) {
200
200
  throw new Error(`Graph node with ID ${graphID} not found`)
@@ -246,7 +246,7 @@ export class OverlayGASPStorage implements GASPStorage {
246
246
  * Deletes all data associated with a temporary graph that has failed to sync, if the graph exists.
247
247
  * @param graphID The TXID and output index (in 36-byte format) for the UTXO at the tip of this graph.
248
248
  */
249
- async discardGraph(graphID: string): Promise<void> {
249
+ async discardGraph (graphID: string): Promise<void> {
250
250
  for (const [nodeId, graphRef] of Object.entries(this.temporaryGraphNodeRefs)) {
251
251
  if (graphRef.graphID === graphID) {
252
252
  // Delete child node
@@ -260,7 +260,7 @@ export class OverlayGASPStorage implements GASPStorage {
260
260
  * Finalizes a graph, solidifying the new UTXO and its ancestors so that it will appear in the list of known UTXOs.
261
261
  * @param graphID The TXID and output index (in 36-byte format) for the UTXO at the root of this graph.
262
262
  */
263
- async finalizeGraph(graphID: string): Promise<void> {
263
+ async finalizeGraph (graphID: string): Promise<void> {
264
264
  const beefs = this.computeOrderedBEEFsForGraph(graphID)
265
265
 
266
266
  // Submit all historical BEEFs in order, finalizing the graph for the current UTXO
@@ -277,11 +277,11 @@ export class OverlayGASPStorage implements GASPStorage {
277
277
  * @param {string} graphID — The ID of the graph for which BEEFs are required
278
278
  * @returns Ordered BEEFs for the graph
279
279
  */
280
- private computeOrderedBEEFsForGraph(graphID: string): number[][] {
280
+ private computeOrderedBEEFsForGraph (graphID: string): number[][] {
281
281
  const beefs: number[][] = []
282
282
  const hydrator = (node: GraphNode): void => {
283
283
  const currentBEEF = this.getBEEFForNode(node)
284
- if (beefs.indexOf(currentBEEF) === -1) {
284
+ if (!beefs.includes(currentBEEF)) {
285
285
  beefs.unshift(currentBEEF)
286
286
  }
287
287
 
@@ -305,7 +305,7 @@ export class OverlayGASPStorage implements GASPStorage {
305
305
  * @param node Graph node for which BEEF is needed.
306
306
  * @returns BEEF array, including all proofs on inputs.
307
307
  */
308
- private getBEEFForNode(node: GraphNode): number[] {
308
+ private getBEEFForNode (node: GraphNode): number[] {
309
309
  // Given a node, hydrate its merkle proof or all inputs, returning a reference to the hydrated node's Transaction object
310
310
  const hydrator = (node: GraphNode): Transaction => {
311
311
  const tx = Transaction.fromHex(node.rawTx)