ox 0.14.11 → 0.14.13

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 (38) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/_cjs/erc8021/Attribution.js +7 -1
  3. package/_cjs/erc8021/Attribution.js.map +1 -1
  4. package/_cjs/tempo/KeyAuthorization.js +150 -21
  5. package/_cjs/tempo/KeyAuthorization.js.map +1 -1
  6. package/_cjs/tempo/Period.js +31 -0
  7. package/_cjs/tempo/Period.js.map +1 -0
  8. package/_cjs/tempo/index.js +2 -1
  9. package/_cjs/tempo/index.js.map +1 -1
  10. package/_cjs/version.js +1 -1
  11. package/_esm/erc8021/Attribution.js +7 -1
  12. package/_esm/erc8021/Attribution.js.map +1 -1
  13. package/_esm/tempo/KeyAuthorization.js +159 -22
  14. package/_esm/tempo/KeyAuthorization.js.map +1 -1
  15. package/_esm/tempo/Period.js +92 -0
  16. package/_esm/tempo/Period.js.map +1 -0
  17. package/_esm/tempo/index.js +29 -0
  18. package/_esm/tempo/index.js.map +1 -1
  19. package/_esm/version.js +1 -1
  20. package/_types/erc8021/Attribution.d.ts +2 -0
  21. package/_types/erc8021/Attribution.d.ts.map +1 -1
  22. package/_types/tempo/KeyAuthorization.d.ts +95 -19
  23. package/_types/tempo/KeyAuthorization.d.ts.map +1 -1
  24. package/_types/tempo/Period.d.ts +78 -0
  25. package/_types/tempo/Period.d.ts.map +1 -0
  26. package/_types/tempo/index.d.ts +29 -0
  27. package/_types/tempo/index.d.ts.map +1 -1
  28. package/_types/version.d.ts +1 -1
  29. package/erc8021/Attribution.ts +12 -1
  30. package/package.json +6 -1
  31. package/tempo/KeyAuthorization.test.ts +407 -3
  32. package/tempo/KeyAuthorization.ts +291 -51
  33. package/tempo/Period/package.json +6 -0
  34. package/tempo/Period.test.ts +44 -0
  35. package/tempo/Period.ts +97 -0
  36. package/tempo/e2e.test.ts +969 -1
  37. package/tempo/index.ts +30 -0
  38. package/version.ts +1 -1
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Returns the number of seconds in `n` days.
3
+ *
4
+ * @example
5
+ * ```ts twoslash
6
+ * import { Period } from 'ox/tempo'
7
+ *
8
+ * const seconds = Period.days(1) // 86400
9
+ * ```
10
+ */
11
+ export declare function days(n: number): number;
12
+ /**
13
+ * Returns the number of seconds in `n` hours.
14
+ *
15
+ * @example
16
+ * ```ts twoslash
17
+ * import { Period } from 'ox/tempo'
18
+ *
19
+ * const seconds = Period.hours(2) // 7200
20
+ * ```
21
+ */
22
+ export declare function hours(n: number): number;
23
+ /**
24
+ * Returns the number of seconds in `n` minutes.
25
+ *
26
+ * @example
27
+ * ```ts twoslash
28
+ * import { Period } from 'ox/tempo'
29
+ *
30
+ * const seconds = Period.minutes(5) // 300
31
+ * ```
32
+ */
33
+ export declare function minutes(n: number): number;
34
+ /**
35
+ * Returns the number of seconds in `n` months (30 days).
36
+ *
37
+ * @example
38
+ * ```ts twoslash
39
+ * import { Period } from 'ox/tempo'
40
+ *
41
+ * const seconds = Period.months(1) // 2592000
42
+ * ```
43
+ */
44
+ export declare function months(n: number): number;
45
+ /**
46
+ * Returns the number of seconds in `n` seconds.
47
+ *
48
+ * @example
49
+ * ```ts twoslash
50
+ * import { Period } from 'ox/tempo'
51
+ *
52
+ * const seconds = Period.seconds(30) // 30
53
+ * ```
54
+ */
55
+ export declare function seconds(n: number): number;
56
+ /**
57
+ * Returns the number of seconds in `n` weeks.
58
+ *
59
+ * @example
60
+ * ```ts twoslash
61
+ * import { Period } from 'ox/tempo'
62
+ *
63
+ * const seconds = Period.weeks(1) // 604800
64
+ * ```
65
+ */
66
+ export declare function weeks(n: number): number;
67
+ /**
68
+ * Returns the number of seconds in `n` years (365 days).
69
+ *
70
+ * @example
71
+ * ```ts twoslash
72
+ * import { Period } from 'ox/tempo'
73
+ *
74
+ * const seconds = Period.years(1) // 31536000
75
+ * ```
76
+ */
77
+ export declare function years(n: number): number;
78
+ //# sourceMappingURL=Period.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Period.d.ts","sourceRoot":"","sources":["../../tempo/Period.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,wBAAgB,IAAI,CAAC,CAAC,EAAE,MAAM,UAE7B;AAED;;;;;;;;;GASG;AACH,wBAAgB,KAAK,CAAC,CAAC,EAAE,MAAM,UAE9B;AAED;;;;;;;;;GASG;AACH,wBAAgB,OAAO,CAAC,CAAC,EAAE,MAAM,UAEhC;AAED;;;;;;;;;GASG;AACH,wBAAgB,MAAM,CAAC,CAAC,EAAE,MAAM,UAE/B;AAED;;;;;;;;;GASG;AACH,wBAAgB,OAAO,CAAC,CAAC,EAAE,MAAM,UAEhC;AAED;;;;;;;;;GASG;AACH,wBAAgB,KAAK,CAAC,CAAC,EAAE,MAAM,UAE9B;AAED;;;;;;;;;GASG;AACH,wBAAgB,KAAK,CAAC,CAAC,EAAE,MAAM,UAE9B"}
@@ -73,6 +73,35 @@ export * as AuthorizationTempo from './AuthorizationTempo.js';
73
73
  * @category Reference
74
74
  */
75
75
  export * as KeyAuthorization from './KeyAuthorization.js';
76
+ /**
77
+ * Utilities for constructing period durations (in seconds) for recurring spending limits.
78
+ *
79
+ * Periods define the reset interval for access key spending limits. A spending limit with a
80
+ * period will reset every `period` seconds. For example, a daily spending limit uses
81
+ * `Period.days(1)` (86400 seconds).
82
+ *
83
+ * @example
84
+ * ```ts twoslash
85
+ * import { Value } from 'ox'
86
+ * import { KeyAuthorization, Period } from 'ox/tempo'
87
+ *
88
+ * const authorization = KeyAuthorization.from({
89
+ * address: '0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c',
90
+ * chainId: 4217n,
91
+ * type: 'secp256k1',
92
+ * limits: [{
93
+ * token: '0x20c0000000000000000000000000000000000001',
94
+ * limit: Value.from('100', 6),
95
+ * period: Period.days(1), // resets daily
96
+ * }],
97
+ * })
98
+ * ```
99
+ *
100
+ * [Access Keys Specification](https://docs.tempo.xyz/protocol/transactions/spec-tempo-transaction#access-keys)
101
+ *
102
+ * @category Reference
103
+ */
104
+ export * as Period from './Period.js';
76
105
  /**
77
106
  * Pool ID utilities for computing pool identifiers from token pairs.
78
107
  *
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../tempo/index.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAEhC,YAAY,EAAE,CAAA;AAEd;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,OAAO,KAAK,kBAAkB,MAAM,yBAAyB,CAAA;AAE7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,OAAO,KAAK,gBAAgB,MAAM,uBAAuB,CAAA;AAEzD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AAErC;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,KAAK,iBAAiB,MAAM,wBAAwB,CAAA;AAE3D;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,YAAY,MAAM,mBAAmB,CAAA;AAEjD;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AAEjC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AAEvC;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,KAAK,SAAS,MAAM,gBAAgB,CAAA;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,OAAO,KAAK,WAAW,MAAM,kBAAkB,CAAA;AAC/C;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,KAAK,kBAAkB,MAAM,yBAAyB,CAAA;AAC7D;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,KAAK,kBAAkB,MAAM,yBAAyB,CAAA;AAC7D;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,OAAO,KAAK,eAAe,MAAM,sBAAsB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../tempo/index.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAEhC,YAAY,EAAE,CAAA;AAEd;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,OAAO,KAAK,kBAAkB,MAAM,yBAAyB,CAAA;AAE7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,OAAO,KAAK,gBAAgB,MAAM,uBAAuB,CAAA;AAEzD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AAErC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AAErC;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,KAAK,iBAAiB,MAAM,wBAAwB,CAAA;AAE3D;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,YAAY,MAAM,mBAAmB,CAAA;AAEjD;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AAEjC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AAEvC;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,KAAK,SAAS,MAAM,gBAAgB,CAAA;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,OAAO,KAAK,WAAW,MAAM,kBAAkB,CAAA;AAC/C;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,KAAK,kBAAkB,MAAM,yBAAyB,CAAA;AAC7D;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,KAAK,kBAAkB,MAAM,yBAAyB,CAAA;AAC7D;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,OAAO,KAAK,eAAe,MAAM,sBAAsB,CAAA"}
@@ -1,3 +1,3 @@
1
1
  /** @internal */
2
- export declare const version = "0.14.11";
2
+ export declare const version = "0.14.13";
3
3
  //# sourceMappingURL=version.d.ts.map
@@ -58,6 +58,8 @@ export type AttributionSchemaId2 = {
58
58
  appCode?: string | undefined
59
59
  /** Wallet attribution code. */
60
60
  walletCode?: string | undefined
61
+ /** Service codes identifying additional service providers (e.g., block builders, relayers, solvers). */
62
+ serviceCodes?: readonly string[] | undefined
61
63
  /** Custom code registries keyed by entity type. */
62
64
  registries?: AttributionSchemaId2Registries | undefined
63
65
  /** Arbitrary metadata key-value pairs. */
@@ -134,7 +136,12 @@ export const ercSuffixSize = /*#__PURE__*/ Hex.size(ercSuffix)
134
136
  * @returns The schema ID (0 for canonical registry, 1 for custom registry, 2 for CBOR-encoded).
135
137
  */
136
138
  export function getSchemaId(attribution: Attribution): SchemaId {
137
- if ('appCode' in attribution || 'walletCode' in attribution) return 2
139
+ if (
140
+ 'appCode' in attribution ||
141
+ 'walletCode' in attribution ||
142
+ 'serviceCodes' in attribution
143
+ )
144
+ return 2
138
145
  if ('codeRegistry' in attribution) return 1
139
146
  return 0
140
147
  }
@@ -416,6 +423,7 @@ export declare namespace fromData {
416
423
  type Schema2CborMap = {
417
424
  a?: string
418
425
  w?: string
426
+ s?: string[]
419
427
  r?: {
420
428
  a?: { c: string; a: string }
421
429
  w?: { c: string; a: string }
@@ -429,6 +437,8 @@ function schema2ToDataSuffix(attribution: AttributionSchemaId2): Hex.Hex {
429
437
 
430
438
  if (attribution.appCode) cborMap.a = attribution.appCode
431
439
  if (attribution.walletCode) cborMap.w = attribution.walletCode
440
+ if (attribution.serviceCodes && attribution.serviceCodes.length > 0)
441
+ cborMap.s = [...attribution.serviceCodes]
432
442
 
433
443
  if (attribution.registries) {
434
444
  const r: Schema2CborMap['r'] = {}
@@ -484,6 +494,7 @@ function schema2FromData(data: Hex.Hex): AttributionSchemaId2 | undefined {
484
494
 
485
495
  if (typeof decoded.a === 'string') result.appCode = decoded.a
486
496
  if (typeof decoded.w === 'string') result.walletCode = decoded.w
497
+ if (Array.isArray(decoded.s)) result.serviceCodes = decoded.s
487
498
 
488
499
  if (decoded.r) {
489
500
  const registries: AttributionSchemaId2Registries = {}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ox",
3
3
  "description": "Ethereum Standard Library",
4
- "version": "0.14.11",
4
+ "version": "0.14.13",
5
5
  "main": "./_cjs/index.js",
6
6
  "module": "./_esm/index.js",
7
7
  "types": "./_types/index.d.ts",
@@ -509,6 +509,11 @@
509
509
  "import": "./_esm/tempo/KeyAuthorization.js",
510
510
  "default": "./_cjs/tempo/KeyAuthorization.js"
511
511
  },
512
+ "./tempo/Period": {
513
+ "types": "./_types/tempo/Period.d.ts",
514
+ "import": "./_esm/tempo/Period.js",
515
+ "default": "./_cjs/tempo/Period.js"
516
+ },
512
517
  "./tempo/PoolId": {
513
518
  "types": "./_types/tempo/PoolId.d.ts",
514
519
  "import": "./_esm/tempo/PoolId.js",
@@ -9,6 +9,7 @@ import {
9
9
  } from 'ox'
10
10
  import { describe, expect, test } from 'vitest'
11
11
  import * as KeyAuthorization from './KeyAuthorization.js'
12
+ import * as Period from './Period.js'
12
13
  import * as SignatureEnvelope from './SignatureEnvelope.js'
13
14
 
14
15
  const address = '0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c'
@@ -1670,11 +1671,10 @@ describe('toTuple', () => {
1670
1671
  expect(restored.limits?.[0]?.limit).toBe(0n)
1671
1672
  })
1672
1673
 
1673
- test('zero expiry roundtrips through RLP', () => {
1674
+ test('undefined expiry roundtrips through RLP', () => {
1674
1675
  const authorization = KeyAuthorization.from({
1675
1676
  address,
1676
1677
  chainId: 0n,
1677
- expiry: 0,
1678
1678
  type: 'secp256k1',
1679
1679
  limits: [
1680
1680
  {
@@ -1709,7 +1709,7 @@ describe('toTuple', () => {
1709
1709
  expect(rlpDecoded).toEqual(authorizationTuple)
1710
1710
 
1711
1711
  const restored = KeyAuthorization.fromTuple(tuple)
1712
- expect(restored.expiry).toBe(0)
1712
+ expect(restored.expiry).toBeUndefined()
1713
1713
  })
1714
1714
 
1715
1715
  test('hash works with zero spending limit', () => {
@@ -1728,4 +1728,408 @@ describe('toTuple', () => {
1728
1728
 
1729
1729
  expect(() => KeyAuthorization.hash(authorization)).not.toThrow()
1730
1730
  })
1731
+
1732
+ test('periodic spending limit (period > 0)', () => {
1733
+ const authorization = KeyAuthorization.from({
1734
+ address,
1735
+ chainId: 1n,
1736
+ expiry,
1737
+ type: 'secp256k1',
1738
+ limits: [
1739
+ {
1740
+ token,
1741
+ limit: Value.from('10', 6),
1742
+ period: Period.months(1),
1743
+ },
1744
+ ],
1745
+ })
1746
+
1747
+ const tuple = KeyAuthorization.toTuple(authorization)
1748
+
1749
+ expect(tuple).toMatchInlineSnapshot(`
1750
+ [
1751
+ [
1752
+ "0x1",
1753
+ "0x",
1754
+ "0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c",
1755
+ "0x499602d2",
1756
+ [
1757
+ [
1758
+ "0x20c0000000000000000000000000000000000001",
1759
+ "0x989680",
1760
+ "0x278d00",
1761
+ ],
1762
+ ],
1763
+ ],
1764
+ ]
1765
+ `)
1766
+
1767
+ // Roundtrip
1768
+ const serialized = KeyAuthorization.serialize(authorization)
1769
+ const restored = KeyAuthorization.deserialize(serialized)
1770
+ expect(restored.limits?.[0]?.period).toBe(2592000)
1771
+ expect(restored.limits?.[0]?.limit).toBe(Value.from('10', 6))
1772
+ })
1773
+
1774
+ test('one-time limit omits period from tuple', () => {
1775
+ const authorization = KeyAuthorization.from({
1776
+ address,
1777
+ chainId: 1n,
1778
+ expiry,
1779
+ type: 'secp256k1',
1780
+ limits: [
1781
+ {
1782
+ token,
1783
+ limit: Value.from('10', 6),
1784
+ },
1785
+ ],
1786
+ })
1787
+
1788
+ const tuple = KeyAuthorization.toTuple(authorization)
1789
+ // Canonical 2-field form — no period element
1790
+ const [authTuple] = tuple
1791
+ const limitTuple = (authTuple as any)[4][0]
1792
+ expect(limitTuple).toHaveLength(2)
1793
+ })
1794
+
1795
+ test('mixed one-time and periodic limits', () => {
1796
+ const authorization = KeyAuthorization.from({
1797
+ address,
1798
+ chainId: 1n,
1799
+ expiry,
1800
+ type: 'secp256k1',
1801
+ limits: [
1802
+ {
1803
+ token: '0x20c0000000000000000000000000000000000001',
1804
+ limit: Value.from('10', 6),
1805
+ },
1806
+ {
1807
+ token: '0x20c0000000000000000000000000000000000002',
1808
+ limit: Value.from('100', 6),
1809
+ period: Period.days(1),
1810
+ },
1811
+ ],
1812
+ })
1813
+
1814
+ const serialized = KeyAuthorization.serialize(authorization)
1815
+ const restored = KeyAuthorization.deserialize(serialized)
1816
+ expect(restored.limits?.[0]?.period).toBeUndefined()
1817
+ expect(restored.limits?.[1]?.period).toBe(86400)
1818
+ })
1819
+
1820
+ test('call scopes: address-only (any selector)', () => {
1821
+ const authorization = KeyAuthorization.from({
1822
+ address,
1823
+ chainId: 1n,
1824
+ type: 'secp256k1',
1825
+ scopes: [
1826
+ {
1827
+ address: '0x1234567890123456789012345678901234567890',
1828
+ },
1829
+ ],
1830
+ })
1831
+
1832
+ const tuple = KeyAuthorization.toTuple(authorization)
1833
+ expect(tuple).toMatchInlineSnapshot(`
1834
+ [
1835
+ [
1836
+ "0x1",
1837
+ "0x",
1838
+ "0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c",
1839
+ "0x",
1840
+ [],
1841
+ [
1842
+ [
1843
+ "0x1234567890123456789012345678901234567890",
1844
+ [],
1845
+ ],
1846
+ ],
1847
+ ],
1848
+ ]
1849
+ `)
1850
+
1851
+ const serialized = KeyAuthorization.serialize(authorization)
1852
+ const restored = KeyAuthorization.deserialize(serialized)
1853
+ expect(restored.scopes).toHaveLength(1)
1854
+ expect(restored.scopes?.[0]?.address).toBe(
1855
+ '0x1234567890123456789012345678901234567890',
1856
+ )
1857
+ expect(restored.scopes?.[0]?.selector).toBeUndefined()
1858
+ })
1859
+
1860
+ test('call scopes: explicit selectors', () => {
1861
+ const authorization = KeyAuthorization.from({
1862
+ address,
1863
+ chainId: 1n,
1864
+ type: 'secp256k1',
1865
+ scopes: [
1866
+ {
1867
+ address: '0x1234567890123456789012345678901234567890',
1868
+ selector: '0xa9059cbb', // transfer
1869
+ },
1870
+ {
1871
+ address: '0x1234567890123456789012345678901234567890',
1872
+ selector: '0x095ea7b3', // approve
1873
+ },
1874
+ ],
1875
+ })
1876
+
1877
+ const serialized = KeyAuthorization.serialize(authorization)
1878
+ const restored = KeyAuthorization.deserialize(serialized)
1879
+ expect(restored.scopes).toHaveLength(2)
1880
+ expect(restored.scopes?.[0]?.selector).toBe('0xa9059cbb')
1881
+ expect(restored.scopes?.[1]?.selector).toBe('0x095ea7b3')
1882
+ expect(restored.scopes?.[0]?.recipients).toBeUndefined()
1883
+ })
1884
+
1885
+ test('call scopes: selectors with recipients', () => {
1886
+ const authorization = KeyAuthorization.from({
1887
+ address,
1888
+ chainId: 1n,
1889
+ type: 'secp256k1',
1890
+ scopes: [
1891
+ {
1892
+ address: token,
1893
+ selector: '0xa9059cbb',
1894
+ recipients: [
1895
+ '0x1111111111111111111111111111111111111111',
1896
+ '0x2222222222222222222222222222222222222222',
1897
+ ],
1898
+ },
1899
+ ],
1900
+ })
1901
+
1902
+ const serialized = KeyAuthorization.serialize(authorization)
1903
+ const restored = KeyAuthorization.deserialize(serialized)
1904
+ expect(restored.scopes?.[0]?.recipients).toEqual([
1905
+ '0x1111111111111111111111111111111111111111',
1906
+ '0x2222222222222222222222222222222222222222',
1907
+ ])
1908
+ })
1909
+
1910
+ test('scopes = undefined (unrestricted, omitted from wire)', () => {
1911
+ const authorization = KeyAuthorization.from({
1912
+ address,
1913
+ chainId: 1n,
1914
+ type: 'secp256k1',
1915
+ })
1916
+
1917
+ const tuple = KeyAuthorization.toTuple(authorization)
1918
+ // No scopes field in tuple
1919
+ const [authTuple] = tuple
1920
+ expect((authTuple as unknown as any[]).length).toBe(3) // chainId, type, address
1921
+
1922
+ const serialized = KeyAuthorization.serialize(authorization)
1923
+ const restored = KeyAuthorization.deserialize(serialized)
1924
+ expect(restored.scopes).toBeUndefined()
1925
+ })
1926
+
1927
+ test('scopes = [] (scoped, no calls allowed)', () => {
1928
+ const authorization = KeyAuthorization.from({
1929
+ address,
1930
+ chainId: 1n,
1931
+ type: 'secp256k1',
1932
+ scopes: [],
1933
+ })
1934
+
1935
+ const serialized = KeyAuthorization.serialize(authorization)
1936
+ const restored = KeyAuthorization.deserialize(serialized)
1937
+ expect(restored.scopes).toEqual([])
1938
+ })
1939
+
1940
+ test('scopes + limits + expiry combined', () => {
1941
+ const authorization = KeyAuthorization.from({
1942
+ address,
1943
+ chainId: 1n,
1944
+ expiry,
1945
+ type: 'secp256k1',
1946
+ limits: [
1947
+ {
1948
+ token,
1949
+ limit: Value.from('10', 6),
1950
+ period: Period.days(1),
1951
+ },
1952
+ ],
1953
+ scopes: [
1954
+ {
1955
+ address: '0x1234567890123456789012345678901234567890',
1956
+ selector: '0xa9059cbb',
1957
+ recipients: ['0x1111111111111111111111111111111111111111'],
1958
+ },
1959
+ ],
1960
+ })
1961
+
1962
+ const serialized = KeyAuthorization.serialize(authorization)
1963
+ const restored = KeyAuthorization.deserialize(serialized)
1964
+ expect(restored.expiry).toBe(expiry)
1965
+ expect(restored.limits?.[0]?.period).toBe(86400)
1966
+ expect(restored.scopes?.[0]?.recipients).toEqual([
1967
+ '0x1111111111111111111111111111111111111111',
1968
+ ])
1969
+ })
1970
+
1971
+ test('hash consistency with scopes', () => {
1972
+ const authorization = KeyAuthorization.from({
1973
+ address,
1974
+ chainId: 1n,
1975
+ type: 'secp256k1',
1976
+ scopes: [
1977
+ {
1978
+ address: '0x1234567890123456789012345678901234567890',
1979
+ selector: '0xa9059cbb',
1980
+ },
1981
+ ],
1982
+ })
1983
+
1984
+ const hash1 = KeyAuthorization.hash(authorization)
1985
+ const hash2 = KeyAuthorization.hash(authorization)
1986
+ expect(hash1).toBe(hash2)
1987
+
1988
+ // Different scopes should produce different hash
1989
+ const authorization2 = KeyAuthorization.from({
1990
+ address,
1991
+ chainId: 1n,
1992
+ type: 'secp256k1',
1993
+ scopes: [
1994
+ {
1995
+ address: '0x1234567890123456789012345678901234567890',
1996
+ selector: '0x095ea7b3',
1997
+ },
1998
+ ],
1999
+ })
2000
+ expect(KeyAuthorization.hash(authorization2)).not.toBe(hash1)
2001
+ })
2002
+
2003
+ test('call scopes: selector from function signature string', () => {
2004
+ const authorization = KeyAuthorization.from({
2005
+ address,
2006
+ chainId: 1n,
2007
+ type: 'secp256k1',
2008
+ scopes: [
2009
+ {
2010
+ address: '0x1234567890123456789012345678901234567890',
2011
+ selector: 'function transfer(address,uint256)',
2012
+ },
2013
+ {
2014
+ address: '0x1234567890123456789012345678901234567890',
2015
+ selector: 'function approve(address,uint256)',
2016
+ },
2017
+ ],
2018
+ })
2019
+
2020
+ const serialized = KeyAuthorization.serialize(authorization)
2021
+ const restored = KeyAuthorization.deserialize(serialized)
2022
+ expect(restored.scopes).toHaveLength(2)
2023
+ // transfer(address,uint256) => 0xa9059cbb
2024
+ expect(restored.scopes?.[0]?.selector).toBe('0xa9059cbb')
2025
+ // approve(address,uint256) => 0x095ea7b3
2026
+ expect(restored.scopes?.[1]?.selector).toBe('0x095ea7b3')
2027
+ })
2028
+
2029
+ test('call scopes: selector from signature with recipients', () => {
2030
+ const authorization = KeyAuthorization.from({
2031
+ address,
2032
+ chainId: 1n,
2033
+ type: 'secp256k1',
2034
+ scopes: [
2035
+ {
2036
+ address: token,
2037
+ selector: 'function transfer(address,uint256)',
2038
+ recipients: ['0x1111111111111111111111111111111111111111'],
2039
+ },
2040
+ ],
2041
+ })
2042
+
2043
+ const serialized = KeyAuthorization.serialize(authorization)
2044
+ const restored = KeyAuthorization.deserialize(serialized)
2045
+ expect(restored.scopes?.[0]?.selector).toBe('0xa9059cbb')
2046
+ expect(restored.scopes?.[0]?.recipients).toEqual([
2047
+ '0x1111111111111111111111111111111111111111',
2048
+ ])
2049
+ })
2050
+
2051
+ test('call scopes: selector from bare signature (no function prefix)', () => {
2052
+ const authorization = KeyAuthorization.from({
2053
+ address,
2054
+ chainId: 1n,
2055
+ type: 'secp256k1',
2056
+ scopes: [
2057
+ {
2058
+ address: '0x1234567890123456789012345678901234567890',
2059
+ selector: 'transfer(address,uint256)',
2060
+ },
2061
+ {
2062
+ address: '0x1234567890123456789012345678901234567890',
2063
+ selector: 'approve(address,uint256)',
2064
+ },
2065
+ ],
2066
+ })
2067
+
2068
+ const serialized = KeyAuthorization.serialize(authorization)
2069
+ const restored = KeyAuthorization.deserialize(serialized)
2070
+ expect(restored.scopes).toHaveLength(2)
2071
+ expect(restored.scopes?.[0]?.selector).toBe('0xa9059cbb')
2072
+ expect(restored.scopes?.[1]?.selector).toBe('0x095ea7b3')
2073
+ })
2074
+
2075
+ test('call scopes: mixed hex and signature selectors', () => {
2076
+ const authorization = KeyAuthorization.from({
2077
+ address,
2078
+ chainId: 1n,
2079
+ type: 'secp256k1',
2080
+ scopes: [
2081
+ {
2082
+ address: '0x1234567890123456789012345678901234567890',
2083
+ selector: '0xa9059cbb',
2084
+ },
2085
+ {
2086
+ address: '0x1234567890123456789012345678901234567890',
2087
+ selector: 'function approve(address,uint256)',
2088
+ },
2089
+ ],
2090
+ })
2091
+
2092
+ const serialized = KeyAuthorization.serialize(authorization)
2093
+ const restored = KeyAuthorization.deserialize(serialized)
2094
+ expect(restored.scopes?.[0]?.selector).toBe('0xa9059cbb')
2095
+ expect(restored.scopes?.[1]?.selector).toBe('0x095ea7b3')
2096
+ })
2097
+
2098
+ test('toRpc/fromRpc roundtrip with period and scopes', () => {
2099
+ const authorization = KeyAuthorization.from(
2100
+ {
2101
+ address,
2102
+ chainId: 1n,
2103
+ expiry,
2104
+ type: 'secp256k1',
2105
+ limits: [
2106
+ {
2107
+ token,
2108
+ limit: Value.from('10', 6),
2109
+ period: Period.months(1),
2110
+ },
2111
+ ],
2112
+ scopes: [
2113
+ {
2114
+ address: token,
2115
+ selector: '0xa9059cbb',
2116
+ recipients: ['0x1111111111111111111111111111111111111111'],
2117
+ },
2118
+ ],
2119
+ },
2120
+ {
2121
+ signature: SignatureEnvelope.from(signature_secp256k1),
2122
+ },
2123
+ )
2124
+
2125
+ const rpc = KeyAuthorization.toRpc(authorization)
2126
+ const restored = KeyAuthorization.fromRpc(rpc)
2127
+
2128
+ expect(restored.limits?.[0]?.period).toBe(2592000)
2129
+ expect(restored.scopes?.[0]?.address).toBe(token)
2130
+ expect(restored.scopes?.[0]?.selector).toBe('0xa9059cbb')
2131
+ expect(restored.scopes?.[0]?.recipients).toEqual([
2132
+ '0x1111111111111111111111111111111111111111',
2133
+ ])
2134
+ })
1731
2135
  })