accounts 0.12.2 → 0.13.0

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 (99) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +6 -5
  3. package/dist/cli/adapter.d.ts.map +1 -1
  4. package/dist/cli/adapter.js +53 -67
  5. package/dist/cli/adapter.js.map +1 -1
  6. package/dist/core/AccessKey.d.ts +89 -106
  7. package/dist/core/AccessKey.d.ts.map +1 -1
  8. package/dist/core/AccessKey.js +226 -210
  9. package/dist/core/AccessKey.js.map +1 -1
  10. package/dist/core/Account.d.ts +0 -9
  11. package/dist/core/Account.d.ts.map +1 -1
  12. package/dist/core/Account.js +1 -13
  13. package/dist/core/Account.js.map +1 -1
  14. package/dist/core/Adapter.d.ts +10 -2
  15. package/dist/core/Adapter.d.ts.map +1 -1
  16. package/dist/core/Provider.d.ts +4 -5
  17. package/dist/core/Provider.d.ts.map +1 -1
  18. package/dist/core/Provider.js +66 -76
  19. package/dist/core/Provider.js.map +1 -1
  20. package/dist/core/Schema.d.ts +7 -7
  21. package/dist/core/adapters/dialog.d.ts.map +1 -1
  22. package/dist/core/adapters/dialog.js +114 -124
  23. package/dist/core/adapters/dialog.js.map +1 -1
  24. package/dist/core/adapters/local.d.ts.map +1 -1
  25. package/dist/core/adapters/local.js +92 -105
  26. package/dist/core/adapters/local.js.map +1 -1
  27. package/dist/core/adapters/privy.d.ts +164 -0
  28. package/dist/core/adapters/privy.d.ts.map +1 -0
  29. package/dist/core/adapters/privy.js +574 -0
  30. package/dist/core/adapters/privy.js.map +1 -0
  31. package/dist/core/adapters/turnkey.d.ts.map +1 -1
  32. package/dist/core/adapters/turnkey.js +136 -133
  33. package/dist/core/adapters/turnkey.js.map +1 -1
  34. package/dist/core/internal/AccessKeyTransaction.d.ts +59 -0
  35. package/dist/core/internal/AccessKeyTransaction.d.ts.map +1 -0
  36. package/dist/core/internal/AccessKeyTransaction.js +155 -0
  37. package/dist/core/internal/AccessKeyTransaction.js.map +1 -0
  38. package/dist/core/zod/rpc.d.ts +9 -4
  39. package/dist/core/zod/rpc.d.ts.map +1 -1
  40. package/dist/core/zod/rpc.js +9 -4
  41. package/dist/core/zod/rpc.js.map +1 -1
  42. package/dist/index.d.ts +1 -1
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +1 -1
  45. package/dist/index.js.map +1 -1
  46. package/dist/react-native/adapter.d.ts.map +1 -1
  47. package/dist/react-native/adapter.js +61 -72
  48. package/dist/react-native/adapter.js.map +1 -1
  49. package/dist/server/Kv.d.ts +19 -9
  50. package/dist/server/Kv.d.ts.map +1 -1
  51. package/dist/server/Kv.js +32 -9
  52. package/dist/server/Kv.js.map +1 -1
  53. package/dist/server/internal/handlers/auth.d.ts.map +1 -1
  54. package/dist/server/internal/handlers/auth.js +2 -0
  55. package/dist/server/internal/handlers/auth.js.map +1 -1
  56. package/dist/server/internal/handlers/relay.d.ts +18 -12
  57. package/dist/server/internal/handlers/relay.d.ts.map +1 -1
  58. package/dist/server/internal/handlers/relay.js +115 -2
  59. package/dist/server/internal/handlers/relay.js.map +1 -1
  60. package/dist/server/internal/handlers/webAuthn.d.ts +6 -1
  61. package/dist/server/internal/handlers/webAuthn.d.ts.map +1 -1
  62. package/dist/server/internal/handlers/webAuthn.js +16 -9
  63. package/dist/server/internal/handlers/webAuthn.js.map +1 -1
  64. package/package.json +2 -2
  65. package/src/cli/adapter.ts +69 -74
  66. package/src/core/AccessKey.test.ts +525 -559
  67. package/src/core/AccessKey.ts +323 -327
  68. package/src/core/Account.test.ts +5 -50
  69. package/src/core/Account.ts +1 -19
  70. package/src/core/Adapter.ts +6 -2
  71. package/src/core/{Provider.test.ts → Provider.localnet.test.ts} +97 -31
  72. package/src/core/Provider.ts +110 -110
  73. package/src/core/Schema.test-d.ts +18 -0
  74. package/src/core/adapters/dialog.test.ts +137 -13
  75. package/src/core/adapters/dialog.ts +109 -121
  76. package/src/core/adapters/local.ts +95 -119
  77. package/src/core/adapters/privy.test-d.ts +128 -0
  78. package/src/core/adapters/privy.test.ts +634 -0
  79. package/src/core/adapters/privy.ts +805 -0
  80. package/src/core/adapters/turnkey.test.ts +117 -13
  81. package/src/core/adapters/turnkey.ts +132 -149
  82. package/src/core/internal/AccessKeyTransaction.ts +243 -0
  83. package/src/core/{mppx.test.ts → mppx.localnet.test.ts} +32 -2
  84. package/src/core/zod/request.test.ts +36 -0
  85. package/src/core/zod/rpc.ts +17 -10
  86. package/src/index.ts +1 -1
  87. package/src/react-native/adapter.ts +77 -84
  88. package/src/server/Kv.test.ts +51 -0
  89. package/src/server/Kv.ts +43 -9
  90. package/src/server/internal/handlers/auth.test.ts +10 -5
  91. package/src/server/internal/handlers/auth.ts +3 -0
  92. package/src/server/internal/handlers/{relay.test.ts → relay.localnet.test.ts} +3 -0
  93. package/src/server/internal/handlers/relay.ts +138 -13
  94. package/src/server/internal/handlers/session.test.ts +1 -1
  95. package/src/server/internal/handlers/webAuthn.test.ts +20 -0
  96. package/src/server/internal/handlers/webAuthn.ts +24 -12
  97. /package/src/cli/{Provider.test.ts → Provider.localnet.test.ts} +0 -0
  98. /package/src/react-native/{Provider.test.ts → Provider.localnet.test.ts} +0 -0
  99. /package/src/server/internal/handlers/{exchange.test.ts → exchange.localnet.test.ts} +0 -0
@@ -1,8 +1,8 @@
1
1
  import { announceProvider } from 'mipd'
2
2
  import { Mppx, tempo as mppx_tempo } from 'mppx/client'
3
3
  import { Address, Hash, Hex, Json, Provider as ox_Provider, RpcResponse } from 'ox'
4
- import { KeyAuthorization } from 'ox/tempo'
5
4
  import { http, parseUnits, type Chain, type Client as ViemClient, type Transport } from 'viem'
5
+ import type { JsonRpcAccount } from 'viem/accounts'
6
6
  import { tempo, tempoDevnet, tempoModerato } from 'viem/chains'
7
7
  import { parseSiweMessage } from 'viem/siwe'
8
8
  import { Actions } from 'viem/tempo'
@@ -13,6 +13,7 @@ import * as Account from './Account.js'
13
13
  import type * as Adapter from './Adapter.js'
14
14
  import { dialog } from './adapters/dialog.js'
15
15
  import * as Client from './Client.js'
16
+ import * as AccessKeyTransaction from './internal/AccessKeyTransaction.js'
16
17
  import { withDedupe } from './internal/withDedupe.js'
17
18
  import * as Schema from './Schema.js'
18
19
  import * as Storage from './Storage.js'
@@ -25,8 +26,8 @@ export type Provider = ox_Provider.Provider<{ schema: Schema.Ox }> &
25
26
  ox_Provider.Emitter & {
26
27
  /** Configured chains. */
27
28
  chains: readonly [Chain, ...Chain[]]
28
- /** Returns a viem Account for the given address (or active account). */
29
- getAccount: Account.Find
29
+ /** Returns the active root account as a viem JSON-RPC account. */
30
+ getAccount(): JsonRpcAccount
30
31
  /** Returns local or on-chain publication status for an access key. */
31
32
  getAccessKeyStatus(
32
33
  options?: getAccessKeyStatus.Options | undefined,
@@ -271,8 +272,8 @@ export function create(options: create.Options = {}): create.ReturnType {
271
272
  type FillParams = z.output<typeof Rpc.transactionRequest> & {
272
273
  keyAuthorization?: unknown
273
274
  }
275
+ const client = getClient({ chainId, feePayer })
274
276
  const fill = (params: FillParams) => {
275
- const client = getClient({ chainId, feePayer })
276
277
  const fillRequest = {
277
278
  ...params,
278
279
  chainId: params.chainId ?? client.chain?.id,
@@ -292,45 +293,38 @@ export function create(options: create.Options = {}): create.ReturnType {
292
293
  // Inject pending keyAuthorization so the node accounts for
293
294
  // key authorization gas during estimation.
294
295
  if (!parameters.keyAuthorization) {
295
- const account = (() => {
296
- try {
297
- const calls =
298
- parameters.calls ??
299
- (parameters.to
300
- ? [
301
- {
302
- data: parameters.data,
303
- to: parameters.to,
304
- },
305
- ]
306
- : undefined)
307
- return getAccount({
308
- address: parameters.from,
309
- calls,
310
- chainId: parameters.chainId ?? store.getState().chainId,
311
- signable: true,
312
- })
313
- } catch {
314
- return undefined
315
- }
316
- })()
317
- if (account?.source === 'accessKey') {
318
- const keyAuth = AccessKey.getPending(account, { store })
319
- if (keyAuth) {
296
+ const state = store.getState()
297
+ const address =
298
+ parameters.from ?? state.accounts[state.activeAccount]?.address
299
+ if (address) {
300
+ const calls =
301
+ parameters.calls ??
302
+ (parameters.to
303
+ ? [
304
+ {
305
+ data: parameters.data,
306
+ to: parameters.to,
307
+ },
308
+ ]
309
+ : undefined)
310
+ const transaction = await AccessKeyTransaction.create({
311
+ address,
312
+ calls,
313
+ chainId: parameters.chainId ?? state.chainId,
314
+ client,
315
+ store,
316
+ })
317
+ if (transaction)
320
318
  try {
321
- const result = await fill({
319
+ return await transaction.fill({
322
320
  ...parameters,
323
- keyAuthorization: {
324
- address: keyAuth.address,
325
- ...KeyAuthorization.toRpc(keyAuth),
326
- } as never,
321
+ chainId: parameters.chainId ?? state.chainId,
322
+ from: parameters.from ?? address,
323
+ ...(feePayer ? { feePayer: true } : {}),
327
324
  })
328
- return result
329
- } catch (error) {
330
- AccessKey.invalidate(account, error, { store })
325
+ } catch {
331
326
  return await fill(parameters)
332
327
  }
333
- }
334
328
  }
335
329
  }
336
330
 
@@ -574,6 +568,11 @@ export function create(options: create.Options = {}): create.ReturnType {
574
568
  auth_input as NonNullable<z.output<typeof Rpc.wallet_connect.auth>>,
575
569
  )
576
570
  : undefined
571
+ if (auth_request && typeof auth_request === 'object' && !auth_request.challenge)
572
+ throw new RpcResponse.InvalidParamsError({
573
+ message:
574
+ '`auth` capability must include either `url` or an explicit `challenge` endpoint.',
575
+ })
577
576
  if (auth_request && capabilities?.personalSign)
578
577
  throw new RpcResponse.InvalidParamsError({
579
578
  message:
@@ -608,56 +607,61 @@ export function create(options: create.Options = {}): create.ReturnType {
608
607
  ? { message: auth.message }
609
608
  : capabilities?.personalSign
610
609
 
611
- const { keyAuthorization, accounts, email, personalSign, signature, username } =
612
- await (async () => {
613
- if (capabilities?.method === 'register') {
614
- // If a stored account already has this label, sign in
615
- // with its credential instead of creating a new one.
616
- const existing = capabilities.name
617
- ? store
618
- .getState()
619
- .accounts.find(
620
- (a) =>
621
- 'credential' in a &&
622
- a.label?.toLowerCase() === capabilities.name!.toLowerCase(),
623
- )
624
- : undefined
625
- if (existing && 'credential' in existing)
626
- return await actions.loadAccounts(
627
- {
628
- credentialId: existing.credential?.id,
629
- digest: capabilities.digest,
630
- authorizeAccessKey,
631
- ...(personalSign_request
632
- ? { personalSign: personalSign_request }
633
- : {}),
634
- },
635
- request,
636
- )
637
- return await actions.createAccount(
610
+ const {
611
+ accounts,
612
+ auth: auth_capability,
613
+ email,
614
+ keyAuthorization,
615
+ personalSign,
616
+ signature,
617
+ username,
618
+ } = await (async () => {
619
+ if (capabilities?.method === 'register') {
620
+ // If a stored account already has this label, sign in
621
+ // with its credential instead of creating a new one.
622
+ const existing = capabilities.name
623
+ ? store
624
+ .getState()
625
+ .accounts.find(
626
+ (a) =>
627
+ 'credential' in a &&
628
+ a.label?.toLowerCase() === capabilities.name!.toLowerCase(),
629
+ )
630
+ : undefined
631
+ if (existing && 'credential' in existing)
632
+ return await actions.loadAccounts(
638
633
  {
634
+ credentialId: existing.credential?.id,
639
635
  digest: capabilities.digest,
640
636
  authorizeAccessKey,
641
- name: capabilities.name ?? 'default',
642
- userId: capabilities.userId ?? Hex.random(16),
643
637
  ...(personalSign_request
644
638
  ? { personalSign: personalSign_request }
645
639
  : {}),
646
640
  },
647
641
  request,
648
642
  )
649
- }
650
- return await actions.loadAccounts(
643
+ return await actions.createAccount(
651
644
  {
652
- credentialId: capabilities?.credentialId,
653
- digest: capabilities?.digest,
645
+ digest: capabilities.digest,
654
646
  authorizeAccessKey,
655
- selectAccount: capabilities?.selectAccount,
647
+ name: capabilities.name ?? 'default',
648
+ userId: capabilities.userId ?? Hex.random(16),
656
649
  ...(personalSign_request ? { personalSign: personalSign_request } : {}),
657
650
  },
658
651
  request,
659
652
  )
660
- })()
653
+ }
654
+ return await actions.loadAccounts(
655
+ {
656
+ credentialId: capabilities?.credentialId,
657
+ digest: capabilities?.digest,
658
+ authorizeAccessKey,
659
+ selectAccount: capabilities?.selectAccount,
660
+ ...(personalSign_request ? { personalSign: personalSign_request } : {}),
661
+ },
662
+ request,
663
+ )
664
+ })()
661
665
 
662
666
  store.setState({
663
667
  accounts: resolveAccounts(accounts),
@@ -713,12 +717,14 @@ export function create(options: create.Options = {}): create.ReturnType {
713
717
  },
714
718
  }
715
719
  : {}),
716
- ...(signature && (!auth_request || auth_result)
720
+ ...(signature && (!auth_request || auth_result || !verifyUrl)
717
721
  ? { signature }
718
722
  : {}),
719
723
  ...(email !== undefined ? { email } : {}),
720
724
  ...(username !== undefined ? { username } : {}),
721
- ...(auth_result ? { auth: auth_result } : {}),
725
+ ...((auth_result ?? auth_capability)
726
+ ? { auth: auth_result ?? auth_capability }
727
+ : {}),
722
728
  ...(personalSign
723
729
  ? { personalSign: { message: personalSign.message } }
724
730
  : {}),
@@ -802,7 +808,7 @@ export function create(options: create.Options = {}): create.ReturnType {
802
808
  message: '`deposit` not supported by adapter.',
803
809
  })
804
810
  return (await actions.deposit(
805
- request._decoded.params[0],
811
+ request._decoded.params?.[0] ?? {},
806
812
  request,
807
813
  )) satisfies Rpc.wallet_deposit.Encoded['returns']
808
814
  }
@@ -973,15 +979,20 @@ export function create(options: create.Options = {}): create.ReturnType {
973
979
  ),
974
980
  {
975
981
  chains,
976
- getAccount,
982
+ getAccount() {
983
+ const account = getAccount()
984
+ return { address: account.address, type: 'json-rpc' as const }
985
+ },
977
986
  async getAccessKeyStatus(options: getAccessKeyStatus.Options = {}) {
978
987
  const state = store.getState()
979
988
  const address = options.address ?? state.accounts[state.activeAccount]?.address
980
- if (!address) return AccessKey.status.missing
989
+ if (!address) return 'missing'
981
990
  const chainId = options.chainId ?? state.chainId
991
+ const { accessKey, calls } = options
982
992
  return await AccessKey.getStatus({
983
- ...options,
984
- address,
993
+ account: address,
994
+ ...(accessKey ? { accessKey } : {}),
995
+ ...(calls ? { calls } : {}),
985
996
  chainId,
986
997
  client: provider.getClient({ chainId }),
987
998
  store,
@@ -1031,31 +1042,22 @@ export function create(options: create.Options = {}): create.ReturnType {
1031
1042
  const polyfill = polyfill_option ?? isFetchWritable()
1032
1043
  const getClient = ({ chainId }: { chainId?: number | undefined }) => {
1033
1044
  const client = provider.getClient({ chainId })
1034
- const account = provider.getAccount()
1035
- return Object.assign(client, { account })
1045
+ const account = store.getState().accounts[store.getState().activeAccount]
1046
+ if (!account) throw new ox_Provider.DisconnectedError({ message: 'No active account.' })
1047
+ return Object.assign(client, {
1048
+ account: {
1049
+ address: account.address,
1050
+ type: 'json-rpc' as const,
1051
+ },
1052
+ })
1036
1053
  }
1037
- const mppx = Mppx.create({
1054
+ Mppx.create({
1038
1055
  methods: [
1039
1056
  mppx_tempo({ ...methodOptions, getClient, mode }),
1040
1057
  mppx_tempo.subscription({ getClient }),
1041
1058
  ],
1042
1059
  polyfill,
1043
1060
  })
1044
- mppx.onPaymentResponse(({ challenge, method }) => {
1045
- if (method.name !== 'tempo' || method.intent !== 'charge') return
1046
- const amount = challenge.request.amount
1047
- if (
1048
- typeof amount !== 'string' &&
1049
- typeof amount !== 'number' &&
1050
- typeof amount !== 'bigint' &&
1051
- typeof amount !== 'boolean'
1052
- )
1053
- return
1054
- if (BigInt(amount) === 0n) return
1055
- const account = provider.getAccount()
1056
- if ('source' in account && account.source === 'accessKey')
1057
- AccessKey.removePending(account, { store })
1058
- })
1059
1061
  }
1060
1062
 
1061
1063
  providerRef = provider
@@ -1161,7 +1163,7 @@ export declare namespace getAccessKeyStatus {
1161
1163
  }
1162
1164
 
1163
1165
  /** Access-key publication status. */
1164
- type ReturnType = AccessKey.Status
1166
+ type ReturnType = 'missing' | 'pending' | 'published' | 'expired'
1165
1167
  }
1166
1168
 
1167
1169
  export declare namespace mpp {
@@ -1266,14 +1268,16 @@ function resolveAuthEndpoint(
1266
1268
  }
1267
1269
 
1268
1270
  /**
1269
- * Pre-resolves the `auth` capability into its absolute, fully-explicit
1270
- * object form. Run once at the dapp-side Provider so forwarding adapters
1271
- * (dialog) carry absolute URLs to the wallet host — the wallet's
1272
- * `window.location.origin` belongs to the wallet, not the dapp, and
1273
- * cannot resolve relative paths correctly.
1271
+ * Pre-resolves the `auth` capability into its absolute object form. Run
1272
+ * once at the dapp-side Provider so forwarding adapters (dialog) carry
1273
+ * absolute URLs to the wallet host — the wallet's `window.location.origin`
1274
+ * belongs to the wallet, not the dapp, and cannot resolve relative paths
1275
+ * correctly.
1274
1276
  *
1275
- * `logout` is omitted when the input doesn't supply enough info to derive
1276
- * one (it's optional in the protocol).
1277
+ * Individual endpoints are omitted when the input doesn't supply enough
1278
+ * info to derive them. `logout` is optional in the protocol; `verify` can
1279
+ * also be omitted by wallet-host re-entry so the dapp-origin Provider runs
1280
+ * verification and receives the session cookie.
1277
1281
  */
1278
1282
  function absolutizeAuth(
1279
1283
  auth: NonNullable<z.output<typeof Rpc.wallet_connect.auth>>,
@@ -1297,10 +1301,6 @@ function absolutizeAuth(
1297
1301
 
1298
1302
  function assertSameAuthOrigin(auth: NonNullable<z.output<typeof Rpc.wallet_connect.auth>>): void {
1299
1303
  if (typeof auth !== 'object') return
1300
- if (!auth.challenge || !auth.verify)
1301
- throw new RpcResponse.InvalidParamsError({
1302
- message: '`auth` requires both `challenge` and `verify` endpoints.',
1303
- })
1304
1304
  const urls = [auth.challenge, auth.verify, auth.logout].filter(
1305
1305
  (u): u is string => typeof u === 'string',
1306
1306
  )
@@ -156,6 +156,24 @@ describe('Encoded', () => {
156
156
  }>()
157
157
  })
158
158
 
159
+ test('wallet_deposit', () => {
160
+ expectTypeOf<Rpc.wallet_deposit.Encoded>().toMatchTypeOf<{
161
+ method: 'wallet_deposit'
162
+ params:
163
+ | readonly [
164
+ {
165
+ address?: Hex | undefined
166
+ amount?: string | undefined
167
+ chainId?: Hex | undefined
168
+ displayName?: string | undefined
169
+ token?: Hex | string | undefined
170
+ },
171
+ ]
172
+ | undefined
173
+ returns: { receipts?: readonly { transactionHash: Hex }[] | undefined } | undefined
174
+ }>()
175
+ })
176
+
159
177
  test('wallet_switchEthereumChain', () => {
160
178
  expectTypeOf<Rpc.wallet_switchEthereumChain.Encoded>().toEqualTypeOf<{
161
179
  method: 'wallet_switchEthereumChain'
@@ -2,8 +2,8 @@ import { Provider as ox_Provider } from 'ox'
2
2
  import { tempoLocalnet } from 'viem/chains'
3
3
  import { afterEach, describe, expect, test, vi } from 'vp/test'
4
4
 
5
- import * as AccessKey from '../AccessKey.js'
6
5
  import * as Dialog from '../Dialog.js'
6
+ import * as AccessKeyTransaction from '../internal/AccessKeyTransaction.js'
7
7
  import * as Storage from '../Storage.js'
8
8
  import * as Store from '../Store.js'
9
9
  import { dialog } from './dialog.js'
@@ -21,16 +21,24 @@ describe('dialog', () => {
21
21
  const store = Store.create({ chainId: tempoLocalnet.id, storage })
22
22
  const clientRequests: unknown[] = []
23
23
  const signRequests: unknown[] = []
24
- vi.spyOn(AccessKey, 'selectAccount').mockReturnValue({
25
- accessKeyAddress: '0x0000000000000000000000000000000000000099',
26
- address: '0x0000000000000000000000000000000000000099',
27
- signTransaction: async (request: unknown) => {
28
- signRequests.push(request)
29
- return '0xsigned'
30
- },
31
- source: 'accessKey',
32
- type: 'local',
33
- } as never)
24
+ vi.spyOn(AccessKeyTransaction, 'create').mockResolvedValue({
25
+ fill: async () => ({ capabilities: { sponsored: false }, tx: {} }),
26
+ prepare: async (request) => ({
27
+ request: request as never,
28
+ send: async () => {
29
+ signRequests.push(request)
30
+ clientRequests.push({ method: 'eth_sendRawTransaction', params: ['0xsigned'] })
31
+ return '0xtransaction'
32
+ },
33
+ sendSync: async () => {
34
+ throw new Error('unexpected sendSync')
35
+ },
36
+ sign: async () => {
37
+ signRequests.push(request)
38
+ return '0xsigned'
39
+ },
40
+ }),
41
+ })
34
42
  const adapter = dialog({ dialog: Dialog.noop() })({
35
43
  getAccount: () => {
36
44
  throw new ox_Provider.UnauthorizedError({ message: 'No local signer.' })
@@ -84,10 +92,76 @@ describe('dialog', () => {
84
92
  expect(store.getState().requestQueue).toMatchInlineSnapshot(`[]`)
85
93
  })
86
94
 
95
+ test('behavior: loadAccounts forwards auth capabilities returned by the dialog', async () => {
96
+ const storage = Storage.memory()
97
+ const store = Store.create({ chainId: tempoLocalnet.id, storage })
98
+ const adapter = dialog({ dialog: Dialog.noop() })({
99
+ getAccount: () => {
100
+ throw new ox_Provider.UnauthorizedError({ message: 'No local signer.' })
101
+ },
102
+ getClient: () => ({}) as never,
103
+ storage,
104
+ store,
105
+ })
106
+ const request = {
107
+ method: 'wallet_connect' as const,
108
+ params: [
109
+ {
110
+ capabilities: {
111
+ auth: {
112
+ url: 'https://app.example/auth',
113
+ returnToken: true,
114
+ },
115
+ },
116
+ chainId: '0x1079' as const,
117
+ },
118
+ ] as const,
119
+ }
120
+
121
+ const promise = adapter.actions.loadAccounts(undefined, request)
122
+
123
+ await vi.waitFor(() => {
124
+ if (!store.getState().requestQueue[0]) throw new Error('request not queued')
125
+ })
126
+
127
+ const queued = store.getState().requestQueue[0]!
128
+ store.setState({
129
+ requestQueue: [
130
+ {
131
+ request: queued.request,
132
+ result: {
133
+ accounts: [
134
+ {
135
+ address,
136
+ capabilities: {
137
+ auth: { token: 'test-token' },
138
+ },
139
+ },
140
+ ],
141
+ },
142
+ status: 'success',
143
+ },
144
+ ],
145
+ })
146
+
147
+ await expect(promise).resolves.toMatchInlineSnapshot(`
148
+ {
149
+ "accounts": [
150
+ {
151
+ "address": "0x0000000000000000000000000000000000000001",
152
+ },
153
+ ],
154
+ "auth": {
155
+ "token": "test-token",
156
+ },
157
+ }
158
+ `)
159
+ })
160
+
87
161
  test('behavior: sendTransaction falls through when no access key is selected', async () => {
88
162
  const storage = Storage.memory()
89
163
  const store = Store.create({ chainId: tempoLocalnet.id, storage })
90
- vi.spyOn(AccessKey, 'selectAccount').mockReturnValue(undefined)
164
+ vi.spyOn(AccessKeyTransaction, 'create').mockResolvedValue(undefined)
91
165
  const lookups: unknown[] = []
92
166
  const adapter = dialog({ dialog: Dialog.noop() })({
93
167
  getAccount: (options) => {
@@ -137,10 +211,60 @@ describe('dialog', () => {
137
211
  expect(lookups).toMatchInlineSnapshot(`[]`)
138
212
  })
139
213
 
214
+ test('behavior: revokeAccessKey clears the forwarded key from local state', async () => {
215
+ const storage = Storage.memory()
216
+ const store = Store.create({ chainId: tempoLocalnet.id, storage })
217
+ store.setState({
218
+ accessKeys: [
219
+ {
220
+ access: address,
221
+ address: recipient,
222
+ chainId: tempoLocalnet.id,
223
+ keyType: 'p256',
224
+ } as never,
225
+ ],
226
+ })
227
+ const adapter = dialog({ dialog: Dialog.noop() })({
228
+ getAccount: () => {
229
+ throw new ox_Provider.UnauthorizedError({ message: 'No local signer.' })
230
+ },
231
+ getClient: () => ({}) as never,
232
+ storage,
233
+ store,
234
+ })
235
+ const request = {
236
+ method: 'wallet_revokeAccessKey' as const,
237
+ params: [{ accessKeyAddress: recipient, address }] as const,
238
+ }
239
+
240
+ const promise = adapter.actions.revokeAccessKey!(
241
+ { accessKeyAddress: recipient, address },
242
+ request,
243
+ )
244
+
245
+ await vi.waitFor(() => {
246
+ if (!store.getState().requestQueue[0]) throw new Error('request not queued')
247
+ })
248
+
249
+ const queued = store.getState().requestQueue[0]!
250
+ store.setState({
251
+ requestQueue: [
252
+ {
253
+ request: queued.request,
254
+ result: undefined,
255
+ status: 'success',
256
+ },
257
+ ],
258
+ })
259
+
260
+ await expect(promise).resolves.toMatchInlineSnapshot(`undefined`)
261
+ expect(store.getState().accessKeys).toMatchInlineSnapshot(`[]`)
262
+ })
263
+
140
264
  test('error: wallet validation errors keep their RPC code', async () => {
141
265
  const storage = Storage.memory()
142
266
  const store = Store.create({ chainId: tempoLocalnet.id, storage })
143
- vi.spyOn(AccessKey, 'selectAccount').mockReturnValue(undefined)
267
+ vi.spyOn(AccessKeyTransaction, 'create').mockResolvedValue(undefined)
144
268
  const adapter = dialog({ dialog: Dialog.noop() })({
145
269
  getAccount: () => {
146
270
  throw new ox_Provider.UnauthorizedError({ message: 'No local signer.' })