ox 0.14.18 → 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.
Files changed (41) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/_cjs/tempo/TxEnvelopeTempo.js.map +1 -1
  3. package/_cjs/tempo/VirtualMaster.js +192 -13
  4. package/_cjs/tempo/VirtualMaster.js.map +1 -1
  5. package/_cjs/tempo/index.js.map +1 -1
  6. package/_cjs/tempo/internal/mine.wasm.js +6 -0
  7. package/_cjs/tempo/internal/mine.wasm.js.map +1 -0
  8. package/_cjs/tempo/internal/virtualMasterPool.js +186 -0
  9. package/_cjs/tempo/internal/virtualMasterPool.js.map +1 -0
  10. package/_cjs/version.js +1 -1
  11. package/_esm/tempo/TxEnvelopeTempo.js +6 -3
  12. package/_esm/tempo/TxEnvelopeTempo.js.map +1 -1
  13. package/_esm/tempo/VirtualMaster.js +305 -22
  14. package/_esm/tempo/VirtualMaster.js.map +1 -1
  15. package/_esm/tempo/index.js +4 -2
  16. package/_esm/tempo/index.js.map +1 -1
  17. package/_esm/tempo/internal/mine.wasm.js +15 -0
  18. package/_esm/tempo/internal/mine.wasm.js.map +1 -0
  19. package/_esm/tempo/internal/virtualMasterPool.js +216 -0
  20. package/_esm/tempo/internal/virtualMasterPool.js.map +1 -0
  21. package/_esm/version.js +1 -1
  22. package/_types/tempo/TxEnvelopeTempo.d.ts +6 -3
  23. package/_types/tempo/TxEnvelopeTempo.d.ts.map +1 -1
  24. package/_types/tempo/VirtualMaster.d.ts +92 -10
  25. package/_types/tempo/VirtualMaster.d.ts.map +1 -1
  26. package/_types/tempo/index.d.ts +4 -2
  27. package/_types/tempo/index.d.ts.map +1 -1
  28. package/_types/tempo/internal/mine.wasm.d.ts +5 -0
  29. package/_types/tempo/internal/mine.wasm.d.ts.map +1 -0
  30. package/_types/tempo/internal/virtualMasterPool.d.ts +46 -0
  31. package/_types/tempo/internal/virtualMasterPool.d.ts.map +1 -0
  32. package/_types/version.d.ts +1 -1
  33. package/package.json +1 -1
  34. package/tempo/TxEnvelopeTempo.ts +6 -3
  35. package/tempo/VirtualMaster.test.ts +77 -0
  36. package/tempo/VirtualMaster.ts +431 -23
  37. package/tempo/index.ts +5 -2
  38. package/tempo/internal/mine.c +182 -0
  39. package/tempo/internal/mine.wasm.ts +17 -0
  40. package/tempo/internal/virtualMasterPool.ts +254 -0
  41. package/version.ts +1 -1
@@ -115,6 +115,82 @@ describe('mineSalt', () => {
115
115
  })
116
116
  })
117
117
 
118
+ describe('mineSaltAsync', () => {
119
+ test('finds the same salt as the sync version', async () => {
120
+ const result = await VirtualMaster.mineSaltAsync({
121
+ address,
122
+ count: 16,
123
+ start: 0xabf52ba0n,
124
+ workers: 1,
125
+ })
126
+
127
+ expect(result).toMatchInlineSnapshot(`
128
+ {
129
+ "masterId": "0x58e21090",
130
+ "registrationHash": "0x0000000058e21090d8f4bee424b90cddc2378aefa1bbbfa1443631a929ae966d",
131
+ "salt": "0x00000000000000000000000000000000000000000000000000000000abf52baf",
132
+ }
133
+ `)
134
+ })
135
+
136
+ test('finds the same salt with workers', async () => {
137
+ const result = await VirtualMaster.mineSaltAsync({
138
+ address,
139
+ count: 16,
140
+ start: 0xabf52ba0n,
141
+ workers: 2,
142
+ })
143
+
144
+ expect(result).toEqual({
145
+ masterId: '0x58e21090',
146
+ registrationHash:
147
+ '0x0000000058e21090d8f4bee424b90cddc2378aefa1bbbfa1443631a929ae966d',
148
+ salt: '0x00000000000000000000000000000000000000000000000000000000abf52baf',
149
+ })
150
+ })
151
+
152
+ test('returns undefined when no salt is found', async () => {
153
+ const result = await VirtualMaster.mineSaltAsync({
154
+ address,
155
+ count: 1,
156
+ start: 0n,
157
+ workers: 1,
158
+ })
159
+
160
+ expect(result).toBeUndefined()
161
+ })
162
+
163
+ test('reports progress', async () => {
164
+ const progressCalls: VirtualMaster.mineSaltAsync.Progress[] = []
165
+
166
+ await VirtualMaster.mineSaltAsync({
167
+ address,
168
+ count: 16,
169
+ start: 0xabf52ba0n,
170
+ workers: 1,
171
+ chunkSize: 8,
172
+ onProgress: (p) => progressCalls.push({ ...p }),
173
+ })
174
+
175
+ expect(progressCalls.length).toBeGreaterThanOrEqual(1)
176
+ expect(progressCalls[0]!.workers).toBe(1)
177
+ expect(progressCalls[0]!.attempts).toBeGreaterThan(0)
178
+ })
179
+
180
+ test('supports AbortSignal cancellation', async () => {
181
+ const controller = new AbortController()
182
+ controller.abort()
183
+
184
+ await expect(
185
+ VirtualMaster.mineSaltAsync({
186
+ address,
187
+ count: 1_000_000,
188
+ signal: controller.signal,
189
+ }),
190
+ ).rejects.toThrow()
191
+ })
192
+ })
193
+
118
194
  test('exports', () => {
119
195
  expect(Object.keys(VirtualMaster)).toMatchInlineSnapshot(`
120
196
  [
@@ -122,6 +198,7 @@ test('exports', () => {
122
198
  "getMasterId",
123
199
  "validateSalt",
124
200
  "mineSalt",
201
+ "mineSaltAsync",
125
202
  ]
126
203
  `)
127
204
  })
@@ -1,8 +1,10 @@
1
+ import { keccak_256 } from '@noble/hashes/sha3'
1
2
  import * as Address from '../core/Address.js'
2
3
  import * as Bytes from '../core/Bytes.js'
3
4
  import * as Errors from '../core/Errors.js'
4
5
  import * as Hash from '../core/Hash.js'
5
6
  import * as Hex from '../core/Hex.js'
7
+ import * as VirtualMasterPool from './internal/virtualMasterPool.js'
6
8
  import * as TempoAddress from './TempoAddress.js'
7
9
  import * as VirtualAddress from './VirtualAddress.js'
8
10
 
@@ -25,11 +27,12 @@ export type Salt = Hex.Hex | Bytes.Bytes | number | bigint
25
27
  *
26
28
  * @example
27
29
  * ```ts twoslash
30
+ * import { Address, Hex } from 'ox'
28
31
  * import { VirtualMaster } from 'ox/tempo'
29
32
  *
30
33
  * const hash = VirtualMaster.getRegistrationHash({
31
- * address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
32
- * salt: '0x00000000000000000000000000000000000000000000000000000000abf52baf',
34
+ * address: Address.from('0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'),
35
+ * salt: Hex.from('0x00000000000000000000000000000000000000000000000000000000abf52baf'),
33
36
  * })
34
37
  *
35
38
  * hash
@@ -79,11 +82,12 @@ export declare namespace getRegistrationHash {
79
82
  *
80
83
  * @example
81
84
  * ```ts twoslash
85
+ * import { Address, Hex } from 'ox'
82
86
  * import { VirtualMaster } from 'ox/tempo'
83
87
  *
84
88
  * const masterId = VirtualMaster.getMasterId({
85
- * address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
86
- * salt: '0x00000000000000000000000000000000000000000000000000000000abf52baf',
89
+ * address: Address.from('0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'),
90
+ * salt: Hex.from('0x00000000000000000000000000000000000000000000000000000000abf52baf'),
87
91
  * })
88
92
  *
89
93
  * masterId
@@ -112,11 +116,12 @@ export declare namespace getMasterId {
112
116
  *
113
117
  * @example
114
118
  * ```ts twoslash
119
+ * import { Address, Hex } from 'ox'
115
120
  * import { VirtualMaster } from 'ox/tempo'
116
121
  *
117
122
  * const valid = VirtualMaster.validateSalt({
118
- * address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
119
- * salt: '0x00000000000000000000000000000000000000000000000000000000abf52baf',
123
+ * address: Address.from('0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'),
124
+ * salt: Hex.from('0x00000000000000000000000000000000000000000000000000000000abf52baf'),
120
125
  * })
121
126
  *
122
127
  * valid
@@ -146,7 +151,7 @@ export declare namespace validateSalt {
146
151
  /**
147
152
  * Searches a bounded range of salts for the first value that satisfies TIP-1022 PoW.
148
153
  *
149
- * [TIP-1022](https://docs.tempo.xyz/protocol/tips/tip-1022)
154
+ * [TIP-1022](https://tips.sh/1022)
150
155
  *
151
156
  * This is intentionally a small, deterministic primitive. It does not coordinate
152
157
  * workers or async execution. Callers that need large searches can shard ranges
@@ -155,14 +160,21 @@ export declare namespace validateSalt {
155
160
  * Master addresses must satisfy TIP-1022 registration constraints: they cannot
156
161
  * be the zero address, another virtual address, or a TIP-20 token address.
157
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
+ *
158
171
  * @example
159
172
  * ```ts twoslash
173
+ * import { Address } from 'ox'
160
174
  * import { VirtualMaster } from 'ox/tempo'
161
175
  *
162
176
  * const result = VirtualMaster.mineSalt({
163
177
  * address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
164
- * start: 0xabf52ba0n,
165
- * count: 16,
166
178
  * })
167
179
  *
168
180
  * result?.salt
@@ -175,28 +187,30 @@ export declare namespace validateSalt {
175
187
  export function mineSalt(
176
188
  value: mineSalt.Value,
177
189
  ): mineSalt.ReturnType | undefined {
178
- assertCount(value.count)
190
+ const count = value.count ?? 2 ** 32
191
+
192
+ assertCount(count)
179
193
 
180
- const address = resolveAddress(value.address)
181
- const addressBytes = Bytes.fromHex(address)
182
- const saltBytes = toFixedBytes(value.start ?? 0n, 32)
183
- const input = new Uint8Array(addressBytes.length + saltBytes.length)
194
+ const addressBytes = Bytes.fromHex(resolveAddress(value.address))
195
+ const input = new Uint8Array(addressBytes.length + 32)
184
196
  input.set(addressBytes)
185
197
 
186
- for (let i = 0; i < value.count; i++) {
187
- input.set(saltBytes, addressBytes.length)
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))
188
201
 
189
- const registrationHash = Hash.keccak256(input, { as: 'Bytes' })
202
+ for (let i = 0; i < count; i++) {
203
+ const hash = keccak_256(input)
190
204
 
191
- if (hasProofOfWork(registrationHash)) {
205
+ if (hash[0] === 0 && hash[1] === 0 && hash[2] === 0 && hash[3] === 0) {
192
206
  return {
193
- masterId: Hex.fromBytes(registrationHash.subarray(4, 8)),
194
- registrationHash: Hex.fromBytes(registrationHash),
195
- salt: Hex.fromBytes(saltBytes),
207
+ masterId: Hex.fromBytes(hash.subarray(4, 8)),
208
+ registrationHash: Hex.fromBytes(hash),
209
+ salt: Hex.fromBytes(saltView),
196
210
  }
197
211
  }
198
212
 
199
- if (i < value.count - 1 && !increment(saltBytes)) break
213
+ if (i < count - 1 && !increment(saltView)) break
200
214
  }
201
215
 
202
216
  return undefined
@@ -207,7 +221,7 @@ export declare namespace mineSalt {
207
221
  /** Master address. Accepts both hex and Tempo addresses. */
208
222
  address: TempoAddress.Address
209
223
  /** Number of consecutive salts to try. */
210
- count: number
224
+ count?: number | undefined
211
225
  /** Starting salt value. @default 0n */
212
226
  start?: Salt | undefined
213
227
  }
@@ -235,10 +249,379 @@ export declare namespace mineSalt {
235
249
  | Errors.GlobalErrorType
236
250
  }
237
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
+ */
238
616
  function hasProofOfWork(hash: Bytes.Bytes): boolean {
239
617
  return hash[0] === 0 && hash[1] === 0 && hash[2] === 0 && hash[3] === 0
240
618
  }
241
619
 
620
+ /**
621
+ * Asserts that `count` is a positive safe integer.
622
+ *
623
+ * @internal
624
+ */
242
625
  function assertCount(count: number) {
243
626
  if (Number.isSafeInteger(count) && count > 0) return
244
627
 
@@ -247,6 +630,11 @@ function assertCount(count: number) {
247
630
  )
248
631
  }
249
632
 
633
+ /**
634
+ * Increments a big-endian byte array by one. Returns `false` on overflow.
635
+ *
636
+ * @internal
637
+ */
250
638
  function increment(bytes: Bytes.Bytes): boolean {
251
639
  for (let i = bytes.length - 1; i >= 0; i--) {
252
640
  const value = bytes[i]!
@@ -262,6 +650,11 @@ function increment(bytes: Bytes.Bytes): boolean {
262
650
  return false
263
651
  }
264
652
 
653
+ /**
654
+ * Resolves a Tempo or hex address, validates it as a valid master.
655
+ *
656
+ * @internal
657
+ */
265
658
  function resolveAddress(address: string): Address.Address {
266
659
  const resolved = TempoAddress.resolve(address as TempoAddress.Address)
267
660
  Address.assert(resolved, { strict: false })
@@ -269,6 +662,11 @@ function resolveAddress(address: string): Address.Address {
269
662
  return resolved
270
663
  }
271
664
 
665
+ /**
666
+ * Throws if the address is zero, virtual, or a TIP-20 token.
667
+ *
668
+ * @internal
669
+ */
272
670
  function assertValidMasterAddress(address: Address.Address) {
273
671
  const normalized = address.toLowerCase()
274
672
 
@@ -288,10 +686,20 @@ function assertValidMasterAddress(address: Address.Address) {
288
686
  )
289
687
  }
290
688
 
689
+ /**
690
+ * Converts a salt to a fixed-size byte array.
691
+ *
692
+ * @internal
693
+ */
291
694
  function toFixedBytes(value: Salt, size: number): Bytes.Bytes {
292
695
  return Bytes.fromHex(toFixedHex(value, size))
293
696
  }
294
697
 
698
+ /**
699
+ * Converts a salt to a zero-padded hex string of the given size.
700
+ *
701
+ * @internal
702
+ */
295
703
  function toFixedHex(value: Salt, size: number): Hex.Hex {
296
704
  if (typeof value === 'number' || typeof value === 'bigint')
297
705
  return Hex.fromNumber(value, { size })
package/tempo/index.ts CHANGED
@@ -131,6 +131,7 @@ export * as PoolId from './PoolId.js'
131
131
  *
132
132
  * @example
133
133
  * ```ts twoslash
134
+ * import 'ox/window'
134
135
  * import { Provider, RpcSchema } from 'ox'
135
136
  * import { RpcSchemaTempo } from 'ox/tempo'
136
137
  *
@@ -407,11 +408,12 @@ export * as VirtualAddress from './VirtualAddress.js'
407
408
  *
408
409
  * @example
409
410
  * ```ts twoslash
411
+ * import { Address, Hex } from 'ox'
410
412
  * import { VirtualMaster } from 'ox/tempo'
411
413
  *
412
414
  * const registration = {
413
- * address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
414
- * salt: '0x00000000000000000000000000000000000000000000000000000000abf52baf',
415
+ * address: Address.from('0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'),
416
+ * salt: Hex.from('0x00000000000000000000000000000000000000000000000000000000abf52baf'),
415
417
  * }
416
418
  *
417
419
  * const registrationHash = VirtualMaster.getRegistrationHash(registration) // keccak256(address || salt)
@@ -421,6 +423,7 @@ export * as VirtualAddress from './VirtualAddress.js'
421
423
  * @category Reference
422
424
  */
423
425
  export * as VirtualMaster from './VirtualMaster.js'
426
+
424
427
  /**
425
428
  * Zone ID utilities for converting between zone IDs and zone chain IDs.
426
429
  *