@bsv/sdk 1.1.33 → 1.2.1

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 (214) hide show
  1. package/dist/cjs/mod.js +4 -0
  2. package/dist/cjs/mod.js.map +1 -1
  3. package/dist/cjs/package.json +4 -3
  4. package/dist/cjs/src/auth/Certificate.js +163 -0
  5. package/dist/cjs/src/auth/Certificate.js.map +1 -0
  6. package/dist/cjs/src/auth/index.js +9 -0
  7. package/dist/cjs/src/auth/index.js.map +1 -0
  8. package/dist/cjs/src/compat/BSM.js +17 -7
  9. package/dist/cjs/src/compat/BSM.js.map +1 -1
  10. package/dist/cjs/src/compat/ECIES.js +17 -7
  11. package/dist/cjs/src/compat/ECIES.js.map +1 -1
  12. package/dist/cjs/src/compat/HD.js +17 -7
  13. package/dist/cjs/src/compat/HD.js.map +1 -1
  14. package/dist/cjs/src/compat/Mnemonic.js +17 -7
  15. package/dist/cjs/src/compat/Mnemonic.js.map +1 -1
  16. package/dist/cjs/src/compat/index.js +17 -7
  17. package/dist/cjs/src/compat/index.js.map +1 -1
  18. package/dist/cjs/src/messages/index.js +17 -7
  19. package/dist/cjs/src/messages/index.js.map +1 -1
  20. package/dist/cjs/src/overlay-tools/LookupResolver.js +170 -0
  21. package/dist/cjs/src/overlay-tools/LookupResolver.js.map +1 -0
  22. package/dist/cjs/src/overlay-tools/OverlayAdminTokenTemplate.js +69 -0
  23. package/dist/cjs/src/overlay-tools/OverlayAdminTokenTemplate.js.map +1 -0
  24. package/dist/cjs/src/overlay-tools/SHIPBroadcaster.js +336 -0
  25. package/dist/cjs/src/overlay-tools/SHIPBroadcaster.js.map +1 -0
  26. package/dist/cjs/src/overlay-tools/index.js +29 -0
  27. package/dist/cjs/src/overlay-tools/index.js.map +1 -0
  28. package/dist/cjs/src/primitives/PrivateKey.js +17 -7
  29. package/dist/cjs/src/primitives/PrivateKey.js.map +1 -1
  30. package/dist/cjs/src/primitives/TransactionSignature.js +17 -7
  31. package/dist/cjs/src/primitives/TransactionSignature.js.map +1 -1
  32. package/dist/cjs/src/primitives/index.js +17 -7
  33. package/dist/cjs/src/primitives/index.js.map +1 -1
  34. package/dist/cjs/src/script/Spend.js +17 -7
  35. package/dist/cjs/src/script/Spend.js.map +1 -1
  36. package/dist/cjs/src/script/templates/PushDrop.js +218 -0
  37. package/dist/cjs/src/script/templates/PushDrop.js.map +1 -0
  38. package/dist/cjs/src/script/templates/index.js +3 -1
  39. package/dist/cjs/src/script/templates/index.js.map +1 -1
  40. package/dist/cjs/src/transaction/http/DefaultHttpClient.js +1 -1
  41. package/dist/cjs/src/transaction/http/DefaultHttpClient.js.map +1 -1
  42. package/dist/cjs/src/wallet/CachedKeyDeriver.js +177 -0
  43. package/dist/cjs/src/wallet/CachedKeyDeriver.js.map +1 -0
  44. package/dist/cjs/src/wallet/KeyDeriver.js +174 -0
  45. package/dist/cjs/src/wallet/KeyDeriver.js.map +1 -0
  46. package/dist/cjs/src/wallet/ProtoWallet.js +245 -0
  47. package/dist/cjs/src/wallet/ProtoWallet.js.map +1 -0
  48. package/dist/cjs/src/wallet/Wallet.interfaces.js +3 -0
  49. package/dist/cjs/src/wallet/Wallet.interfaces.js.map +1 -0
  50. package/dist/cjs/src/wallet/WalletClient.js +181 -0
  51. package/dist/cjs/src/wallet/WalletClient.js.map +1 -0
  52. package/dist/cjs/src/wallet/WalletError.js +28 -0
  53. package/dist/cjs/src/wallet/WalletError.js.map +1 -0
  54. package/dist/cjs/src/wallet/index.js +34 -0
  55. package/dist/cjs/src/wallet/index.js.map +1 -0
  56. package/dist/cjs/src/wallet/substrates/HTTPWalletWire.js +45 -0
  57. package/dist/cjs/src/wallet/substrates/HTTPWalletWire.js.map +1 -0
  58. package/dist/cjs/src/wallet/substrates/WalletWire.js +3 -0
  59. package/dist/cjs/src/wallet/substrates/WalletWire.js.map +1 -0
  60. package/dist/cjs/src/wallet/substrates/WalletWireCalls.js +36 -0
  61. package/dist/cjs/src/wallet/substrates/WalletWireCalls.js.map +1 -0
  62. package/dist/cjs/src/wallet/substrates/WalletWireProcessor.js +1821 -0
  63. package/dist/cjs/src/wallet/substrates/WalletWireProcessor.js.map +1 -0
  64. package/dist/cjs/src/wallet/substrates/WalletWireTransceiver.js +1305 -0
  65. package/dist/cjs/src/wallet/substrates/WalletWireTransceiver.js.map +1 -0
  66. package/dist/cjs/src/wallet/substrates/XDM.js +130 -0
  67. package/dist/cjs/src/wallet/substrates/XDM.js.map +1 -0
  68. package/dist/cjs/src/wallet/substrates/index.js +33 -0
  69. package/dist/cjs/src/wallet/substrates/index.js.map +1 -0
  70. package/dist/cjs/src/wallet/substrates/window.CWI.js +102 -0
  71. package/dist/cjs/src/wallet/substrates/window.CWI.js.map +1 -0
  72. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  73. package/dist/esm/mod.js +4 -0
  74. package/dist/esm/mod.js.map +1 -1
  75. package/dist/esm/src/auth/Certificate.js +185 -0
  76. package/dist/esm/src/auth/Certificate.js.map +1 -0
  77. package/dist/esm/src/auth/index.js +2 -0
  78. package/dist/esm/src/auth/index.js.map +1 -0
  79. package/dist/esm/src/overlay-tools/LookupResolver.js +167 -0
  80. package/dist/esm/src/overlay-tools/LookupResolver.js.map +1 -0
  81. package/dist/esm/src/overlay-tools/OverlayAdminTokenTemplate.js +64 -0
  82. package/dist/esm/src/overlay-tools/OverlayAdminTokenTemplate.js.map +1 -0
  83. package/dist/esm/src/overlay-tools/SHIPBroadcaster.js +335 -0
  84. package/dist/esm/src/overlay-tools/SHIPBroadcaster.js.map +1 -0
  85. package/dist/esm/src/overlay-tools/index.js +6 -0
  86. package/dist/esm/src/overlay-tools/index.js.map +1 -0
  87. package/dist/esm/src/script/templates/PushDrop.js +215 -0
  88. package/dist/esm/src/script/templates/PushDrop.js.map +1 -0
  89. package/dist/esm/src/script/templates/index.js +1 -0
  90. package/dist/esm/src/script/templates/index.js.map +1 -1
  91. package/dist/esm/src/transaction/http/DefaultHttpClient.js +1 -1
  92. package/dist/esm/src/transaction/http/DefaultHttpClient.js.map +1 -1
  93. package/dist/esm/src/wallet/CachedKeyDeriver.js +174 -0
  94. package/dist/esm/src/wallet/CachedKeyDeriver.js.map +1 -0
  95. package/dist/esm/src/wallet/KeyDeriver.js +172 -0
  96. package/dist/esm/src/wallet/KeyDeriver.js.map +1 -0
  97. package/dist/esm/src/wallet/ProtoWallet.js +207 -0
  98. package/dist/esm/src/wallet/ProtoWallet.js.map +1 -0
  99. package/dist/esm/src/wallet/Wallet.interfaces.js +2 -0
  100. package/dist/esm/src/wallet/Wallet.interfaces.js.map +1 -0
  101. package/dist/esm/src/wallet/WalletClient.js +177 -0
  102. package/dist/esm/src/wallet/WalletClient.js.map +1 -0
  103. package/dist/esm/src/wallet/WalletError.js +25 -0
  104. package/dist/esm/src/wallet/WalletError.js.map +1 -0
  105. package/dist/esm/src/wallet/index.js +9 -0
  106. package/dist/esm/src/wallet/index.js.map +1 -0
  107. package/dist/esm/src/wallet/substrates/HTTPWalletWire.js +42 -0
  108. package/dist/esm/src/wallet/substrates/HTTPWalletWire.js.map +1 -0
  109. package/dist/esm/src/wallet/substrates/WalletWire.js +2 -0
  110. package/dist/esm/src/wallet/substrates/WalletWire.js.map +1 -0
  111. package/dist/esm/src/wallet/substrates/WalletWireCalls.js +34 -0
  112. package/dist/esm/src/wallet/substrates/WalletWireCalls.js.map +1 -0
  113. package/dist/esm/src/wallet/substrates/WalletWireProcessor.js +1816 -0
  114. package/dist/esm/src/wallet/substrates/WalletWireProcessor.js.map +1 -0
  115. package/dist/esm/src/wallet/substrates/WalletWireTransceiver.js +1300 -0
  116. package/dist/esm/src/wallet/substrates/WalletWireTransceiver.js.map +1 -0
  117. package/dist/esm/src/wallet/substrates/XDM.js +128 -0
  118. package/dist/esm/src/wallet/substrates/XDM.js.map +1 -0
  119. package/dist/esm/src/wallet/substrates/index.js +8 -0
  120. package/dist/esm/src/wallet/substrates/index.js.map +1 -0
  121. package/dist/esm/src/wallet/substrates/window.CWI.js +100 -0
  122. package/dist/esm/src/wallet/substrates/window.CWI.js.map +1 -0
  123. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  124. package/dist/types/mod.d.ts +4 -0
  125. package/dist/types/mod.d.ts.map +1 -1
  126. package/dist/types/src/auth/Certificate.d.ts +76 -0
  127. package/dist/types/src/auth/Certificate.d.ts.map +1 -0
  128. package/dist/types/src/auth/index.d.ts +2 -0
  129. package/dist/types/src/auth/index.d.ts.map +1 -0
  130. package/dist/types/src/overlay-tools/LookupResolver.d.ts +71 -0
  131. package/dist/types/src/overlay-tools/LookupResolver.d.ts.map +1 -0
  132. package/dist/types/src/overlay-tools/OverlayAdminTokenTemplate.d.ts +44 -0
  133. package/dist/types/src/overlay-tools/OverlayAdminTokenTemplate.d.ts.map +1 -0
  134. package/dist/types/src/overlay-tools/SHIPBroadcaster.d.ts +90 -0
  135. package/dist/types/src/overlay-tools/SHIPBroadcaster.d.ts.map +1 -0
  136. package/dist/types/src/overlay-tools/index.d.ts +6 -0
  137. package/dist/types/src/overlay-tools/index.d.ts.map +1 -0
  138. package/dist/types/src/script/templates/PushDrop.d.ts +53 -0
  139. package/dist/types/src/script/templates/PushDrop.d.ts.map +1 -0
  140. package/dist/types/src/script/templates/index.d.ts +1 -0
  141. package/dist/types/src/script/templates/index.d.ts.map +1 -1
  142. package/dist/types/src/wallet/CachedKeyDeriver.d.ts +92 -0
  143. package/dist/types/src/wallet/CachedKeyDeriver.d.ts.map +1 -0
  144. package/dist/types/src/wallet/KeyDeriver.d.ts +72 -0
  145. package/dist/types/src/wallet/KeyDeriver.d.ts.map +1 -0
  146. package/dist/types/src/wallet/ProtoWallet.d.ts +415 -0
  147. package/dist/types/src/wallet/ProtoWallet.d.ts.map +1 -0
  148. package/dist/types/src/wallet/Wallet.interfaces.d.ts +996 -0
  149. package/dist/types/src/wallet/Wallet.interfaces.d.ts.map +1 -0
  150. package/dist/types/src/wallet/WalletClient.d.ts +182 -0
  151. package/dist/types/src/wallet/WalletClient.d.ts.map +1 -0
  152. package/dist/types/src/wallet/WalletError.d.ts +14 -0
  153. package/dist/types/src/wallet/WalletError.d.ts.map +1 -0
  154. package/dist/types/src/wallet/index.d.ts +9 -0
  155. package/dist/types/src/wallet/index.d.ts.map +1 -0
  156. package/dist/types/src/wallet/substrates/HTTPWalletWire.d.ts +9 -0
  157. package/dist/types/src/wallet/substrates/HTTPWalletWire.d.ts.map +1 -0
  158. package/dist/types/src/wallet/substrates/WalletWire.d.ts +7 -0
  159. package/dist/types/src/wallet/substrates/WalletWire.d.ts.map +1 -0
  160. package/dist/types/src/wallet/substrates/WalletWireCalls.d.ts +33 -0
  161. package/dist/types/src/wallet/substrates/WalletWireCalls.d.ts.map +1 -0
  162. package/dist/types/src/wallet/substrates/WalletWireProcessor.d.ts +18 -0
  163. package/dist/types/src/wallet/substrates/WalletWireProcessor.d.ts.map +1 -0
  164. package/dist/types/src/wallet/substrates/WalletWireTransceiver.d.ts +196 -0
  165. package/dist/types/src/wallet/substrates/WalletWireTransceiver.d.ts.map +1 -0
  166. package/dist/types/src/wallet/substrates/XDM.d.ts +412 -0
  167. package/dist/types/src/wallet/substrates/XDM.d.ts.map +1 -0
  168. package/dist/types/src/wallet/substrates/index.d.ts +8 -0
  169. package/dist/types/src/wallet/substrates/index.d.ts.map +1 -0
  170. package/dist/types/src/wallet/substrates/window.CWI.d.ts +410 -0
  171. package/dist/types/src/wallet/substrates/window.CWI.d.ts.map +1 -0
  172. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  173. package/dist/umd/bundle.js +1 -1
  174. package/docs/overlay-tools.md +537 -0
  175. package/docs/script.md +135 -0
  176. package/docs/totp.md +119 -0
  177. package/docs/wallet-substrates.md +10 -0
  178. package/docs/wallet.md +3718 -0
  179. package/mod.ts +5 -1
  180. package/package.json +44 -3
  181. package/src/auth/Certificate.ts +233 -0
  182. package/src/auth/__tests/Certificate.test.ts +282 -0
  183. package/src/auth/index.ts +1 -0
  184. package/src/overlay-tools/LookupResolver.ts +228 -0
  185. package/src/overlay-tools/OverlayAdminTokenTemplate.ts +79 -0
  186. package/src/overlay-tools/SHIPBroadcaster.ts +405 -0
  187. package/src/overlay-tools/__tests/LookupResolver.test.ts +1403 -0
  188. package/src/overlay-tools/__tests/OverlayAdminTokenTemplate.test.ts +69 -0
  189. package/src/overlay-tools/__tests/SHIPBroadcaster.test.ts +904 -0
  190. package/src/overlay-tools/index.ts +5 -0
  191. package/src/script/templates/PushDrop.ts +246 -0
  192. package/src/script/templates/__tests/PushDrop.test.ts +158 -0
  193. package/src/script/templates/index.ts +1 -0
  194. package/src/transaction/http/DefaultHttpClient.ts +1 -1
  195. package/src/wallet/CachedKeyDeriver.ts +193 -0
  196. package/src/wallet/KeyDeriver.ts +178 -0
  197. package/src/wallet/ProtoWallet.ts +732 -0
  198. package/src/wallet/Wallet.interfaces.ts +1170 -0
  199. package/src/wallet/WalletClient.ts +201 -0
  200. package/src/wallet/WalletError.ts +27 -0
  201. package/src/wallet/__tests/CachedKeyDeriver.test.ts +322 -0
  202. package/src/wallet/__tests/KeyDeriver.test.ts +118 -0
  203. package/src/wallet/__tests/ProtoWallet.test.ts +543 -0
  204. package/src/wallet/index.ts +8 -0
  205. package/src/wallet/substrates/HTTPWalletWire.ts +47 -0
  206. package/src/wallet/substrates/WalletWire.ts +6 -0
  207. package/src/wallet/substrates/WalletWireCalls.ts +34 -0
  208. package/src/wallet/substrates/WalletWireProcessor.ts +2046 -0
  209. package/src/wallet/substrates/WalletWireTransceiver.ts +1454 -0
  210. package/src/wallet/substrates/XDM.ts +157 -0
  211. package/src/wallet/substrates/__tests/WalletWire.integration.test.ts +2194 -0
  212. package/src/wallet/substrates/__tests/XDM.test.ts +659 -0
  213. package/src/wallet/substrates/index.ts +7 -0
  214. package/src/wallet/substrates/window.CWI.ts +133 -0
@@ -0,0 +1,405 @@
1
+ import { Transaction, BroadcastResponse, BroadcastFailure, Broadcaster } from '../transaction/index.js'
2
+ import LookupResolver from './LookupResolver.js'
3
+ import OverlayAdminTokenTemplate from './OverlayAdminTokenTemplate.js'
4
+
5
+ /**
6
+ * Tagged BEEF
7
+ *
8
+ * @description
9
+ * Tagged BEEF ([Background Evaluation Extended Format](https://brc.dev/62)) structure. Comprises a transaction, its SPV information, and the overlay topics where its inclusion is requested.
10
+ */
11
+ export interface TaggedBEEF {
12
+ beef: number[]
13
+ topics: string[]
14
+ }
15
+
16
+ /**
17
+ * Instructs the Overlay Services Engine about which outputs to admit and which previous outputs to retain. Returned by a Topic Manager.
18
+ */
19
+ export interface AdmittanceInstructions {
20
+ /**
21
+ * The indices of all admissable outputs into the managed topic from the provided transaction.
22
+ */
23
+ outputsToAdmit: number[]
24
+
25
+ /**
26
+ * The indices of all inputs from the provided transaction which spend previously-admitted outputs that should be retained for historical record-keeping.
27
+ */
28
+ coinsToRetain: number[]
29
+ }
30
+
31
+ /**
32
+ * Submitted Transaction Execution AcKnowledgment
33
+ *
34
+ * @description
35
+ * Comprises the topics where a transaction was submitted, and for each one, the output indices for the UTXOs newly admitted into the topics, and the coins retained.
36
+ * An object whose keys are topic names and whose values are topical admittance instructions denoting the state of the submitted transaction with respect to the associated topic.
37
+ */
38
+ export type STEAK = Record<string, AdmittanceInstructions>
39
+
40
+ /** Configuration options for the SHIP broadcaster. */
41
+ export interface SHIPBroadcasterConfig {
42
+ /** The facilitator used to make requests to Overlay Services hosts. */
43
+ facilitator?: OverlayBroadcastFacilitator
44
+ /** The resolver used to locate suitable hosts with SHIP */
45
+ resolver: LookupResolver
46
+ /** Determines which topics (all, any, or a specific list) must be present within all STEAKs received from every host for the broadcast to be considered a success. By default, all hosts must acknowledge all topics. */
47
+ requireAcknowledgmentFromAllHostsForTopics?: 'all' | 'any' | string[]
48
+ /** Determines which topics (all, any, or a specific list) must be present within STEAK received from at least one host for the broadcast to be considered a success. */
49
+ requireAcknowledgmentFromAnyHostForTopics?: 'all' | 'any' | string[]
50
+ /** Determines a mapping whose keys are specific hosts and whose values are the topics (all, any, or a specific list) that must be present within the STEAK received by the given hosts, in order for the broadcast to be considered a success. */
51
+ requireAcknowledgmentFromSpecificHostsForTopics?: Record<string, 'all' | 'any' | string[]>
52
+ }
53
+
54
+ /** Facilitates transaction broadcasts that return STEAK. */
55
+ export interface OverlayBroadcastFacilitator {
56
+ send: (url: string, taggedBEEF: TaggedBEEF) => Promise<STEAK>
57
+ }
58
+
59
+ export class HTTPSOverlayBroadcastFacilitator implements OverlayBroadcastFacilitator {
60
+ httpClient: typeof fetch
61
+
62
+ constructor(httpClient = fetch) {
63
+ this.httpClient = httpClient
64
+ }
65
+
66
+ async send(url: string, taggedBEEF: TaggedBEEF): Promise<STEAK> {
67
+ if (!url.startsWith('https:')) {
68
+ throw new Error('HTTPS facilitator can only use URLs that start with "https:"')
69
+ }
70
+ const response = await fetch(`${url}/submit`, {
71
+ method: 'POST',
72
+ headers: {
73
+ 'Content-Type': 'application/octet-stream',
74
+ 'X-Topics': JSON.stringify(taggedBEEF.topics)
75
+ },
76
+ body: new Uint8Array(taggedBEEF.beef)
77
+ })
78
+ if (response.ok) {
79
+ return await response.json()
80
+ } else {
81
+ throw new Error('Failed to facilitate broadcast')
82
+ }
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Represents a SHIP transaction broadcaster.
88
+ */
89
+ export default class SHIPCast implements Broadcaster {
90
+ private readonly topics: string[]
91
+ private readonly facilitator: OverlayBroadcastFacilitator
92
+ private readonly resolver: LookupResolver
93
+ private readonly requireAcknowledgmentFromAllHostsForTopics: 'all' | 'any' | string[]
94
+ private readonly requireAcknowledgmentFromAnyHostForTopics: 'all' | 'any' | string[]
95
+ private readonly requireAcknowledgmentFromSpecificHostsForTopics: Record<string, 'all' | 'any' | string[]>
96
+
97
+ /**
98
+ * Constructs an instance of the SHIP broadcaster.
99
+ *
100
+ * @param {string[]} topics - The list of SHIP topic names where transactions are to be sent.
101
+ * @param {SHIPBroadcasterConfig} config - Configuration options for the SHIP broadcaster.
102
+ */
103
+ constructor(topics: string[], config?: SHIPBroadcasterConfig) {
104
+ if (topics.length === 0) {
105
+ throw new Error('At least one topic is required for broadcast.')
106
+ }
107
+ if (topics.some(x => !x.startsWith('tm_'))) {
108
+ throw new Error('Every topic must start with "tm_".')
109
+ }
110
+ this.topics = topics
111
+ const {
112
+ facilitator, resolver,
113
+ requireAcknowledgmentFromAllHostsForTopics,
114
+ requireAcknowledgmentFromAnyHostForTopics,
115
+ requireAcknowledgmentFromSpecificHostsForTopics
116
+ } = config ?? {} as SHIPBroadcasterConfig
117
+ this.facilitator = facilitator ?? new HTTPSOverlayBroadcastFacilitator()
118
+ this.resolver = resolver ?? new LookupResolver()
119
+ this.requireAcknowledgmentFromAllHostsForTopics = requireAcknowledgmentFromAllHostsForTopics ?? 'all'
120
+ this.requireAcknowledgmentFromAnyHostForTopics = requireAcknowledgmentFromAnyHostForTopics ?? []
121
+ this.requireAcknowledgmentFromSpecificHostsForTopics = requireAcknowledgmentFromSpecificHostsForTopics ?? {}
122
+ }
123
+
124
+ /**
125
+ * Broadcasts a transaction to Overlay Services via SHIP.
126
+ *
127
+ * @param {Transaction} tx - The transaction to be sent.
128
+ * @returns {Promise<BroadcastResponse | BroadcastFailure>} A promise that resolves to either a success or failure response.
129
+ */
130
+ async broadcast(tx: Transaction): Promise<BroadcastResponse | BroadcastFailure> {
131
+ let beef: number[]
132
+ try {
133
+ beef = tx.toBEEF()
134
+ } catch (error) {
135
+ throw new Error('Transactions sent via SHIP to Overlay Services must be serializable to BEEF format.')
136
+ }
137
+ const interestedHosts = await this.findInterestedHosts()
138
+ if (Object.keys(interestedHosts).length === 0) {
139
+ return {
140
+ status: 'error',
141
+ code: 'ERR_NO_HOSTS_INTERESTED',
142
+ description: 'No hosts are interested in receiving this transaction.'
143
+ }
144
+ }
145
+ const hostPromises = Object.entries(interestedHosts).map(async ([host, topics]) => {
146
+ try {
147
+ const steak = await this.facilitator.send(host, { beef, topics: [...topics] })
148
+ if (!steak || Object.keys(steak).length === 0) {
149
+ throw new Error('Steak has no topics.')
150
+ }
151
+ return { host, success: true, steak }
152
+ } catch (error) {
153
+ // Log error if needed
154
+ return { host, success: false, error }
155
+ }
156
+ })
157
+
158
+ const results = await Promise.all(hostPromises)
159
+ const successfulHosts = results.filter(result => result.success)
160
+
161
+ if (successfulHosts.length === 0) {
162
+ return {
163
+ status: 'error',
164
+ code: 'ERR_ALL_HOSTS_REJECTED',
165
+ description: 'All SHIP hosts have rejected the transaction.'
166
+ }
167
+ }
168
+
169
+ // Collect host acknowledgments
170
+ const hostAcknowledgments: Record<string, Set<string>> = {}
171
+
172
+ for (const result of successfulHosts) {
173
+ const host = result.host
174
+ const steak = result.steak
175
+
176
+ const acknowledgedTopics = new Set<string>()
177
+
178
+ for (const [topic, instructions] of Object.entries(steak)) {
179
+ const outputsToAdmit = instructions.outputsToAdmit
180
+ const coinsToRetain = instructions.coinsToRetain
181
+ if ((outputsToAdmit && outputsToAdmit.length > 0) || (coinsToRetain && coinsToRetain.length > 0)) {
182
+ acknowledgedTopics.add(topic)
183
+ }
184
+ }
185
+
186
+ hostAcknowledgments[host] = acknowledgedTopics
187
+ }
188
+
189
+ // Now, perform the checks
190
+
191
+ // Check requireAcknowledgmentFromAllHostsForTopics
192
+ let requiredTopicsAllHosts: string[]
193
+ let requireAllHosts: 'all' | 'any'
194
+
195
+ if (this.requireAcknowledgmentFromAllHostsForTopics === 'all') {
196
+ requiredTopicsAllHosts = this.topics
197
+ requireAllHosts = 'all'
198
+ } else if (this.requireAcknowledgmentFromAllHostsForTopics === 'any') {
199
+ requiredTopicsAllHosts = this.topics
200
+ requireAllHosts = 'any'
201
+ } else if (Array.isArray(this.requireAcknowledgmentFromAllHostsForTopics)) {
202
+ requiredTopicsAllHosts = this.requireAcknowledgmentFromAllHostsForTopics
203
+ requireAllHosts = 'all'
204
+ } else {
205
+ // Default to 'all' and 'all'
206
+ requiredTopicsAllHosts = this.topics
207
+ requireAllHosts = 'all'
208
+ }
209
+
210
+ if (requiredTopicsAllHosts.length > 0) {
211
+ const allHostsAcknowledged = this.checkAcknowledgmentFromAllHosts(hostAcknowledgments, requiredTopicsAllHosts, requireAllHosts)
212
+ if (!allHostsAcknowledged) {
213
+ return {
214
+ status: 'error',
215
+ code: 'ERR_REQUIRE_ACK_FROM_ALL_HOSTS_FAILED',
216
+ description: 'Not all hosts acknowledged the required topics.'
217
+ }
218
+ }
219
+ }
220
+
221
+ // Check requireAcknowledgmentFromAnyHostForTopics
222
+ let requiredTopicsAnyHost: string[]
223
+ let requireAnyHost: 'all' | 'any'
224
+
225
+ if (this.requireAcknowledgmentFromAnyHostForTopics === 'all') {
226
+ requiredTopicsAnyHost = this.topics
227
+ requireAnyHost = 'all'
228
+ } else if (this.requireAcknowledgmentFromAnyHostForTopics === 'any') {
229
+ requiredTopicsAnyHost = this.topics
230
+ requireAnyHost = 'any'
231
+ } else if (Array.isArray(this.requireAcknowledgmentFromAnyHostForTopics)) {
232
+ requiredTopicsAnyHost = this.requireAcknowledgmentFromAnyHostForTopics
233
+ requireAnyHost = 'all'
234
+ } else {
235
+ // No requirement
236
+ requiredTopicsAnyHost = []
237
+ requireAnyHost = 'all'
238
+ }
239
+
240
+ if (requiredTopicsAnyHost.length > 0) {
241
+ const anyHostAcknowledged = this.checkAcknowledgmentFromAnyHost(hostAcknowledgments, requiredTopicsAnyHost, requireAnyHost)
242
+ if (!anyHostAcknowledged) {
243
+ return {
244
+ status: 'error',
245
+ code: 'ERR_REQUIRE_ACK_FROM_ANY_HOST_FAILED',
246
+ description: 'No host acknowledged the required topics.'
247
+ }
248
+ }
249
+ }
250
+
251
+ // Check requireAcknowledgmentFromSpecificHostsForTopics
252
+ if (Object.keys(this.requireAcknowledgmentFromSpecificHostsForTopics).length > 0) {
253
+ const specificHostsAcknowledged = this.checkAcknowledgmentFromSpecificHosts(hostAcknowledgments, this.requireAcknowledgmentFromSpecificHostsForTopics)
254
+ if (!specificHostsAcknowledged) {
255
+ return {
256
+ status: 'error',
257
+ code: 'ERR_REQUIRE_ACK_FROM_SPECIFIC_HOSTS_FAILED',
258
+ description: 'Specific hosts did not acknowledge the required topics.'
259
+ }
260
+ }
261
+ }
262
+
263
+ // If all checks pass, return success
264
+ return {
265
+ status: 'success',
266
+ txid: tx.id('hex'),
267
+ message: `Sent to ${successfulHosts.length} Overlay Services ${successfulHosts.length === 1 ? 'host' : 'hosts'}.`
268
+ }
269
+ }
270
+
271
+ private checkAcknowledgmentFromAllHosts(hostAcknowledgments: Record<string, Set<string>>, requiredTopics: string[], require: 'all' | 'any'): boolean {
272
+ for (const acknowledgedTopics of Object.values(hostAcknowledgments)) {
273
+ if (require === 'all') {
274
+ for (const topic of requiredTopics) {
275
+ if (!acknowledgedTopics.has(topic)) {
276
+ return false
277
+ }
278
+ }
279
+ } else if (require === 'any') {
280
+ let anyAcknowledged = false
281
+ for (const topic of requiredTopics) {
282
+ if (acknowledgedTopics.has(topic)) {
283
+ anyAcknowledged = true
284
+ break
285
+ }
286
+ }
287
+ if (!anyAcknowledged) {
288
+ return false
289
+ }
290
+ }
291
+ }
292
+ return true
293
+ }
294
+
295
+ private checkAcknowledgmentFromAnyHost(hostAcknowledgments: Record<string, Set<string>>, requiredTopics: string[], require: 'all' | 'any'): boolean {
296
+ if (require === 'all') {
297
+ // All required topics must be acknowledged by at least one host
298
+ for (const acknowledgedTopics of Object.values(hostAcknowledgments)) {
299
+ let acknowledgesAllRequiredTopics = true
300
+ for (const topic of requiredTopics) {
301
+ if (!acknowledgedTopics.has(topic)) {
302
+ acknowledgesAllRequiredTopics = false
303
+ break
304
+ }
305
+ }
306
+ if (acknowledgesAllRequiredTopics) {
307
+ return true
308
+ }
309
+ }
310
+ return false
311
+ } else {
312
+ // At least one required topic must be acknowledged by at least one host
313
+ for (const acknowledgedTopics of Object.values(hostAcknowledgments)) {
314
+ for (const topic of requiredTopics) {
315
+ if (acknowledgedTopics.has(topic)) {
316
+ return true
317
+ }
318
+ }
319
+ }
320
+ return false
321
+ }
322
+ }
323
+
324
+ private checkAcknowledgmentFromSpecificHosts(hostAcknowledgments: Record<string, Set<string>>, requirements: Record<string, 'all' | 'any' | string[]>): boolean {
325
+ for (const [host, requiredTopicsOrAllAny] of Object.entries(requirements)) {
326
+ const acknowledgedTopics = hostAcknowledgments[host]
327
+ if (!acknowledgedTopics) {
328
+ // Host did not respond successfully
329
+ return false
330
+ }
331
+ let requiredTopics: string[]
332
+ let require: 'all' | 'any'
333
+ if (requiredTopicsOrAllAny === 'all' || requiredTopicsOrAllAny === 'any') {
334
+ require = requiredTopicsOrAllAny
335
+ requiredTopics = this.topics
336
+ } else if (Array.isArray(requiredTopicsOrAllAny)) {
337
+ requiredTopics = requiredTopicsOrAllAny
338
+ require = 'all'
339
+ } else {
340
+ // Invalid configuration
341
+ continue
342
+ }
343
+ if (require === 'all') {
344
+ for (const topic of requiredTopics) {
345
+ if (!acknowledgedTopics.has(topic)) {
346
+ return false
347
+ }
348
+ }
349
+ } else if (require === 'any') {
350
+ let anyAcknowledged = false
351
+ for (const topic of requiredTopics) {
352
+ if (acknowledgedTopics.has(topic)) {
353
+ anyAcknowledged = true
354
+ break
355
+ }
356
+ }
357
+ if (!anyAcknowledged) {
358
+ return false
359
+ }
360
+ }
361
+ }
362
+ return true
363
+ }
364
+
365
+ /**
366
+ * Finds which hosts are interested in transactions tagged with the given set of topics.
367
+ *
368
+ * @returns A mapping of URLs for hosts interested in this transaction. Keys are URLs, values are which of our topics the specific host cares about.
369
+ */
370
+ private async findInterestedHosts(): Promise<Record<string, Set<string>>> {
371
+ // TODO: cache the list of interested hosts to avoid spamming SHIP trackers.
372
+ // TODO: Monetize the operation of the SHIP tracker system.
373
+ // TODO: Cache ship/slap lookup with expiry (every 5min)
374
+
375
+ // Find all SHIP advertisements for the topics we care about
376
+ const results: Record<string, Set<string>> = {}
377
+ const answer = await this.resolver.query({
378
+ service: 'ls_ship',
379
+ query: {
380
+ topics: this.topics
381
+ }
382
+ })
383
+ if (answer.type !== 'output-list') {
384
+ throw new Error('SHIP answer is not an output list.')
385
+ }
386
+ for (const output of answer.outputs) {
387
+ try {
388
+ const tx = Transaction.fromBEEF(output.beef)
389
+ const script = tx.outputs[output.outputIndex].lockingScript
390
+ const parsed = OverlayAdminTokenTemplate.decode(script)
391
+ if (!this.topics.includes(parsed.topicOrService) || parsed.protocol !== 'SHIP') {
392
+ // This should make us think a LOT less highly of this SHIP tracker if it ever happens...
393
+ continue
394
+ }
395
+ if (!results[parsed.domain]) {
396
+ results[parsed.domain] = new Set()
397
+ }
398
+ results[parsed.domain].add(parsed.topicOrService)
399
+ } catch (e) {
400
+ continue
401
+ }
402
+ }
403
+ return results
404
+ }
405
+ }