@bsv/sdk 1.6.4 → 1.6.6
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/package.json +1 -1
- package/dist/cjs/src/kvstore/LocalKVStore.js +78 -52
- package/dist/cjs/src/kvstore/LocalKVStore.js.map +1 -1
- package/dist/cjs/src/storage/StorageDownloader.js +10 -11
- package/dist/cjs/src/storage/StorageDownloader.js.map +1 -1
- package/dist/cjs/src/transaction/chaintrackers/BlockHeadersService.js +91 -0
- package/dist/cjs/src/transaction/chaintrackers/BlockHeadersService.js.map +1 -0
- package/dist/cjs/src/transaction/chaintrackers/index.js +3 -1
- package/dist/cjs/src/transaction/chaintrackers/index.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/kvstore/LocalKVStore.js +78 -52
- package/dist/esm/src/kvstore/LocalKVStore.js.map +1 -1
- package/dist/esm/src/storage/StorageDownloader.js +10 -11
- package/dist/esm/src/storage/StorageDownloader.js.map +1 -1
- package/dist/esm/src/transaction/chaintrackers/BlockHeadersService.js +90 -0
- package/dist/esm/src/transaction/chaintrackers/BlockHeadersService.js.map +1 -0
- package/dist/esm/src/transaction/chaintrackers/index.js +1 -0
- package/dist/esm/src/transaction/chaintrackers/index.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/src/kvstore/LocalKVStore.d.ts +10 -2
- package/dist/types/src/kvstore/LocalKVStore.d.ts.map +1 -1
- package/dist/types/src/storage/StorageDownloader.d.ts +10 -11
- package/dist/types/src/storage/StorageDownloader.d.ts.map +1 -1
- package/dist/types/src/transaction/chaintrackers/BlockHeadersService.d.ts +46 -0
- package/dist/types/src/transaction/chaintrackers/BlockHeadersService.d.ts.map +1 -0
- package/dist/types/src/transaction/chaintrackers/index.d.ts +1 -0
- package/dist/types/src/transaction/chaintrackers/index.d.ts.map +1 -1
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/dist/umd/bundle.js +1 -1
- package/docs/kvstore.md +6 -3
- package/docs/overlay-tools.md +2 -3
- package/docs/primitives.md +20 -53
- package/docs/storage.md +35 -2
- package/docs/transaction.md +1 -1
- package/package.json +1 -1
- package/src/kvstore/LocalKVStore.ts +93 -55
- package/src/storage/StorageDownloader.ts +10 -11
- package/src/transaction/chaintrackers/BlockHeadersService.ts +138 -0
- package/src/transaction/chaintrackers/index.ts +1 -0
package/docs/kvstore.md
CHANGED
|
@@ -14,7 +14,8 @@ Allows setting, getting, and removing key-value pairs, with optional encryption.
|
|
|
14
14
|
|
|
15
15
|
```ts
|
|
16
16
|
export default class LocalKVStore {
|
|
17
|
-
|
|
17
|
+
acceptDelayedBroadcast: boolean = false;
|
|
18
|
+
constructor(wallet: WalletInterface = new WalletClient(), context = "kvstore default", encrypt = true, originator?: string, acceptDelayedBroadcast = false)
|
|
18
19
|
async get(key: string, defaultValue: string | undefined = undefined): Promise<string | undefined>
|
|
19
20
|
async set(key: string, value: string): Promise<OutpointString>
|
|
20
21
|
async remove(key: string): Promise<string[]>
|
|
@@ -28,7 +29,7 @@ See also: [OutpointString](./wallet.md#type-outpointstring), [WalletClient](./wa
|
|
|
28
29
|
Creates an instance of the localKVStore.
|
|
29
30
|
|
|
30
31
|
```ts
|
|
31
|
-
constructor(wallet: WalletInterface = new WalletClient(), context = "kvstore default", encrypt = true, originator?: string)
|
|
32
|
+
constructor(wallet: WalletInterface = new WalletClient(), context = "kvstore default", encrypt = true, originator?: string, acceptDelayedBroadcast = false)
|
|
32
33
|
```
|
|
33
34
|
See also: [WalletClient](./wallet.md#class-walletclient), [WalletInterface](./wallet.md#interface-walletinterface), [encrypt](./messages.md#variable-encrypt)
|
|
34
35
|
|
|
@@ -96,13 +97,15 @@ Argument Details
|
|
|
96
97
|
|
|
97
98
|
#### Method set
|
|
98
99
|
|
|
99
|
-
Sets or updates the value associated with a given key.
|
|
100
|
+
Sets or updates the value associated with a given key atomically.
|
|
100
101
|
If the key already exists (one or more outputs found), it spends the existing output(s)
|
|
101
102
|
and creates a new one with the updated value. If multiple outputs exist for the key,
|
|
102
103
|
they are collapsed into a single new output.
|
|
103
104
|
If the key does not exist, it creates a new output.
|
|
104
105
|
Handles encryption if enabled.
|
|
105
106
|
If signing the update/collapse transaction fails, it relinquishes the original outputs and starts over with a new chain.
|
|
107
|
+
Ensures atomicity by locking the key during the operation, preventing concurrent updates
|
|
108
|
+
to the same key from missing earlier changes.
|
|
106
109
|
|
|
107
110
|
```ts
|
|
108
111
|
async set(key: string, value: string): Promise<OutpointString>
|
package/docs/overlay-tools.md
CHANGED
|
@@ -272,6 +272,7 @@ Tagged BEEF
|
|
|
272
272
|
export interface TaggedBEEF {
|
|
273
273
|
beef: number[];
|
|
274
274
|
topics: string[];
|
|
275
|
+
offChainValues?: number[];
|
|
275
276
|
}
|
|
276
277
|
```
|
|
277
278
|
|
|
@@ -530,10 +531,8 @@ export type LookupAnswer = {
|
|
|
530
531
|
outputs: Array<{
|
|
531
532
|
beef: number[];
|
|
532
533
|
outputIndex: number;
|
|
534
|
+
context?: number[];
|
|
533
535
|
}>;
|
|
534
|
-
} | {
|
|
535
|
-
type: "freeform";
|
|
536
|
-
result: unknown;
|
|
537
536
|
}
|
|
538
537
|
```
|
|
539
538
|
|
package/docs/primitives.md
CHANGED
|
@@ -93,8 +93,7 @@ export default class BigNumber {
|
|
|
93
93
|
zeroBits(): number
|
|
94
94
|
byteLength(): number { if (this._magnitude === 0n)
|
|
95
95
|
return 0; return Math.ceil(this.bitLength() / 8); }
|
|
96
|
-
toTwos(width: number): BigNumber
|
|
97
|
-
v = (1n << Bw) + v; const m = (1n << Bw) - 1n; v &= m; const r = new BigNumber(0n); r._initializeState(v, 0); return r; }
|
|
96
|
+
toTwos(width: number): BigNumber
|
|
98
97
|
fromTwos(width: number): BigNumber
|
|
99
98
|
isNeg(): boolean
|
|
100
99
|
neg(): BigNumber
|
|
@@ -132,15 +131,7 @@ export default class BigNumber {
|
|
|
132
131
|
iushln(bits: number): this { this.assert(typeof bits === "number" && bits >= 0); if (bits === 0)
|
|
133
132
|
return this; this._magnitude <<= BigInt(bits); this._finishInitialization(); return this.strip(); }
|
|
134
133
|
ishln(bits: number): this
|
|
135
|
-
iushrn(bits: number, hint?: number, extended?: BigNumber): this
|
|
136
|
-
if (extended != null)
|
|
137
|
-
extended._initializeState(0n, 0);
|
|
138
|
-
return this;
|
|
139
|
-
} if (extended != null) {
|
|
140
|
-
const m = (1n << BigInt(bits)) - 1n;
|
|
141
|
-
const sOut = this._magnitude & m;
|
|
142
|
-
extended._initializeState(sOut, 0);
|
|
143
|
-
} this._magnitude >>= BigInt(bits); this._finishInitialization(); return this.strip(); }
|
|
134
|
+
iushrn(bits: number, hint?: number, extended?: BigNumber): this
|
|
144
135
|
ishrn(bits: number, hint?: number, extended?: BigNumber): this
|
|
145
136
|
shln(bits: number): BigNumber
|
|
146
137
|
ushln(bits: number): BigNumber
|
|
@@ -168,31 +159,8 @@ export default class BigNumber {
|
|
|
168
159
|
a: BigNumber;
|
|
169
160
|
b: BigNumber;
|
|
170
161
|
gcd: BigNumber;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
let t = vV;
|
|
174
|
-
vV = uV % vV;
|
|
175
|
-
uV = t;
|
|
176
|
-
t = pa;
|
|
177
|
-
pa = a - q * pa;
|
|
178
|
-
a = t;
|
|
179
|
-
t = pb;
|
|
180
|
-
pb = b - q * pb;
|
|
181
|
-
b = t;
|
|
182
|
-
} const ra = new BigNumber(0n); ra._setValueFromSigned(a); const rb = new BigNumber(0n); rb._setValueFromSigned(b); const rg = new BigNumber(0n); rg._initializeState(uV < 0n ? -uV : uV, 0); return { a: ra, b: rb, gcd: rg }; }
|
|
183
|
-
gcd(num: BigNumber): BigNumber { let u = this._magnitude; let v = num._magnitude; if (u === 0n) {
|
|
184
|
-
const r = new BigNumber(0n);
|
|
185
|
-
r._setValueFromSigned(v);
|
|
186
|
-
return r.iabs();
|
|
187
|
-
} if (v === 0n) {
|
|
188
|
-
const r = new BigNumber(0n);
|
|
189
|
-
r._setValueFromSigned(u);
|
|
190
|
-
return r.iabs();
|
|
191
|
-
} while (v !== 0n) {
|
|
192
|
-
const t = u % v;
|
|
193
|
-
u = v;
|
|
194
|
-
v = t;
|
|
195
|
-
} const res = new BigNumber(0n); res._initializeState(u, 0); return res; }
|
|
162
|
+
}
|
|
163
|
+
gcd(num: BigNumber): BigNumber
|
|
196
164
|
invm(num: BigNumber): BigNumber
|
|
197
165
|
isEven(): boolean
|
|
198
166
|
isOdd(): boolean
|
|
@@ -3247,8 +3215,8 @@ export class Reader {
|
|
|
3247
3215
|
public pos: number;
|
|
3248
3216
|
constructor(bin: number[] = [], pos: number = 0)
|
|
3249
3217
|
public eof(): boolean
|
|
3250
|
-
public read(len = this.
|
|
3251
|
-
public readReverse(len = this.
|
|
3218
|
+
public read(len = this.length): number[]
|
|
3219
|
+
public readReverse(len = this.length): number[]
|
|
3252
3220
|
public readUInt8(): number
|
|
3253
3221
|
public readInt8(): number
|
|
3254
3222
|
public readUInt16BE(): number
|
|
@@ -5047,9 +5015,9 @@ Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](
|
|
|
5047
5015
|
|
|
5048
5016
|
```ts
|
|
5049
5017
|
exclusiveOR = function (block0: number[], block1: number[]): number[] {
|
|
5050
|
-
|
|
5051
|
-
const result =
|
|
5052
|
-
for (i = 0; i <
|
|
5018
|
+
const len = block0.length;
|
|
5019
|
+
const result = new Array(len);
|
|
5020
|
+
for (let i = 0; i < len; i++) {
|
|
5053
5021
|
result[i] = block0[i] ^ block1[i];
|
|
5054
5022
|
}
|
|
5055
5023
|
return result;
|
|
@@ -5231,20 +5199,19 @@ Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](
|
|
|
5231
5199
|
|
|
5232
5200
|
```ts
|
|
5233
5201
|
multiply = function (block0: number[], block1: number[]): number[] {
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
let
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
if (checkBit(block0, i, j) !== 0) {
|
|
5241
|
-
z = exclusiveOR(z, v);
|
|
5202
|
+
const v = block1.slice();
|
|
5203
|
+
const z = createZeroBlock(16);
|
|
5204
|
+
for (let i = 0; i < 16; i++) {
|
|
5205
|
+
for (let j = 7; j >= 0; j--) {
|
|
5206
|
+
if ((block0[i] & (1 << j)) !== 0) {
|
|
5207
|
+
xorInto(z, v);
|
|
5242
5208
|
}
|
|
5243
|
-
if (
|
|
5244
|
-
|
|
5209
|
+
if ((v[15] & 1) !== 0) {
|
|
5210
|
+
rightShift(v);
|
|
5211
|
+
xorInto(v, R);
|
|
5245
5212
|
}
|
|
5246
5213
|
else {
|
|
5247
|
-
|
|
5214
|
+
rightShift(v);
|
|
5248
5215
|
}
|
|
5249
5216
|
}
|
|
5250
5217
|
}
|
|
@@ -5252,7 +5219,7 @@ multiply = function (block0: number[], block1: number[]): number[] {
|
|
|
5252
5219
|
}
|
|
5253
5220
|
```
|
|
5254
5221
|
|
|
5255
|
-
See also: [
|
|
5222
|
+
See also: [rightShift](./primitives.md#variable-rightshift)
|
|
5256
5223
|
|
|
5257
5224
|
Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](#functions), [Types](#types), [Enums](#enums), [Variables](#variables)
|
|
5258
5225
|
|
package/docs/storage.md
CHANGED
|
@@ -120,8 +120,6 @@ Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](
|
|
|
120
120
|
|
|
121
121
|
### Class: StorageDownloader
|
|
122
122
|
|
|
123
|
-
Locates HTTP URLs where content can be downloaded. It uses the passed or the default one.
|
|
124
|
-
|
|
125
123
|
```ts
|
|
126
124
|
export class StorageDownloader {
|
|
127
125
|
constructor(config?: DownloaderConfig)
|
|
@@ -132,6 +130,41 @@ export class StorageDownloader {
|
|
|
132
130
|
|
|
133
131
|
See also: [DownloadResult](./storage.md#interface-downloadresult), [DownloaderConfig](./storage.md#interface-downloaderconfig)
|
|
134
132
|
|
|
133
|
+
#### Method download
|
|
134
|
+
|
|
135
|
+
Downloads the content from the UHRP URL after validating the hash for integrity.
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
public async download(uhrpUrl: string): Promise<DownloadResult>
|
|
139
|
+
```
|
|
140
|
+
See also: [DownloadResult](./storage.md#interface-downloadresult)
|
|
141
|
+
|
|
142
|
+
Returns
|
|
143
|
+
|
|
144
|
+
A promise that resolves to the downloaded content.
|
|
145
|
+
|
|
146
|
+
Argument Details
|
|
147
|
+
|
|
148
|
+
+ **uhrpUrl**
|
|
149
|
+
+ The UHRP URL to download.
|
|
150
|
+
|
|
151
|
+
#### Method resolve
|
|
152
|
+
|
|
153
|
+
Resolves the UHRP URL to a list of HTTP URLs where content can be downloaded.
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
public async resolve(uhrpUrl: string): Promise<string[]>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Returns
|
|
160
|
+
|
|
161
|
+
A promise that resolves to an array of HTTP URLs.
|
|
162
|
+
|
|
163
|
+
Argument Details
|
|
164
|
+
|
|
165
|
+
+ **uhrpUrl**
|
|
166
|
+
+ The UHRP URL to resolve.
|
|
167
|
+
|
|
135
168
|
Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](#functions), [Types](#types), [Enums](#enums), [Variables](#variables)
|
|
136
169
|
|
|
137
170
|
---
|
package/docs/transaction.md
CHANGED
|
@@ -1506,7 +1506,7 @@ export default class Transaction {
|
|
|
1506
1506
|
static fromHex(hex: string): Transaction
|
|
1507
1507
|
static fromHexEF(hex: string): Transaction
|
|
1508
1508
|
static fromHexBEEF(hex: string, txid?: string): Transaction
|
|
1509
|
-
constructor(version: number = 1, inputs: TransactionInput[] = [], outputs: TransactionOutput[] = [], lockTime: number = 0, metadata: Record<string, any> =
|
|
1509
|
+
constructor(version: number = 1, inputs: TransactionInput[] = [], outputs: TransactionOutput[] = [], lockTime: number = 0, metadata: Record<string, any> = new Map(), merklePath?: MerklePath)
|
|
1510
1510
|
addInput(input: TransactionInput): void
|
|
1511
1511
|
addOutput(output: TransactionOutput): void
|
|
1512
1512
|
addP2PKHOutput(address: number[] | string, satoshis?: number): void
|
package/package.json
CHANGED
|
@@ -38,6 +38,14 @@ export default class LocalKVStore {
|
|
|
38
38
|
*/
|
|
39
39
|
private readonly originator?: string
|
|
40
40
|
|
|
41
|
+
acceptDelayedBroadcast: boolean = false
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* A map to store locks for each key to ensure atomic updates.
|
|
45
|
+
* @private
|
|
46
|
+
*/
|
|
47
|
+
private readonly keyLocks: Map<string, Promise<void>> = new Map()
|
|
48
|
+
|
|
41
49
|
/**
|
|
42
50
|
* Creates an instance of the localKVStore.
|
|
43
51
|
*
|
|
@@ -51,7 +59,8 @@ export default class LocalKVStore {
|
|
|
51
59
|
wallet: WalletInterface = new WalletClient(),
|
|
52
60
|
context = 'kvstore default',
|
|
53
61
|
encrypt = true,
|
|
54
|
-
originator?: string
|
|
62
|
+
originator?: string,
|
|
63
|
+
acceptDelayedBroadcast = false
|
|
55
64
|
) {
|
|
56
65
|
if (typeof context !== 'string' || context.length < 1) {
|
|
57
66
|
throw new Error('A context in which to operate is required.')
|
|
@@ -60,6 +69,7 @@ export default class LocalKVStore {
|
|
|
60
69
|
this.context = context
|
|
61
70
|
this.encrypt = encrypt
|
|
62
71
|
this.originator = originator
|
|
72
|
+
this.acceptDelayedBroadcast = acceptDelayedBroadcast
|
|
63
73
|
}
|
|
64
74
|
|
|
65
75
|
private getProtocol (key: string): { protocolID: WalletProtocol, keyID: string } {
|
|
@@ -160,78 +170,106 @@ export default class LocalKVStore {
|
|
|
160
170
|
}
|
|
161
171
|
|
|
162
172
|
/**
|
|
163
|
-
* Sets or updates the value associated with a given key.
|
|
173
|
+
* Sets or updates the value associated with a given key atomically.
|
|
164
174
|
* If the key already exists (one or more outputs found), it spends the existing output(s)
|
|
165
175
|
* and creates a new one with the updated value. If multiple outputs exist for the key,
|
|
166
176
|
* they are collapsed into a single new output.
|
|
167
177
|
* If the key does not exist, it creates a new output.
|
|
168
178
|
* Handles encryption if enabled.
|
|
169
179
|
* If signing the update/collapse transaction fails, it relinquishes the original outputs and starts over with a new chain.
|
|
180
|
+
* Ensures atomicity by locking the key during the operation, preventing concurrent updates
|
|
181
|
+
* to the same key from missing earlier changes.
|
|
170
182
|
*
|
|
171
183
|
* @param {string} key - The key to set or update.
|
|
172
184
|
* @param {string} value - The value to associate with the key.
|
|
173
185
|
* @returns {Promise<OutpointString>} A promise that resolves to the outpoint string (txid.vout) of the new or updated token output.
|
|
174
186
|
*/
|
|
175
187
|
async set (key: string, value: string): Promise<OutpointString> {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
return current.outpoint
|
|
181
|
-
}
|
|
182
|
-
const protocol = this.getProtocol(key)
|
|
183
|
-
let valueAsArray = Utils.toArray(value, 'utf8')
|
|
184
|
-
if (this.encrypt) {
|
|
185
|
-
const { ciphertext } = await this.wallet.encrypt({
|
|
186
|
-
...protocol,
|
|
187
|
-
plaintext: valueAsArray
|
|
188
|
-
})
|
|
189
|
-
valueAsArray = ciphertext
|
|
188
|
+
// Check if a lock exists for this key and wait for it to resolve
|
|
189
|
+
const existingLock = this.keyLocks.get(key)
|
|
190
|
+
if (existingLock != null) {
|
|
191
|
+
await existingLock
|
|
190
192
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
const { outputs, BEEF: inputBEEF } = current.lor
|
|
199
|
-
let outpoint: OutpointString
|
|
193
|
+
|
|
194
|
+
let resolveNewLock: () => void = () => {}
|
|
195
|
+
const newLock = new Promise<void>((resolve) => {
|
|
196
|
+
resolveNewLock = resolve
|
|
197
|
+
})
|
|
198
|
+
this.keyLocks.set(key, newLock)
|
|
199
|
+
|
|
200
200
|
try {
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
inputs,
|
|
206
|
-
outputs: [{
|
|
207
|
-
basket: this.context,
|
|
208
|
-
tags: [key],
|
|
209
|
-
lockingScript: lockingScript.toHex(),
|
|
210
|
-
satoshis: 1,
|
|
211
|
-
outputDescription: 'Key-value token'
|
|
212
|
-
}],
|
|
213
|
-
options: {
|
|
214
|
-
acceptDelayedBroadcast: false,
|
|
215
|
-
randomizeOutputs: false
|
|
201
|
+
const current = await this.lookupValue(key, undefined, 10)
|
|
202
|
+
if (current.value === value) {
|
|
203
|
+
if (current.outpoint === undefined) {
|
|
204
|
+
throw new Error('outpoint must be valid when value is valid and unchanged')
|
|
216
205
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
throw new Error('Wallet did not return a signable transaction when expected.')
|
|
206
|
+
// Don't create a new transaction if the value doesn't need to change
|
|
207
|
+
return current.outpoint
|
|
220
208
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const {
|
|
226
|
-
|
|
227
|
-
|
|
209
|
+
|
|
210
|
+
const protocol = this.getProtocol(key)
|
|
211
|
+
let valueAsArray = Utils.toArray(value, 'utf8')
|
|
212
|
+
if (this.encrypt) {
|
|
213
|
+
const { ciphertext } = await this.wallet.encrypt({
|
|
214
|
+
...protocol,
|
|
215
|
+
plaintext: valueAsArray
|
|
228
216
|
})
|
|
229
|
-
|
|
217
|
+
valueAsArray = ciphertext
|
|
230
218
|
}
|
|
231
|
-
|
|
232
|
-
|
|
219
|
+
|
|
220
|
+
const pushdrop = new PushDrop(this.wallet, this.originator)
|
|
221
|
+
const lockingScript = await pushdrop.lock(
|
|
222
|
+
[valueAsArray],
|
|
223
|
+
protocol.protocolID,
|
|
224
|
+
protocol.keyID,
|
|
225
|
+
'self'
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
const { outputs, BEEF: inputBEEF } = current.lor
|
|
229
|
+
let outpoint: OutpointString
|
|
230
|
+
try {
|
|
231
|
+
const inputs = this.getInputs(outputs)
|
|
232
|
+
const { txid, signableTransaction } = await this.wallet.createAction({
|
|
233
|
+
description: `Update ${key} in ${this.context}`,
|
|
234
|
+
inputBEEF,
|
|
235
|
+
inputs,
|
|
236
|
+
outputs: [{
|
|
237
|
+
basket: this.context,
|
|
238
|
+
tags: [key],
|
|
239
|
+
lockingScript: lockingScript.toHex(),
|
|
240
|
+
satoshis: 1,
|
|
241
|
+
outputDescription: 'Key-value token'
|
|
242
|
+
}],
|
|
243
|
+
options: {
|
|
244
|
+
acceptDelayedBroadcast: this.acceptDelayedBroadcast,
|
|
245
|
+
randomizeOutputs: false
|
|
246
|
+
}
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
if (outputs.length > 0 && typeof signableTransaction !== 'object') {
|
|
250
|
+
throw new Error('Wallet did not return a signable transaction when expected.')
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (signableTransaction == null) {
|
|
254
|
+
outpoint = `${txid as string}.0`
|
|
255
|
+
} else {
|
|
256
|
+
const spends = await this.getSpends(key, outputs, pushdrop, signableTransaction.tx)
|
|
257
|
+
const { txid } = await this.wallet.signAction({
|
|
258
|
+
reference: signableTransaction.reference,
|
|
259
|
+
spends
|
|
260
|
+
})
|
|
261
|
+
outpoint = `${txid as string}.0`
|
|
262
|
+
}
|
|
263
|
+
} catch (_) {
|
|
264
|
+
throw new Error(`There are ${outputs.length} outputs with tag ${key} that cannot be unlocked.`)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return outpoint
|
|
268
|
+
} finally {
|
|
269
|
+
// Release the lock by resolving the promise and removing it from the map
|
|
270
|
+
this.keyLocks.delete(key)
|
|
271
|
+
resolveNewLock()
|
|
233
272
|
}
|
|
234
|
-
return outpoint
|
|
235
273
|
}
|
|
236
274
|
|
|
237
275
|
/**
|
|
@@ -257,7 +295,7 @@ export default class LocalKVStore {
|
|
|
257
295
|
inputBEEF,
|
|
258
296
|
inputs,
|
|
259
297
|
options: {
|
|
260
|
-
acceptDelayedBroadcast:
|
|
298
|
+
acceptDelayedBroadcast: this.acceptDelayedBroadcast
|
|
261
299
|
}
|
|
262
300
|
})
|
|
263
301
|
if (typeof signableTransaction !== 'object') {
|
|
@@ -13,17 +13,6 @@ export interface DownloadResult {
|
|
|
13
13
|
mimeType: string | null
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
/**
|
|
17
|
-
* Locates HTTP URLs where content can be downloaded. It uses the passed or the default one.
|
|
18
|
-
*
|
|
19
|
-
* @param {Object} obj All parameters are passed in an object.
|
|
20
|
-
* @param {String} obj.uhrpUrl The UHRP url to resolve.
|
|
21
|
-
* @param {string} obj.confederacyHost HTTPS URL for for the with default setting.
|
|
22
|
-
*
|
|
23
|
-
* @return {Array<String>} An array of HTTP URLs where content can be downloaded.
|
|
24
|
-
* @throws {Error} If UHRP url parameter invalid or is not an array
|
|
25
|
-
* or there is an error retrieving url(s) stored in the UHRP token.
|
|
26
|
-
*/
|
|
27
16
|
export class StorageDownloader {
|
|
28
17
|
private readonly networkPreset?: 'mainnet' | 'testnet' | 'local' = 'mainnet'
|
|
29
18
|
|
|
@@ -31,6 +20,11 @@ export class StorageDownloader {
|
|
|
31
20
|
this.networkPreset = config?.networkPreset
|
|
32
21
|
}
|
|
33
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Resolves the UHRP URL to a list of HTTP URLs where content can be downloaded.
|
|
25
|
+
* @param uhrpUrl The UHRP URL to resolve.
|
|
26
|
+
* @returns A promise that resolves to an array of HTTP URLs.
|
|
27
|
+
*/
|
|
34
28
|
public async resolve (uhrpUrl: string): Promise<string[]> {
|
|
35
29
|
// Use UHRP lookup service
|
|
36
30
|
const lookupResolver = new LookupResolver({ networkPreset: this.networkPreset })
|
|
@@ -54,6 +48,11 @@ export class StorageDownloader {
|
|
|
54
48
|
return decodedResults
|
|
55
49
|
}
|
|
56
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Downloads the content from the UHRP URL after validating the hash for integrity.
|
|
53
|
+
* @param uhrpUrl The UHRP URL to download.
|
|
54
|
+
* @returns A promise that resolves to the downloaded content.
|
|
55
|
+
*/
|
|
57
56
|
public async download (uhrpUrl: string): Promise<DownloadResult> {
|
|
58
57
|
if (!StorageUtils.isValidURL(uhrpUrl)) {
|
|
59
58
|
throw new Error('Invalid parameter UHRP url')
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import ChainTracker from '../ChainTracker.js'
|
|
2
|
+
import { HttpClient } from '../http/HttpClient.js'
|
|
3
|
+
import { defaultHttpClient } from '../http/DefaultHttpClient.js'
|
|
4
|
+
|
|
5
|
+
/** Configuration options for the BlockHeadersService ChainTracker. */
|
|
6
|
+
export interface BlockHeadersServiceConfig {
|
|
7
|
+
/** The HTTP client used to make requests to the API. */
|
|
8
|
+
httpClient?: HttpClient
|
|
9
|
+
|
|
10
|
+
/** The API key used to authenticate requests to the BlockHeadersService API. */
|
|
11
|
+
apiKey?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface MerkleRootVerificationRequest {
|
|
15
|
+
blockHeight: number
|
|
16
|
+
merkleRoot: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface MerkleRootConfirmation {
|
|
20
|
+
blockHash: string
|
|
21
|
+
blockHeight: number
|
|
22
|
+
merkleRoot: string
|
|
23
|
+
confirmation: 'CONFIRMED' | 'UNCONFIRMED'
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface MerkleRootVerificationResponse {
|
|
27
|
+
confirmationState: 'CONFIRMED' | 'UNCONFIRMED'
|
|
28
|
+
confirmations: MerkleRootConfirmation[]
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Represents a chain tracker based on a BlockHeadersService API.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```typescript
|
|
36
|
+
* const chainTracker = new BlockHeadersService('https://headers.spv.money', {
|
|
37
|
+
* apiKey: '17JxRHcJerGBEbusx56W8o1m8Js73TFGo'
|
|
38
|
+
* })
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export class BlockHeadersService implements ChainTracker {
|
|
42
|
+
protected readonly baseUrl: string
|
|
43
|
+
protected readonly httpClient: HttpClient
|
|
44
|
+
protected readonly apiKey: string
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Constructs an instance of the BlockHeadersService ChainTracker.
|
|
48
|
+
*
|
|
49
|
+
* @param {string} baseUrl - The base URL for the BlockHeadersService API (e.g. https://headers.spv.money)
|
|
50
|
+
* @param {BlockHeadersServiceConfig} config - Configuration options for the BlockHeadersService ChainTracker.
|
|
51
|
+
*/
|
|
52
|
+
constructor(
|
|
53
|
+
baseUrl: string,
|
|
54
|
+
config: BlockHeadersServiceConfig = {}
|
|
55
|
+
) {
|
|
56
|
+
const { httpClient, apiKey } = config
|
|
57
|
+
this.baseUrl = baseUrl
|
|
58
|
+
this.httpClient = httpClient ?? defaultHttpClient()
|
|
59
|
+
this.apiKey = apiKey ?? ''
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Verifies if a given merkle root is valid for a specific block height.
|
|
64
|
+
*
|
|
65
|
+
* @param {string} root - The merkle root to verify.
|
|
66
|
+
* @param {number} height - The block height to check against.
|
|
67
|
+
* @returns {Promise<boolean>} - A promise that resolves to true if the merkle root is valid for the specified block height, false otherwise.
|
|
68
|
+
*/
|
|
69
|
+
async isValidRootForHeight(root: string, height: number): Promise<boolean> {
|
|
70
|
+
const requestOptions = {
|
|
71
|
+
method: 'POST',
|
|
72
|
+
headers: {
|
|
73
|
+
'Content-Type': 'application/json',
|
|
74
|
+
'Accept': 'application/json',
|
|
75
|
+
'Authorization': `Bearer ${this.apiKey}`
|
|
76
|
+
},
|
|
77
|
+
data: [
|
|
78
|
+
{
|
|
79
|
+
blockHeight: height,
|
|
80
|
+
merkleRoot: root
|
|
81
|
+
}
|
|
82
|
+
] as MerkleRootVerificationRequest[]
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
const response = await this.httpClient.request<MerkleRootVerificationResponse>(
|
|
87
|
+
`${this.baseUrl}/api/v1/chain/merkleroot/verify`,
|
|
88
|
+
requestOptions
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
if (response.ok) {
|
|
92
|
+
return response.data.confirmationState === 'CONFIRMED'
|
|
93
|
+
} else {
|
|
94
|
+
throw new Error(
|
|
95
|
+
`Failed to verify merkleroot for height ${height} because of an error: ${JSON.stringify(response.data)}`
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
} catch (error) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
`Failed to verify merkleroot for height ${height} because of an error: ${error instanceof Error ? error.message : String(error)}`
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Gets the current block height from the BlockHeadersService API.
|
|
107
|
+
*
|
|
108
|
+
* @returns {Promise<number>} - A promise that resolves to the current block height.
|
|
109
|
+
*/
|
|
110
|
+
async currentHeight(): Promise<number> {
|
|
111
|
+
const requestOptions = {
|
|
112
|
+
method: 'GET',
|
|
113
|
+
headers: {
|
|
114
|
+
'Accept': 'application/json',
|
|
115
|
+
'Authorization': `Bearer ${this.apiKey}`
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
const response = await this.httpClient.request<{ height: number }>(
|
|
121
|
+
`${this.baseUrl}/api/v1/chain/tip/longest`,
|
|
122
|
+
requestOptions
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
if (response.ok && response.data && typeof response.data.height === 'number') {
|
|
126
|
+
return response.data.height
|
|
127
|
+
} else {
|
|
128
|
+
throw new Error(
|
|
129
|
+
`Failed to get current height because of an error: ${JSON.stringify(response.data)}`
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
} catch (error) {
|
|
133
|
+
throw new Error(
|
|
134
|
+
`Failed to get current height because of an error: ${error instanceof Error ? error.message : String(error)}`
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|