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,201 @@
|
|
|
1
|
+
import * as Address from '../core/Address.js'
|
|
2
|
+
import * as Bytes from '../core/Bytes.js'
|
|
3
|
+
import * as Errors from '../core/Errors.js'
|
|
4
|
+
import * as Hex from '../core/Hex.js'
|
|
5
|
+
import * as TempoAddress from './TempoAddress.js'
|
|
6
|
+
|
|
7
|
+
/** Fixed 10-byte marker used by TIP-1022 virtual addresses. */
|
|
8
|
+
export const magic = '0xfdfdfdfdfdfdfdfdfdfd' as const
|
|
9
|
+
|
|
10
|
+
/** A fixed-width virtual address component. */
|
|
11
|
+
export type Part = Hex.Hex | Bytes.Bytes | number | bigint
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Builds a TIP-1022 virtual address from a `masterId` and `userTag`.
|
|
15
|
+
*
|
|
16
|
+
* [TIP-1022](https://docs.tempo.xyz/protocol/tips/tip-1022)
|
|
17
|
+
*
|
|
18
|
+
* TIP-1022 encodes virtual addresses as:
|
|
19
|
+
* `[4-byte masterId][10-byte VIRTUAL_MAGIC][6-byte userTag]`
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts twoslash
|
|
23
|
+
* import { VirtualAddress } from 'ox/tempo'
|
|
24
|
+
*
|
|
25
|
+
* const address = VirtualAddress.from({
|
|
26
|
+
* masterId: '0x58e21090',
|
|
27
|
+
* userTag: '0x010203040506',
|
|
28
|
+
* })
|
|
29
|
+
*
|
|
30
|
+
* address
|
|
31
|
+
* // @log: '0x58e21090fdfdfdfdfdfdfdfdfdfd010203040506'
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* @param value - The virtual address parts.
|
|
35
|
+
* @returns The virtual address.
|
|
36
|
+
*/
|
|
37
|
+
export function from(value: from.Value): Address.Address {
|
|
38
|
+
return Address.from(
|
|
39
|
+
Hex.concat(
|
|
40
|
+
toFixedHex(value.masterId, 4),
|
|
41
|
+
magic,
|
|
42
|
+
toFixedHex(value.userTag, 6),
|
|
43
|
+
),
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export declare namespace from {
|
|
48
|
+
type Value = {
|
|
49
|
+
/** 4-byte master identifier. */
|
|
50
|
+
masterId: Part
|
|
51
|
+
/** 6-byte opaque user tag. */
|
|
52
|
+
userTag: Part
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
type ErrorType =
|
|
56
|
+
| Address.from.ErrorType
|
|
57
|
+
| Bytes.padLeft.ErrorType
|
|
58
|
+
| Hex.assert.ErrorType
|
|
59
|
+
| Hex.fromBytes.ErrorType
|
|
60
|
+
| Hex.fromNumber.ErrorType
|
|
61
|
+
| Hex.padLeft.ErrorType
|
|
62
|
+
| Errors.GlobalErrorType
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Checks whether an address matches the TIP-1022 virtual address format.
|
|
67
|
+
*
|
|
68
|
+
* [TIP-1022](https://docs.tempo.xyz/protocol/tips/tip-1022)
|
|
69
|
+
*
|
|
70
|
+
* This only checks the reserved byte layout, not whether the `masterId`
|
|
71
|
+
* is registered onchain.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```ts twoslash
|
|
75
|
+
* import { VirtualAddress } from 'ox/tempo'
|
|
76
|
+
*
|
|
77
|
+
* const isVirtual = VirtualAddress.isVirtual(
|
|
78
|
+
* '0x58e21090fdfdfdfdfdfdfdfdfdfd010203040506',
|
|
79
|
+
* )
|
|
80
|
+
*
|
|
81
|
+
* isVirtual
|
|
82
|
+
* // @log: true
|
|
83
|
+
* ```
|
|
84
|
+
*
|
|
85
|
+
* @param address - Address to check.
|
|
86
|
+
* @returns `true` if the address matches the virtual-address layout.
|
|
87
|
+
*/
|
|
88
|
+
export function isVirtual(address: string): boolean {
|
|
89
|
+
try {
|
|
90
|
+
const resolved = resolveAddress(address)
|
|
91
|
+
return Hex.slice(resolved, 4, 14).toLowerCase() === magic
|
|
92
|
+
} catch {
|
|
93
|
+
return false
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Parses a TIP-1022 virtual address into its `masterId` and `userTag` parts.
|
|
99
|
+
*
|
|
100
|
+
* [TIP-1022](https://docs.tempo.xyz/protocol/tips/tip-1022)
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```ts twoslash
|
|
104
|
+
* import { VirtualAddress } from 'ox/tempo'
|
|
105
|
+
*
|
|
106
|
+
* const parsed = VirtualAddress.parse(
|
|
107
|
+
* '0x58e21090fdfdfdfdfdfdfdfdfdfd010203040506',
|
|
108
|
+
* )
|
|
109
|
+
*
|
|
110
|
+
* parsed
|
|
111
|
+
* // @log: { masterId: '0x58e21090', userTag: '0x010203040506' }
|
|
112
|
+
* ```
|
|
113
|
+
*
|
|
114
|
+
* @param address - The virtual address to parse.
|
|
115
|
+
* @returns The decoded virtual address components.
|
|
116
|
+
*/
|
|
117
|
+
export function parse(address: string): parse.ReturnType {
|
|
118
|
+
const resolved = resolveAddress(address)
|
|
119
|
+
|
|
120
|
+
if (Hex.slice(resolved, 4, 14).toLowerCase() !== magic)
|
|
121
|
+
throw new InvalidMagicError({ address: resolved })
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
masterId: Hex.slice(resolved, 0, 4),
|
|
125
|
+
userTag: Hex.slice(resolved, 14, 20),
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export declare namespace parse {
|
|
130
|
+
type ReturnType = {
|
|
131
|
+
/** 4-byte master identifier. */
|
|
132
|
+
masterId: Hex.Hex
|
|
133
|
+
/** 6-byte opaque user tag. */
|
|
134
|
+
userTag: Hex.Hex
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
type ErrorType =
|
|
138
|
+
| Address.assert.ErrorType
|
|
139
|
+
| InvalidMagicError
|
|
140
|
+
| TempoAddress.parse.ErrorType
|
|
141
|
+
| Errors.GlobalErrorType
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Validates that an address matches the TIP-1022 virtual address format.
|
|
146
|
+
*
|
|
147
|
+
* [TIP-1022](https://docs.tempo.xyz/protocol/tips/tip-1022)
|
|
148
|
+
*
|
|
149
|
+
* This only validates the reserved byte layout, not whether the `masterId`
|
|
150
|
+
* resolves to a registered master onchain.
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* ```ts twoslash
|
|
154
|
+
* import { VirtualAddress } from 'ox/tempo'
|
|
155
|
+
*
|
|
156
|
+
* const valid = VirtualAddress.validate(
|
|
157
|
+
* '0x58e21090fdfdfdfdfdfdfdfdfdfd010203040506',
|
|
158
|
+
* )
|
|
159
|
+
*
|
|
160
|
+
* valid
|
|
161
|
+
* // @log: true
|
|
162
|
+
* ```
|
|
163
|
+
*
|
|
164
|
+
* @param address - Address to validate.
|
|
165
|
+
* @returns `true` if the address has a valid virtual-address layout.
|
|
166
|
+
*/
|
|
167
|
+
export function validate(address: string): boolean {
|
|
168
|
+
try {
|
|
169
|
+
parse(address)
|
|
170
|
+
return true
|
|
171
|
+
} catch {
|
|
172
|
+
return false
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Thrown when an address does not contain the TIP-1022 virtual marker. */
|
|
177
|
+
export class InvalidMagicError extends Errors.BaseError {
|
|
178
|
+
override readonly name = 'VirtualAddress.InvalidMagicError'
|
|
179
|
+
|
|
180
|
+
constructor({ address }: { address: string }) {
|
|
181
|
+
super(
|
|
182
|
+
`Address "${address}" does not contain the TIP-1022 virtual address marker.`,
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function resolveAddress(address: string): Address.Address {
|
|
188
|
+
const resolved = TempoAddress.resolve(address as TempoAddress.Address)
|
|
189
|
+
Address.assert(resolved, { strict: false })
|
|
190
|
+
return resolved
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function toFixedHex(value: Part, size: number): Hex.Hex {
|
|
194
|
+
if (typeof value === 'number' || typeof value === 'bigint')
|
|
195
|
+
return Hex.fromNumber(value, { size })
|
|
196
|
+
if (typeof value === 'string') {
|
|
197
|
+
Hex.assert(value, { strict: true })
|
|
198
|
+
return Hex.padLeft(value, size)
|
|
199
|
+
}
|
|
200
|
+
return Hex.fromBytes(Bytes.padLeft(value, size))
|
|
201
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { TempoAddress, VirtualMaster } from 'ox/tempo'
|
|
2
|
+
import { describe, expect, test } from 'vitest'
|
|
3
|
+
|
|
4
|
+
const address = '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'
|
|
5
|
+
const tempoAddress = TempoAddress.format(address)
|
|
6
|
+
const salt =
|
|
7
|
+
'0x00000000000000000000000000000000000000000000000000000000abf52baf'
|
|
8
|
+
const masterId = '0x58e21090'
|
|
9
|
+
const registrationHash =
|
|
10
|
+
'0x0000000058e21090d8f4bee424b90cddc2378aefa1bbbfa1443631a929ae966d'
|
|
11
|
+
const virtualAddress = '0x58e21090fdfdfdfdfdfdfdfdfdfd010203040506'
|
|
12
|
+
const tip20Address = '0x20c0000000000000000000000000000000000001'
|
|
13
|
+
|
|
14
|
+
describe('getRegistrationHash', () => {
|
|
15
|
+
test('raw address', () => {
|
|
16
|
+
expect(
|
|
17
|
+
VirtualMaster.getRegistrationHash({
|
|
18
|
+
address,
|
|
19
|
+
salt,
|
|
20
|
+
}),
|
|
21
|
+
).toBe(registrationHash)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
test('tempo address', () => {
|
|
25
|
+
expect(
|
|
26
|
+
VirtualMaster.getRegistrationHash({
|
|
27
|
+
address: tempoAddress,
|
|
28
|
+
salt,
|
|
29
|
+
}),
|
|
30
|
+
).toBe(registrationHash)
|
|
31
|
+
})
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
describe('getMasterId', () => {
|
|
35
|
+
test('default', () => {
|
|
36
|
+
expect(
|
|
37
|
+
VirtualMaster.getMasterId({
|
|
38
|
+
address,
|
|
39
|
+
salt,
|
|
40
|
+
}),
|
|
41
|
+
).toBe(masterId)
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
describe('validateSalt', () => {
|
|
46
|
+
test('returns true for valid salt', () => {
|
|
47
|
+
expect(VirtualMaster.validateSalt({ address, salt })).toBe(true)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test('returns false for invalid salt', () => {
|
|
51
|
+
expect(VirtualMaster.validateSalt({ address, salt: 0n })).toBe(false)
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
describe('mineSalt', () => {
|
|
56
|
+
test('finds the first salt in range with the default keccak path', () => {
|
|
57
|
+
expect(
|
|
58
|
+
VirtualMaster.mineSalt({
|
|
59
|
+
address,
|
|
60
|
+
count: 16,
|
|
61
|
+
start: 0xabf52ba0n,
|
|
62
|
+
}),
|
|
63
|
+
).toMatchInlineSnapshot(`
|
|
64
|
+
{
|
|
65
|
+
"masterId": "0x58e21090",
|
|
66
|
+
"registrationHash": "0x0000000058e21090d8f4bee424b90cddc2378aefa1bbbfa1443631a929ae966d",
|
|
67
|
+
"salt": "0x00000000000000000000000000000000000000000000000000000000abf52baf",
|
|
68
|
+
}
|
|
69
|
+
`)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test('returns undefined when no salt is found in range', () => {
|
|
73
|
+
expect(
|
|
74
|
+
VirtualMaster.mineSalt({
|
|
75
|
+
address,
|
|
76
|
+
count: 1,
|
|
77
|
+
start: 0n,
|
|
78
|
+
}),
|
|
79
|
+
).toBeUndefined()
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
test('throws for a non-integer count', () => {
|
|
83
|
+
expect(() =>
|
|
84
|
+
VirtualMaster.mineSalt({
|
|
85
|
+
address,
|
|
86
|
+
count: 1.5,
|
|
87
|
+
}),
|
|
88
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
89
|
+
`[BaseError: Count "1.5" is invalid. Expected a positive safe integer.]`,
|
|
90
|
+
)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
test('throws for a non-finite count', () => {
|
|
94
|
+
expect(() =>
|
|
95
|
+
VirtualMaster.mineSalt({
|
|
96
|
+
address,
|
|
97
|
+
count: Number.POSITIVE_INFINITY,
|
|
98
|
+
}),
|
|
99
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
100
|
+
`[BaseError: Count "Infinity" is invalid. Expected a positive safe integer.]`,
|
|
101
|
+
)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
test.each([
|
|
105
|
+
['zero address', '0x0000000000000000000000000000000000000000'],
|
|
106
|
+
['virtual address', virtualAddress],
|
|
107
|
+
['TIP-20 token address', tip20Address],
|
|
108
|
+
])('rejects %s as a virtual master', (_label, invalidAddress) => {
|
|
109
|
+
expect(() =>
|
|
110
|
+
VirtualMaster.mineSalt({
|
|
111
|
+
address: invalidAddress as VirtualMaster.mineSalt.Value['address'],
|
|
112
|
+
count: 1,
|
|
113
|
+
}),
|
|
114
|
+
).toThrowError()
|
|
115
|
+
})
|
|
116
|
+
})
|
|
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
|
+
|
|
194
|
+
test('exports', () => {
|
|
195
|
+
expect(Object.keys(VirtualMaster)).toMatchInlineSnapshot(`
|
|
196
|
+
[
|
|
197
|
+
"getRegistrationHash",
|
|
198
|
+
"getMasterId",
|
|
199
|
+
"validateSalt",
|
|
200
|
+
"mineSalt",
|
|
201
|
+
"mineSaltAsync",
|
|
202
|
+
]
|
|
203
|
+
`)
|
|
204
|
+
})
|