@bsv/sdk 1.1.33 → 1.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.
- package/dist/cjs/mod.js +4 -0
- package/dist/cjs/mod.js.map +1 -1
- package/dist/cjs/package.json +4 -3
- package/dist/cjs/src/auth/Certificate.js +163 -0
- package/dist/cjs/src/auth/Certificate.js.map +1 -0
- package/dist/cjs/src/auth/index.js +9 -0
- package/dist/cjs/src/auth/index.js.map +1 -0
- package/dist/cjs/src/compat/BSM.js +17 -7
- package/dist/cjs/src/compat/BSM.js.map +1 -1
- package/dist/cjs/src/compat/ECIES.js +17 -7
- package/dist/cjs/src/compat/ECIES.js.map +1 -1
- package/dist/cjs/src/compat/HD.js +17 -7
- package/dist/cjs/src/compat/HD.js.map +1 -1
- package/dist/cjs/src/compat/Mnemonic.js +17 -7
- package/dist/cjs/src/compat/Mnemonic.js.map +1 -1
- package/dist/cjs/src/compat/index.js +17 -7
- package/dist/cjs/src/compat/index.js.map +1 -1
- package/dist/cjs/src/messages/index.js +17 -7
- package/dist/cjs/src/messages/index.js.map +1 -1
- package/dist/cjs/src/overlay-tools/LookupResolver.js +170 -0
- package/dist/cjs/src/overlay-tools/LookupResolver.js.map +1 -0
- package/dist/cjs/src/overlay-tools/OverlayAdminTokenTemplate.js +69 -0
- package/dist/cjs/src/overlay-tools/OverlayAdminTokenTemplate.js.map +1 -0
- package/dist/cjs/src/overlay-tools/SHIPBroadcaster.js +336 -0
- package/dist/cjs/src/overlay-tools/SHIPBroadcaster.js.map +1 -0
- package/dist/cjs/src/overlay-tools/index.js +29 -0
- package/dist/cjs/src/overlay-tools/index.js.map +1 -0
- package/dist/cjs/src/primitives/PrivateKey.js +17 -7
- package/dist/cjs/src/primitives/PrivateKey.js.map +1 -1
- package/dist/cjs/src/primitives/TransactionSignature.js +17 -7
- package/dist/cjs/src/primitives/TransactionSignature.js.map +1 -1
- package/dist/cjs/src/primitives/index.js +17 -7
- package/dist/cjs/src/primitives/index.js.map +1 -1
- package/dist/cjs/src/script/Spend.js +17 -7
- package/dist/cjs/src/script/Spend.js.map +1 -1
- package/dist/cjs/src/script/templates/PushDrop.js +218 -0
- package/dist/cjs/src/script/templates/PushDrop.js.map +1 -0
- package/dist/cjs/src/script/templates/index.js +3 -1
- package/dist/cjs/src/script/templates/index.js.map +1 -1
- package/dist/cjs/src/transaction/http/DefaultHttpClient.js +1 -1
- package/dist/cjs/src/transaction/http/DefaultHttpClient.js.map +1 -1
- package/dist/cjs/src/wallet/CachedKeyDeriver.js +177 -0
- package/dist/cjs/src/wallet/CachedKeyDeriver.js.map +1 -0
- package/dist/cjs/src/wallet/KeyDeriver.js +174 -0
- package/dist/cjs/src/wallet/KeyDeriver.js.map +1 -0
- package/dist/cjs/src/wallet/ProtoWallet.js +245 -0
- package/dist/cjs/src/wallet/ProtoWallet.js.map +1 -0
- package/dist/cjs/src/wallet/Wallet.interfaces.js +3 -0
- package/dist/cjs/src/wallet/Wallet.interfaces.js.map +1 -0
- package/dist/cjs/src/wallet/WalletClient.js +181 -0
- package/dist/cjs/src/wallet/WalletClient.js.map +1 -0
- package/dist/cjs/src/wallet/WalletError.js +28 -0
- package/dist/cjs/src/wallet/WalletError.js.map +1 -0
- package/dist/cjs/src/wallet/index.js +34 -0
- package/dist/cjs/src/wallet/index.js.map +1 -0
- package/dist/cjs/src/wallet/substrates/HTTPWalletWire.js +45 -0
- package/dist/cjs/src/wallet/substrates/HTTPWalletWire.js.map +1 -0
- package/dist/cjs/src/wallet/substrates/WalletWire.js +3 -0
- package/dist/cjs/src/wallet/substrates/WalletWire.js.map +1 -0
- package/dist/cjs/src/wallet/substrates/WalletWireCalls.js +36 -0
- package/dist/cjs/src/wallet/substrates/WalletWireCalls.js.map +1 -0
- package/dist/cjs/src/wallet/substrates/WalletWireProcessor.js +1821 -0
- package/dist/cjs/src/wallet/substrates/WalletWireProcessor.js.map +1 -0
- package/dist/cjs/src/wallet/substrates/WalletWireTransceiver.js +1305 -0
- package/dist/cjs/src/wallet/substrates/WalletWireTransceiver.js.map +1 -0
- package/dist/cjs/src/wallet/substrates/XDM.js +130 -0
- package/dist/cjs/src/wallet/substrates/XDM.js.map +1 -0
- package/dist/cjs/src/wallet/substrates/index.js +33 -0
- package/dist/cjs/src/wallet/substrates/index.js.map +1 -0
- package/dist/cjs/src/wallet/substrates/window.CWI.js +102 -0
- package/dist/cjs/src/wallet/substrates/window.CWI.js.map +1 -0
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/mod.js +4 -0
- package/dist/esm/mod.js.map +1 -1
- package/dist/esm/src/auth/Certificate.js +185 -0
- package/dist/esm/src/auth/Certificate.js.map +1 -0
- package/dist/esm/src/auth/index.js +2 -0
- package/dist/esm/src/auth/index.js.map +1 -0
- package/dist/esm/src/overlay-tools/LookupResolver.js +167 -0
- package/dist/esm/src/overlay-tools/LookupResolver.js.map +1 -0
- package/dist/esm/src/overlay-tools/OverlayAdminTokenTemplate.js +64 -0
- package/dist/esm/src/overlay-tools/OverlayAdminTokenTemplate.js.map +1 -0
- package/dist/esm/src/overlay-tools/SHIPBroadcaster.js +335 -0
- package/dist/esm/src/overlay-tools/SHIPBroadcaster.js.map +1 -0
- package/dist/esm/src/overlay-tools/index.js +6 -0
- package/dist/esm/src/overlay-tools/index.js.map +1 -0
- package/dist/esm/src/script/templates/PushDrop.js +215 -0
- package/dist/esm/src/script/templates/PushDrop.js.map +1 -0
- package/dist/esm/src/script/templates/index.js +1 -0
- package/dist/esm/src/script/templates/index.js.map +1 -1
- package/dist/esm/src/transaction/http/DefaultHttpClient.js +1 -1
- package/dist/esm/src/transaction/http/DefaultHttpClient.js.map +1 -1
- package/dist/esm/src/wallet/CachedKeyDeriver.js +174 -0
- package/dist/esm/src/wallet/CachedKeyDeriver.js.map +1 -0
- package/dist/esm/src/wallet/KeyDeriver.js +172 -0
- package/dist/esm/src/wallet/KeyDeriver.js.map +1 -0
- package/dist/esm/src/wallet/ProtoWallet.js +207 -0
- package/dist/esm/src/wallet/ProtoWallet.js.map +1 -0
- package/dist/esm/src/wallet/Wallet.interfaces.js +2 -0
- package/dist/esm/src/wallet/Wallet.interfaces.js.map +1 -0
- package/dist/esm/src/wallet/WalletClient.js +177 -0
- package/dist/esm/src/wallet/WalletClient.js.map +1 -0
- package/dist/esm/src/wallet/WalletError.js +25 -0
- package/dist/esm/src/wallet/WalletError.js.map +1 -0
- package/dist/esm/src/wallet/index.js +9 -0
- package/dist/esm/src/wallet/index.js.map +1 -0
- package/dist/esm/src/wallet/substrates/HTTPWalletWire.js +42 -0
- package/dist/esm/src/wallet/substrates/HTTPWalletWire.js.map +1 -0
- package/dist/esm/src/wallet/substrates/WalletWire.js +2 -0
- package/dist/esm/src/wallet/substrates/WalletWire.js.map +1 -0
- package/dist/esm/src/wallet/substrates/WalletWireCalls.js +34 -0
- package/dist/esm/src/wallet/substrates/WalletWireCalls.js.map +1 -0
- package/dist/esm/src/wallet/substrates/WalletWireProcessor.js +1816 -0
- package/dist/esm/src/wallet/substrates/WalletWireProcessor.js.map +1 -0
- package/dist/esm/src/wallet/substrates/WalletWireTransceiver.js +1300 -0
- package/dist/esm/src/wallet/substrates/WalletWireTransceiver.js.map +1 -0
- package/dist/esm/src/wallet/substrates/XDM.js +128 -0
- package/dist/esm/src/wallet/substrates/XDM.js.map +1 -0
- package/dist/esm/src/wallet/substrates/index.js +8 -0
- package/dist/esm/src/wallet/substrates/index.js.map +1 -0
- package/dist/esm/src/wallet/substrates/window.CWI.js +100 -0
- package/dist/esm/src/wallet/substrates/window.CWI.js.map +1 -0
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/mod.d.ts +4 -0
- package/dist/types/mod.d.ts.map +1 -1
- package/dist/types/src/auth/Certificate.d.ts +76 -0
- package/dist/types/src/auth/Certificate.d.ts.map +1 -0
- package/dist/types/src/auth/index.d.ts +2 -0
- package/dist/types/src/auth/index.d.ts.map +1 -0
- package/dist/types/src/overlay-tools/LookupResolver.d.ts +71 -0
- package/dist/types/src/overlay-tools/LookupResolver.d.ts.map +1 -0
- package/dist/types/src/overlay-tools/OverlayAdminTokenTemplate.d.ts +44 -0
- package/dist/types/src/overlay-tools/OverlayAdminTokenTemplate.d.ts.map +1 -0
- package/dist/types/src/overlay-tools/SHIPBroadcaster.d.ts +90 -0
- package/dist/types/src/overlay-tools/SHIPBroadcaster.d.ts.map +1 -0
- package/dist/types/src/overlay-tools/index.d.ts +6 -0
- package/dist/types/src/overlay-tools/index.d.ts.map +1 -0
- package/dist/types/src/script/templates/PushDrop.d.ts +53 -0
- package/dist/types/src/script/templates/PushDrop.d.ts.map +1 -0
- package/dist/types/src/script/templates/index.d.ts +1 -0
- package/dist/types/src/script/templates/index.d.ts.map +1 -1
- package/dist/types/src/wallet/CachedKeyDeriver.d.ts +92 -0
- package/dist/types/src/wallet/CachedKeyDeriver.d.ts.map +1 -0
- package/dist/types/src/wallet/KeyDeriver.d.ts +72 -0
- package/dist/types/src/wallet/KeyDeriver.d.ts.map +1 -0
- package/dist/types/src/wallet/ProtoWallet.d.ts +415 -0
- package/dist/types/src/wallet/ProtoWallet.d.ts.map +1 -0
- package/dist/types/src/wallet/Wallet.interfaces.d.ts +996 -0
- package/dist/types/src/wallet/Wallet.interfaces.d.ts.map +1 -0
- package/dist/types/src/wallet/WalletClient.d.ts +182 -0
- package/dist/types/src/wallet/WalletClient.d.ts.map +1 -0
- package/dist/types/src/wallet/WalletError.d.ts +14 -0
- package/dist/types/src/wallet/WalletError.d.ts.map +1 -0
- package/dist/types/src/wallet/index.d.ts +9 -0
- package/dist/types/src/wallet/index.d.ts.map +1 -0
- package/dist/types/src/wallet/substrates/HTTPWalletWire.d.ts +9 -0
- package/dist/types/src/wallet/substrates/HTTPWalletWire.d.ts.map +1 -0
- package/dist/types/src/wallet/substrates/WalletWire.d.ts +7 -0
- package/dist/types/src/wallet/substrates/WalletWire.d.ts.map +1 -0
- package/dist/types/src/wallet/substrates/WalletWireCalls.d.ts +33 -0
- package/dist/types/src/wallet/substrates/WalletWireCalls.d.ts.map +1 -0
- package/dist/types/src/wallet/substrates/WalletWireProcessor.d.ts +18 -0
- package/dist/types/src/wallet/substrates/WalletWireProcessor.d.ts.map +1 -0
- package/dist/types/src/wallet/substrates/WalletWireTransceiver.d.ts +196 -0
- package/dist/types/src/wallet/substrates/WalletWireTransceiver.d.ts.map +1 -0
- package/dist/types/src/wallet/substrates/XDM.d.ts +412 -0
- package/dist/types/src/wallet/substrates/XDM.d.ts.map +1 -0
- package/dist/types/src/wallet/substrates/index.d.ts +8 -0
- package/dist/types/src/wallet/substrates/index.d.ts.map +1 -0
- package/dist/types/src/wallet/substrates/window.CWI.d.ts +410 -0
- package/dist/types/src/wallet/substrates/window.CWI.d.ts.map +1 -0
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/dist/umd/bundle.js +1 -1
- package/docs/overlay-tools.md +551 -0
- package/docs/script.md +135 -0
- package/docs/totp.md +119 -0
- package/docs/wallet-substrates.md +10 -0
- package/docs/wallet.md +4182 -0
- package/mod.ts +5 -1
- package/package.json +44 -3
- package/src/auth/Certificate.ts +233 -0
- package/src/auth/__tests/Certificate.test.ts +282 -0
- package/src/auth/index.ts +1 -0
- package/src/overlay-tools/LookupResolver.ts +228 -0
- package/src/overlay-tools/OverlayAdminTokenTemplate.ts +79 -0
- package/src/overlay-tools/SHIPBroadcaster.ts +405 -0
- package/src/overlay-tools/__tests/LookupResolver.test.ts +1403 -0
- package/src/overlay-tools/__tests/OverlayAdminTokenTemplate.test.ts +69 -0
- package/src/overlay-tools/__tests/SHIPBroadcaster.test.ts +904 -0
- package/src/overlay-tools/index.ts +5 -0
- package/src/script/templates/PushDrop.ts +246 -0
- package/src/script/templates/__tests/PushDrop.test.ts +158 -0
- package/src/script/templates/index.ts +1 -0
- package/src/transaction/http/DefaultHttpClient.ts +1 -1
- package/src/wallet/CachedKeyDeriver.ts +193 -0
- package/src/wallet/KeyDeriver.ts +178 -0
- package/src/wallet/ProtoWallet.ts +732 -0
- package/src/wallet/Wallet.interfaces.ts +1170 -0
- package/src/wallet/WalletClient.ts +201 -0
- package/src/wallet/WalletError.ts +27 -0
- package/src/wallet/__tests/CachedKeyDeriver.test.ts +322 -0
- package/src/wallet/__tests/KeyDeriver.test.ts +118 -0
- package/src/wallet/__tests/ProtoWallet.test.ts +543 -0
- package/src/wallet/index.ts +8 -0
- package/src/wallet/substrates/HTTPWalletWire.ts +47 -0
- package/src/wallet/substrates/WalletWire.ts +6 -0
- package/src/wallet/substrates/WalletWireCalls.ts +34 -0
- package/src/wallet/substrates/WalletWireProcessor.ts +2046 -0
- package/src/wallet/substrates/WalletWireTransceiver.ts +1454 -0
- package/src/wallet/substrates/XDM.ts +157 -0
- package/src/wallet/substrates/__tests/WalletWire.integration.test.ts +2194 -0
- package/src/wallet/substrates/__tests/XDM.test.ts +659 -0
- package/src/wallet/substrates/index.ts +7 -0
- 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
|
+
}
|