ox 0.14.17 → 0.14.19
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/CHANGELOG.md +12 -0
- package/_cjs/tempo/TxEnvelopeTempo.js.map +1 -1
- package/_cjs/tempo/VirtualAddress.js +70 -0
- package/_cjs/tempo/VirtualAddress.js.map +1 -0
- package/_cjs/tempo/VirtualMaster.js +278 -0
- package/_cjs/tempo/VirtualMaster.js.map +1 -0
- package/_cjs/tempo/index.js +3 -1
- package/_cjs/tempo/index.js.map +1 -1
- package/_cjs/tempo/internal/mine.wasm.js +6 -0
- package/_cjs/tempo/internal/mine.wasm.js.map +1 -0
- package/_cjs/tempo/internal/virtualMasterPool.js +186 -0
- package/_cjs/tempo/internal/virtualMasterPool.js.map +1 -0
- package/_cjs/version.js +1 -1
- package/_esm/tempo/TxEnvelopeTempo.js +6 -3
- package/_esm/tempo/TxEnvelopeTempo.js.map +1 -1
- package/_esm/tempo/VirtualAddress.js +154 -0
- package/_esm/tempo/VirtualAddress.js.map +1 -0
- package/_esm/tempo/VirtualMaster.js +483 -0
- package/_esm/tempo/VirtualMaster.js.map +1 -0
- package/_esm/tempo/index.js +54 -0
- package/_esm/tempo/index.js.map +1 -1
- package/_esm/tempo/internal/mine.wasm.js +15 -0
- package/_esm/tempo/internal/mine.wasm.js.map +1 -0
- package/_esm/tempo/internal/virtualMasterPool.js +216 -0
- package/_esm/tempo/internal/virtualMasterPool.js.map +1 -0
- package/_esm/version.js +1 -1
- package/_types/core/RpcSchema.d.ts +0 -2
- package/_types/core/RpcSchema.d.ts.map +1 -1
- package/_types/tempo/TxEnvelopeTempo.d.ts +6 -3
- package/_types/tempo/TxEnvelopeTempo.d.ts.map +1 -1
- package/_types/tempo/VirtualAddress.d.ts +129 -0
- package/_types/tempo/VirtualAddress.d.ts.map +1 -0
- package/_types/tempo/VirtualMaster.d.ts +237 -0
- package/_types/tempo/VirtualMaster.d.ts.map +1 -0
- package/_types/tempo/index.d.ts +54 -0
- package/_types/tempo/index.d.ts.map +1 -1
- package/_types/tempo/internal/mine.wasm.d.ts +5 -0
- package/_types/tempo/internal/mine.wasm.d.ts.map +1 -0
- package/_types/tempo/internal/virtualMasterPool.d.ts +46 -0
- package/_types/tempo/internal/virtualMasterPool.d.ts.map +1 -0
- package/_types/version.d.ts +1 -1
- package/core/RpcSchema.ts +0 -2
- package/package.json +11 -1
- package/tempo/TxEnvelopeTempo.ts +6 -3
- package/tempo/VirtualAddress/package.json +6 -0
- package/tempo/VirtualAddress.test.ts +88 -0
- package/tempo/VirtualAddress.ts +201 -0
- package/tempo/VirtualMaster/package.json +6 -0
- package/tempo/VirtualMaster.test.ts +204 -0
- package/tempo/VirtualMaster.ts +711 -0
- package/tempo/e2e.test.ts +0 -2
- package/tempo/index.ts +55 -0
- package/tempo/internal/mine.c +182 -0
- package/tempo/internal/mine.wasm.ts +17 -0
- package/tempo/internal/virtualMasterPool.ts +254 -0
- package/version.ts +1 -1
|
@@ -0,0 +1,711 @@
|
|
|
1
|
+
import { keccak_256 } from '@noble/hashes/sha3'
|
|
2
|
+
import * as Address from '../core/Address.js'
|
|
3
|
+
import * as Bytes from '../core/Bytes.js'
|
|
4
|
+
import * as Errors from '../core/Errors.js'
|
|
5
|
+
import * as Hash from '../core/Hash.js'
|
|
6
|
+
import * as Hex from '../core/Hex.js'
|
|
7
|
+
import * as VirtualMasterPool from './internal/virtualMasterPool.js'
|
|
8
|
+
import * as TempoAddress from './TempoAddress.js'
|
|
9
|
+
import * as VirtualAddress from './VirtualAddress.js'
|
|
10
|
+
|
|
11
|
+
const tip20Prefix = '0x20c000000000000000000000'
|
|
12
|
+
const zeroAddress = '0x0000000000000000000000000000000000000000'
|
|
13
|
+
|
|
14
|
+
/** A valid salt input for TIP-1022 master registration. */
|
|
15
|
+
export type Salt = Hex.Hex | Bytes.Bytes | number | bigint
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Computes the TIP-1022 registration hash for a master address and salt.
|
|
19
|
+
*
|
|
20
|
+
* [TIP-1022](https://docs.tempo.xyz/protocol/tips/tip-1022)
|
|
21
|
+
*
|
|
22
|
+
* The registration hash is `keccak256(masterAddress || salt)` where `salt`
|
|
23
|
+
* is encoded as a 32-byte value.
|
|
24
|
+
*
|
|
25
|
+
* Master addresses must satisfy TIP-1022 registration constraints: they cannot
|
|
26
|
+
* be the zero address, another virtual address, or a TIP-20 token address.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts twoslash
|
|
30
|
+
* import { Address, Hex } from 'ox'
|
|
31
|
+
* import { VirtualMaster } from 'ox/tempo'
|
|
32
|
+
*
|
|
33
|
+
* const hash = VirtualMaster.getRegistrationHash({
|
|
34
|
+
* address: Address.from('0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'),
|
|
35
|
+
* salt: Hex.from('0x00000000000000000000000000000000000000000000000000000000abf52baf'),
|
|
36
|
+
* })
|
|
37
|
+
*
|
|
38
|
+
* hash
|
|
39
|
+
* // @log: '0x0000000058e21090d8f4bee424b90cddc2378aefa1bbbfa1443631a929ae966d'
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* @param value - Master address and salt.
|
|
43
|
+
* @returns The registration hash.
|
|
44
|
+
*/
|
|
45
|
+
export function getRegistrationHash(value: getRegistrationHash.Value): Hex.Hex {
|
|
46
|
+
return Hash.keccak256(
|
|
47
|
+
Hex.concat(resolveAddress(value.address), toFixedHex(value.salt, 32)),
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export declare namespace getRegistrationHash {
|
|
52
|
+
type Value = {
|
|
53
|
+
/** Master address. Accepts both hex and Tempo addresses. */
|
|
54
|
+
address: TempoAddress.Address
|
|
55
|
+
/** 32-byte salt used for registration. */
|
|
56
|
+
salt: Salt
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
type ErrorType =
|
|
60
|
+
| Address.assert.ErrorType
|
|
61
|
+
| Bytes.padLeft.ErrorType
|
|
62
|
+
| Errors.BaseError
|
|
63
|
+
| Hash.keccak256.ErrorType
|
|
64
|
+
| Hex.assert.ErrorType
|
|
65
|
+
| Hex.fromBytes.ErrorType
|
|
66
|
+
| Hex.fromNumber.ErrorType
|
|
67
|
+
| Hex.padLeft.ErrorType
|
|
68
|
+
| TempoAddress.parse.ErrorType
|
|
69
|
+
| Errors.GlobalErrorType
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Derives the 4-byte TIP-1022 `masterId` from a master address and salt.
|
|
74
|
+
*
|
|
75
|
+
* [TIP-1022](https://docs.tempo.xyz/protocol/tips/tip-1022)
|
|
76
|
+
*
|
|
77
|
+
* This returns bytes `[4:8]` of the registration hash, regardless of whether the
|
|
78
|
+
* salt satisfies the proof-of-work requirement.
|
|
79
|
+
*
|
|
80
|
+
* Master addresses must satisfy TIP-1022 registration constraints: they cannot
|
|
81
|
+
* be the zero address, another virtual address, or a TIP-20 token address.
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```ts twoslash
|
|
85
|
+
* import { Address, Hex } from 'ox'
|
|
86
|
+
* import { VirtualMaster } from 'ox/tempo'
|
|
87
|
+
*
|
|
88
|
+
* const masterId = VirtualMaster.getMasterId({
|
|
89
|
+
* address: Address.from('0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'),
|
|
90
|
+
* salt: Hex.from('0x00000000000000000000000000000000000000000000000000000000abf52baf'),
|
|
91
|
+
* })
|
|
92
|
+
*
|
|
93
|
+
* masterId
|
|
94
|
+
* // @log: '0x58e21090'
|
|
95
|
+
* ```
|
|
96
|
+
*
|
|
97
|
+
* @param value - Master address and salt.
|
|
98
|
+
* @returns The derived master identifier.
|
|
99
|
+
*/
|
|
100
|
+
export function getMasterId(value: getMasterId.Value): Hex.Hex {
|
|
101
|
+
return Hex.slice(getRegistrationHash(value), 4, 8)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export declare namespace getMasterId {
|
|
105
|
+
type Value = getRegistrationHash.Value
|
|
106
|
+
type ErrorType = getRegistrationHash.ErrorType | Errors.GlobalErrorType
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Validates that a salt satisfies the TIP-1022 32-bit proof-of-work requirement.
|
|
111
|
+
*
|
|
112
|
+
* [TIP-1022](https://docs.tempo.xyz/protocol/tips/tip-1022)
|
|
113
|
+
*
|
|
114
|
+
* Returns `false` for invalid master addresses, including the zero address,
|
|
115
|
+
* virtual addresses, and TIP-20 token addresses.
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```ts twoslash
|
|
119
|
+
* import { Address, Hex } from 'ox'
|
|
120
|
+
* import { VirtualMaster } from 'ox/tempo'
|
|
121
|
+
*
|
|
122
|
+
* const valid = VirtualMaster.validateSalt({
|
|
123
|
+
* address: Address.from('0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'),
|
|
124
|
+
* salt: Hex.from('0x00000000000000000000000000000000000000000000000000000000abf52baf'),
|
|
125
|
+
* })
|
|
126
|
+
*
|
|
127
|
+
* valid
|
|
128
|
+
* // @log: true
|
|
129
|
+
* ```
|
|
130
|
+
*
|
|
131
|
+
* @param value - Master address and salt.
|
|
132
|
+
* @returns `true` if the first 4 bytes of the registration hash are zero.
|
|
133
|
+
*/
|
|
134
|
+
export function validateSalt(value: validateSalt.Value): boolean {
|
|
135
|
+
try {
|
|
136
|
+
return hasProofOfWork(
|
|
137
|
+
Hash.keccak256(
|
|
138
|
+
Hex.concat(resolveAddress(value.address), toFixedHex(value.salt, 32)),
|
|
139
|
+
{ as: 'Bytes' },
|
|
140
|
+
),
|
|
141
|
+
)
|
|
142
|
+
} catch {
|
|
143
|
+
return false
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export declare namespace validateSalt {
|
|
148
|
+
type Value = getRegistrationHash.Value
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Searches a bounded range of salts for the first value that satisfies TIP-1022 PoW.
|
|
153
|
+
*
|
|
154
|
+
* [TIP-1022](https://tips.sh/1022)
|
|
155
|
+
*
|
|
156
|
+
* This is intentionally a small, deterministic primitive. It does not coordinate
|
|
157
|
+
* workers or async execution. Callers that need large searches can shard ranges
|
|
158
|
+
* externally.
|
|
159
|
+
*
|
|
160
|
+
* Master addresses must satisfy TIP-1022 registration constraints: they cannot
|
|
161
|
+
* be the zero address, another virtual address, or a TIP-20 token address.
|
|
162
|
+
*
|
|
163
|
+
* :::warning
|
|
164
|
+
*
|
|
165
|
+
* It is strongly recommended to use {@link ox#VirtualMaster.(mineSaltAsync:function)} instead of this
|
|
166
|
+
* function. `mineSaltAsync` uses WASM-accelerated keccak256 with parallel
|
|
167
|
+
* workers and is a lot faster than the pure JS implementation used here.
|
|
168
|
+
*
|
|
169
|
+
* :::
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* ```ts twoslash
|
|
173
|
+
* import { Address } from 'ox'
|
|
174
|
+
* import { VirtualMaster } from 'ox/tempo'
|
|
175
|
+
*
|
|
176
|
+
* const result = VirtualMaster.mineSalt({
|
|
177
|
+
* address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
|
|
178
|
+
* })
|
|
179
|
+
*
|
|
180
|
+
* result?.salt
|
|
181
|
+
* // @log: '0x00000000000000000000000000000000000000000000000000000000abf52baf'
|
|
182
|
+
* ```
|
|
183
|
+
*
|
|
184
|
+
* @param value - Search range parameters.
|
|
185
|
+
* @returns The first matching salt in the range, if any.
|
|
186
|
+
*/
|
|
187
|
+
export function mineSalt(
|
|
188
|
+
value: mineSalt.Value,
|
|
189
|
+
): mineSalt.ReturnType | undefined {
|
|
190
|
+
const count = value.count ?? 2 ** 32
|
|
191
|
+
|
|
192
|
+
assertCount(count)
|
|
193
|
+
|
|
194
|
+
const addressBytes = Bytes.fromHex(resolveAddress(value.address))
|
|
195
|
+
const input = new Uint8Array(addressBytes.length + 32)
|
|
196
|
+
input.set(addressBytes)
|
|
197
|
+
|
|
198
|
+
// Salt is a view into input — increment mutates input directly, no copy.
|
|
199
|
+
const saltView = input.subarray(addressBytes.length)
|
|
200
|
+
saltView.set(toFixedBytes(value.start ?? 0n, 32))
|
|
201
|
+
|
|
202
|
+
for (let i = 0; i < count; i++) {
|
|
203
|
+
const hash = keccak_256(input)
|
|
204
|
+
|
|
205
|
+
if (hash[0] === 0 && hash[1] === 0 && hash[2] === 0 && hash[3] === 0) {
|
|
206
|
+
return {
|
|
207
|
+
masterId: Hex.fromBytes(hash.subarray(4, 8)),
|
|
208
|
+
registrationHash: Hex.fromBytes(hash),
|
|
209
|
+
salt: Hex.fromBytes(saltView),
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (i < count - 1 && !increment(saltView)) break
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return undefined
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export declare namespace mineSalt {
|
|
220
|
+
type Value = {
|
|
221
|
+
/** Master address. Accepts both hex and Tempo addresses. */
|
|
222
|
+
address: TempoAddress.Address
|
|
223
|
+
/** Number of consecutive salts to try. */
|
|
224
|
+
count?: number | undefined
|
|
225
|
+
/** Starting salt value. @default 0n */
|
|
226
|
+
start?: Salt | undefined
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
type ReturnType = {
|
|
230
|
+
/** The 4-byte master identifier derived from the matching salt. */
|
|
231
|
+
masterId: Hex.Hex
|
|
232
|
+
/** The matching registration hash. */
|
|
233
|
+
registrationHash: Hex.Hex
|
|
234
|
+
/** The discovered 32-byte salt. */
|
|
235
|
+
salt: Hex.Hex
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
type ErrorType =
|
|
239
|
+
| Address.assert.ErrorType
|
|
240
|
+
| Bytes.fromHex.ErrorType
|
|
241
|
+
| Bytes.padLeft.ErrorType
|
|
242
|
+
| Errors.BaseError
|
|
243
|
+
| Hash.keccak256.ErrorType
|
|
244
|
+
| Hex.assert.ErrorType
|
|
245
|
+
| Hex.fromBytes.ErrorType
|
|
246
|
+
| Hex.fromNumber.ErrorType
|
|
247
|
+
| Hex.padLeft.ErrorType
|
|
248
|
+
| TempoAddress.parse.ErrorType
|
|
249
|
+
| Errors.GlobalErrorType
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Searches for a salt that satisfies TIP-1022 PoW using parallel workers and
|
|
254
|
+
* WASM-accelerated keccak256.
|
|
255
|
+
*
|
|
256
|
+
* [TIP-1022](https://tips.sh/1022)
|
|
257
|
+
*
|
|
258
|
+
* Uses WASM-accelerated keccak256 with parallel
|
|
259
|
+
* workers when available. Falls back to chunked single-threaded mining in
|
|
260
|
+
* environments without worker support.
|
|
261
|
+
*
|
|
262
|
+
* - **Node.js / Bun / Deno**: Spawns `worker_threads` with inline WASM keccak256.
|
|
263
|
+
* - **Browsers**: Spawns Web Workers via Blob URLs with inline WASM keccak256.
|
|
264
|
+
*
|
|
265
|
+
* @example
|
|
266
|
+
* ```ts twoslash
|
|
267
|
+
* import { Address } from 'ox'
|
|
268
|
+
* import { VirtualMaster } from 'ox/tempo'
|
|
269
|
+
*
|
|
270
|
+
* const result = await VirtualMaster.mineSaltAsync({
|
|
271
|
+
* address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
|
|
272
|
+
* })
|
|
273
|
+
* ```
|
|
274
|
+
*
|
|
275
|
+
* @param parameters - Search parameters.
|
|
276
|
+
* @returns The first matching salt, if any.
|
|
277
|
+
*/
|
|
278
|
+
export async function mineSaltAsync(
|
|
279
|
+
parameters: mineSaltAsync.Parameters,
|
|
280
|
+
): Promise<mineSalt.ReturnType | undefined> {
|
|
281
|
+
const {
|
|
282
|
+
chunkSize = 100_000,
|
|
283
|
+
count = 2 ** 32,
|
|
284
|
+
onProgress,
|
|
285
|
+
signal,
|
|
286
|
+
start: start_ = 0n,
|
|
287
|
+
workers = getDefaultWorkerCount(),
|
|
288
|
+
} = parameters
|
|
289
|
+
|
|
290
|
+
const address = resolveAddress(parameters.address)
|
|
291
|
+
const start = toFixedHex(start_, 32)
|
|
292
|
+
|
|
293
|
+
assertCount(count)
|
|
294
|
+
if (workers !== undefined) assertWorkers(workers)
|
|
295
|
+
throwIfAborted(signal)
|
|
296
|
+
|
|
297
|
+
const workerCount = Math.max(
|
|
298
|
+
1,
|
|
299
|
+
Math.min(workers, Math.ceil(count / chunkSize)),
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
if (workerCount <= 1)
|
|
303
|
+
return mineSaltAsyncFallback({
|
|
304
|
+
address,
|
|
305
|
+
chunkSize,
|
|
306
|
+
count,
|
|
307
|
+
onProgress,
|
|
308
|
+
signal,
|
|
309
|
+
start,
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
const pool = await VirtualMasterPool.resolve()
|
|
313
|
+
if (!pool)
|
|
314
|
+
return mineSaltAsyncFallback({
|
|
315
|
+
address,
|
|
316
|
+
chunkSize,
|
|
317
|
+
count,
|
|
318
|
+
onProgress,
|
|
319
|
+
signal,
|
|
320
|
+
start,
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
return mineSaltWithWorkerPool({
|
|
324
|
+
address,
|
|
325
|
+
chunkSize,
|
|
326
|
+
count,
|
|
327
|
+
onProgress,
|
|
328
|
+
pool,
|
|
329
|
+
signal,
|
|
330
|
+
start,
|
|
331
|
+
workerCount,
|
|
332
|
+
})
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export declare namespace mineSaltAsync {
|
|
336
|
+
type Parameters = {
|
|
337
|
+
/** Master address. Accepts both hex and Tempo addresses. */
|
|
338
|
+
address: TempoAddress.Address
|
|
339
|
+
/**
|
|
340
|
+
* Number of salts each worker processes before sending a progress update.
|
|
341
|
+
*
|
|
342
|
+
* @default 100_000
|
|
343
|
+
*/
|
|
344
|
+
chunkSize?: number | undefined
|
|
345
|
+
/** Number of consecutive salts to try. @default 2 ** 32 */
|
|
346
|
+
count?: number | undefined
|
|
347
|
+
/** Progress callback invoked after each completed chunk. */
|
|
348
|
+
onProgress?: ((progress: Progress) => void) | undefined
|
|
349
|
+
/** AbortSignal for cancellation. */
|
|
350
|
+
signal?: AbortSignal | undefined
|
|
351
|
+
/** Starting salt value. @default 0n */
|
|
352
|
+
start?: Salt | undefined
|
|
353
|
+
/**
|
|
354
|
+
* Number of workers to use.
|
|
355
|
+
*
|
|
356
|
+
* Set to `0` or `1` to disable worker parallelism.
|
|
357
|
+
*
|
|
358
|
+
* @default os.availableParallelism() - 1
|
|
359
|
+
*/
|
|
360
|
+
workers?: number | undefined
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
type Progress = {
|
|
364
|
+
/** Total attempts so far. */
|
|
365
|
+
attempts: number
|
|
366
|
+
/** Configured chunk size. */
|
|
367
|
+
chunkSize: number
|
|
368
|
+
/** Total count requested. */
|
|
369
|
+
count: number
|
|
370
|
+
/** Elapsed time in milliseconds. */
|
|
371
|
+
elapsed: number
|
|
372
|
+
/** Fraction complete (0–1). */
|
|
373
|
+
progress: number
|
|
374
|
+
/** Hashes per second. */
|
|
375
|
+
rate: number
|
|
376
|
+
/** Number of workers in use. */
|
|
377
|
+
workers: number
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
type ErrorType =
|
|
381
|
+
| mineSalt.ErrorType
|
|
382
|
+
| Errors.BaseError
|
|
383
|
+
| Errors.GlobalErrorType
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Runs parallel salt mining across a worker pool.
|
|
388
|
+
*
|
|
389
|
+
* @internal
|
|
390
|
+
*/
|
|
391
|
+
// biome-ignore lint/correctness/noUnusedVariables: _
|
|
392
|
+
function mineSaltWithWorkerPool(
|
|
393
|
+
value: mineSaltWithWorkerPool.Options,
|
|
394
|
+
): Promise<mineSalt.ReturnType | undefined> {
|
|
395
|
+
const startedAt = Date.now()
|
|
396
|
+
|
|
397
|
+
return new Promise<mineSalt.ReturnType | undefined>((resolve, reject) => {
|
|
398
|
+
let settled = false
|
|
399
|
+
let attempts = 0
|
|
400
|
+
let completedWorkers = 0
|
|
401
|
+
const handles: { terminate(): void }[] = []
|
|
402
|
+
|
|
403
|
+
const emitProgress = () => {
|
|
404
|
+
if (!value.onProgress) return
|
|
405
|
+
const elapsed = Date.now() - startedAt
|
|
406
|
+
const seconds = elapsed / 1000
|
|
407
|
+
value.onProgress({
|
|
408
|
+
attempts,
|
|
409
|
+
chunkSize: value.chunkSize,
|
|
410
|
+
count: value.count,
|
|
411
|
+
elapsed,
|
|
412
|
+
progress: Math.min(1, attempts / value.count),
|
|
413
|
+
rate: seconds === 0 ? 0 : attempts / seconds,
|
|
414
|
+
workers: value.workerCount,
|
|
415
|
+
})
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const terminateAll = () => {
|
|
419
|
+
for (const h of handles) h.terminate()
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const succeed = (result: mineSalt.ReturnType | undefined) => {
|
|
423
|
+
if (settled) return
|
|
424
|
+
settled = true
|
|
425
|
+
value.signal?.removeEventListener('abort', onAbort)
|
|
426
|
+
terminateAll()
|
|
427
|
+
resolve(result)
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const fail = (error: unknown) => {
|
|
431
|
+
if (settled) return
|
|
432
|
+
settled = true
|
|
433
|
+
value.signal?.removeEventListener('abort', onAbort)
|
|
434
|
+
terminateAll()
|
|
435
|
+
reject(
|
|
436
|
+
error instanceof Error
|
|
437
|
+
? error
|
|
438
|
+
: new Errors.BaseError('Failed to mine virtual master salt.'),
|
|
439
|
+
)
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const onMessage = (msg: VirtualMasterPool.Message) => {
|
|
443
|
+
if (settled) return
|
|
444
|
+
switch (msg.type) {
|
|
445
|
+
case 'found':
|
|
446
|
+
succeed(msg.result as mineSalt.ReturnType)
|
|
447
|
+
return
|
|
448
|
+
case 'progress':
|
|
449
|
+
attempts += msg.attempts
|
|
450
|
+
emitProgress()
|
|
451
|
+
return
|
|
452
|
+
case 'done':
|
|
453
|
+
completedWorkers++
|
|
454
|
+
if (completedWorkers === value.workerCount) succeed(undefined)
|
|
455
|
+
return
|
|
456
|
+
case 'error':
|
|
457
|
+
fail(new Errors.BaseError(msg.message))
|
|
458
|
+
return
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const onAbort = () => fail(getAbortError(value.signal))
|
|
463
|
+
value.signal?.addEventListener('abort', onAbort, { once: true })
|
|
464
|
+
|
|
465
|
+
for (let i = 0; i < value.workerCount; i++) {
|
|
466
|
+
const handle = value.pool.spawn(i, onMessage, fail)
|
|
467
|
+
handles.push(handle)
|
|
468
|
+
handle.postMessage({
|
|
469
|
+
type: 'start',
|
|
470
|
+
address: value.address,
|
|
471
|
+
chunkSize: value.chunkSize,
|
|
472
|
+
count: value.count,
|
|
473
|
+
start: value.start,
|
|
474
|
+
workerCount: value.workerCount,
|
|
475
|
+
workerIndex: i,
|
|
476
|
+
})
|
|
477
|
+
}
|
|
478
|
+
})
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
declare namespace mineSaltWithWorkerPool {
|
|
482
|
+
type Options = {
|
|
483
|
+
/** Resolved master address. */
|
|
484
|
+
address: Address.Address
|
|
485
|
+
/** Salts per chunk before a progress update. */
|
|
486
|
+
chunkSize: number
|
|
487
|
+
/** Total number of salts to try. */
|
|
488
|
+
count: number
|
|
489
|
+
/** Progress callback. */
|
|
490
|
+
onProgress: mineSaltAsync.Parameters['onProgress']
|
|
491
|
+
/** Worker pool to distribute work across. */
|
|
492
|
+
pool: VirtualMasterPool.Pool
|
|
493
|
+
/** AbortSignal for cancellation. */
|
|
494
|
+
signal: AbortSignal | undefined
|
|
495
|
+
/** Starting salt as a hex string. */
|
|
496
|
+
start: Hex.Hex
|
|
497
|
+
/** Number of workers to use. */
|
|
498
|
+
workerCount: number
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Single-threaded chunked fallback when no worker pool is available.
|
|
504
|
+
*
|
|
505
|
+
* @internal
|
|
506
|
+
*/
|
|
507
|
+
// biome-ignore lint/correctness/noUnusedVariables: _
|
|
508
|
+
async function mineSaltAsyncFallback(
|
|
509
|
+
value: mineSaltAsyncFallback.Options,
|
|
510
|
+
): Promise<mineSalt.ReturnType | undefined> {
|
|
511
|
+
const startedAt = Date.now()
|
|
512
|
+
const startBigInt = BigInt(value.start)
|
|
513
|
+
|
|
514
|
+
for (let offset = 0; offset < value.count; offset += value.chunkSize) {
|
|
515
|
+
throwIfAborted(value.signal)
|
|
516
|
+
|
|
517
|
+
const count = Math.min(value.chunkSize, value.count - offset)
|
|
518
|
+
const result = mineSalt({
|
|
519
|
+
address: value.address,
|
|
520
|
+
count,
|
|
521
|
+
start: startBigInt + BigInt(offset),
|
|
522
|
+
})
|
|
523
|
+
|
|
524
|
+
if (value.onProgress) {
|
|
525
|
+
const attempts = Math.min(value.count, offset + count)
|
|
526
|
+
const elapsed = Date.now() - startedAt
|
|
527
|
+
const seconds = elapsed / 1000
|
|
528
|
+
value.onProgress({
|
|
529
|
+
attempts,
|
|
530
|
+
chunkSize: value.chunkSize,
|
|
531
|
+
count: value.count,
|
|
532
|
+
elapsed,
|
|
533
|
+
progress: Math.min(1, attempts / value.count),
|
|
534
|
+
rate: seconds === 0 ? 0 : attempts / seconds,
|
|
535
|
+
workers: 1,
|
|
536
|
+
})
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if (result) return result
|
|
540
|
+
|
|
541
|
+
// Yield to the event loop between chunks.
|
|
542
|
+
await new Promise<void>((r) => setTimeout(r, 0))
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
return undefined
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
declare namespace mineSaltAsyncFallback {
|
|
549
|
+
type Options = {
|
|
550
|
+
/** Resolved master address. */
|
|
551
|
+
address: Address.Address
|
|
552
|
+
/** Salts per chunk before yielding to the event loop. */
|
|
553
|
+
chunkSize: number
|
|
554
|
+
/** Total number of salts to try. */
|
|
555
|
+
count: number
|
|
556
|
+
/** Progress callback. */
|
|
557
|
+
onProgress: mineSaltAsync.Parameters['onProgress']
|
|
558
|
+
/** AbortSignal for cancellation. */
|
|
559
|
+
signal: AbortSignal | undefined
|
|
560
|
+
/** Starting salt as a hex string. */
|
|
561
|
+
start: Hex.Hex
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Asserts that `workers` is a non-negative safe integer.
|
|
567
|
+
*
|
|
568
|
+
* @internal
|
|
569
|
+
*/
|
|
570
|
+
function assertWorkers(workers: number) {
|
|
571
|
+
if (Number.isSafeInteger(workers) && workers >= 0) return
|
|
572
|
+
throw new Errors.BaseError(
|
|
573
|
+
`Workers "${workers}" is invalid. Expected a non-negative safe integer.`,
|
|
574
|
+
)
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Extracts or creates an error from an `AbortSignal`.
|
|
579
|
+
*
|
|
580
|
+
* @internal
|
|
581
|
+
*/
|
|
582
|
+
function getAbortError(signal?: AbortSignal): Error {
|
|
583
|
+
const reason = signal?.reason
|
|
584
|
+
if (reason instanceof Error) return reason
|
|
585
|
+
return new Errors.BaseError('The operation was aborted.')
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Returns the default number of workers for the current platform.
|
|
590
|
+
*
|
|
591
|
+
* @internal
|
|
592
|
+
*/
|
|
593
|
+
function getDefaultWorkerCount(): number {
|
|
594
|
+
if (typeof navigator !== 'undefined') {
|
|
595
|
+
const c = navigator.hardwareConcurrency
|
|
596
|
+
if (c && c > 1) return c - 1
|
|
597
|
+
}
|
|
598
|
+
return 1
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* Throws the signal's abort reason if the signal is aborted.
|
|
603
|
+
*
|
|
604
|
+
* @internal
|
|
605
|
+
*/
|
|
606
|
+
function throwIfAborted(signal?: AbortSignal) {
|
|
607
|
+
if (!signal?.aborted) return
|
|
608
|
+
throw getAbortError(signal)
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* Returns `true` if the first 4 bytes of a hash are zero.
|
|
613
|
+
*
|
|
614
|
+
* @internal
|
|
615
|
+
*/
|
|
616
|
+
function hasProofOfWork(hash: Bytes.Bytes): boolean {
|
|
617
|
+
return hash[0] === 0 && hash[1] === 0 && hash[2] === 0 && hash[3] === 0
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Asserts that `count` is a positive safe integer.
|
|
622
|
+
*
|
|
623
|
+
* @internal
|
|
624
|
+
*/
|
|
625
|
+
function assertCount(count: number) {
|
|
626
|
+
if (Number.isSafeInteger(count) && count > 0) return
|
|
627
|
+
|
|
628
|
+
throw new Errors.BaseError(
|
|
629
|
+
`Count "${count}" is invalid. Expected a positive safe integer.`,
|
|
630
|
+
)
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* Increments a big-endian byte array by one. Returns `false` on overflow.
|
|
635
|
+
*
|
|
636
|
+
* @internal
|
|
637
|
+
*/
|
|
638
|
+
function increment(bytes: Bytes.Bytes): boolean {
|
|
639
|
+
for (let i = bytes.length - 1; i >= 0; i--) {
|
|
640
|
+
const value = bytes[i]!
|
|
641
|
+
if (value === 0xff) {
|
|
642
|
+
bytes[i] = 0
|
|
643
|
+
continue
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
bytes[i] = value + 1
|
|
647
|
+
return true
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
return false
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Resolves a Tempo or hex address, validates it as a valid master.
|
|
655
|
+
*
|
|
656
|
+
* @internal
|
|
657
|
+
*/
|
|
658
|
+
function resolveAddress(address: string): Address.Address {
|
|
659
|
+
const resolved = TempoAddress.resolve(address as TempoAddress.Address)
|
|
660
|
+
Address.assert(resolved, { strict: false })
|
|
661
|
+
assertValidMasterAddress(resolved)
|
|
662
|
+
return resolved
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* Throws if the address is zero, virtual, or a TIP-20 token.
|
|
667
|
+
*
|
|
668
|
+
* @internal
|
|
669
|
+
*/
|
|
670
|
+
function assertValidMasterAddress(address: Address.Address) {
|
|
671
|
+
const normalized = address.toLowerCase()
|
|
672
|
+
|
|
673
|
+
if (normalized === zeroAddress)
|
|
674
|
+
throw new Errors.BaseError(
|
|
675
|
+
'Virtual master address cannot be the zero address.',
|
|
676
|
+
)
|
|
677
|
+
|
|
678
|
+
if (VirtualAddress.isVirtual(address))
|
|
679
|
+
throw new Errors.BaseError(
|
|
680
|
+
'Virtual master address cannot itself be a virtual address.',
|
|
681
|
+
)
|
|
682
|
+
|
|
683
|
+
if (normalized.startsWith(tip20Prefix))
|
|
684
|
+
throw new Errors.BaseError(
|
|
685
|
+
'Virtual master address cannot be a TIP-20 token address.',
|
|
686
|
+
)
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* Converts a salt to a fixed-size byte array.
|
|
691
|
+
*
|
|
692
|
+
* @internal
|
|
693
|
+
*/
|
|
694
|
+
function toFixedBytes(value: Salt, size: number): Bytes.Bytes {
|
|
695
|
+
return Bytes.fromHex(toFixedHex(value, size))
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/**
|
|
699
|
+
* Converts a salt to a zero-padded hex string of the given size.
|
|
700
|
+
*
|
|
701
|
+
* @internal
|
|
702
|
+
*/
|
|
703
|
+
function toFixedHex(value: Salt, size: number): Hex.Hex {
|
|
704
|
+
if (typeof value === 'number' || typeof value === 'bigint')
|
|
705
|
+
return Hex.fromNumber(value, { size })
|
|
706
|
+
if (typeof value === 'string') {
|
|
707
|
+
Hex.assert(value, { strict: true })
|
|
708
|
+
return Hex.padLeft(value, size)
|
|
709
|
+
}
|
|
710
|
+
return Hex.fromBytes(Bytes.padLeft(value, size))
|
|
711
|
+
}
|
package/tempo/e2e.test.ts
CHANGED
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
Address,
|
|
4
4
|
Hex,
|
|
5
5
|
P256,
|
|
6
|
-
RpcTransport,
|
|
7
6
|
Secp256k1,
|
|
8
7
|
Value,
|
|
9
8
|
WebAuthnP256,
|
|
@@ -17,7 +16,6 @@ import {
|
|
|
17
16
|
KeyAuthorization,
|
|
18
17
|
Period,
|
|
19
18
|
SignatureEnvelope,
|
|
20
|
-
ZoneRpcAuthentication,
|
|
21
19
|
} from './index.js'
|
|
22
20
|
import * as Transaction from './Transaction.js'
|
|
23
21
|
import * as TransactionReceipt from './TransactionReceipt.js'
|