ox 0.14.17 → 0.14.18
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 +6 -0
- package/_cjs/tempo/VirtualAddress.js +70 -0
- package/_cjs/tempo/VirtualAddress.js.map +1 -0
- package/_cjs/tempo/VirtualMaster.js +99 -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/version.js +1 -1
- package/_esm/tempo/VirtualAddress.js +154 -0
- package/_esm/tempo/VirtualAddress.js.map +1 -0
- package/_esm/tempo/VirtualMaster.js +200 -0
- package/_esm/tempo/VirtualMaster.js.map +1 -0
- package/_esm/tempo/index.js +52 -0
- package/_esm/tempo/index.js.map +1 -1
- 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/VirtualAddress.d.ts +129 -0
- package/_types/tempo/VirtualAddress.d.ts.map +1 -0
- package/_types/tempo/VirtualMaster.d.ts +155 -0
- package/_types/tempo/VirtualMaster.d.ts.map +1 -0
- package/_types/tempo/index.d.ts +52 -0
- package/_types/tempo/index.d.ts.map +1 -1
- package/_types/version.d.ts +1 -1
- package/core/RpcSchema.ts +0 -2
- package/package.json +11 -1
- 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 +127 -0
- package/tempo/VirtualMaster.ts +303 -0
- package/tempo/e2e.test.ts +0 -2
- package/tempo/index.ts +52 -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,127 @@
|
|
|
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
|
+
test('exports', () => {
|
|
119
|
+
expect(Object.keys(VirtualMaster)).toMatchInlineSnapshot(`
|
|
120
|
+
[
|
|
121
|
+
"getRegistrationHash",
|
|
122
|
+
"getMasterId",
|
|
123
|
+
"validateSalt",
|
|
124
|
+
"mineSalt",
|
|
125
|
+
]
|
|
126
|
+
`)
|
|
127
|
+
})
|
|
@@ -0,0 +1,303 @@
|
|
|
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 Hash from '../core/Hash.js'
|
|
5
|
+
import * as Hex from '../core/Hex.js'
|
|
6
|
+
import * as TempoAddress from './TempoAddress.js'
|
|
7
|
+
import * as VirtualAddress from './VirtualAddress.js'
|
|
8
|
+
|
|
9
|
+
const tip20Prefix = '0x20c000000000000000000000'
|
|
10
|
+
const zeroAddress = '0x0000000000000000000000000000000000000000'
|
|
11
|
+
|
|
12
|
+
/** A valid salt input for TIP-1022 master registration. */
|
|
13
|
+
export type Salt = Hex.Hex | Bytes.Bytes | number | bigint
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Computes the TIP-1022 registration hash for a master address and salt.
|
|
17
|
+
*
|
|
18
|
+
* [TIP-1022](https://docs.tempo.xyz/protocol/tips/tip-1022)
|
|
19
|
+
*
|
|
20
|
+
* The registration hash is `keccak256(masterAddress || salt)` where `salt`
|
|
21
|
+
* is encoded as a 32-byte value.
|
|
22
|
+
*
|
|
23
|
+
* Master addresses must satisfy TIP-1022 registration constraints: they cannot
|
|
24
|
+
* be the zero address, another virtual address, or a TIP-20 token address.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts twoslash
|
|
28
|
+
* import { VirtualMaster } from 'ox/tempo'
|
|
29
|
+
*
|
|
30
|
+
* const hash = VirtualMaster.getRegistrationHash({
|
|
31
|
+
* address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
|
|
32
|
+
* salt: '0x00000000000000000000000000000000000000000000000000000000abf52baf',
|
|
33
|
+
* })
|
|
34
|
+
*
|
|
35
|
+
* hash
|
|
36
|
+
* // @log: '0x0000000058e21090d8f4bee424b90cddc2378aefa1bbbfa1443631a929ae966d'
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* @param value - Master address and salt.
|
|
40
|
+
* @returns The registration hash.
|
|
41
|
+
*/
|
|
42
|
+
export function getRegistrationHash(value: getRegistrationHash.Value): Hex.Hex {
|
|
43
|
+
return Hash.keccak256(
|
|
44
|
+
Hex.concat(resolveAddress(value.address), toFixedHex(value.salt, 32)),
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export declare namespace getRegistrationHash {
|
|
49
|
+
type Value = {
|
|
50
|
+
/** Master address. Accepts both hex and Tempo addresses. */
|
|
51
|
+
address: TempoAddress.Address
|
|
52
|
+
/** 32-byte salt used for registration. */
|
|
53
|
+
salt: Salt
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
type ErrorType =
|
|
57
|
+
| Address.assert.ErrorType
|
|
58
|
+
| Bytes.padLeft.ErrorType
|
|
59
|
+
| Errors.BaseError
|
|
60
|
+
| Hash.keccak256.ErrorType
|
|
61
|
+
| Hex.assert.ErrorType
|
|
62
|
+
| Hex.fromBytes.ErrorType
|
|
63
|
+
| Hex.fromNumber.ErrorType
|
|
64
|
+
| Hex.padLeft.ErrorType
|
|
65
|
+
| TempoAddress.parse.ErrorType
|
|
66
|
+
| Errors.GlobalErrorType
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Derives the 4-byte TIP-1022 `masterId` from a master address and salt.
|
|
71
|
+
*
|
|
72
|
+
* [TIP-1022](https://docs.tempo.xyz/protocol/tips/tip-1022)
|
|
73
|
+
*
|
|
74
|
+
* This returns bytes `[4:8]` of the registration hash, regardless of whether the
|
|
75
|
+
* salt satisfies the proof-of-work requirement.
|
|
76
|
+
*
|
|
77
|
+
* Master addresses must satisfy TIP-1022 registration constraints: they cannot
|
|
78
|
+
* be the zero address, another virtual address, or a TIP-20 token address.
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```ts twoslash
|
|
82
|
+
* import { VirtualMaster } from 'ox/tempo'
|
|
83
|
+
*
|
|
84
|
+
* const masterId = VirtualMaster.getMasterId({
|
|
85
|
+
* address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
|
|
86
|
+
* salt: '0x00000000000000000000000000000000000000000000000000000000abf52baf',
|
|
87
|
+
* })
|
|
88
|
+
*
|
|
89
|
+
* masterId
|
|
90
|
+
* // @log: '0x58e21090'
|
|
91
|
+
* ```
|
|
92
|
+
*
|
|
93
|
+
* @param value - Master address and salt.
|
|
94
|
+
* @returns The derived master identifier.
|
|
95
|
+
*/
|
|
96
|
+
export function getMasterId(value: getMasterId.Value): Hex.Hex {
|
|
97
|
+
return Hex.slice(getRegistrationHash(value), 4, 8)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export declare namespace getMasterId {
|
|
101
|
+
type Value = getRegistrationHash.Value
|
|
102
|
+
type ErrorType = getRegistrationHash.ErrorType | Errors.GlobalErrorType
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Validates that a salt satisfies the TIP-1022 32-bit proof-of-work requirement.
|
|
107
|
+
*
|
|
108
|
+
* [TIP-1022](https://docs.tempo.xyz/protocol/tips/tip-1022)
|
|
109
|
+
*
|
|
110
|
+
* Returns `false` for invalid master addresses, including the zero address,
|
|
111
|
+
* virtual addresses, and TIP-20 token addresses.
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```ts twoslash
|
|
115
|
+
* import { VirtualMaster } from 'ox/tempo'
|
|
116
|
+
*
|
|
117
|
+
* const valid = VirtualMaster.validateSalt({
|
|
118
|
+
* address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
|
|
119
|
+
* salt: '0x00000000000000000000000000000000000000000000000000000000abf52baf',
|
|
120
|
+
* })
|
|
121
|
+
*
|
|
122
|
+
* valid
|
|
123
|
+
* // @log: true
|
|
124
|
+
* ```
|
|
125
|
+
*
|
|
126
|
+
* @param value - Master address and salt.
|
|
127
|
+
* @returns `true` if the first 4 bytes of the registration hash are zero.
|
|
128
|
+
*/
|
|
129
|
+
export function validateSalt(value: validateSalt.Value): boolean {
|
|
130
|
+
try {
|
|
131
|
+
return hasProofOfWork(
|
|
132
|
+
Hash.keccak256(
|
|
133
|
+
Hex.concat(resolveAddress(value.address), toFixedHex(value.salt, 32)),
|
|
134
|
+
{ as: 'Bytes' },
|
|
135
|
+
),
|
|
136
|
+
)
|
|
137
|
+
} catch {
|
|
138
|
+
return false
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export declare namespace validateSalt {
|
|
143
|
+
type Value = getRegistrationHash.Value
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Searches a bounded range of salts for the first value that satisfies TIP-1022 PoW.
|
|
148
|
+
*
|
|
149
|
+
* [TIP-1022](https://docs.tempo.xyz/protocol/tips/tip-1022)
|
|
150
|
+
*
|
|
151
|
+
* This is intentionally a small, deterministic primitive. It does not coordinate
|
|
152
|
+
* workers or async execution. Callers that need large searches can shard ranges
|
|
153
|
+
* externally.
|
|
154
|
+
*
|
|
155
|
+
* Master addresses must satisfy TIP-1022 registration constraints: they cannot
|
|
156
|
+
* be the zero address, another virtual address, or a TIP-20 token address.
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* ```ts twoslash
|
|
160
|
+
* import { VirtualMaster } from 'ox/tempo'
|
|
161
|
+
*
|
|
162
|
+
* const result = VirtualMaster.mineSalt({
|
|
163
|
+
* address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
|
|
164
|
+
* start: 0xabf52ba0n,
|
|
165
|
+
* count: 16,
|
|
166
|
+
* })
|
|
167
|
+
*
|
|
168
|
+
* result?.salt
|
|
169
|
+
* // @log: '0x00000000000000000000000000000000000000000000000000000000abf52baf'
|
|
170
|
+
* ```
|
|
171
|
+
*
|
|
172
|
+
* @param value - Search range parameters.
|
|
173
|
+
* @returns The first matching salt in the range, if any.
|
|
174
|
+
*/
|
|
175
|
+
export function mineSalt(
|
|
176
|
+
value: mineSalt.Value,
|
|
177
|
+
): mineSalt.ReturnType | undefined {
|
|
178
|
+
assertCount(value.count)
|
|
179
|
+
|
|
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)
|
|
184
|
+
input.set(addressBytes)
|
|
185
|
+
|
|
186
|
+
for (let i = 0; i < value.count; i++) {
|
|
187
|
+
input.set(saltBytes, addressBytes.length)
|
|
188
|
+
|
|
189
|
+
const registrationHash = Hash.keccak256(input, { as: 'Bytes' })
|
|
190
|
+
|
|
191
|
+
if (hasProofOfWork(registrationHash)) {
|
|
192
|
+
return {
|
|
193
|
+
masterId: Hex.fromBytes(registrationHash.subarray(4, 8)),
|
|
194
|
+
registrationHash: Hex.fromBytes(registrationHash),
|
|
195
|
+
salt: Hex.fromBytes(saltBytes),
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (i < value.count - 1 && !increment(saltBytes)) break
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return undefined
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export declare namespace mineSalt {
|
|
206
|
+
type Value = {
|
|
207
|
+
/** Master address. Accepts both hex and Tempo addresses. */
|
|
208
|
+
address: TempoAddress.Address
|
|
209
|
+
/** Number of consecutive salts to try. */
|
|
210
|
+
count: number
|
|
211
|
+
/** Starting salt value. @default 0n */
|
|
212
|
+
start?: Salt | undefined
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
type ReturnType = {
|
|
216
|
+
/** The 4-byte master identifier derived from the matching salt. */
|
|
217
|
+
masterId: Hex.Hex
|
|
218
|
+
/** The matching registration hash. */
|
|
219
|
+
registrationHash: Hex.Hex
|
|
220
|
+
/** The discovered 32-byte salt. */
|
|
221
|
+
salt: Hex.Hex
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
type ErrorType =
|
|
225
|
+
| Address.assert.ErrorType
|
|
226
|
+
| Bytes.fromHex.ErrorType
|
|
227
|
+
| Bytes.padLeft.ErrorType
|
|
228
|
+
| Errors.BaseError
|
|
229
|
+
| Hash.keccak256.ErrorType
|
|
230
|
+
| Hex.assert.ErrorType
|
|
231
|
+
| Hex.fromBytes.ErrorType
|
|
232
|
+
| Hex.fromNumber.ErrorType
|
|
233
|
+
| Hex.padLeft.ErrorType
|
|
234
|
+
| TempoAddress.parse.ErrorType
|
|
235
|
+
| Errors.GlobalErrorType
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function hasProofOfWork(hash: Bytes.Bytes): boolean {
|
|
239
|
+
return hash[0] === 0 && hash[1] === 0 && hash[2] === 0 && hash[3] === 0
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function assertCount(count: number) {
|
|
243
|
+
if (Number.isSafeInteger(count) && count > 0) return
|
|
244
|
+
|
|
245
|
+
throw new Errors.BaseError(
|
|
246
|
+
`Count "${count}" is invalid. Expected a positive safe integer.`,
|
|
247
|
+
)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function increment(bytes: Bytes.Bytes): boolean {
|
|
251
|
+
for (let i = bytes.length - 1; i >= 0; i--) {
|
|
252
|
+
const value = bytes[i]!
|
|
253
|
+
if (value === 0xff) {
|
|
254
|
+
bytes[i] = 0
|
|
255
|
+
continue
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
bytes[i] = value + 1
|
|
259
|
+
return true
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return false
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function resolveAddress(address: string): Address.Address {
|
|
266
|
+
const resolved = TempoAddress.resolve(address as TempoAddress.Address)
|
|
267
|
+
Address.assert(resolved, { strict: false })
|
|
268
|
+
assertValidMasterAddress(resolved)
|
|
269
|
+
return resolved
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function assertValidMasterAddress(address: Address.Address) {
|
|
273
|
+
const normalized = address.toLowerCase()
|
|
274
|
+
|
|
275
|
+
if (normalized === zeroAddress)
|
|
276
|
+
throw new Errors.BaseError(
|
|
277
|
+
'Virtual master address cannot be the zero address.',
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
if (VirtualAddress.isVirtual(address))
|
|
281
|
+
throw new Errors.BaseError(
|
|
282
|
+
'Virtual master address cannot itself be a virtual address.',
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
if (normalized.startsWith(tip20Prefix))
|
|
286
|
+
throw new Errors.BaseError(
|
|
287
|
+
'Virtual master address cannot be a TIP-20 token address.',
|
|
288
|
+
)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function toFixedBytes(value: Salt, size: number): Bytes.Bytes {
|
|
292
|
+
return Bytes.fromHex(toFixedHex(value, size))
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function toFixedHex(value: Salt, size: number): Hex.Hex {
|
|
296
|
+
if (typeof value === 'number' || typeof value === 'bigint')
|
|
297
|
+
return Hex.fromNumber(value, { size })
|
|
298
|
+
if (typeof value === 'string') {
|
|
299
|
+
Hex.assert(value, { strict: true })
|
|
300
|
+
return Hex.padLeft(value, size)
|
|
301
|
+
}
|
|
302
|
+
return Hex.fromBytes(Bytes.padLeft(value, size))
|
|
303
|
+
}
|
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'
|
package/tempo/index.ts
CHANGED
|
@@ -369,6 +369,58 @@ export * as TransactionRequest from './TransactionRequest.js'
|
|
|
369
369
|
* @category Reference
|
|
370
370
|
*/
|
|
371
371
|
export * as TxEnvelopeTempo from './TxEnvelopeTempo.js'
|
|
372
|
+
/**
|
|
373
|
+
* TIP-1022 virtual address encoding and parsing utilities.
|
|
374
|
+
*
|
|
375
|
+
* [TIP-1022](https://docs.tempo.xyz/protocol/tips/tip-1022)
|
|
376
|
+
*
|
|
377
|
+
* Virtual addresses reserve the following 20-byte layout:
|
|
378
|
+
* `[4-byte masterId][10-byte VIRTUAL_MAGIC][6-byte userTag]`.
|
|
379
|
+
* These helpers only operate on the reserved byte layout and do not query
|
|
380
|
+
* onchain registration state.
|
|
381
|
+
*
|
|
382
|
+
* @example
|
|
383
|
+
* ```ts twoslash
|
|
384
|
+
* import { TempoAddress, VirtualAddress } from 'ox/tempo'
|
|
385
|
+
*
|
|
386
|
+
* const masterId = '0x58e21090' // derived when the master registers
|
|
387
|
+
* const userTag = '0x010203040506' // operator-defined deposit identifier
|
|
388
|
+
*
|
|
389
|
+
* const address = VirtualAddress.from({
|
|
390
|
+
* masterId,
|
|
391
|
+
* userTag,
|
|
392
|
+
* })
|
|
393
|
+
*
|
|
394
|
+
* const tempoAddress = TempoAddress.format(address) // optional display format
|
|
395
|
+
* ```
|
|
396
|
+
*
|
|
397
|
+
* @category Reference
|
|
398
|
+
*/
|
|
399
|
+
export * as VirtualAddress from './VirtualAddress.js'
|
|
400
|
+
/**
|
|
401
|
+
* TIP-1022 master registration utilities.
|
|
402
|
+
*
|
|
403
|
+
* [TIP-1022](https://docs.tempo.xyz/protocol/tips/tip-1022)
|
|
404
|
+
*
|
|
405
|
+
* These utilities expose deterministic hashing and bounded salt mining helpers for
|
|
406
|
+
* `registerVirtualMaster(bytes32 salt)` without introducing any extra hashing dependency.
|
|
407
|
+
*
|
|
408
|
+
* @example
|
|
409
|
+
* ```ts twoslash
|
|
410
|
+
* import { VirtualMaster } from 'ox/tempo'
|
|
411
|
+
*
|
|
412
|
+
* const registration = {
|
|
413
|
+
* address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
|
|
414
|
+
* salt: '0x00000000000000000000000000000000000000000000000000000000abf52baf',
|
|
415
|
+
* }
|
|
416
|
+
*
|
|
417
|
+
* const registrationHash = VirtualMaster.getRegistrationHash(registration) // keccak256(address || salt)
|
|
418
|
+
* const masterId = VirtualMaster.getMasterId(registration) // bytes [4:8] of the hash
|
|
419
|
+
* ```
|
|
420
|
+
*
|
|
421
|
+
* @category Reference
|
|
422
|
+
*/
|
|
423
|
+
export * as VirtualMaster from './VirtualMaster.js'
|
|
372
424
|
/**
|
|
373
425
|
* Zone ID utilities for converting between zone IDs and zone chain IDs.
|
|
374
426
|
*
|