ox 0.14.0 → 0.14.2

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 (88) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/_cjs/core/internal/webauthn.js +2 -2
  3. package/_cjs/core/internal/webauthn.js.map +1 -1
  4. package/_cjs/tempo/AuthorizationTempo.js +7 -2
  5. package/_cjs/tempo/AuthorizationTempo.js.map +1 -1
  6. package/_cjs/tempo/KeyAuthorization.js +18 -4
  7. package/_cjs/tempo/KeyAuthorization.js.map +1 -1
  8. package/_cjs/tempo/SignatureEnvelope.js +27 -7
  9. package/_cjs/tempo/SignatureEnvelope.js.map +1 -1
  10. package/_cjs/tempo/TempoAddress.js +10 -3
  11. package/_cjs/tempo/TempoAddress.js.map +1 -1
  12. package/_cjs/tempo/TokenId.js +8 -5
  13. package/_cjs/tempo/TokenId.js.map +1 -1
  14. package/_cjs/tempo/TransactionRequest.js +2 -1
  15. package/_cjs/tempo/TransactionRequest.js.map +1 -1
  16. package/_cjs/tempo/TxEnvelopeTempo.js +13 -3
  17. package/_cjs/tempo/TxEnvelopeTempo.js.map +1 -1
  18. package/_cjs/tempo/index.js.map +1 -1
  19. package/_cjs/version.js +1 -1
  20. package/_cjs/webauthn/Authentication.js +9 -5
  21. package/_cjs/webauthn/Authentication.js.map +1 -1
  22. package/_cjs/webauthn/Registration.js +8 -5
  23. package/_cjs/webauthn/Registration.js.map +1 -1
  24. package/_esm/core/internal/webauthn.js +5 -2
  25. package/_esm/core/internal/webauthn.js.map +1 -1
  26. package/_esm/tempo/AuthorizationTempo.js +24 -19
  27. package/_esm/tempo/AuthorizationTempo.js.map +1 -1
  28. package/_esm/tempo/KeyAuthorization.js +19 -4
  29. package/_esm/tempo/KeyAuthorization.js.map +1 -1
  30. package/_esm/tempo/SignatureEnvelope.js +27 -7
  31. package/_esm/tempo/SignatureEnvelope.js.map +1 -1
  32. package/_esm/tempo/TempoAddress.js +33 -3
  33. package/_esm/tempo/TempoAddress.js.map +1 -1
  34. package/_esm/tempo/TokenId.js +9 -6
  35. package/_esm/tempo/TokenId.js.map +1 -1
  36. package/_esm/tempo/TransactionRequest.js +2 -1
  37. package/_esm/tempo/TransactionRequest.js.map +1 -1
  38. package/_esm/tempo/TxEnvelopeTempo.js +26 -15
  39. package/_esm/tempo/TxEnvelopeTempo.js.map +1 -1
  40. package/_esm/tempo/index.js +9 -8
  41. package/_esm/tempo/index.js.map +1 -1
  42. package/_esm/version.js +1 -1
  43. package/_esm/webauthn/Authentication.js +12 -5
  44. package/_esm/webauthn/Authentication.js.map +1 -1
  45. package/_esm/webauthn/Registration.js +11 -5
  46. package/_esm/webauthn/Registration.js.map +1 -1
  47. package/_types/core/internal/webauthn.d.ts +4 -1
  48. package/_types/core/internal/webauthn.d.ts.map +1 -1
  49. package/_types/tempo/AuthorizationTempo.d.ts +19 -19
  50. package/_types/tempo/AuthorizationTempo.d.ts.map +1 -1
  51. package/_types/tempo/KeyAuthorization.d.ts +4 -2
  52. package/_types/tempo/KeyAuthorization.d.ts.map +1 -1
  53. package/_types/tempo/SignatureEnvelope.d.ts +11 -2
  54. package/_types/tempo/SignatureEnvelope.d.ts.map +1 -1
  55. package/_types/tempo/TempoAddress.d.ts +32 -4
  56. package/_types/tempo/TempoAddress.d.ts.map +1 -1
  57. package/_types/tempo/TokenId.d.ts +6 -5
  58. package/_types/tempo/TokenId.d.ts.map +1 -1
  59. package/_types/tempo/TransactionRequest.d.ts +2 -1
  60. package/_types/tempo/TransactionRequest.d.ts.map +1 -1
  61. package/_types/tempo/TxEnvelopeTempo.d.ts +19 -18
  62. package/_types/tempo/TxEnvelopeTempo.d.ts.map +1 -1
  63. package/_types/tempo/index.d.ts +9 -8
  64. package/_types/tempo/index.d.ts.map +1 -1
  65. package/_types/version.d.ts +1 -1
  66. package/_types/webauthn/Authentication.d.ts.map +1 -1
  67. package/_types/webauthn/Registration.d.ts.map +1 -1
  68. package/core/internal/webauthn.ts +4 -1
  69. package/package.json +1 -1
  70. package/tempo/AuthorizationTempo.test.ts +13 -0
  71. package/tempo/AuthorizationTempo.ts +27 -21
  72. package/tempo/KeyAuthorization.test.ts +25 -0
  73. package/tempo/KeyAuthorization.ts +23 -7
  74. package/tempo/PoolId.test.ts +9 -0
  75. package/tempo/SignatureEnvelope.test.ts +108 -0
  76. package/tempo/SignatureEnvelope.ts +47 -9
  77. package/tempo/TempoAddress.test.ts +21 -0
  78. package/tempo/TempoAddress.ts +41 -10
  79. package/tempo/TokenId.test.ts +14 -0
  80. package/tempo/TokenId.ts +13 -10
  81. package/tempo/TransactionRequest.ts +3 -2
  82. package/tempo/TxEnvelopeTempo.test.ts +72 -0
  83. package/tempo/TxEnvelopeTempo.ts +37 -21
  84. package/tempo/e2e.test.ts +10 -21
  85. package/tempo/index.ts +9 -8
  86. package/version.ts +1 -1
  87. package/webauthn/Authentication.ts +14 -11
  88. package/webauthn/Registration.ts +17 -7
@@ -5,6 +5,7 @@ import * as Hex from '../core/Hex.js'
5
5
  import type { Compute } from '../core/internal/types.js'
6
6
  import * as Rlp from '../core/Rlp.js'
7
7
  import * as SignatureEnvelope from './SignatureEnvelope.js'
8
+ import * as TempoAddress from './TempoAddress.js'
8
9
 
9
10
  /**
10
11
  * Key authorization for provisioning access keys.
@@ -33,7 +34,7 @@ export type KeyAuthorization<
33
34
  numberType = number,
34
35
  > = {
35
36
  /** Address derived from the public key of the key type. */
36
- address: Address.Address
37
+ address: TempoAddress.Address
37
38
  /** Chain ID for replay protection. */
38
39
  chainId: bigintType
39
40
  /** Unix timestamp when key expires (0 = never expires). */
@@ -107,7 +108,7 @@ export type Tuple<signed extends boolean = boolean> = signed extends true
107
108
  */
108
109
  export type TokenLimit<bigintType = bigint> = {
109
110
  /** Address of the TIP-20 token. */
110
- token: Address.Address
111
+ token: TempoAddress.Address
111
112
  /** Maximum spending amount for this token (enforced over the key's lifetime). */
112
113
  limit: bigintType
113
114
  }
@@ -255,14 +256,28 @@ export function from<
255
256
  authorization: authorization | KeyAuthorization,
256
257
  options: from.Options<signature> = {},
257
258
  ): from.ReturnType<authorization, signature> {
258
- if (typeof authorization.expiry === 'string')
259
- return fromRpc(authorization as Rpc) as never
259
+ if ('keyId' in authorization) return fromRpc(authorization as Rpc) as never
260
+ const auth = authorization as KeyAuthorization & {
261
+ limits?: readonly { token: TempoAddress.Address; limit: bigint }[]
262
+ }
263
+ const resolved = {
264
+ ...auth,
265
+ address: TempoAddress.resolve(auth.address as TempoAddress.Address),
266
+ ...(auth.limits
267
+ ? {
268
+ limits: auth.limits.map((l) => ({
269
+ ...l,
270
+ token: TempoAddress.resolve(l.token as TempoAddress.Address),
271
+ })),
272
+ }
273
+ : {}),
274
+ }
260
275
  if (options.signature)
261
276
  return {
262
- ...authorization,
277
+ ...resolved,
263
278
  signature: SignatureEnvelope.from(options.signature),
264
279
  } as never
265
- return authorization as never
280
+ return resolved as never
266
281
  }
267
282
 
268
283
  export declare namespace from {
@@ -300,6 +315,7 @@ export declare namespace from {
300
315
  * import { KeyAuthorization } from 'ox/tempo'
301
316
  *
302
317
  * const keyAuthorization = KeyAuthorization.fromRpc({
318
+ * chainId: '0x1079',
303
319
  * expiry: '0x174876e800',
304
320
  * keyId: '0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c',
305
321
  * keyType: 'secp256k1',
@@ -619,7 +635,7 @@ export function toRpc(authorization: Signed): Rpc {
619
635
  token,
620
636
  limit: Hex.fromNumber(limit),
621
637
  })),
622
- keyId: address,
638
+ keyId: TempoAddress.resolve(address),
623
639
  signature: SignatureEnvelope.toRpc(signature),
624
640
  keyType: type,
625
641
  }
@@ -30,4 +30,13 @@ test('from', () => {
30
30
  validatorToken: 1n,
31
31
  })
32
32
  expect(poolId4).toBe(poolId1)
33
+
34
+ // Test with tempo address inputs
35
+ const tempoAddr0 = 'tempo1qqsvqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqv0ywuh'
36
+ const tempoAddr1 = 'tempo1qqsvqqqqqqqqqqqqqqqqqqqqqqqqqqqqqyr9xgnd'
37
+ const poolId5 = PoolId.from({
38
+ userToken: tempoAddr0,
39
+ validatorToken: tempoAddr1,
40
+ })
41
+ expect(poolId5).toBe(poolId1)
33
42
  })
@@ -1040,6 +1040,61 @@ describe('from', () => {
1040
1040
 
1041
1041
  expect(envelope.type).toBe('keychain')
1042
1042
  })
1043
+
1044
+ test('behavior: computes keyId from p256 inner publicKey', () => {
1045
+ const envelope = SignatureEnvelope.from({
1046
+ userAddress: '0x1234567890123456789012345678901234567890',
1047
+ inner: signature_p256,
1048
+ })
1049
+
1050
+ expect(envelope.keyId).toBe(Address.fromPublicKey(publicKey))
1051
+ })
1052
+
1053
+ test('behavior: computes keyId from webAuthn inner publicKey', () => {
1054
+ const envelope = SignatureEnvelope.from({
1055
+ userAddress: '0x1234567890123456789012345678901234567890',
1056
+ inner: signature_webauthn,
1057
+ })
1058
+
1059
+ expect(envelope.keyId).toBe(Address.fromPublicKey(publicKey))
1060
+ })
1061
+
1062
+ test('behavior: computes keyId from secp256k1 inner with payload', () => {
1063
+ const privateKey = Secp256k1.randomPrivateKey()
1064
+ const accessKeyPublicKey = Secp256k1.getPublicKey({ privateKey })
1065
+ const payload = '0xdeadbeef'
1066
+ const sig = Secp256k1.sign({ payload, privateKey })
1067
+
1068
+ const envelope = SignatureEnvelope.from(
1069
+ {
1070
+ userAddress: '0x1234567890123456789012345678901234567890',
1071
+ inner: SignatureEnvelope.from(sig),
1072
+ },
1073
+ { payload },
1074
+ )
1075
+
1076
+ expect(envelope.keyId).toBe(Address.fromPublicKey(accessKeyPublicKey))
1077
+ })
1078
+
1079
+ test('behavior: does not compute keyId for secp256k1 without payload', () => {
1080
+ const envelope = SignatureEnvelope.from({
1081
+ userAddress: '0x1234567890123456789012345678901234567890',
1082
+ inner: SignatureEnvelope.from(signature_secp256k1),
1083
+ })
1084
+
1085
+ expect(envelope.keyId).toBeUndefined()
1086
+ })
1087
+
1088
+ test('behavior: preserves explicit keyId', () => {
1089
+ const explicitKeyId = '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
1090
+ const envelope = SignatureEnvelope.from({
1091
+ userAddress: '0x1234567890123456789012345678901234567890',
1092
+ inner: signature_p256,
1093
+ keyId: explicitKeyId,
1094
+ })
1095
+
1096
+ expect(envelope.keyId).toBe(explicitKeyId)
1097
+ })
1043
1098
  })
1044
1099
  })
1045
1100
 
@@ -2235,6 +2290,24 @@ describe('fromRpc', () => {
2235
2290
  },
2236
2291
  })
2237
2292
  })
2293
+
2294
+ test('behavior: preserves keyId from RPC', () => {
2295
+ const rpc: SignatureEnvelope.KeychainRpc = {
2296
+ type: 'keychain',
2297
+ userAddress: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266',
2298
+ keyId: '0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c',
2299
+ signature: {
2300
+ type: 'secp256k1',
2301
+ r: '0xa2bb71146c20ce932456c043ebb2973ed205e07cd32c35a60bdefca1285fd132',
2302
+ s: '0x7cba10692bccdbfba9a215418443c2903dbee6fe5cb55c91172e47efc607840e',
2303
+ yParity: '0x1',
2304
+ },
2305
+ }
2306
+
2307
+ const envelope = SignatureEnvelope.fromRpc(rpc)
2308
+
2309
+ expect(envelope.keyId).toBe('0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c')
2310
+ })
2238
2311
  })
2239
2312
  })
2240
2313
 
@@ -2352,6 +2425,41 @@ describe('toRpc', () => {
2352
2425
  expect(typeof rpc.signature.pubKeyX).toBe('string')
2353
2426
  expect(typeof rpc.signature.webauthnData).toBe('string')
2354
2427
  })
2428
+
2429
+ test('behavior: preserves keyId in toRpc', () => {
2430
+ const envelope: SignatureEnvelope.Keychain = {
2431
+ type: 'keychain',
2432
+ userAddress: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266',
2433
+ keyId: '0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c',
2434
+ inner: {
2435
+ signature: signature_secp256k1,
2436
+ type: 'secp256k1',
2437
+ },
2438
+ }
2439
+
2440
+ const rpc = SignatureEnvelope.toRpc(
2441
+ envelope,
2442
+ ) as SignatureEnvelope.KeychainRpc
2443
+
2444
+ expect(rpc.keyId).toBe('0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c')
2445
+ })
2446
+
2447
+ test('behavior: omits keyId in toRpc when not set', () => {
2448
+ const envelope: SignatureEnvelope.Keychain = {
2449
+ type: 'keychain',
2450
+ userAddress: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266',
2451
+ inner: {
2452
+ signature: signature_secp256k1,
2453
+ type: 'secp256k1',
2454
+ },
2455
+ }
2456
+
2457
+ const rpc = SignatureEnvelope.toRpc(
2458
+ envelope,
2459
+ ) as SignatureEnvelope.KeychainRpc
2460
+
2461
+ expect(rpc.keyId).toBeUndefined()
2462
+ })
2355
2463
  })
2356
2464
  })
2357
2465
 
@@ -122,6 +122,8 @@ export type Keychain<bigintType = bigint, numberType = number> = {
122
122
  userAddress: Address.Address
123
123
  /** The actual signature from the access key (can be Secp256k1, P256, or WebAuthn) */
124
124
  inner: SignatureEnvelope<bigintType, numberType>
125
+ /** The access key address (recovered address of the access key signer). */
126
+ keyId?: Address.Address | undefined
125
127
  type: 'keychain'
126
128
  /** Keychain signature version. @default 'v1' */
127
129
  version?: KeychainVersion | undefined
@@ -130,6 +132,7 @@ export type Keychain<bigintType = bigint, numberType = number> = {
130
132
  export type KeychainRpc = {
131
133
  type: 'keychain'
132
134
  userAddress: Address.Address
135
+ keyId?: Address.Address | undefined
133
136
  signature: SignatureEnvelopeRpc
134
137
  version?: KeychainVersion | undefined
135
138
  }
@@ -663,6 +666,7 @@ export function deserialize(value: Serialized): SignatureEnvelope {
663
666
  */
664
667
  export function from<const value extends from.Value>(
665
668
  value: value | from.Value,
669
+ options?: from.Options | undefined,
666
670
  ): from.ReturnValue<value> {
667
671
  if (typeof value === 'string') return deserialize(value) as never
668
672
 
@@ -679,20 +683,45 @@ export function from<const value extends from.Value>(
679
683
  return {
680
684
  ...value,
681
685
  ...(type === 'p256' ? { prehash: value.prehash } : {}),
682
- ...(type === 'keychain' &&
683
- !(
684
- typeof value === 'object' &&
685
- value !== null &&
686
- 'version' in value &&
687
- value.version
688
- )
689
- ? { version: 'v1' }
686
+ ...(type === 'keychain'
687
+ ? {
688
+ ...(!(
689
+ typeof value === 'object' &&
690
+ value !== null &&
691
+ 'version' in value &&
692
+ value.version
693
+ )
694
+ ? { version: 'v2' }
695
+ : {}),
696
+ ...(!(typeof value === 'object' && 'keyId' in value && value.keyId)
697
+ ? (() => {
698
+ const inner = (value as Keychain).inner
699
+ if (inner.type === 'p256' || inner.type === 'webAuthn')
700
+ return { keyId: Address.fromPublicKey(inner.publicKey) }
701
+ if (inner.type === 'secp256k1' && options?.payload)
702
+ return {
703
+ keyId: Address.fromPublicKey(
704
+ ox_Secp256k1.recoverPublicKey({
705
+ payload: options.payload,
706
+ signature: inner.signature,
707
+ }),
708
+ ),
709
+ }
710
+ return {}
711
+ })()
712
+ : {}),
713
+ }
690
714
  : {}),
691
715
  type,
692
716
  } as never
693
717
  }
694
718
 
695
719
  export declare namespace from {
720
+ type Options = {
721
+ /** Payload that was signed. Used to recover `keyId` for keychain envelopes with secp256k1 inner signatures. */
722
+ payload?: Hex.Hex | Bytes.Bytes | undefined
723
+ }
724
+
696
725
  type Value =
697
726
  | UnionPartialBy<SignatureEnvelope, 'prehash' | 'type'>
698
727
  | Secp256k1Flat
@@ -706,7 +735,14 @@ export declare namespace from {
706
735
  ? Secp256k1
707
736
  : IsNarrowable<value, SignatureEnvelope> extends true
708
737
  ? SignatureEnvelope
709
- : Assign<value, { readonly type: GetType<value> }>
738
+ : Assign<
739
+ value,
740
+ {
741
+ readonly type: GetType<value>
742
+ } & (GetType<value> extends 'keychain'
743
+ ? { keyId?: Address.Address | undefined }
744
+ : {})
745
+ >
710
746
  >
711
747
  >
712
748
  }
@@ -806,6 +842,7 @@ export function fromRpc(envelope: SignatureEnvelopeRpc): SignatureEnvelope {
806
842
  type: 'keychain',
807
843
  userAddress: envelope.userAddress,
808
844
  inner: fromRpc(envelope.signature),
845
+ ...(envelope.keyId ? { keyId: envelope.keyId } : {}),
809
846
  ...(envelope.version ? { version: envelope.version } : {}),
810
847
  }
811
848
 
@@ -1053,6 +1090,7 @@ export function toRpc(envelope: SignatureEnvelope): SignatureEnvelopeRpc {
1053
1090
  type: 'keychain',
1054
1091
  userAddress: keychain.userAddress,
1055
1092
  signature: toRpc(keychain.inner),
1093
+ ...(keychain.keyId ? { keyId: keychain.keyId } : {}),
1056
1094
  ...(keychain.version ? { version: keychain.version } : {}),
1057
1095
  }
1058
1096
  }
@@ -6,6 +6,22 @@ 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(
@@ -13,6 +29,11 @@ describe('format', () => {
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 }),
@@ -1,12 +1,45 @@
1
- import * as Address from '../core/Address.js'
1
+ import * as core_Address from '../core/Address.js'
2
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
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
7
11
 
8
12
  /** Root type for a Tempo Address. */
9
- 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
+ }
10
43
 
11
44
  /**
12
45
  * Formats a raw Ethereum address (and optional zone ID) into a Tempo address string.
@@ -35,18 +68,16 @@ export type TempoAddress = `tempo1${string}` | `tempoz1${string}`
35
68
  * @param options - Options.
36
69
  * @returns The encoded Tempo address string.
37
70
  */
38
- export function format(
39
- address: Address.Address,
40
- options: format.Options = {},
41
- ): TempoAddress {
71
+ export function format(address: Address, options: format.Options = {}): Tempo {
42
72
  const { zoneId } = options
43
73
 
74
+ const resolved = resolve(address)
44
75
  const hrp = zoneId != null ? 'tempoz' : 'tempo'
45
76
  const version = new Uint8Array([0x00])
46
77
  const zone = zoneId != null ? CompactSize.toBytes(zoneId) : new Uint8Array()
47
- const data = Bytes.concat(version, zone, Bytes.fromHex(address))
78
+ const data = Bytes.concat(version, zone, Bytes.fromHex(resolved))
48
79
 
49
- return Bech32m.encode(hrp, data) as TempoAddress
80
+ return Bech32m.encode(hrp, data) as Tempo
50
81
  }
51
82
 
52
83
  export declare namespace format {
@@ -126,7 +157,7 @@ export function parse(tempoAddress: string): parse.ReturnType {
126
157
  actual: rawAddress.length,
127
158
  })
128
159
 
129
- const address = Address.checksum(Hex.fromBytes(rawAddress) as Address.Address)
160
+ const address = core_Address.checksum(Hex.fromBytes(rawAddress) as never)
130
161
 
131
162
  return { address, zoneId }
132
163
  }
@@ -134,7 +165,7 @@ export function parse(tempoAddress: string): parse.ReturnType {
134
165
  export declare namespace parse {
135
166
  type ReturnType = {
136
167
  /** The raw 20-byte Ethereum address. */
137
- address: Address.Address
168
+ address: core_Address.Address
138
169
  /** The zone ID, or `undefined` for mainnet addresses. */
139
170
  zoneId: number | bigint | undefined
140
171
  }
@@ -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
  }
@@ -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
  }))
@@ -692,6 +692,32 @@ describe('from', () => {
692
692
  }
693
693
  })
694
694
 
695
+ test('tempo address input for calls.to', () => {
696
+ const hexAddr = '0x70997970c51812dc3a010c7d01b50e0d17dc79c8'
697
+ const tempoAddr = 'tempo1qpcfj7tsc5vp9hp6qyx86qd4pcx30hreeqlrsqqr'
698
+
699
+ const envelope = TxEnvelopeTempo.from({
700
+ chainId: 1,
701
+ calls: [{ to: tempoAddr }],
702
+ nonce: 0n,
703
+ nonceKey: 0n,
704
+ })
705
+ expect(envelope.calls[0]!.to).toBe(Address.checksum(hexAddr))
706
+ })
707
+
708
+ test('tempo address input for from', () => {
709
+ const hexAddr = '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'
710
+ const tempoAddr = 'tempo1qreel4h9r2kc3ah5ee4t3qnj088llwfzvccugvl7'
711
+
712
+ const envelope = TxEnvelopeTempo.from({
713
+ chainId: 1,
714
+ calls: [{}],
715
+ nonce: 0n,
716
+ from: tempoAddr as any,
717
+ })
718
+ expect(envelope.from).toBe(Address.checksum(hexAddr))
719
+ })
720
+
695
721
  test('options: signature', () => {
696
722
  const envelope = TxEnvelopeTempo.from(
697
723
  {
@@ -1382,6 +1408,29 @@ describe('getSignPayload', () => {
1382
1408
  `"0xe1222a45806457acbe3a13940aae4c34f3180659fa16613b5a45dc183adae07c"`,
1383
1409
  )
1384
1410
  })
1411
+
1412
+ test('tempo address input for from', () => {
1413
+ const hexAddr = '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'
1414
+ const tempoAddr = 'tempo1qreel4h9r2kc3ah5ee4t3qnj088llwfzvccugvl7'
1415
+
1416
+ const transaction = TxEnvelopeTempo.from({
1417
+ chainId: 1,
1418
+ calls: [
1419
+ {
1420
+ to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
1421
+ },
1422
+ ],
1423
+ nonce: 0n,
1424
+ })
1425
+
1426
+ const hashHex = TxEnvelopeTempo.getSignPayload(transaction, {
1427
+ from: hexAddr,
1428
+ })
1429
+ const hashTempo = TxEnvelopeTempo.getSignPayload(transaction, {
1430
+ from: tempoAddr,
1431
+ })
1432
+ expect(hashTempo).toBe(hashHex)
1433
+ })
1385
1434
  })
1386
1435
 
1387
1436
  describe('getFeePayerSignPayload', () => {
@@ -1404,6 +1453,29 @@ describe('getFeePayerSignPayload', () => {
1404
1453
  )
1405
1454
  })
1406
1455
 
1456
+ test('tempo address input for sender', () => {
1457
+ const hexAddr = '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266'
1458
+ const tempoAddr = 'tempo1qreel4h9r2kc3ah5ee4t3qnj088llwfzvccugvl7'
1459
+
1460
+ const transaction = TxEnvelopeTempo.from({
1461
+ chainId: 1,
1462
+ calls: [
1463
+ {
1464
+ to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
1465
+ },
1466
+ ],
1467
+ nonce: 0n,
1468
+ })
1469
+
1470
+ const hashHex = TxEnvelopeTempo.getFeePayerSignPayload(transaction, {
1471
+ sender: hexAddr,
1472
+ })
1473
+ const hashTempo = TxEnvelopeTempo.getFeePayerSignPayload(transaction, {
1474
+ sender: tempoAddr,
1475
+ })
1476
+ expect(hashTempo).toBe(hashHex)
1477
+ })
1478
+
1407
1479
  test('with feeToken', () => {
1408
1480
  const transaction = TxEnvelopeTempo.from({
1409
1481
  chainId: 1,