ox 0.14.16 → 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 +16 -0
- package/_cjs/tempo/TransactionRequest.js +10 -1
- package/_cjs/tempo/TransactionRequest.js.map +1 -1
- 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/ZoneId.js +13 -0
- package/_cjs/tempo/ZoneId.js.map +1 -0
- package/_cjs/tempo/ZoneRpcAuthentication.js +101 -0
- package/_cjs/tempo/ZoneRpcAuthentication.js.map +1 -0
- package/_cjs/tempo/index.js +5 -1
- package/_cjs/tempo/index.js.map +1 -1
- package/_cjs/version.js +1 -1
- package/_esm/tempo/TransactionRequest.js +10 -1
- package/_esm/tempo/TransactionRequest.js.map +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/ZoneId.js +47 -0
- package/_esm/tempo/ZoneId.js.map +1 -0
- package/_esm/tempo/ZoneRpcAuthentication.js +256 -0
- package/_esm/tempo/ZoneRpcAuthentication.js.map +1 -0
- package/_esm/tempo/index.js +107 -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/TransactionRequest.d.ts +6 -4
- package/_types/tempo/TransactionRequest.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/ZoneId.d.ts +50 -0
- package/_types/tempo/ZoneId.d.ts.map +1 -0
- package/_types/tempo/ZoneRpcAuthentication.d.ts +268 -0
- package/_types/tempo/ZoneRpcAuthentication.d.ts.map +1 -0
- package/_types/tempo/index.d.ts +107 -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 +21 -1
- package/tempo/TransactionRequest.test.ts +26 -2
- package/tempo/TransactionRequest.ts +17 -7
- 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/ZoneId/package.json +6 -0
- package/tempo/ZoneId.test.ts +42 -0
- package/tempo/ZoneId.ts +58 -0
- package/tempo/ZoneRpcAuthentication/package.json +6 -0
- package/tempo/ZoneRpcAuthentication.test.ts +226 -0
- package/tempo/ZoneRpcAuthentication.ts +423 -0
- package/tempo/index.ts +107 -8
- package/version.ts +1 -1
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest'
|
|
2
|
+
import * as ZoneId from './ZoneId.js'
|
|
3
|
+
|
|
4
|
+
describe('fromChainId', () => {
|
|
5
|
+
test('default', () => {
|
|
6
|
+
expect(ZoneId.fromChainId(4_217_000_006)).toBe(6)
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
test('zone 1', () => {
|
|
10
|
+
expect(ZoneId.fromChainId(4_217_000_001)).toBe(1)
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
test('zone 28', () => {
|
|
14
|
+
expect(ZoneId.fromChainId(4_217_000_028)).toBe(28)
|
|
15
|
+
})
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
describe('toChainId', () => {
|
|
19
|
+
test('default', () => {
|
|
20
|
+
expect(ZoneId.toChainId(6)).toBe(4_217_000_006)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
test('zone 1', () => {
|
|
24
|
+
expect(ZoneId.toChainId(1)).toBe(4_217_000_001)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
test('zone 28', () => {
|
|
28
|
+
expect(ZoneId.toChainId(28)).toBe(4_217_000_028)
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
describe('roundtrip', () => {
|
|
33
|
+
test('fromChainId → toChainId', () => {
|
|
34
|
+
const chainId = 4_217_000_006
|
|
35
|
+
expect(ZoneId.toChainId(ZoneId.fromChainId(chainId))).toBe(chainId)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
test('toChainId → fromChainId', () => {
|
|
39
|
+
const zoneId = 42
|
|
40
|
+
expect(ZoneId.fromChainId(ZoneId.toChainId(zoneId))).toBe(zoneId)
|
|
41
|
+
})
|
|
42
|
+
})
|
package/tempo/ZoneId.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type * as Errors from '../core/Errors.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Base offset for deriving zone chain IDs.
|
|
5
|
+
*
|
|
6
|
+
* Zone chain IDs are computed as `chainIdBase + zoneId`.
|
|
7
|
+
*/
|
|
8
|
+
export const chainIdBase = 4_217_000_000 as const
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Derives a zone ID from a zone chain ID.
|
|
12
|
+
*
|
|
13
|
+
* Zone chain IDs follow the formula `4_217_000_000 + zoneId`, so a chain ID
|
|
14
|
+
* of `4217000006` corresponds to zone ID `6`.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts twoslash
|
|
18
|
+
* import { ZoneId } from 'ox/tempo'
|
|
19
|
+
*
|
|
20
|
+
* const zoneId = ZoneId.fromChainId(4_217_000_006)
|
|
21
|
+
* // @log: 6
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @param chainId - The zone chain ID.
|
|
25
|
+
* @returns The zone ID.
|
|
26
|
+
*/
|
|
27
|
+
export function fromChainId(chainId: number): number {
|
|
28
|
+
return chainId - chainIdBase
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export declare namespace fromChainId {
|
|
32
|
+
type ErrorType = Errors.GlobalErrorType
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Derives a zone chain ID from a zone ID.
|
|
37
|
+
*
|
|
38
|
+
* Zone chain IDs follow the formula `4_217_000_000 + zoneId`, so zone ID
|
|
39
|
+
* `6` corresponds to chain ID `4217000006`.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```ts twoslash
|
|
43
|
+
* import { ZoneId } from 'ox/tempo'
|
|
44
|
+
*
|
|
45
|
+
* const chainId = ZoneId.toChainId(6)
|
|
46
|
+
* // @log: 4217000006
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* @param zoneId - The zone ID.
|
|
50
|
+
* @returns The zone chain ID.
|
|
51
|
+
*/
|
|
52
|
+
export function toChainId(zoneId: number): number {
|
|
53
|
+
return chainIdBase + zoneId
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export declare namespace toChainId {
|
|
57
|
+
type ErrorType = Errors.GlobalErrorType
|
|
58
|
+
}
|