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
@@ -87,12 +87,9 @@ describe('hydrate', () => {
87
87
  })
88
88
 
89
89
  describe('find', () => {
90
- function setup(
91
- storeAccounts: readonly Account.Store[] = [],
92
- accessKeys: readonly Store.AccessKey[] = [],
93
- ) {
90
+ function setup(storeAccounts: readonly Account.Store[] = []) {
94
91
  const store = Store.create({ chainId: tempoLocalnet.id })
95
- store.setState({ accounts: storeAccounts, accessKeys, activeAccount: 0 })
92
+ store.setState({ accounts: storeAccounts, activeAccount: 0 })
96
93
  return store
97
94
  }
98
95
 
@@ -145,52 +142,10 @@ describe('find', () => {
145
142
  expect(typeof result.sign).toMatchInlineSnapshot(`"function"`)
146
143
  })
147
144
 
148
- test('behavior: prefers access key over root account', async () => {
149
- const keyPair = await WebCryptoP256.createKeyPair()
150
- const store = setup(
151
- [{ address: accounts[0].address, keyType: 'secp256k1', privateKey: privateKeys[0] }],
152
- [
153
- {
154
- address: '0x0000000000000000000000000000000000000099',
155
- access: accounts[0].address,
156
- chainId: tempoLocalnet.id,
157
- keyType: 'webCrypto',
158
- keyPair,
159
- },
160
- ],
161
- )
162
-
163
- const result = Account.find({ chainId: tempoLocalnet.id, store, signable: true })
164
-
165
- expect(result.source).toMatchInlineSnapshot(`"accessKey"`)
166
- })
167
-
168
- test('behavior: accessKey false skips access key', async () => {
169
- const keyPair = await WebCryptoP256.createKeyPair()
170
- const store = setup(
171
- [{ address: accounts[0].address, keyType: 'secp256k1', privateKey: privateKeys[0] }],
172
- [
173
- {
174
- address: '0x0000000000000000000000000000000000000099',
175
- access: accounts[0].address,
176
- chainId: tempoLocalnet.id,
177
- keyType: 'webCrypto',
178
- keyPair,
179
- },
180
- ],
181
- )
182
-
183
- const result = Account.find({ accessKey: false, signable: true, store })
184
-
185
- expect(result.address).toMatchInlineSnapshot(`"${accounts[0].address}"`)
186
- expect(result.source).not.toBe('accessKey')
187
- })
188
-
189
145
  test('behavior: falls back to root when no access key exists', () => {
190
- const store = setup(
191
- [{ address: accounts[0].address, keyType: 'secp256k1', privateKey: privateKeys[0] }],
192
- [],
193
- )
146
+ const store = setup([
147
+ { address: accounts[0].address, keyType: 'secp256k1', privateKey: privateKeys[0] },
148
+ ])
194
149
 
195
150
  const result = Account.find({ signable: true, store })
196
151
 
@@ -4,7 +4,6 @@ import type { Address, JsonRpcAccount } from 'viem/accounts'
4
4
  import { Account as TempoAccount } from 'viem/tempo'
5
5
 
6
6
  import type { OneOf } from '../internal/types.js'
7
- import * as core_AccessKey from './AccessKey.js'
8
7
  import type * as core_Store from './Store.js'
9
8
 
10
9
  /** Account stored in the provider state. */
@@ -35,7 +34,7 @@ export type Store = {
35
34
  export function find(options: find.Options & { signable: true }): TempoAccount.Account
36
35
  export function find(options: find.Options): TempoAccount.Account | JsonRpcAccount
37
36
  export function find(options: find.Options): TempoAccount.Account | JsonRpcAccount {
38
- const { accessKey = true, address, signable = false, store } = options
37
+ const { address, signable = false, store } = options
39
38
  const { accounts, activeAccount } = store.getState()
40
39
 
41
40
  const activeAddr = accounts[activeAccount]?.address
@@ -47,30 +46,13 @@ export function find(options: find.Options): TempoAccount.Account | JsonRpcAccou
47
46
  ? new Provider.UnauthorizedError({ message: `Account "${address}" not found.` })
48
47
  : new Provider.DisconnectedError({ message: 'No active account.' })
49
48
 
50
- // When accessKey is requested, prefer a locally-signable access key for this address.
51
- if (accessKey) {
52
- const account = core_AccessKey.selectAccount({
53
- address: root.address,
54
- calls: options.calls,
55
- chainId: options.chainId ?? store.getState().chainId,
56
- store,
57
- })
58
- if (account) return account
59
- }
60
-
61
49
  return hydrate(root, { signable }) as never
62
50
  }
63
51
 
64
52
  export declare namespace find {
65
53
  type Options = {
66
- /** Whether to prefer an access key for this account. @default true */
67
- accessKey?: boolean | undefined
68
54
  /** Address to find. Defaults to the active account. */
69
55
  address?: Address | undefined
70
- /** Calls to match against access key scopes. When provided, access keys whose scopes don't cover these calls are skipped. */
71
- calls?: readonly { to?: Address | undefined; data?: Hex | undefined }[] | undefined
72
- /** Chain ID the access key must be authorized on. Defaults to the active chain. */
73
- chainId?: number | undefined
74
56
  /** Whether to hydrate signing capability. @default false */
75
57
  signable?: boolean | undefined
76
58
  /** Reactive state store. */
@@ -143,7 +143,7 @@ export type Instance = {
143
143
  export declare namespace SetupFn {
144
144
  /** Parameters passed to an adapter's setup function. */
145
145
  export type Parameters = {
146
- /** Returns the rehydrated local account for the given address, or the active account if omitted. */
146
+ /** Returns the rehydrated root account for the given address, or the active account if omitted. */
147
147
  getAccount: Account.Find
148
148
  /** Get the viem client for a given chain ID. Defaults to the active chain. */
149
149
  getClient: (options?: getClient.Options | undefined) => Client<Transport, typeof tempo>
@@ -166,7 +166,7 @@ export function define(meta: Meta, fn: SetupFn): Adapter {
166
166
 
167
167
  /** Spreads decoded params. */
168
168
  export type ActionRequest<item extends Schema.Item> =
169
- Schema.Decoded<item>['params'] extends readonly [infer first] ? first : never
169
+ NonNullable<Schema.Decoded<item>['params']> extends readonly [infer first] ? first : never
170
170
 
171
171
  export declare namespace getClient {
172
172
  type Options = {
@@ -201,6 +201,8 @@ export declare namespace createAccount {
201
201
  email?: string | null | undefined
202
202
  /** Signed key authorization, if an access key was granted. */
203
203
  keyAuthorization?: KeyAuthorization.Rpc | undefined
204
+ /** Server Authentication result, if the auth capability was requested. */
205
+ auth?: { token?: string | undefined } | undefined
204
206
  /**
205
207
  * Echo of the `personalSign` request, present iff the caller supplied
206
208
  * `personalSign`. The signature lives on the top-level `signature`
@@ -257,6 +259,8 @@ export declare namespace loadAccounts {
257
259
  email?: string | null | undefined
258
260
  /** Signed key authorization, if an access key was granted. */
259
261
  keyAuthorization?: KeyAuthorization.Rpc | undefined
262
+ /** Server Authentication result, if the auth capability was requested. */
263
+ auth?: { token?: string | undefined } | undefined
260
264
  /**
261
265
  * Echo of the `personalSign` request, present iff the caller supplied
262
266
  * `personalSign`. The signature lives on the top-level `signature`
@@ -349,37 +349,43 @@ describe.each(adapters)('$name', ({ adapter }: (typeof adapters)[number]) => {
349
349
  // Real Hono app: mount the auth handler under `/auth` and add a
350
350
  // protected `/me` route — exactly as a dapp would compose them
351
351
  // — so the e2e test below exercises the full flow.
352
- const auth = Handler.auth()
353
- const app = Handler.compose([auth], { path: '/auth' })
354
- app.get('/me', async (c) => {
355
- const session = await auth.getSession(c.req.raw)
356
- if (!session) return c.json({ error: 'unauthenticated' }, 401)
357
- return c.json({ address: session.address, chainId: session.chainId })
352
+ let listener: Parameters<typeof createServer>[0] | undefined
353
+ server = await createServer((req, res) => {
354
+ if (!listener) {
355
+ const auth = Handler.auth({ origin: server.url })
356
+ const app = Handler.compose([auth], { path: '/auth' })
357
+ app.get('/me', async (c) => {
358
+ const session = await auth.getSession(c.req.raw)
359
+ if (!session) return c.json({ error: 'unauthenticated' }, 401)
360
+ return c.json({ address: session.address, chainId: session.chainId })
361
+ })
362
+ // Bad-challenge / bad-verify endpoints mounted on the same origin
363
+ // as `/auth` so the same-origin enforcement (`absolutizeAuth`)
364
+ // doesn't reject the request before the bad-content paths under
365
+ // test can run. `app.all` so we don't depend on the SDK's request
366
+ // method (POST).
367
+ app.all('/bad/verify-401', (c) => c.json({ error: 'unauthorized' }, 401))
368
+ app.all('/bad/challenge-500', (c) => c.json({ error: 'boom' }, 500))
369
+ app.all('/bad/challenge-empty', (c) => c.json({}))
370
+ app.all('/bad/challenge-evil-domain', (c) =>
371
+ c.json({
372
+ message: [
373
+ 'evil.example wants you to sign in with your Ethereum account:',
374
+ '0x0000000000000000000000000000000000000000',
375
+ '',
376
+ '',
377
+ 'URI: https://evil.example',
378
+ 'Version: 1',
379
+ 'Chain ID: 0',
380
+ 'Nonce: deadbeef00',
381
+ 'Issued At: 2025-01-01T00:00:00Z',
382
+ ].join('\n'),
383
+ }),
384
+ )
385
+ listener = app.listener
386
+ }
387
+ return listener(req, res)
358
388
  })
359
- // Bad-challenge / bad-verify endpoints mounted on the same origin
360
- // as `/auth` so the same-origin enforcement (`absolutizeAuth`)
361
- // doesn't reject the request before the bad-content paths under
362
- // test can run. `app.all` so we don't depend on the SDK's request
363
- // method (POST).
364
- app.all('/bad/verify-401', (c) => c.json({ error: 'unauthorized' }, 401))
365
- app.all('/bad/challenge-500', (c) => c.json({ error: 'boom' }, 500))
366
- app.all('/bad/challenge-empty', (c) => c.json({}))
367
- app.all('/bad/challenge-evil-domain', (c) =>
368
- c.json({
369
- message: [
370
- 'evil.example wants you to sign in with your Ethereum account:',
371
- '0x0000000000000000000000000000000000000000',
372
- '',
373
- '',
374
- 'URI: https://evil.example',
375
- 'Version: 1',
376
- 'Chain ID: 0',
377
- 'Nonce: deadbeef00',
378
- 'Issued At: 2025-01-01T00:00:00Z',
379
- ].join('\n'),
380
- }),
381
- )
382
- server = await createServer(app.listener)
383
389
  authBase = `${server.url}/auth`
384
390
 
385
391
  // Cross-origin bad server kept around for the same-origin enforcement
@@ -412,6 +418,22 @@ describe.each(adapters)('$name', ({ adapter }: (typeof adapters)[number]) => {
412
418
  expect(capabilities.signature).toMatch(/^0x[0-9a-f]+$/)
413
419
  })
414
420
 
421
+ test('default: object-form auth with url derives challenge and verify', async () => {
422
+ const provider = Provider.create({ adapter: adapter() })
423
+
424
+ const result = await provider.request({
425
+ method: 'wallet_connect',
426
+ params: [{ capabilities: { method: 'register', auth: { url: authBase } } }],
427
+ })
428
+
429
+ const capabilities = result.accounts[0]!.capabilities
430
+ expect(capabilities.auth).toEqual({ token: expect.any(String) })
431
+ expect(capabilities.personalSign).toEqual({
432
+ message: expect.stringContaining('wants you to sign in'),
433
+ })
434
+ expect(capabilities.signature).toMatch(/^0x[0-9a-f]+$/)
435
+ })
436
+
415
437
  test('default: object-form auth with explicit endpoints uses the override URLs', async () => {
416
438
  const provider = Provider.create({ adapter: adapter() })
417
439
 
@@ -433,6 +455,32 @@ describe.each(adapters)('$name', ({ adapter }: (typeof adapters)[number]) => {
433
455
  expect(result.accounts[0]!.capabilities.auth).toEqual({ token: expect.any(String) })
434
456
  })
435
457
 
458
+ test('default: forwarded auth without verify returns signature for downstream verify', async () => {
459
+ const provider = Provider.create({ adapter: adapter() })
460
+
461
+ const result = await provider.request({
462
+ method: 'wallet_connect',
463
+ params: [
464
+ {
465
+ capabilities: {
466
+ method: 'register',
467
+ auth: {
468
+ challenge: `${authBase}/challenge`,
469
+ logout: `${authBase}/logout`,
470
+ },
471
+ },
472
+ },
473
+ ],
474
+ })
475
+
476
+ const capabilities = result.accounts[0]!.capabilities
477
+ expect(capabilities.auth).toBeUndefined()
478
+ expect(capabilities.personalSign).toEqual({
479
+ message: expect.stringContaining('wants you to sign in'),
480
+ })
481
+ expect(capabilities.signature).toMatch(/^0x[0-9a-f]+$/)
482
+ })
483
+
436
484
  test('error: verify endpoint returns 401 → InternalError; user already signed', async () => {
437
485
  const provider = Provider.create({ adapter: adapter() })
438
486
 
@@ -1133,7 +1181,7 @@ describe.each(adapters)('$name', ({ adapter }: (typeof adapters)[number]) => {
1133
1181
  from: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
1134
1182
 
1135
1183
  Details: plain send failure
1136
- Version: viem@2.49.2]
1184
+ Version: viem@2.50.4]
1137
1185
  `)
1138
1186
  })
1139
1187
  })
@@ -1957,6 +2005,24 @@ describe.each(adapters)('$name', ({ adapter }: (typeof adapters)[number]) => {
1957
2005
  expect(provider.store.getState().accessKeys[0]!.keyAuthorization).toBeUndefined()
1958
2006
  })
1959
2007
 
2008
+ test('behavior: fills pending keyAuthorization with the active account when from is omitted', async () => {
2009
+ const provider = Provider.create({ adapter: adapter(), chains: [chain] })
2010
+ const address = await connect(provider)
2011
+ await fund(address)
2012
+
2013
+ await provider.request({
2014
+ method: 'wallet_authorizeAccessKey',
2015
+ params: [{ expiry: Expiry.days(1) }],
2016
+ })
2017
+
2018
+ const result = await provider.request({
2019
+ method: 'eth_fillTransaction',
2020
+ params: [fillTx],
2021
+ })
2022
+ expect(result.tx.gas).toBeDefined()
2023
+ expect(provider.store.getState().accessKeys[0]!.keyAuthorization).toBeDefined()
2024
+ })
2025
+
1960
2026
  test('behavior: removes stale access key and retries on error', async () => {
1961
2027
  const provider = Provider.create({ adapter: adapter(), chains: [chain] })
1962
2028
  const address = await connect(provider)