accounts 0.8.2 → 0.8.3

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 (47) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/core/Adapter.d.ts +7 -0
  3. package/dist/core/Adapter.d.ts.map +1 -1
  4. package/dist/core/Adapter.js.map +1 -1
  5. package/dist/core/Dialog.d.ts.map +1 -1
  6. package/dist/core/Dialog.js +1 -1
  7. package/dist/core/Dialog.js.map +1 -1
  8. package/dist/core/Provider.d.ts.map +1 -1
  9. package/dist/core/Provider.js +8 -0
  10. package/dist/core/Provider.js.map +1 -1
  11. package/dist/core/Schema.d.ts +87 -0
  12. package/dist/core/Schema.d.ts.map +1 -1
  13. package/dist/core/Schema.js +1 -0
  14. package/dist/core/Schema.js.map +1 -1
  15. package/dist/core/TrustedHosts.d.ts +7 -2
  16. package/dist/core/TrustedHosts.d.ts.map +1 -1
  17. package/dist/core/TrustedHosts.js +18 -3
  18. package/dist/core/TrustedHosts.js.map +1 -1
  19. package/dist/core/adapters/dialog.d.ts.map +1 -1
  20. package/dist/core/adapters/dialog.js +3 -0
  21. package/dist/core/adapters/dialog.js.map +1 -1
  22. package/dist/core/zod/rpc.d.ts +57 -0
  23. package/dist/core/zod/rpc.d.ts.map +1 -1
  24. package/dist/core/zod/rpc.js +24 -0
  25. package/dist/core/zod/rpc.js.map +1 -1
  26. package/dist/react/Remote.js +1 -1
  27. package/dist/react/Remote.js.map +1 -1
  28. package/dist/server/CliAuth.d.ts +5 -3
  29. package/dist/server/CliAuth.d.ts.map +1 -1
  30. package/dist/server/CliAuth.js +19 -19
  31. package/dist/server/CliAuth.js.map +1 -1
  32. package/package.json +2 -2
  33. package/src/core/Adapter.ts +13 -0
  34. package/src/core/Dialog.test-d.ts +1 -0
  35. package/src/core/Dialog.ts +3 -1
  36. package/src/core/Provider.test-d.ts +6 -0
  37. package/src/core/Provider.test.ts +16 -0
  38. package/src/core/Provider.ts +12 -0
  39. package/src/core/Schema.test-d.ts +20 -1
  40. package/src/core/Schema.ts +1 -0
  41. package/src/core/TrustedHosts.ts +19 -3
  42. package/src/core/adapters/dialog.ts +4 -0
  43. package/src/core/zod/request.test.ts +72 -0
  44. package/src/core/zod/rpc.ts +27 -0
  45. package/src/react/Remote.ts +1 -1
  46. package/src/server/CliAuth.test.ts +106 -13
  47. package/src/server/CliAuth.ts +26 -23
@@ -47,6 +47,12 @@ describe('request', () => {
47
47
  expectTypeOf<Result<'wallet_disconnect'>>().toEqualTypeOf<undefined>()
48
48
  })
49
49
 
50
+ test('wallet_swap', () => {
51
+ expectTypeOf<Result<'wallet_swap'>>().toMatchTypeOf<{
52
+ receipt: { transactionHash: `0x${string}` }
53
+ }>()
54
+ })
55
+
50
56
  test('wallet_switchEthereumChain', () => {
51
57
  expectTypeOf<Result<'wallet_switchEthereumChain'>>().toEqualTypeOf<undefined>()
52
58
  })
@@ -664,6 +664,22 @@ describe.each(adapters)('$name', ({ adapter }: (typeof adapters)[number]) => {
664
664
  })
665
665
  })
666
666
 
667
+ describe('wallet_swap', () => {
668
+ test('error: throws UnsupportedMethodError when adapter has no swap action', async () => {
669
+ const provider = Provider.create({ adapter: adapter(), chains: [chain] })
670
+ await connect(provider)
671
+
672
+ await expect(
673
+ provider.request({
674
+ method: 'wallet_swap',
675
+ params: [{ amount: '0x1', token: Addresses.pathUsd, type: 'sell' }],
676
+ }),
677
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
678
+ `[Provider.UnsupportedMethodError: \`swap\` not supported by adapter.]`,
679
+ )
680
+ })
681
+ })
682
+
667
683
  describe('wallet_getCapabilities', () => {
668
684
  test('default: returns atomic supported for all chains', async () => {
669
685
  const provider = Provider.create({ adapter: adapter() })
@@ -638,6 +638,18 @@ export function create(options: create.Options = {}): create.ReturnType {
638
638
  )) satisfies Rpc.wallet_send.Encoded['returns']
639
639
  }
640
640
 
641
+ case 'wallet_swap': {
642
+ assertConnected()
643
+ if (!actions.swap)
644
+ throw new ox_Provider.UnsupportedMethodError({
645
+ message: '`swap` not supported by adapter.',
646
+ })
647
+ return (await actions.swap(
648
+ (request._decoded.params?.[0] ?? {}) as Adapter.swap.Parameters,
649
+ request,
650
+ )) satisfies Rpc.wallet_swap.Encoded['returns']
651
+ }
652
+
641
653
  case 'wallet_switchEthereumChain': {
642
654
  const { chainId } = request._decoded.params[0]
643
655
  if (!chains.some((c) => c.id === chainId))
@@ -137,6 +137,24 @@ describe('Encoded', () => {
137
137
  }>()
138
138
  })
139
139
 
140
+ test('wallet_swap', () => {
141
+ expectTypeOf<Rpc.wallet_swap.Encoded>().toMatchTypeOf<{
142
+ method: 'wallet_swap'
143
+ params:
144
+ | readonly [
145
+ {
146
+ amount?: Hex | undefined
147
+ pairToken?: Hex | undefined
148
+ slippage?: number | undefined
149
+ token?: Hex | undefined
150
+ type?: 'buy' | 'sell' | undefined
151
+ },
152
+ ]
153
+ | undefined
154
+ returns: { receipt: { transactionHash: Hex } }
155
+ }>()
156
+ })
157
+
140
158
  test('wallet_switchEthereumChain', () => {
141
159
  expectTypeOf<Rpc.wallet_switchEthereumChain.Encoded>().toEqualTypeOf<{
142
160
  method: 'wallet_switchEthereumChain'
@@ -175,7 +193,7 @@ describe('Ox', () => {
175
193
  describe('Viem', () => {
176
194
  test('is a tuple of all provider methods', () => {
177
195
  expectTypeOf<Schema.Viem[0]['Method']>().toEqualTypeOf<'eth_accounts'>()
178
- expectTypeOf<Schema.Viem[19]['Method']>().toEqualTypeOf<'wallet_switchEthereumChain'>()
196
+ expectTypeOf<Schema.Viem[20]['Method']>().toEqualTypeOf<'wallet_switchEthereumChain'>()
179
197
  })
180
198
  })
181
199
 
@@ -203,6 +221,7 @@ describe('Request', () => {
203
221
  | 'wallet_getBalances'
204
222
  | 'wallet_revokeAccessKey'
205
223
  | 'wallet_send'
224
+ | 'wallet_swap'
206
225
  >()
207
226
  })
208
227
 
@@ -95,6 +95,7 @@ export const schema = from([
95
95
  Rpc.wallet_revokeAccessKey.schema,
96
96
  Rpc.wallet_send.schema,
97
97
  Rpc.wallet_sendCalls.schema,
98
+ Rpc.wallet_swap.schema,
98
99
  Rpc.wallet_switchEthereumChain.schema,
99
100
  ])
100
101
 
@@ -10,15 +10,31 @@ import _hosts from '../trusted-hosts.json' with { type: 'json' }
10
10
  export const hosts: Record<string, readonly string[]> = _hosts
11
11
 
12
12
  /**
13
- * Returns `true` if `hostname` matches any pattern in `trustedHosts`.
13
+ * Returns `true` if `hostname` matches any pattern in `trustedHosts`,
14
+ * or (when `source` is provided) if `hostname` shares the same
15
+ * registrable domain ("eTLD+1") as `source`.
16
+ *
14
17
  * Patterns starting with `*.` match any subdomain suffix
15
18
  * (e.g. `*.workers.dev` matches `foo.workers.dev`).
16
19
  */
17
- export function match(trustedHosts: readonly string[], hostname: string) {
18
- if (hostname.endsWith('.local')) return true
20
+ export function match(trustedHosts: readonly string[], hostname: string, source?: string) {
21
+ if (source && sameRegistrableDomain(hostname, source)) return true
19
22
  return trustedHosts.some((pattern) => {
20
23
  if (pattern.startsWith('*.'))
21
24
  return hostname.endsWith(pattern.slice(1)) && hostname.length > pattern.length - 1
22
25
  return pattern === hostname
23
26
  })
24
27
  }
28
+
29
+ /** Returns `true` if `a` and `b` share the same registrable domain ("eTLD+1"). */
30
+ export function sameRegistrableDomain(a: string, b: string) {
31
+ return registrableDomain(a) === registrableDomain(b)
32
+ }
33
+
34
+ /** Returns the registrable domain ("eTLD+1") for a hostname. */
35
+ function registrableDomain(host: string) {
36
+ const hostname = host.split(':')[0]!.toLowerCase()
37
+ const labels = hostname.split('.')
38
+ if (labels.length <= 2) return hostname
39
+ return labels.slice(-2).join('.')
40
+ }
@@ -403,6 +403,10 @@ export function dialog(options: dialog.Options = {}): Adapter.Adapter {
403
403
  })
404
404
  },
405
405
 
406
+ async swap(_params, request) {
407
+ return await provider.request(request)
408
+ },
409
+
406
410
  async disconnect() {
407
411
  store.setState({ accessKeys: [], accounts: [], activeAccount: 0 })
408
412
  },
@@ -84,6 +84,35 @@ describe('validate', () => {
84
84
  `)
85
85
  })
86
86
 
87
+ test('default: validates wallet_swap with sell amount', () => {
88
+ const result = RpcRequest.validate(Schema.Request, {
89
+ method: 'wallet_swap',
90
+ params: [
91
+ {
92
+ amount: '0x64',
93
+ pairToken: '0x0000000000000000000000000000000000000002',
94
+ slippage: 0.01,
95
+ token: '0x0000000000000000000000000000000000000001',
96
+ type: 'sell',
97
+ },
98
+ ],
99
+ })
100
+ expect(result._decoded).toMatchInlineSnapshot(`
101
+ {
102
+ "method": "wallet_swap",
103
+ "params": [
104
+ {
105
+ "amount": "0x64",
106
+ "pairToken": "0x0000000000000000000000000000000000000002",
107
+ "slippage": 0.01,
108
+ "token": "0x0000000000000000000000000000000000000001",
109
+ "type": "sell",
110
+ },
111
+ ],
112
+ }
113
+ `)
114
+ })
115
+
87
116
  test('behavior: preserves original request properties', () => {
88
117
  const result = RpcRequest.validate(Schema.Request, {
89
118
  method: 'eth_accounts',
@@ -118,4 +147,47 @@ describe('validate', () => {
118
147
  `[ProviderRpcError: Invalid params: params.0.chainId: Expected hex value, params.0.chainId: Invalid input]`,
119
148
  )
120
149
  })
150
+
151
+ test('error: rejects wallet_swap with invalid type', () => {
152
+ expect(() =>
153
+ RpcRequest.validate(Schema.Request, {
154
+ method: 'wallet_swap',
155
+ params: [
156
+ {
157
+ type: 'hold',
158
+ },
159
+ ],
160
+ }),
161
+ ).toThrowErrorMatchingInlineSnapshot(
162
+ `[ProviderRpcError: Invalid params: params.0.type: Invalid input, params.0.type: Invalid input]`,
163
+ )
164
+ })
165
+
166
+ test('error: rejects wallet_swap with out-of-range slippage', () => {
167
+ expect(() =>
168
+ RpcRequest.validate(Schema.Request, {
169
+ method: 'wallet_swap',
170
+ params: [
171
+ {
172
+ slippage: 1.1,
173
+ },
174
+ ],
175
+ }),
176
+ ).toThrowErrorMatchingInlineSnapshot(
177
+ `[ProviderRpcError: Invalid params: params.0.slippage: Invalid input]`,
178
+ )
179
+
180
+ expect(() =>
181
+ RpcRequest.validate(Schema.Request, {
182
+ method: 'wallet_swap',
183
+ params: [
184
+ {
185
+ slippage: -0.1,
186
+ },
187
+ ],
188
+ }),
189
+ ).toThrowErrorMatchingInlineSnapshot(
190
+ `[ProviderRpcError: Invalid params: params.0.slippage: Invalid input]`,
191
+ )
192
+ })
121
193
  })
@@ -559,6 +559,33 @@ export namespace wallet_send {
559
559
  export type Decoded = Schema.Decoded<typeof schema>
560
560
  }
561
561
 
562
+ const swapParameters = z.object({
563
+ /** Raw token amount to pre-fill. Omit to let the user choose. */
564
+ amount: z.optional(u.hex()),
565
+ /** Other side of the swap pair. For buys, this is the token to sell. For sells, this is the token to buy. */
566
+ pairToken: z.optional(u.address()),
567
+ /** Maximum allowed slippage as a decimal fraction (for example `0.05` for 5%). */
568
+ slippage: z.optional(z.number().check(z.minimum(0), z.maximum(1))),
569
+ /** Token to buy or sell. Omit to let the user choose. */
570
+ token: z.optional(u.address()),
571
+ /** Whether the amount is an exact buy amount (`swapExactAmountOut`) or sell amount (`swapExactAmountIn`). */
572
+ type: z.optional(z.union([z.literal('buy'), z.literal('sell')])),
573
+ })
574
+
575
+ /** Opens the wallet swap flow with optional pre-filled swap intent fields. */
576
+ export namespace wallet_swap {
577
+ export const schema = Schema.defineItem({
578
+ method: z.literal('wallet_swap'),
579
+ params: z.optional(z.readonly(z.tuple([swapParameters]))),
580
+ returns: z.object({
581
+ /** Receipt of the submitted swap. */
582
+ receipt,
583
+ }),
584
+ })
585
+ export type Encoded = Schema.Encoded<typeof schema>
586
+ export type Decoded = Schema.Decoded<typeof schema>
587
+ }
588
+
562
589
  export namespace wallet_switchEthereumChain {
563
590
  export const schema = Schema.defineItem({
564
591
  method: z.literal('wallet_switchEthereumChain'),
@@ -18,7 +18,7 @@ export function useEnsureVisibility(
18
18
  if (!origin) return false
19
19
  try {
20
20
  const hostname = new URL(origin).hostname.replace(/^www\./, '')
21
- return TrustedHosts.match(remote.trustedHosts, hostname)
21
+ return TrustedHosts.match(remote.trustedHosts, hostname, window.location.hostname)
22
22
  } catch {
23
23
  return false
24
24
  }
@@ -343,6 +343,7 @@ describe('createDeviceCode', () => {
343
343
  const store = CliAuth.Store.memory()
344
344
  const now = () => 1_000
345
345
  const defaultExpiry = 4_600
346
+ let validatedChainId: bigint | undefined
346
347
  const { request } = await createRequest('device-code-verifier', {
347
348
  accessKey: secpAccessKey,
348
349
  expiry: undefined,
@@ -354,7 +355,8 @@ describe('createDeviceCode', () => {
354
355
  chainId: chain.id,
355
356
  now,
356
357
  policy: {
357
- validate({ expiry, limits }) {
358
+ validate({ chainId, expiry, limits }) {
359
+ validatedChainId = chainId
358
360
  return {
359
361
  expiry: expiry ?? defaultExpiry,
360
362
  ...(limits ? { limits } : {}),
@@ -368,17 +370,20 @@ describe('createDeviceCode', () => {
368
370
  })
369
371
  const entry = await store.get(result.code)
370
372
 
371
- expect(entry).toMatchInlineSnapshot(`
373
+ expect({ entry, validatedChainId }).toMatchInlineSnapshot(`
372
374
  {
373
- "chainId": 1337n,
374
- "code": "ABCDEFGH",
375
- "codeChallenge": "NUwjc1h8PuXcsvSOG44Rp4bMayBXnOkriHEJ19CaSQM",
376
- "createdAt": 1000,
377
- "expiresAt": 31000,
378
- "expiry": 4600,
379
- "keyType": "secp256k1",
380
- "pubKey": "${secpAccessKey.publicKey}",
381
- "status": "pending",
375
+ "entry": {
376
+ "chainId": 1337n,
377
+ "code": "ABCDEFGH",
378
+ "codeChallenge": "NUwjc1h8PuXcsvSOG44Rp4bMayBXnOkriHEJ19CaSQM",
379
+ "createdAt": 1000,
380
+ "expiresAt": 31000,
381
+ "expiry": 4600,
382
+ "keyType": "secp256k1",
383
+ "pubKey": "${secpAccessKey.publicKey}",
384
+ "status": "pending",
385
+ },
386
+ "validatedChainId": 1337n,
382
387
  }
383
388
  `)
384
389
  })
@@ -734,6 +739,94 @@ describe('authorize', () => {
734
739
  expect(polled.status).toMatchInlineSnapshot(`"authorized"`)
735
740
  })
736
741
 
742
+ test('behavior: accepts user-approved expiry and limit changes', async () => {
743
+ const store = CliAuth.Store.memory()
744
+ const { codeVerifier, request } = await createRequest()
745
+ const { code } = await CliAuth.createDeviceCode({
746
+ chainId: chain.id,
747
+ request,
748
+ store,
749
+ })
750
+ const approvedLimits = [
751
+ {
752
+ limit: 2_000n,
753
+ token: limits[0]!.token,
754
+ },
755
+ ] as const
756
+
757
+ const authorized = await CliAuth.authorize({
758
+ chainId: chain.id,
759
+ request: await authorize(code, { expiry: expiry + 60 * 60 * 24 * 6, limits: approvedLimits }),
760
+ store,
761
+ })
762
+ const polled = await CliAuth.poll({
763
+ code,
764
+ request: {
765
+ codeVerifier: codeVerifier,
766
+ },
767
+ store,
768
+ })
769
+
770
+ if (polled.status !== 'authorized') throw new Error('Expected device code to be authorized.')
771
+
772
+ expect(authorized).toMatchInlineSnapshot(`
773
+ {
774
+ "status": "authorized",
775
+ }
776
+ `)
777
+ expect({ expiry: polled.keyAuthorization.expiry, limits: polled.keyAuthorization.limits })
778
+ .toMatchInlineSnapshot(`
779
+ {
780
+ "expiry": ${expiry + 60 * 60 * 24 * 6},
781
+ "limits": [
782
+ {
783
+ "limit": 2000n,
784
+ "token": "0x20c0000000000000000000000000000000000001",
785
+ },
786
+ ],
787
+ }
788
+ `)
789
+ })
790
+
791
+ test('behavior: rejects unsigned expiry and limit changes', async () => {
792
+ const store = CliAuth.Store.memory()
793
+ const { request } = await createRequest()
794
+ const { code } = await CliAuth.createDeviceCode({
795
+ chainId: chain.id,
796
+ request,
797
+ store,
798
+ })
799
+ const authorized = await authorize(code)
800
+
801
+ await expect(
802
+ CliAuth.authorize({
803
+ chainId: chain.id,
804
+ request: {
805
+ ...authorized,
806
+ keyAuthorization: {
807
+ ...authorized.keyAuthorization,
808
+ expiry: expiry + 1,
809
+ },
810
+ },
811
+ store,
812
+ }),
813
+ ).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: Key authorization signature is invalid.]`)
814
+
815
+ await expect(
816
+ CliAuth.authorize({
817
+ chainId: chain.id,
818
+ request: {
819
+ ...authorized,
820
+ keyAuthorization: {
821
+ ...authorized.keyAuthorization,
822
+ limits: [{ limit: limits[0]!.limit + 1n, token: limits[0]!.token }],
823
+ },
824
+ },
825
+ store,
826
+ }),
827
+ ).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: Key authorization signature is invalid.]`)
828
+ })
829
+
737
830
  test('behavior: rejects a mismatched key authorization', async () => {
738
831
  const store = CliAuth.Store.memory()
739
832
  const { request } = await createRequest()
@@ -746,11 +839,11 @@ describe('authorize', () => {
746
839
  await expect(
747
840
  CliAuth.authorize({
748
841
  chainId: chain.id,
749
- request: await authorize(code, { expiry: expiry + 1 }),
842
+ request: await authorize(code, { accessKeyAddress: secpAccessKey.address }),
750
843
  store,
751
844
  }),
752
845
  ).rejects.toThrowErrorMatchingInlineSnapshot(
753
- `[Error: Key authorization expiry does not match the device-code request.]`,
846
+ `[Error: Key authorization key does not match the device-code request.]`,
754
847
  )
755
848
  })
756
849
 
@@ -170,7 +170,7 @@ export type Store = {
170
170
 
171
171
  /** Host validation and sanitization for requested CLI auth defaults. */
172
172
  export type Policy = {
173
- /** Validates and optionally rewrites requested policy before the entry is stored. */
173
+ /** Validates and optionally rewrites requested defaults before the entry is stored. */
174
174
  validate: (options: Policy.validate.Options) => MaybePromise<Policy.validate.ReturnType>
175
175
  }
176
176
 
@@ -208,6 +208,8 @@ export declare namespace Policy {
208
208
  export type Options = {
209
209
  /** Requested root account restriction. */
210
210
  account?: Address.Address | undefined
211
+ /** Requested chain ID. */
212
+ chainId: bigint
211
213
  /** Requested access-key expiry timestamp. Omit to let the server choose one. */
212
214
  expiry?: number | undefined
213
215
  /** Requested key type. */
@@ -219,9 +221,9 @@ export declare namespace Policy {
219
221
  }
220
222
 
221
223
  export type ReturnType = {
222
- /** Approved access-key expiry timestamp. */
224
+ /** Suggested access-key expiry timestamp. */
223
225
  expiry: number
224
- /** Approved spending limits. */
226
+ /** Suggested spending limits. */
225
227
  limits?: readonly { token: Address.Address; limit: bigint }[] | undefined
226
228
  }
227
229
  }
@@ -417,24 +419,38 @@ export function from(options: from.Options = {}): CliAuth {
417
419
  throw new Error('Key authorization key type does not match the device-code request.')
418
420
  if (actual.chainId !== expected.chainId)
419
421
  throw new Error('Key authorization chain does not match the device-code request.')
420
- if ((actual.expiry ?? undefined) !== (expected.expiry ?? undefined))
421
- throw new Error('Key authorization expiry does not match the device-code request.')
422
- if (!sameLimits(actual.limits, expected.limits))
423
- throw new Error('Key authorization limits do not match the device-code request.')
422
+
423
+ const signed = TempoKeyAuthorization.from({
424
+ address: actual.address,
425
+ chainId: actual.chainId,
426
+ expiry: actual.expiry,
427
+ ...(actual.limits ? { limits: actual.limits } : {}),
428
+ type: actual.keyType,
429
+ })
424
430
 
425
431
  const valid = await verifyHash((options.client ?? cache.get(current.chainId)) as never, {
426
432
  address: options.request.accountAddress,
427
- hash: TempoKeyAuthorization.getSignPayload(expected),
433
+ hash: TempoKeyAuthorization.getSignPayload(signed),
428
434
  signature: SignatureEnvelope.serialize(SignatureEnvelope.fromRpc(actual.signature), {
429
435
  magic: actual.signature.type === 'webAuthn',
430
436
  }),
431
437
  })
432
438
  if (!valid) throw new Error('Key authorization signature is invalid.')
433
439
 
440
+ const signedKeyAuthorization = {
441
+ address: options.request.keyAuthorization.address,
442
+ chainId: options.request.keyAuthorization.chainId,
443
+ expiry: actual.expiry,
444
+ keyId: options.request.keyAuthorization.keyId,
445
+ keyType: options.request.keyAuthorization.keyType,
446
+ ...(actual.limits ? { limits: actual.limits } : {}),
447
+ signature: options.request.keyAuthorization.signature,
448
+ } satisfies z.output<typeof keyAuthorization>
449
+
434
450
  const authorized = await store.authorize({
435
451
  accountAddress: options.request.accountAddress,
436
452
  code,
437
- keyAuthorization: options.request.keyAuthorization,
453
+ keyAuthorization: signedKeyAuthorization,
438
454
  })
439
455
  if (!authorized) throw new Error('Unable to authorize device code.')
440
456
 
@@ -446,6 +462,7 @@ export function from(options: from.Options = {}): CliAuth {
446
462
  const keyType = options.request.keyType ?? 'secp256k1'
447
463
  const approved = await policy.validate({
448
464
  ...(account ? { account } : {}),
465
+ chainId: typeof nextChainId === 'bigint' ? nextChainId : BigInt(nextChainId),
449
466
  expiry: options.request.expiry,
450
467
  keyType,
451
468
  ...(options.request.limits ? { limits: options.request.limits } : {}),
@@ -808,20 +825,6 @@ function normalizeKeyAuthorization(value: z.output<typeof keyAuthorization>) {
808
825
  }
809
826
  }
810
827
 
811
- /** @internal */
812
- function sameLimits(
813
- a: Policy.validate.ReturnType['limits'],
814
- b: Policy.validate.ReturnType['limits'],
815
- ) {
816
- if (!a && !b) return true
817
- if (!a || !b || a.length !== b.length) return false
818
- return a.every((limit, i) => {
819
- const other = b[i]
820
- if (!other) return false
821
- return limit.token.toLowerCase() === other.token.toLowerCase() && limit.limit === other.limit
822
- })
823
- }
824
-
825
828
  /** @internal */
826
829
  async function verifyCodeChallenge(codeVerifier: string, codeChallenge: string) {
827
830
  const hash = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(codeVerifier))