ox 0.13.2 → 0.14.1

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 (85) hide show
  1. package/Bech32m/package.json +6 -0
  2. package/CHANGELOG.md +25 -0
  3. package/_cjs/core/Bech32m.js +205 -0
  4. package/_cjs/core/Bech32m.js.map +1 -0
  5. package/_cjs/index.js +3 -2
  6. package/_cjs/index.js.map +1 -1
  7. package/_cjs/tempo/AuthorizationTempo.js +7 -2
  8. package/_cjs/tempo/AuthorizationTempo.js.map +1 -1
  9. package/_cjs/tempo/KeyAuthorization.js +22 -8
  10. package/_cjs/tempo/KeyAuthorization.js.map +1 -1
  11. package/_cjs/tempo/SignatureEnvelope.js +39 -4
  12. package/_cjs/tempo/SignatureEnvelope.js.map +1 -1
  13. package/_cjs/tempo/TempoAddress.js +49 -41
  14. package/_cjs/tempo/TempoAddress.js.map +1 -1
  15. package/_cjs/tempo/TokenId.js +8 -5
  16. package/_cjs/tempo/TokenId.js.map +1 -1
  17. package/_cjs/tempo/TransactionRequest.js +2 -1
  18. package/_cjs/tempo/TransactionRequest.js.map +1 -1
  19. package/_cjs/tempo/TxEnvelopeTempo.js +17 -4
  20. package/_cjs/tempo/TxEnvelopeTempo.js.map +1 -1
  21. package/_cjs/tempo/index.js.map +1 -1
  22. package/_cjs/version.js +1 -1
  23. package/_esm/core/Bech32m.js +238 -0
  24. package/_esm/core/Bech32m.js.map +1 -0
  25. package/_esm/index.js +24 -0
  26. package/_esm/index.js.map +1 -1
  27. package/_esm/tempo/AuthorizationTempo.js +24 -19
  28. package/_esm/tempo/AuthorizationTempo.js.map +1 -1
  29. package/_esm/tempo/KeyAuthorization.js +38 -13
  30. package/_esm/tempo/KeyAuthorization.js.map +1 -1
  31. package/_esm/tempo/SignatureEnvelope.js +43 -6
  32. package/_esm/tempo/SignatureEnvelope.js.map +1 -1
  33. package/_esm/tempo/TempoAddress.js +79 -48
  34. package/_esm/tempo/TempoAddress.js.map +1 -1
  35. package/_esm/tempo/TokenId.js +9 -6
  36. package/_esm/tempo/TokenId.js.map +1 -1
  37. package/_esm/tempo/TransactionRequest.js +2 -1
  38. package/_esm/tempo/TransactionRequest.js.map +1 -1
  39. package/_esm/tempo/TxEnvelopeTempo.js +66 -15
  40. package/_esm/tempo/TxEnvelopeTempo.js.map +1 -1
  41. package/_esm/tempo/index.js +11 -10
  42. package/_esm/tempo/index.js.map +1 -1
  43. package/_esm/version.js +1 -1
  44. package/_types/core/Bech32m.d.ts +93 -0
  45. package/_types/core/Bech32m.d.ts.map +1 -0
  46. package/_types/index.d.ts +24 -0
  47. package/_types/index.d.ts.map +1 -1
  48. package/_types/tempo/AuthorizationTempo.d.ts +19 -19
  49. package/_types/tempo/AuthorizationTempo.d.ts.map +1 -1
  50. package/_types/tempo/KeyAuthorization.d.ts +21 -9
  51. package/_types/tempo/KeyAuthorization.d.ts.map +1 -1
  52. package/_types/tempo/SignatureEnvelope.d.ts +30 -7
  53. package/_types/tempo/SignatureEnvelope.d.ts.map +1 -1
  54. package/_types/tempo/TempoAddress.d.ts +51 -15
  55. package/_types/tempo/TempoAddress.d.ts.map +1 -1
  56. package/_types/tempo/TokenId.d.ts +6 -5
  57. package/_types/tempo/TokenId.d.ts.map +1 -1
  58. package/_types/tempo/TransactionRequest.d.ts +2 -1
  59. package/_types/tempo/TransactionRequest.d.ts.map +1 -1
  60. package/_types/tempo/TxEnvelopeTempo.d.ts +64 -17
  61. package/_types/tempo/TxEnvelopeTempo.d.ts.map +1 -1
  62. package/_types/tempo/index.d.ts +11 -10
  63. package/_types/tempo/index.d.ts.map +1 -1
  64. package/_types/version.d.ts +1 -1
  65. package/core/Bech32m.ts +263 -0
  66. package/index.ts +24 -2
  67. package/package.json +6 -1
  68. package/tempo/AuthorizationTempo.test.ts +13 -0
  69. package/tempo/AuthorizationTempo.ts +27 -21
  70. package/tempo/KeyAuthorization.test.ts +95 -23
  71. package/tempo/KeyAuthorization.ts +44 -25
  72. package/tempo/PoolId.test.ts +9 -0
  73. package/tempo/SignatureEnvelope.test.ts +123 -8
  74. package/tempo/SignatureEnvelope.ts +82 -9
  75. package/tempo/TempoAddress.test.ts +70 -14
  76. package/tempo/TempoAddress.ts +95 -67
  77. package/tempo/TokenId.test.ts +14 -0
  78. package/tempo/TokenId.ts +13 -10
  79. package/tempo/Transaction.test.ts +4 -2
  80. package/tempo/TransactionRequest.ts +3 -2
  81. package/tempo/TxEnvelopeTempo.test.ts +79 -3
  82. package/tempo/TxEnvelopeTempo.ts +86 -19
  83. package/tempo/e2e.test.ts +33 -9
  84. package/tempo/index.ts +15 -10
  85. package/version.ts +1 -1
@@ -1,4 +1,4 @@
1
- import { Address } from 'ox'
1
+ import { Address, Bech32m } from 'ox'
2
2
  import { TempoAddress } from 'ox/tempo'
3
3
  import { describe, expect, test } from 'vitest'
4
4
 
@@ -6,30 +6,55 @@ const rawAddress = Address.checksum(
6
6
  '0x742d35Cc6634C0532925a3b844Bc9e7595f2bD28',
7
7
  )
8
8
 
9
+ describe('resolve', () => {
10
+ test('hex address passthrough', () => {
11
+ expect(TempoAddress.resolve(rawAddress)).toBe(rawAddress)
12
+ })
13
+
14
+ test('tempo address', () => {
15
+ const tempoAddr = TempoAddress.format(rawAddress)
16
+ expect(TempoAddress.resolve(tempoAddr)).toBe(rawAddress)
17
+ })
18
+
19
+ test('tempo zone address', () => {
20
+ const tempoAddr = TempoAddress.format(rawAddress, { zoneId: 1 })
21
+ expect(TempoAddress.resolve(tempoAddr)).toBe(rawAddress)
22
+ })
23
+ })
24
+
9
25
  describe('format', () => {
10
26
  test('mainnet address', () => {
11
27
  expect(TempoAddress.format(rawAddress)).toMatchInlineSnapshot(
12
- `"tempo1wskntnrxxnq9x2f95wuyf0y7wk2l90fg8zd8djs"`,
28
+ `"tempo1qp6z6dwvvc6vq5efyk3ms39une6etu4a9qtj2kk0"`,
13
29
  )
14
30
  })
15
31
 
32
+ test('tempo address input', () => {
33
+ const tempoAddr = TempoAddress.format(rawAddress)
34
+ expect(TempoAddress.format(tempoAddr)).toBe(tempoAddr)
35
+ })
36
+
16
37
  test('zone address (zone ID = 1)', () => {
17
38
  expect(
18
39
  TempoAddress.format(rawAddress, { zoneId: 1 }),
19
- ).toMatchInlineSnapshot(`"tempoz1q96z6dwvvc6vq5efyk3ms39une6etu4a9zeqtx3q"`)
40
+ ).toMatchInlineSnapshot(
41
+ `"tempoz1qqqhgtf4e3nrfszn9yj68wzyhj08t90jh55q74d9uj"`,
42
+ )
20
43
  })
21
44
 
22
45
  test('zone address (zone ID = 252)', () => {
23
46
  expect(
24
47
  TempoAddress.format(rawAddress, { zoneId: 252 }),
25
- ).toMatchInlineSnapshot(`"tempoz1l36z6dwvvc6vq5efyk3ms39une6etu4a9z8vgw44"`)
48
+ ).toMatchInlineSnapshot(
49
+ `"tempoz1qr78gtf4e3nrfszn9yj68wzyhj08t90jh55q9k62jd"`,
50
+ )
26
51
  })
27
52
 
28
53
  test('zone address (zone ID = 253)', () => {
29
54
  expect(
30
55
  TempoAddress.format(rawAddress, { zoneId: 253 }),
31
56
  ).toMatchInlineSnapshot(
32
- `"tempoz1lh7sqapdxhxxvdxq2v5jtgacgj7fuav4727jsx0032us"`,
57
+ `"tempoz1qr7l6qr5956uce35cpfjjfdrhpzte8n4jhet62q0j8hus"`,
33
58
  )
34
59
  })
35
60
 
@@ -37,7 +62,7 @@ describe('format', () => {
37
62
  expect(
38
63
  TempoAddress.format(rawAddress, { zoneId: 65535 }),
39
64
  ).toMatchInlineSnapshot(
40
- `"tempoz1lhll7apdxhxxvdxq2v5jtgacgj7fuav4727j37cldu7q"`,
65
+ `"tempoz1qr7lllm5956uce35cpfjjfdrhpzte8n4jhet62q8pdj6j"`,
41
66
  )
42
67
  })
43
68
 
@@ -45,7 +70,7 @@ describe('format', () => {
45
70
  expect(
46
71
  TempoAddress.format(rawAddress, { zoneId: 65536 }),
47
72
  ).toMatchInlineSnapshot(
48
- `"tempoz1lcqqqqgqwskntnrxxnq9x2f95wuyf0y7wk2l90fga965qjc"`,
73
+ `"tempoz1qrlqqqqpqp6z6dwvvc6vq5efyk3ms39une6etu4a9qdupk5c"`,
49
74
  )
50
75
  })
51
76
 
@@ -53,7 +78,7 @@ describe('format', () => {
53
78
  expect(
54
79
  TempoAddress.format(rawAddress, { zoneId: 4294967295 }),
55
80
  ).toMatchInlineSnapshot(
56
- `"tempoz1lmllllllwskntnrxxnq9x2f95wuyf0y7wk2l90fgxg58ulq"`,
81
+ `"tempoz1qrl0llllla6z6dwvvc6vq5efyk3ms39une6etu4a9qnk36qy"`,
57
82
  )
58
83
  })
59
84
 
@@ -61,7 +86,7 @@ describe('format', () => {
61
86
  expect(
62
87
  TempoAddress.format(rawAddress, { zoneId: BigInt('4294967296') }),
63
88
  ).toMatchInlineSnapshot(
64
- `"tempoz1luqqqqqqqyqqqqr5956uce35cpfjjfdrhpzte8n4jhet62pnyj7cc"`,
89
+ `"tempoz1qrlsqqqqqqqsqqqqwskntnrxxnq9x2f95wuyf0y7wk2l90fg4306kk"`,
65
90
  )
66
91
  })
67
92
 
@@ -69,6 +94,28 @@ describe('format', () => {
69
94
  const result = TempoAddress.format(rawAddress)
70
95
  expect(result).toBe(result.toLowerCase())
71
96
  })
97
+
98
+ test('spec test vectors', () => {
99
+ expect(TempoAddress.format(rawAddress)).toBe(
100
+ 'tempo1qp6z6dwvvc6vq5efyk3ms39une6etu4a9qtj2kk0',
101
+ )
102
+ expect(TempoAddress.format(rawAddress, { zoneId: 1 })).toBe(
103
+ 'tempoz1qqqhgtf4e3nrfszn9yj68wzyhj08t90jh55q74d9uj',
104
+ )
105
+ expect(TempoAddress.format(rawAddress, { zoneId: 1000 })).toBe(
106
+ 'tempoz1qr77sqm5956uce35cpfjjfdrhpzte8n4jhet62qxx4zvx',
107
+ )
108
+ expect(TempoAddress.format(rawAddress, { zoneId: 100000 })).toBe(
109
+ 'tempoz1qrl2ppspqp6z6dwvvc6vq5efyk3ms39une6etu4a9qg5477g',
110
+ )
111
+ })
112
+
113
+ test('address lengths match spec', () => {
114
+ expect(TempoAddress.format(rawAddress).length).toBe(46)
115
+ expect(TempoAddress.format(rawAddress, { zoneId: 1 }).length).toBe(49)
116
+ expect(TempoAddress.format(rawAddress, { zoneId: 1000 }).length).toBe(52)
117
+ expect(TempoAddress.format(rawAddress, { zoneId: 100000 }).length).toBe(55)
118
+ })
72
119
  })
73
120
 
74
121
  describe('parse', () => {
@@ -144,17 +191,26 @@ describe('parse', () => {
144
191
  `)
145
192
  })
146
193
 
147
- test('case insensitive', () => {
194
+ test('all uppercase', () => {
148
195
  const encoded = TempoAddress.format(rawAddress)
149
- const upper = encoded.slice(0, 6) + encoded.slice(6).toUpperCase()
196
+ const upper = encoded.toUpperCase()
150
197
  expect(TempoAddress.parse(upper).address).toBe(rawAddress)
151
198
  })
152
199
 
153
200
  test('error: invalid prefix', () => {
201
+ const encoded = Bech32m.encode('bitcoin', new Uint8Array(20))
154
202
  expect(() =>
155
- TempoAddress.parse('bitcoin1abc'),
203
+ TempoAddress.parse(encoded),
156
204
  ).toThrowErrorMatchingInlineSnapshot(
157
- `[TempoAddress.InvalidPrefixError: Tempo address "bitcoin1abc" has an invalid prefix. Expected "tempo1" or "tempoz1".]`,
205
+ `[TempoAddress.InvalidPrefixError: Tempo address "${encoded}" has an invalid prefix. Expected "tempo1" or "tempoz1".]`,
206
+ )
207
+ })
208
+
209
+ test('error: unsupported version', () => {
210
+ const data = new Uint8Array([0x01, ...new Uint8Array(20)])
211
+ const encoded = Bech32m.encode('tempo', data)
212
+ expect(() => TempoAddress.parse(encoded)).toThrow(
213
+ TempoAddress.InvalidVersionError,
158
214
  )
159
215
  })
160
216
 
@@ -174,7 +230,7 @@ describe('parse', () => {
174
230
  expect(() =>
175
231
  TempoAddress.parse(swapped),
176
232
  ).toThrowErrorMatchingInlineSnapshot(
177
- `[TempoAddress.InvalidLengthError: Tempo address "${swapped}" has an invalid payload length. Expected 24 bytes, got 23.]`,
233
+ `[TempoAddress.InvalidChecksumError: Tempo address "tempoz1qp6z6dwvvc6vq5efyk3ms39une6etu4a9qtj2kk0" has an invalid checksum.]`,
178
234
  )
179
235
  })
180
236
  })
@@ -1,13 +1,45 @@
1
- import * as Address from '../core/Address.js'
2
- import * as Base32 from '../core/Base32.js'
1
+ import * as core_Address from '../core/Address.js'
2
+ import * as Bech32m from '../core/Bech32m.js'
3
3
  import * as Bytes from '../core/Bytes.js'
4
4
  import * as CompactSize from '../core/CompactSize.js'
5
5
  import * as Errors from '../core/Errors.js'
6
- import * as Hash from '../core/Hash.js'
7
6
  import * as Hex from '../core/Hex.js'
7
+ import type { Compute } from '../core/internal/types.js'
8
+
9
+ /** An address that can be either an Ethereum hex address or a Tempo bech32m address. */
10
+ export type Address = core_Address.Address | Tempo
8
11
 
9
12
  /** Root type for a Tempo Address. */
10
- export type TempoAddress = `tempo1${string}` | `tempoz1${string}`
13
+ export type Tempo = Compute<`tempo1${string}` | `tempoz1${string}`>
14
+
15
+ /**
16
+ * Resolves an address input (either an Ethereum hex address or a Tempo bech32m address)
17
+ * to an Ethereum hex address.
18
+ *
19
+ * @example
20
+ * ```ts twoslash
21
+ * import { TempoAddress } from 'ox/tempo'
22
+ *
23
+ * const address = TempoAddress.resolve('tempo1qp6z6dwvvc6vq5efyk3ms39une6etu4a9qtj2kk0')
24
+ * // @log: '0x742d35CC6634c0532925a3B844bc9e7595F2Bd28'
25
+ * ```
26
+ *
27
+ * @example
28
+ * ### Hex Address Passthrough
29
+ * ```ts twoslash
30
+ * import { TempoAddress } from 'ox/tempo'
31
+ *
32
+ * const address = TempoAddress.resolve('0x742d35Cc6634C0532925a3b844Bc9e7595f2bD28')
33
+ * // @log: '0x742d35CC6634c0532925a3B844bc9e7595F2Bd28'
34
+ * ```
35
+ *
36
+ * @param address - An Ethereum hex address or Tempo bech32m address.
37
+ * @returns The resolved Ethereum hex address.
38
+ */
39
+ export function resolve(address: Address): core_Address.Address {
40
+ if (address.startsWith('tempo')) return parse(address).address
41
+ return address as core_Address.Address
42
+ }
11
43
 
12
44
  /**
13
45
  * Formats a raw Ethereum address (and optional zone ID) into a Tempo address string.
@@ -17,7 +49,7 @@ export type TempoAddress = `tempo1${string}` | `tempoz1${string}`
17
49
  * import { TempoAddress } from 'ox/tempo'
18
50
  *
19
51
  * const address = TempoAddress.format('0x742d35Cc6634C0532925a3b844Bc9e7595f2bD28')
20
- * // @log: 'tempo1wskntnrxxnq9x2f95wuyf0y7wk2l90fg8zd8djs'
52
+ * // @log: 'tempo1qp6z6dwvvc6vq5efyk3ms39une6etu4a9qtj2kk0'
21
53
  * ```
22
54
  *
23
55
  * @example
@@ -29,36 +61,29 @@ export type TempoAddress = `tempo1${string}` | `tempoz1${string}`
29
61
  * '0x742d35Cc6634C0532925a3b844Bc9e7595f2bD28',
30
62
  * { zoneId: 1 },
31
63
  * )
32
- * // @log: 'tempoz1q96z6dwvvc6vq5efyk3ms39une6etu4a9zeqtx3q'
64
+ * // @log: 'tempoz1qqqhgtf4e3nrfszn9yj68wzyhj08t90jh55q74d9uj'
33
65
  * ```
34
66
  *
35
67
  * @param address - The raw 20-byte Ethereum address.
36
68
  * @param options - Options.
37
69
  * @returns The encoded Tempo address string.
38
70
  */
39
- export function format(
40
- address: Address.Address,
41
- options: format.Options = {},
42
- ): TempoAddress {
71
+ export function format(address: Address, options: format.Options = {}): Tempo {
43
72
  const { zoneId } = options
44
73
 
45
- const prefix = zoneId != null ? 'tempoz1' : 'tempo1'
74
+ const resolved = resolve(address)
75
+ const hrp = zoneId != null ? 'tempoz' : 'tempo'
76
+ const version = new Uint8Array([0x00])
46
77
  const zone = zoneId != null ? CompactSize.toBytes(zoneId) : new Uint8Array()
47
- const address_bytes = Bytes.fromHex(address)
78
+ const data = Bytes.concat(version, zone, Bytes.fromHex(resolved))
48
79
 
49
- const input = Bytes.concat(Bytes.fromString(prefix), zone, address_bytes)
50
- const checksum = Hash.sha256(Hash.sha256(input, { as: 'Bytes' }), {
51
- as: 'Bytes',
52
- }).slice(0, 4)
53
-
54
- const payload = Bytes.concat(zone, address_bytes, checksum)
55
- return `${prefix}${Base32.fromBytes(payload)}` as TempoAddress
80
+ return Bech32m.encode(hrp, data) as Tempo
56
81
  }
57
82
 
58
83
  export declare namespace format {
59
84
  type Options = {
60
85
  /** Zone ID for zone addresses. */
61
- zoneId?: bigint | number | undefined
86
+ zoneId?: number | bigint | undefined
62
87
  }
63
88
 
64
89
  type ErrorType = Errors.GlobalErrorType
@@ -73,9 +98,9 @@ export declare namespace format {
73
98
  * import { TempoAddress } from 'ox/tempo'
74
99
  *
75
100
  * const result = TempoAddress.parse(
76
- * 'tempo1wst8d6qejxtdg4y5r3zarvary0c5xw7kvmgh8pm',
101
+ * 'tempo1qp6z6dwvvc6vq5efyk3ms39une6etu4a9qtj2kk0',
77
102
  * )
78
- * // { address: '0x742d35Cc6634C0532925a3b844Bc9e7595f2bD28' }
103
+ * // @log: { address: '0x742d35CC6634c0532925a3B844bc9e7595F2Bd28', zoneId: undefined }
79
104
  * ```
80
105
  *
81
106
  * @example
@@ -84,67 +109,55 @@ export declare namespace format {
84
109
  * import { TempoAddress } from 'ox/tempo'
85
110
  *
86
111
  * const result = TempoAddress.parse(
87
- * 'tempoz1qwst8d6qejxtdg4y5r3zarvary0c5xw7kvmgh8pm',
112
+ * 'tempoz1qqqhgtf4e3nrfszn9yj68wzyhj08t90jh55q74d9uj',
88
113
  * )
89
- * // { address: '0x742d35Cc6634C0532925a3b844Bc9e7595f2bD28', zoneId: 1 }
114
+ * // @log: { address: '0x742d35CC6634c0532925a3B844bc9e7595F2Bd28', zoneId: 1 }
90
115
  * ```
91
116
  *
92
117
  * @param tempoAddress - The Tempo address string to parse.
93
118
  * @returns The parsed raw address and optional zone ID.
94
119
  */
95
120
  export function parse(tempoAddress: string): parse.ReturnType {
96
- const lower = tempoAddress.toLowerCase()
97
-
98
- let prefix: string
99
- let hasZone: boolean
100
- if (lower.startsWith('tempoz1')) {
101
- prefix = 'tempoz1'
102
- hasZone = true
103
- } else if (lower.startsWith('tempo1')) {
104
- prefix = 'tempo1'
105
- hasZone = false
106
- } else {
107
- throw new InvalidPrefixError({ address: tempoAddress })
121
+ let hrp: string
122
+ let data: Uint8Array
123
+ try {
124
+ const decoded = Bech32m.decode(tempoAddress)
125
+ hrp = decoded.hrp
126
+ data = decoded.data
127
+ } catch {
128
+ throw new InvalidChecksumError({ address: tempoAddress })
108
129
  }
109
130
 
110
- const payload = Base32.toBytes(lower.slice(prefix.length))
131
+ if (hrp !== 'tempo' && hrp !== 'tempoz')
132
+ throw new InvalidPrefixError({ address: tempoAddress })
133
+
134
+ if (data.length < 1 || data[0] !== 0x00)
135
+ throw new InvalidVersionError({
136
+ address: tempoAddress,
137
+ version: data.length > 0 ? data[0]! : undefined,
138
+ })
111
139
 
112
- let zoneId: bigint | undefined
113
- let remaining: Uint8Array
114
- if (hasZone) {
140
+ const payload = data.slice(1)
141
+
142
+ let zoneId: number | bigint | undefined
143
+ let rawAddress: Uint8Array
144
+ if (hrp === 'tempoz') {
115
145
  const { value, size } = CompactSize.fromBytes(payload)
116
146
  zoneId = value
117
- remaining = payload.slice(size)
147
+ rawAddress = payload.slice(size)
118
148
  } else {
119
149
  zoneId = undefined
120
- remaining = payload
150
+ rawAddress = payload
121
151
  }
122
152
 
123
- if (remaining.length !== 24)
153
+ if (rawAddress.length !== 20)
124
154
  throw new InvalidLengthError({
125
155
  address: tempoAddress,
126
- expected: 24,
127
- actual: remaining.length,
156
+ expected: 20,
157
+ actual: rawAddress.length,
128
158
  })
129
159
 
130
- const rawAddress = remaining.slice(0, 20)
131
- const checksum = remaining.slice(20, 24)
132
-
133
- const zoneBytes =
134
- zoneId != null ? CompactSize.toBytes(zoneId) : new Uint8Array()
135
- const checksumInput = Bytes.concat(
136
- Bytes.fromString(prefix),
137
- zoneBytes,
138
- rawAddress,
139
- )
140
- const expected = Hash.sha256(Hash.sha256(checksumInput, { as: 'Bytes' }), {
141
- as: 'Bytes',
142
- }).slice(0, 4)
143
-
144
- if (!Bytes.isEqual(checksum, expected))
145
- throw new InvalidChecksumError({ address: tempoAddress })
146
-
147
- const address = Address.checksum(Hex.fromBytes(rawAddress) as Address.Address)
160
+ const address = core_Address.checksum(Hex.fromBytes(rawAddress) as never)
148
161
 
149
162
  return { address, zoneId }
150
163
  }
@@ -152,13 +165,14 @@ export function parse(tempoAddress: string): parse.ReturnType {
152
165
  export declare namespace parse {
153
166
  type ReturnType = {
154
167
  /** The raw 20-byte Ethereum address. */
155
- address: Address.Address
168
+ address: core_Address.Address
156
169
  /** The zone ID, or `undefined` for mainnet addresses. */
157
- zoneId: bigint | undefined
170
+ zoneId: number | bigint | undefined
158
171
  }
159
172
 
160
173
  type ErrorType =
161
174
  | InvalidPrefixError
175
+ | InvalidVersionError
162
176
  | InvalidLengthError
163
177
  | InvalidChecksumError
164
178
  | Errors.GlobalErrorType
@@ -172,9 +186,9 @@ export declare namespace parse {
172
186
  * import { TempoAddress } from 'ox/tempo'
173
187
  *
174
188
  * const valid = TempoAddress.validate(
175
- * 'tempo1wst8d6qejxtdg4y5r3zarvary0c5xw7kvmgh8pm',
189
+ * 'tempo1qp6z6dwvvc6vq5efyk3ms39une6etu4a9qtj2kk0',
176
190
  * )
177
- * // true
191
+ * // @log: true
178
192
  * ```
179
193
  *
180
194
  * @param tempoAddress - The Tempo address string to validate.
@@ -200,6 +214,20 @@ export class InvalidPrefixError extends Errors.BaseError {
200
214
  }
201
215
  }
202
216
 
217
+ /** Thrown when a Tempo address has an unsupported version byte. */
218
+ export class InvalidVersionError extends Errors.BaseError {
219
+ override readonly name = 'TempoAddress.InvalidVersionError'
220
+
221
+ constructor({
222
+ address,
223
+ version,
224
+ }: { address: string; version: number | undefined }) {
225
+ super(
226
+ `Tempo address "${address}" has unsupported version ${version === undefined ? '(missing)' : `0x${version.toString(16).padStart(2, '0')}`}. Only version 0x00 is supported.`,
227
+ )
228
+ }
229
+ }
230
+
203
231
  /** Thrown when a Tempo address has an invalid payload length. */
204
232
  export class InvalidLengthError extends Errors.BaseError {
205
233
  override readonly name = 'TempoAddress.InvalidLengthError'
@@ -23,6 +23,10 @@ test('fromAddress', () => {
23
23
  expect(
24
24
  TokenId.fromAddress('0x20c0000000000000000000000000000000000def'),
25
25
  ).toBe(0xdefn)
26
+
27
+ // tempo address input
28
+ const tempoAddr = 'tempo1qqsvqqqqqqqqqqqqqqqqqqqqqqqqqqqqqyr9xgnd'
29
+ expect(TokenId.fromAddress(tempoAddr)).toBe(1n)
26
30
  })
27
31
 
28
32
  test('toAddress', () => {
@@ -38,6 +42,12 @@ test('toAddress', () => {
38
42
  expect(TokenId.toAddress(0xdefn)).toBe(
39
43
  '0x20c0000000000000000000000000000000000def',
40
44
  )
45
+
46
+ // tempo address input
47
+ const tempoAddr = 'tempo1qqsvqqqqqqqqqqqqqqqqqqqqqqqqqqqqqyr9xgnd'
48
+ expect(TokenId.toAddress(tempoAddr)).toBe(
49
+ '0x20C0000000000000000000000000000000000001',
50
+ )
41
51
  })
42
52
 
43
53
  test('compute', () => {
@@ -76,4 +86,8 @@ test('compute', () => {
76
86
  const otherSender = '0xabcdefabcdefabcdefabcdefabcdefabcdefabcd'
77
87
  const address3 = TokenId.compute({ sender: otherSender, salt: salt1 })
78
88
  expect(address3).not.toBe(address1)
89
+
90
+ // tempo address input produces same result
91
+ const tempoSender = 'tempo1qqfrg4ncjqfrg4ncjqfrg4ncjqfrg4ncjqgmv79k'
92
+ expect(TokenId.compute({ sender: tempoSender, salt: salt1 })).toBe(id1)
79
93
  })
package/tempo/TokenId.ts CHANGED
@@ -2,11 +2,12 @@ import * as AbiParameters from '../core/AbiParameters.js'
2
2
  import * as Address from '../core/Address.js'
3
3
  import * as Hash from '../core/Hash.js'
4
4
  import * as Hex from '../core/Hex.js'
5
+ import * as TempoAddress from './TempoAddress.js'
5
6
 
6
7
  const tip20Prefix = '0x20c0'
7
8
 
8
9
  export type TokenId = bigint
9
- export type TokenIdOrAddress = TokenId | Address.Address
10
+ export type TokenIdOrAddress = TokenId | TempoAddress.Address
10
11
 
11
12
  /**
12
13
  * Converts a token ID or address to a token ID.
@@ -44,16 +45,17 @@ export function from(tokenIdOrAddress: TokenIdOrAddress | number): TokenId {
44
45
  * ```ts twoslash
45
46
  * import { TokenId } from 'ox/tempo'
46
47
  *
47
- * const tokenId = TokenId.fromAddress('0x20c00000000000000000000000000000000000000001')
48
+ * const tokenId = TokenId.fromAddress('0x20c0000000000000000000000000000000000001')
48
49
  * ```
49
50
  *
50
51
  * @param address - The token address.
51
52
  * @returns The token ID.
52
53
  */
53
- export function fromAddress(address: Address.Address): TokenId {
54
- if (!address.toLowerCase().startsWith(tip20Prefix))
54
+ export function fromAddress(address: TempoAddress.Address): TokenId {
55
+ const resolved = TempoAddress.resolve(address)
56
+ if (!resolved.toLowerCase().startsWith(tip20Prefix))
55
57
  throw new Error('invalid tip20 address.')
56
- return Hex.toBigInt(Hex.slice(address, tip20Prefix.length))
58
+ return Hex.toBigInt(Hex.slice(resolved, tip20Prefix.length))
57
59
  }
58
60
 
59
61
  /**
@@ -73,8 +75,9 @@ export function fromAddress(address: Address.Address): TokenId {
73
75
  */
74
76
  export function toAddress(tokenId: TokenIdOrAddress): Address.Address {
75
77
  if (typeof tokenId === 'string') {
76
- Address.assert(tokenId)
77
- return tokenId
78
+ const resolved = TempoAddress.resolve(tokenId as TempoAddress.Address)
79
+ Address.assert(resolved)
80
+ return resolved
78
81
  }
79
82
 
80
83
  const tokenIdHex = Hex.fromNumber(tokenId, { size: 18 })
@@ -104,7 +107,7 @@ export function toAddress(tokenId: TokenIdOrAddress): Address.Address {
104
107
  export function compute(value: compute.Value): bigint {
105
108
  const hash = Hash.keccak256(
106
109
  AbiParameters.encode(AbiParameters.from('address, bytes32'), [
107
- value.sender,
110
+ TempoAddress.resolve(value.sender),
108
111
  value.salt,
109
112
  ]),
110
113
  )
@@ -115,7 +118,7 @@ export declare namespace compute {
115
118
  export type Value = {
116
119
  /** The salt (32 bytes). */
117
120
  salt: Hex.Hex
118
- /** The sender address. */
119
- sender: Address.Address
121
+ /** The sender address. Accepts both hex and Tempo bech32m addresses. */
122
+ sender: TempoAddress.Address
120
123
  }
121
124
  }
@@ -164,6 +164,7 @@ describe('fromRpc', () => {
164
164
  },
165
165
  ],
166
166
  keyAuthorization: {
167
+ chainId: '0x1',
167
168
  expiry: '0xffffffffffff',
168
169
  keyId: '0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c',
169
170
  keyType: 'secp256k1',
@@ -224,7 +225,7 @@ describe('fromRpc', () => {
224
225
  "hash": "0x353fdfc38a2f26115daadee9f5b8392ce62b84f410957967e2ed56b35338cdd0",
225
226
  "keyAuthorization": {
226
227
  "address": "0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c",
227
- "chainId": 0n,
228
+ "chainId": 1n,
228
229
  "expiry": 281474976710655,
229
230
  "limits": [
230
231
  {
@@ -431,6 +432,7 @@ describe('toRpc', () => {
431
432
  data: undefined,
432
433
  keyAuthorization: {
433
434
  address: '0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c',
435
+ chainId: 1n,
434
436
  expiry: 281474976710655,
435
437
  type: 'secp256k1',
436
438
  limits: [
@@ -487,7 +489,7 @@ describe('toRpc', () => {
487
489
  "hash": "0x353fdfc38a2f26115daadee9f5b8392ce62b84f410957967e2ed56b35338cdd0",
488
490
  "input": undefined,
489
491
  "keyAuthorization": {
490
- "chainId": "0x",
492
+ "chainId": "0x1",
491
493
  "expiry": "0xffffffffffff",
492
494
  "keyId": "0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c",
493
495
  "keyType": "secp256k1",
@@ -4,6 +4,7 @@ import type { Compute } from '../core/internal/types.js'
4
4
  import * as ox_TransactionRequest from '../core/TransactionRequest.js'
5
5
  import * as AuthorizationTempo from './AuthorizationTempo.js'
6
6
  import * as KeyAuthorization from './KeyAuthorization.js'
7
+ import * as TempoAddress from './TempoAddress.js'
7
8
  import * as TokenId from './TokenId.js'
8
9
  import * as Transaction from './Transaction.js'
9
10
  import type { Call } from './TxEnvelopeTempo.js'
@@ -30,7 +31,7 @@ export type TransactionRequest<
30
31
  authorizationList?:
31
32
  | AuthorizationTempo.ListSigned<bigintType, numberType>
32
33
  | undefined
33
- calls?: readonly Call<bigintType>[] | undefined
34
+ calls?: readonly Call<bigintType, TempoAddress.Address>[] | undefined
34
35
  keyAuthorization?: KeyAuthorization.KeyAuthorization<true> | undefined
35
36
  keyData?: Hex.Hex | undefined
36
37
  keyType?: KeyType | undefined
@@ -112,7 +113,7 @@ export function toRpc(request: TransactionRequest): Rpc {
112
113
  )
113
114
  if (request.calls)
114
115
  request_rpc.calls = request.calls.map((call) => ({
115
- to: call.to,
116
+ to: call.to ? TempoAddress.resolve(call.to) : call.to,
116
117
  value: call.value ? Hex.fromNumber(call.value) : '0x',
117
118
  data: call.data ?? '0x',
118
119
  }))