ox 0.14.16 → 0.14.17

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 (40) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/_cjs/tempo/TransactionRequest.js +10 -1
  3. package/_cjs/tempo/TransactionRequest.js.map +1 -1
  4. package/_cjs/tempo/ZoneId.js +13 -0
  5. package/_cjs/tempo/ZoneId.js.map +1 -0
  6. package/_cjs/tempo/ZoneRpcAuthentication.js +101 -0
  7. package/_cjs/tempo/ZoneRpcAuthentication.js.map +1 -0
  8. package/_cjs/tempo/index.js +3 -1
  9. package/_cjs/tempo/index.js.map +1 -1
  10. package/_cjs/version.js +1 -1
  11. package/_esm/tempo/TransactionRequest.js +10 -1
  12. package/_esm/tempo/TransactionRequest.js.map +1 -1
  13. package/_esm/tempo/ZoneId.js +47 -0
  14. package/_esm/tempo/ZoneId.js.map +1 -0
  15. package/_esm/tempo/ZoneRpcAuthentication.js +256 -0
  16. package/_esm/tempo/ZoneRpcAuthentication.js.map +1 -0
  17. package/_esm/tempo/index.js +55 -0
  18. package/_esm/tempo/index.js.map +1 -1
  19. package/_esm/version.js +1 -1
  20. package/_types/tempo/TransactionRequest.d.ts +6 -4
  21. package/_types/tempo/TransactionRequest.d.ts.map +1 -1
  22. package/_types/tempo/ZoneId.d.ts +50 -0
  23. package/_types/tempo/ZoneId.d.ts.map +1 -0
  24. package/_types/tempo/ZoneRpcAuthentication.d.ts +268 -0
  25. package/_types/tempo/ZoneRpcAuthentication.d.ts.map +1 -0
  26. package/_types/tempo/index.d.ts +55 -0
  27. package/_types/tempo/index.d.ts.map +1 -1
  28. package/_types/version.d.ts +1 -1
  29. package/package.json +11 -1
  30. package/tempo/TransactionRequest.test.ts +26 -2
  31. package/tempo/TransactionRequest.ts +17 -7
  32. package/tempo/ZoneId/package.json +6 -0
  33. package/tempo/ZoneId.test.ts +42 -0
  34. package/tempo/ZoneId.ts +58 -0
  35. package/tempo/ZoneRpcAuthentication/package.json +6 -0
  36. package/tempo/ZoneRpcAuthentication.test.ts +226 -0
  37. package/tempo/ZoneRpcAuthentication.ts +423 -0
  38. package/tempo/e2e.test.ts +2 -0
  39. package/tempo/index.ts +55 -8
  40. package/version.ts +1 -1
@@ -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
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "type": "module",
3
+ "types": "../../_types/tempo/ZoneRpcAuthentication.d.ts",
4
+ "main": "../../_cjs/tempo/ZoneRpcAuthentication.js",
5
+ "module": "../../_esm/tempo/ZoneRpcAuthentication.js"
6
+ }
@@ -0,0 +1,226 @@
1
+ import { PublicKey, RpcTransport, Secp256k1, Signature, WebAuthnP256 } from 'ox'
2
+ import { describe, expect, expectTypeOf, test } from 'vitest'
3
+ import * as SignatureEnvelope from './SignatureEnvelope.js'
4
+ import * as ZoneRpcAuthentication from './ZoneRpcAuthentication.js'
5
+
6
+ const privateKey =
7
+ '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80' as const
8
+
9
+ const publicKey = PublicKey.from({
10
+ prefix: 4,
11
+ x: 78495282704852028275327922540131762143565388050940484317945369745559774511861n,
12
+ y: 8109764566587999957624872393871720746996669263962991155166704261108473113504n,
13
+ })
14
+
15
+ const p256Signature = Signature.from({
16
+ r: 92602584010956101470289867944347135737570451066466093224269890121909314569518n,
17
+ s: 54171125190222965779385658110416711469231271457324878825831748147306957269813n,
18
+ yParity: 0,
19
+ })
20
+
21
+ const authentication = {
22
+ chainId: 4217000006,
23
+ expiresAt: 1711235160,
24
+ issuedAt: 1711234560,
25
+ zoneId: 6,
26
+ } as const
27
+
28
+ describe('from', () => {
29
+ test('default', () => {
30
+ const token = ZoneRpcAuthentication.from(authentication)
31
+
32
+ expectTypeOf(token).toExtend<
33
+ ZoneRpcAuthentication.ZoneRpcAuthentication<false>
34
+ >()
35
+
36
+ expect(token).toMatchInlineSnapshot(`
37
+ {
38
+ "chainId": 4217000006,
39
+ "expiresAt": 1711235160,
40
+ "issuedAt": 1711234560,
41
+ "version": 0,
42
+ "zoneId": 6,
43
+ }
44
+ `)
45
+ })
46
+
47
+ test('options: signature', () => {
48
+ const token = ZoneRpcAuthentication.from(authentication)
49
+ const signature = Secp256k1.sign({
50
+ payload: ZoneRpcAuthentication.getSignPayload(token),
51
+ privateKey,
52
+ })
53
+
54
+ const token_signed = ZoneRpcAuthentication.from(token, { signature })
55
+
56
+ expectTypeOf(token_signed).toExtend<
57
+ ZoneRpcAuthentication.ZoneRpcAuthentication<true>
58
+ >()
59
+ expect(token_signed).toMatchInlineSnapshot(`
60
+ {
61
+ "chainId": 4217000006,
62
+ "expiresAt": 1711235160,
63
+ "issuedAt": 1711234560,
64
+ "signature": {
65
+ "signature": {
66
+ "r": 97341227674200655943359900797834667459856344667825437562901364609374126504358n,
67
+ "s": 25574506064018953291781981030565094064718304178734850538810668278475710350395n,
68
+ "yParity": 0,
69
+ },
70
+ "type": "secp256k1",
71
+ },
72
+ "version": 0,
73
+ "zoneId": 6,
74
+ }
75
+ `)
76
+ })
77
+ })
78
+
79
+ describe('getFields', () => {
80
+ test('default', () => {
81
+ const token = ZoneRpcAuthentication.from(authentication)
82
+
83
+ const fields = ZoneRpcAuthentication.getFields(token)
84
+ // 29 bytes = 58 hex chars + 0x prefix
85
+ expect(fields).toMatch(/^0x[0-9a-f]{58}$/i)
86
+
87
+ const payload = ZoneRpcAuthentication.getSignPayload(token)
88
+ // keccak256 = 32 bytes
89
+ expect(payload).toMatch(/^0x[0-9a-f]{64}$/)
90
+
91
+ const keychainPayload = ZoneRpcAuthentication.getSignPayload(token, {
92
+ userAddress: '0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c',
93
+ })
94
+ expect(keychainPayload).toMatch(/^0x[0-9a-f]{64}$/)
95
+ expect(keychainPayload).not.toBe(payload)
96
+ })
97
+ })
98
+
99
+ describe('serialize', () => {
100
+ test('roundtrip', () => {
101
+ const token = ZoneRpcAuthentication.from(authentication)
102
+ const signature = Secp256k1.sign({
103
+ payload: ZoneRpcAuthentication.getSignPayload(token),
104
+ privateKey,
105
+ })
106
+
107
+ const serialized = ZoneRpcAuthentication.serialize(token, {
108
+ signature,
109
+ })
110
+
111
+ const deserialized = ZoneRpcAuthentication.deserialize(serialized)
112
+ expect(deserialized.chainId).toBe(authentication.chainId)
113
+ expect(deserialized.zoneId).toBe(authentication.zoneId)
114
+ expect(deserialized.issuedAt).toBe(authentication.issuedAt)
115
+ expect(deserialized.expiresAt).toBe(authentication.expiresAt)
116
+ expect(deserialized.version).toBe(0)
117
+ expect(deserialized.signature).toBeDefined()
118
+ })
119
+
120
+ test('parses variable-length keychain signatures from the end', () => {
121
+ const token = ZoneRpcAuthentication.from(authentication)
122
+ const inner = SignatureEnvelope.from(
123
+ Secp256k1.sign({
124
+ payload: ZoneRpcAuthentication.getSignPayload(token, {
125
+ userAddress: '0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c',
126
+ }),
127
+ privateKey,
128
+ }),
129
+ )
130
+
131
+ const serialized = ZoneRpcAuthentication.serialize(token, {
132
+ signature: SignatureEnvelope.from({
133
+ inner,
134
+ type: 'keychain',
135
+ userAddress: '0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c',
136
+ version: 'v2',
137
+ }),
138
+ })
139
+
140
+ const deserialized = ZoneRpcAuthentication.deserialize(serialized)
141
+ expect(deserialized.chainId).toBe(authentication.chainId)
142
+ expect(deserialized.zoneId).toBe(authentication.zoneId)
143
+ expect(deserialized.signature.type).toBe('keychain')
144
+ })
145
+
146
+ test('parses variable-length webAuthn signatures from the end', () => {
147
+ const token = ZoneRpcAuthentication.from(authentication)
148
+ const signature = SignatureEnvelope.from({
149
+ signature: p256Signature,
150
+ publicKey,
151
+ metadata: {
152
+ authenticatorData: WebAuthnP256.getAuthenticatorData({
153
+ rpId: 'localhost',
154
+ }),
155
+ clientDataJSON: WebAuthnP256.getClientDataJSON({
156
+ challenge: ZoneRpcAuthentication.getSignPayload(token),
157
+ origin: 'http://localhost',
158
+ }),
159
+ },
160
+ })
161
+
162
+ const serialized = ZoneRpcAuthentication.serialize(token, {
163
+ signature,
164
+ })
165
+
166
+ const deserialized = ZoneRpcAuthentication.deserialize(serialized)
167
+ expect(deserialized.chainId).toBe(authentication.chainId)
168
+ expect(deserialized.zoneId).toBe(authentication.zoneId)
169
+ expect(deserialized.signature.type).toBe('webAuthn')
170
+ })
171
+ })
172
+
173
+ const credentials = import.meta.env.VITE_TEMPO_CREDENTIALS
174
+ const rpcUrl = 'https://rpc-zone-006-private.tempoxyz.dev'
175
+
176
+ describe('e2e', () => {
177
+ test.skipIf(!credentials)('succeeds with auth token', async () => {
178
+ const privateKey = Secp256k1.randomPrivateKey()
179
+ const now = Math.floor(Date.now() / 1000)
180
+
181
+ const auth = ZoneRpcAuthentication.from({
182
+ chainId: 4217000007,
183
+ expiresAt: now + 600,
184
+ issuedAt: now,
185
+ zoneId: 7,
186
+ })
187
+
188
+ const serialized = ZoneRpcAuthentication.serialize(auth, {
189
+ signature: SignatureEnvelope.from(
190
+ Secp256k1.sign({
191
+ payload: ZoneRpcAuthentication.getSignPayload(auth),
192
+ privateKey,
193
+ }),
194
+ ),
195
+ })
196
+
197
+ const transport = RpcTransport.fromHttp(rpcUrl, {
198
+ fetchOptions: {
199
+ headers: {
200
+ Authorization: `Basic ${btoa(credentials!)}`,
201
+ [ZoneRpcAuthentication.headerName]: serialized,
202
+ },
203
+ },
204
+ })
205
+
206
+ const blockNumber = await transport.request({
207
+ method: 'eth_blockNumber',
208
+ })
209
+ expect(blockNumber).toBeDefined()
210
+ expect(typeof blockNumber).toBe('string')
211
+ })
212
+
213
+ test.skipIf(!credentials)('fails without auth token', async () => {
214
+ const transport = RpcTransport.fromHttp(rpcUrl, {
215
+ fetchOptions: {
216
+ headers: {
217
+ Authorization: `Basic ${btoa(credentials!)}`,
218
+ },
219
+ },
220
+ })
221
+
222
+ await expect(
223
+ transport.request({ method: 'eth_blockNumber' }),
224
+ ).rejects.toThrow()
225
+ })
226
+ })
@@ -0,0 +1,423 @@
1
+ import * as Errors from '../core/Errors.js'
2
+ import * as Hash from '../core/Hash.js'
3
+ import * as Hex from '../core/Hex.js'
4
+ import type { Compute, PartialBy } from '../core/internal/types.js'
5
+ import * as SignatureEnvelope from './SignatureEnvelope.js'
6
+ import * as TempoAddress from './TempoAddress.js'
7
+
8
+ /**
9
+ * Header name used to transport Zone RPC authentication tokens.
10
+ */
11
+ export const headerName = 'X-Authorization-Token' as const
12
+
13
+ /**
14
+ * 32-byte domain separator used when hashing Zone RPC authentication tokens.
15
+ */
16
+ export const magicBytes =
17
+ '0x54656d706f5a6f6e655250430000000000000000000000000000000000000000' as const
18
+
19
+ /**
20
+ * Size, in bytes, of the fixed Zone RPC authentication fields.
21
+ */
22
+ export const fieldsSize = 29 as const
23
+
24
+ /** Current Zone RPC authentication version. */
25
+ export const version = 0 as const
26
+
27
+ /** Current Zone RPC authentication version. */
28
+ export type Version = typeof version
29
+
30
+ /**
31
+ * Root type for a Tempo Zone RPC authentication token.
32
+ *
33
+ * Zone RPC authentication tokens are short-lived, read-only credentials used to
34
+ * authenticate requests to Tempo private zone RPC endpoints.
35
+ *
36
+ * [Zone RPC Specification](https://docs.tempo.xyz/protocol/privacy/rpc#authorization-tokens)
37
+ */
38
+ export type ZoneRpcAuthentication<
39
+ signed extends boolean = boolean,
40
+ bigintType = bigint,
41
+ numberType = number,
42
+ > = Compute<
43
+ {
44
+ /** Zone chain ID for replay protection. */
45
+ chainId: numberType
46
+ /** Unix timestamp when the token expires. */
47
+ expiresAt: numberType
48
+ /** Unix timestamp when the token was issued. */
49
+ issuedAt: numberType
50
+ /** Zone RPC authentication version. Always `0` for the current spec. */
51
+ version: Version
52
+ /** Numeric zone identifier. */
53
+ zoneId: numberType
54
+ } & (signed extends true
55
+ ? { signature: SignatureEnvelope.SignatureEnvelope<bigintType, numberType> }
56
+ : {
57
+ signature?:
58
+ | SignatureEnvelope.SignatureEnvelope<bigintType, numberType>
59
+ | undefined
60
+ })
61
+ >
62
+
63
+ /** Input type for a Zone RPC authentication token. */
64
+ export type Input = PartialBy<ZoneRpcAuthentication<false>, 'version'>
65
+
66
+ /** 29-byte fixed Zone RPC authentication field suffix. */
67
+ export type Fields = Hex.Hex
68
+
69
+ /** Hex-encoded serialized Zone RPC authentication token. */
70
+ export type Serialized = Hex.Hex
71
+
72
+ /** Signed Zone RPC authentication token. */
73
+ export type Signed<
74
+ bigintType = bigint,
75
+ numberType = number,
76
+ > = ZoneRpcAuthentication<true, bigintType, numberType>
77
+
78
+ /**
79
+ * Instantiates a typed Zone RPC authentication token.
80
+ *
81
+ * @example
82
+ * ```ts twoslash
83
+ * import { ZoneRpcAuthentication } from 'ox/tempo'
84
+ *
85
+ * const authentication = ZoneRpcAuthentication.from({
86
+ * chainId: 4217000026,
87
+ * expiresAt: 1711235160,
88
+ * issuedAt: 1711234560,
89
+ * zoneId: 26,
90
+ * })
91
+ * ```
92
+ *
93
+ * @example
94
+ * ### Attaching Signatures
95
+ *
96
+ * ```ts twoslash
97
+ * import { Secp256k1 } from 'ox'
98
+ * import { ZoneRpcAuthentication } from 'ox/tempo'
99
+ *
100
+ * const authentication = ZoneRpcAuthentication.from({
101
+ * chainId: 4217000026,
102
+ * expiresAt: 1711235160,
103
+ * issuedAt: 1711234560,
104
+ * zoneId: 26,
105
+ * })
106
+ *
107
+ * const signature = Secp256k1.sign({
108
+ * payload: ZoneRpcAuthentication.getSignPayload(authentication),
109
+ * privateKey: '0x...',
110
+ * })
111
+ *
112
+ * const authentication_signed = ZoneRpcAuthentication.from(authentication, {
113
+ * signature,
114
+ * })
115
+ * ```
116
+ *
117
+ * @param authentication - Zone RPC authentication token fields.
118
+ * @param options - Zone RPC authentication options.
119
+ * @returns The instantiated Zone RPC authentication token.
120
+ */
121
+ export function from<
122
+ const authentication extends Input | ZoneRpcAuthentication,
123
+ const signature extends SignatureEnvelope.from.Value | undefined = undefined,
124
+ >(
125
+ authentication: authentication | ZoneRpcAuthentication,
126
+ options: from.Options<signature> = {},
127
+ ): from.ReturnType<authentication, signature> {
128
+ const auth = authentication as ZoneRpcAuthentication
129
+ const resolved = {
130
+ ...auth,
131
+ version,
132
+ }
133
+ if (options.signature)
134
+ return {
135
+ ...resolved,
136
+ signature: SignatureEnvelope.from(options.signature),
137
+ } as never
138
+ return resolved as never
139
+ }
140
+
141
+ export declare namespace from {
142
+ type Options<
143
+ signature extends SignatureEnvelope.from.Value | undefined =
144
+ | SignatureEnvelope.from.Value
145
+ | undefined,
146
+ > = {
147
+ /** The signature to attach to the authentication token. */
148
+ signature?: signature | SignatureEnvelope.SignatureEnvelope | undefined
149
+ }
150
+
151
+ type ReturnType<
152
+ authentication extends
153
+ | ZoneRpcAuthentication
154
+ | Input = ZoneRpcAuthentication,
155
+ signature extends SignatureEnvelope.from.Value | undefined =
156
+ | SignatureEnvelope.from.Value
157
+ | undefined,
158
+ > = Compute<
159
+ authentication & {
160
+ readonly version: Version
161
+ } & (signature extends SignatureEnvelope.from.Value
162
+ ? { signature: SignatureEnvelope.from.ReturnValue<signature> }
163
+ : {})
164
+ >
165
+
166
+ type ErrorType = Errors.GlobalErrorType
167
+ }
168
+
169
+ /**
170
+ * Parses a serialized Zone RPC authentication token.
171
+ *
172
+ * The serialized format is `<signature><29-byte fields>`. The signature is parsed
173
+ * from the prefix and the fixed-length fields are parsed from the suffix.
174
+ *
175
+ * @example
176
+ * ```ts twoslash
177
+ * import { ZoneRpcAuthentication } from 'ox/tempo'
178
+ *
179
+ * const authentication = ZoneRpcAuthentication.deserialize('0x...')
180
+ * ```
181
+ *
182
+ * @param serialized - The serialized Zone RPC authentication token.
183
+ * @returns The parsed Zone RPC authentication token.
184
+ */
185
+ export function deserialize(serialized: Serialized): Signed {
186
+ const size = Hex.size(serialized)
187
+ if (size <= fieldsSize)
188
+ throw new InvalidSerializedError({
189
+ reason: `Serialized authentication must be longer than ${fieldsSize} bytes.`,
190
+ serialized,
191
+ })
192
+
193
+ const fieldsOffset = size - fieldsSize
194
+ const signature = Hex.slice(serialized, 0, fieldsOffset)
195
+ const fields = Hex.slice(serialized, fieldsOffset)
196
+
197
+ const parsedVersion = Hex.toNumber(Hex.slice(fields, 0, 1), { size: 1 })
198
+ if (parsedVersion !== version)
199
+ throw new InvalidSerializedError({
200
+ reason: `Unsupported authentication version "${parsedVersion}". Expected "${version}".`,
201
+ serialized,
202
+ })
203
+
204
+ return {
205
+ chainId: Hex.toNumber(Hex.slice(fields, 5, 13), { size: 8 }),
206
+ expiresAt: Hex.toNumber(Hex.slice(fields, 21, 29), { size: 8 }),
207
+ issuedAt: Hex.toNumber(Hex.slice(fields, 13, 21), { size: 8 }),
208
+ signature: SignatureEnvelope.deserialize(signature),
209
+ version,
210
+ zoneId: Hex.toNumber(Hex.slice(fields, 1, 5), { size: 4 }),
211
+ }
212
+ }
213
+
214
+ export declare namespace deserialize {
215
+ type ErrorType =
216
+ | InvalidSerializedError
217
+ | SignatureEnvelope.CoercionError
218
+ | SignatureEnvelope.InvalidSerializedError
219
+ | Hex.size.ErrorType
220
+ | Hex.slice.ErrorType
221
+ | Hex.toNumber.ErrorType
222
+ | Errors.GlobalErrorType
223
+ }
224
+
225
+ /**
226
+ * Returns the 29-byte fixed field suffix for a Zone RPC authentication token.
227
+ *
228
+ * @example
229
+ * ```ts twoslash
230
+ * import { ZoneRpcAuthentication } from 'ox/tempo'
231
+ *
232
+ * const fields = ZoneRpcAuthentication.getFields({
233
+ * chainId: 4217000026,
234
+ * expiresAt: 1711235160,
235
+ * issuedAt: 1711234560,
236
+ * zoneId: 26,
237
+ * })
238
+ * ```
239
+ *
240
+ * @param authentication - The Zone RPC authentication token.
241
+ * @returns The fixed 29-byte field suffix.
242
+ */
243
+ export function getFields(
244
+ authentication: PartialBy<ZoneRpcAuthentication, 'version'>,
245
+ ): Fields {
246
+ return Hex.concat(
247
+ Hex.fromNumber(version, { size: 1 }),
248
+ Hex.fromNumber(authentication.zoneId, { size: 4 }),
249
+ Hex.fromNumber(authentication.chainId, { size: 8 }),
250
+ Hex.fromNumber(authentication.issuedAt, { size: 8 }),
251
+ Hex.fromNumber(authentication.expiresAt, { size: 8 }),
252
+ )
253
+ }
254
+
255
+ export declare namespace getFields {
256
+ type ErrorType =
257
+ | Hex.concat.ErrorType
258
+ | Hex.fromNumber.ErrorType
259
+ | Errors.GlobalErrorType
260
+ }
261
+
262
+ /**
263
+ * Computes the sign payload for a Zone RPC authentication token.
264
+ *
265
+ * When `userAddress` is provided, the payload is wrapped as
266
+ * `keccak256(0x04 || authHash || userAddress)` to match V2 keychain signing.
267
+ *
268
+ * @example
269
+ * ```ts twoslash
270
+ * import { ZoneRpcAuthentication } from 'ox/tempo'
271
+ *
272
+ * const authentication = ZoneRpcAuthentication.from({
273
+ * chainId: 4217000026,
274
+ * expiresAt: 1711235160,
275
+ * issuedAt: 1711234560,
276
+ * zoneId: 26,
277
+ * })
278
+ *
279
+ * const payload = ZoneRpcAuthentication.getSignPayload(authentication)
280
+ * ```
281
+ *
282
+ * @param authentication - The Zone RPC authentication token.
283
+ * @param options - Options.
284
+ * @returns The sign payload.
285
+ */
286
+ export function getSignPayload(
287
+ authentication: PartialBy<ZoneRpcAuthentication, 'version'>,
288
+ options: getSignPayload.Options = {},
289
+ ): Hex.Hex {
290
+ const authHash = hash(authentication)
291
+ if (options.userAddress)
292
+ return Hash.keccak256(
293
+ Hex.concat('0x04', authHash, TempoAddress.resolve(options.userAddress)),
294
+ )
295
+ return authHash
296
+ }
297
+
298
+ export declare namespace getSignPayload {
299
+ type Options = {
300
+ /**
301
+ * Root account address for keychain access-key signing.
302
+ *
303
+ * When provided, computes `keccak256(0x04 || authHash || userAddress)`
304
+ * instead of the raw `authHash`.
305
+ */
306
+ userAddress?: TempoAddress.Address | undefined
307
+ }
308
+
309
+ type ErrorType = hash.ErrorType | Errors.GlobalErrorType
310
+ }
311
+
312
+ /**
313
+ * Computes the raw authorization hash for a Zone RPC authentication token.
314
+ *
315
+ * The hash is `keccak256(magicBytes || fields)`.
316
+ *
317
+ * @example
318
+ * ```ts twoslash
319
+ * import { ZoneRpcAuthentication } from 'ox/tempo'
320
+ *
321
+ * const authentication = ZoneRpcAuthentication.from({
322
+ * chainId: 4217000026,
323
+ * expiresAt: 1711235160,
324
+ * issuedAt: 1711234560,
325
+ * zoneId: 26,
326
+ * })
327
+ *
328
+ * const hash = ZoneRpcAuthentication.hash(authentication)
329
+ * ```
330
+ *
331
+ * @param authentication - The Zone RPC authentication token.
332
+ * @returns The authorization hash.
333
+ */
334
+ export function hash(
335
+ authentication: PartialBy<ZoneRpcAuthentication, 'version'>,
336
+ ): Hex.Hex {
337
+ return Hash.keccak256(Hex.concat(magicBytes, getFields(authentication)))
338
+ }
339
+
340
+ export declare namespace hash {
341
+ type ErrorType =
342
+ | getFields.ErrorType
343
+ | Hash.keccak256.ErrorType
344
+ | Hex.concat.ErrorType
345
+ | Errors.GlobalErrorType
346
+ }
347
+
348
+ /**
349
+ * Serializes a Zone RPC authentication token to hex.
350
+ *
351
+ * The serialized format is `<signature><29-byte fields>`.
352
+ *
353
+ * @example
354
+ * ```ts twoslash
355
+ * import { Secp256k1 } from 'ox'
356
+ * import { ZoneRpcAuthentication } from 'ox/tempo'
357
+ *
358
+ * const authentication = ZoneRpcAuthentication.from({
359
+ * chainId: 4217000026,
360
+ * expiresAt: 1711235160,
361
+ * issuedAt: 1711234560,
362
+ * zoneId: 26,
363
+ * })
364
+ *
365
+ * const signature = Secp256k1.sign({
366
+ * payload: ZoneRpcAuthentication.getSignPayload(authentication),
367
+ * privateKey: '0x...',
368
+ * })
369
+ *
370
+ * const serialized = ZoneRpcAuthentication.serialize(authentication, {
371
+ * signature,
372
+ * })
373
+ * ```
374
+ *
375
+ * @param authentication - The Zone RPC authentication token.
376
+ * @param options - Serialization options.
377
+ * @returns The serialized authentication token.
378
+ */
379
+ export function serialize(
380
+ authentication: PartialBy<ZoneRpcAuthentication, 'version'>,
381
+ options: serialize.Options = {},
382
+ ): Serialized {
383
+ const signature = options.signature || authentication.signature
384
+ if (!signature) throw new MissingSignatureError()
385
+
386
+ return Hex.concat(
387
+ SignatureEnvelope.serialize(SignatureEnvelope.from(signature)),
388
+ getFields(authentication),
389
+ )
390
+ }
391
+
392
+ export declare namespace serialize {
393
+ type Options = {
394
+ /** Signature to attach to the serialized authentication token. */
395
+ signature?: SignatureEnvelope.from.Value | undefined
396
+ }
397
+
398
+ type ErrorType =
399
+ | getFields.ErrorType
400
+ | MissingSignatureError
401
+ | SignatureEnvelope.CoercionError
402
+ | Errors.GlobalErrorType
403
+ }
404
+
405
+ /** Error thrown when a serialized authentication token cannot be deserialized. */
406
+ export class InvalidSerializedError extends Errors.BaseError {
407
+ override readonly name = 'ZoneRpcAuthentication.InvalidSerializedError'
408
+
409
+ constructor({ reason, serialized }: { reason: string; serialized: Hex.Hex }) {
410
+ super(`Unable to deserialize Zone RPC authentication: ${reason}`, {
411
+ metaMessages: [`Serialized: ${serialized}`],
412
+ })
413
+ }
414
+ }
415
+
416
+ /** Error thrown when serializing an authentication token without a signature. */
417
+ export class MissingSignatureError extends Errors.BaseError {
418
+ override readonly name = 'ZoneRpcAuthentication.MissingSignatureError'
419
+
420
+ constructor() {
421
+ super('Zone RPC authentication is missing a signature.')
422
+ }
423
+ }