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
@@ -0,0 +1,243 @@
1
+ import { Address, Hex } from 'ox'
2
+ import { KeyAuthorization } from 'ox/tempo'
3
+ import type { Client, Transport } from 'viem'
4
+ import { prepareTransactionRequest } from 'viem/actions'
5
+ import type { PrepareTransactionRequestReturnType } from 'viem/actions'
6
+ import type { Transaction as TempoTransaction } from 'viem/tempo'
7
+
8
+ import * as AccessKey from '../AccessKey.js'
9
+ import * as ExecutionError from '../ExecutionError.js'
10
+ import type * as Store from '../Store.js'
11
+ import type * as Rpc from '../zod/rpc.js'
12
+
13
+ type Call = {
14
+ to?: Address.Address | undefined
15
+ data?: Hex.Hex | undefined
16
+ }
17
+
18
+ type Selection = NonNullable<Awaited<ReturnType<typeof AccessKey.select>>>
19
+
20
+ const removalErrorNames = new Set([
21
+ 'InvalidSignature',
22
+ 'InvalidSignatureFormat',
23
+ 'InvalidSignatureType',
24
+ 'KeyAlreadyRevoked',
25
+ 'KeyExpired',
26
+ 'KeyNotFound',
27
+ 'SignatureTypeMismatch',
28
+ ])
29
+
30
+ /** Creates a lifecycle-aware access-key transaction when a matching key is available. */
31
+ export async function create(options: create.Options): Promise<create.ReturnType> {
32
+ const { address, calls, chainId, client, store } = options
33
+ const selection = await AccessKey.select({
34
+ account: address,
35
+ calls,
36
+ chainId,
37
+ client,
38
+ store,
39
+ })
40
+ if (!selection) return undefined
41
+ return createTransaction({ client, selection, store })
42
+ }
43
+
44
+ export declare namespace create {
45
+ /** Options for {@link create}. */
46
+ type Options = {
47
+ /** Root account address. */
48
+ address: Address.Address
49
+ /** Calls to match against access key scopes. */
50
+ calls?: readonly Call[] | undefined
51
+ /** Chain ID the access key must be authorized on. */
52
+ chainId: number
53
+ /** Client used to prepare, submit, and check access-key transactions. */
54
+ client: Client<Transport>
55
+ /** Reactive state store. */
56
+ store: Store.Store
57
+ }
58
+
59
+ /** Parameters accepted when preparing an access-key transaction. */
60
+ type PrepareParameters = Omit<
61
+ TempoTransaction.TransactionRequestTempo,
62
+ 'account' | 'keyAuthorization' | 'type'
63
+ >
64
+
65
+ /** Prepared transaction request returned by viem. */
66
+ type PreparedRequest = PrepareTransactionRequestReturnType
67
+
68
+ /** Parameters accepted by `eth_fillTransaction`. */
69
+ type FillParameters = Rpc.eth_fillTransaction.Decoded['params'][0]
70
+
71
+ /** Result returned by `eth_fillTransaction`. */
72
+ type FillReturnType = Rpc.eth_fillTransaction.Encoded['returns']
73
+
74
+ /** Result returned by `eth_sendTransactionSync`. */
75
+ type SendSyncReturnType = Rpc.eth_sendTransactionSync.Encoded['returns']
76
+
77
+ /** Prepared access-key transaction with lifecycle-aware execution methods. */
78
+ type Prepared = {
79
+ /** Prepared request that will be signed by the selected access key. */
80
+ request: PreparedRequest
81
+ /** Signs the prepared transaction and marks an attached authorization as pending. */
82
+ sign(): Promise<Hex.Hex>
83
+ /** Signs and submits the transaction asynchronously. */
84
+ send(): Promise<Hex.Hex>
85
+ /** Signs, submits, and waits for the transaction to be accepted. */
86
+ sendSync(): Promise<SendSyncReturnType>
87
+ }
88
+
89
+ /** Lifecycle-aware access-key transaction. */
90
+ type Transaction = {
91
+ /** Fills a transaction, attaching a pending key authorization when needed. */
92
+ fill(parameters: FillParameters): Promise<FillReturnType>
93
+ /** Prepares a transaction, attaching a pending key authorization when needed. */
94
+ prepare(parameters: PrepareParameters): Promise<Prepared>
95
+ }
96
+
97
+ /** Lifecycle-aware access-key transaction, if one is available. */
98
+ type ReturnType = Transaction | undefined
99
+ }
100
+
101
+ function createTransaction(options: {
102
+ client: Client<Transport>
103
+ selection: Selection
104
+ store: Store.Store
105
+ }): create.Transaction {
106
+ const { client, selection, store } = options
107
+ return {
108
+ async fill(parameters) {
109
+ try {
110
+ return await fillTransaction(client, {
111
+ ...parameters,
112
+ ...(!parameters.keyAuthorization && selection.authorization
113
+ ? {
114
+ keyAuthorization: {
115
+ address: selection.authorization.address,
116
+ ...KeyAuthorization.toRpc(selection.authorization),
117
+ } as never,
118
+ }
119
+ : {}),
120
+ } as never)
121
+ } catch (error) {
122
+ removeForError(error, selection, { store })
123
+ throw error
124
+ }
125
+ },
126
+ async prepare(parameters) {
127
+ try {
128
+ const request = await prepareTransactionRequest(client, {
129
+ account: selection.account,
130
+ ...parameters,
131
+ ...(selection.authorization ? { keyAuthorization: selection.authorization } : {}),
132
+ type: 'tempo',
133
+ } as never)
134
+ return createPreparedTransaction({
135
+ client,
136
+ request: request as never,
137
+ selection,
138
+ store,
139
+ })
140
+ } catch (error) {
141
+ removeForError(error, selection, { store })
142
+ throw error
143
+ }
144
+ },
145
+ }
146
+ }
147
+
148
+ function createPreparedTransaction(options: {
149
+ client: Client<Transport>
150
+ request: create.PreparedRequest
151
+ selection: Selection
152
+ store: Store.Store
153
+ }): create.Prepared {
154
+ const { client, request, selection, store } = options
155
+
156
+ async function sign() {
157
+ try {
158
+ const signed = await selection.account.signTransaction(request as never)
159
+ if (selection.authorization)
160
+ AccessKey.markPending({
161
+ accessKey: selection.accessKey,
162
+ account: selection.record.access,
163
+ chainId: selection.record.chainId,
164
+ store,
165
+ })
166
+ return signed
167
+ } catch (error) {
168
+ removeForError(error, selection, { store })
169
+ throw error
170
+ }
171
+ }
172
+
173
+ return {
174
+ request,
175
+ sign,
176
+ async send() {
177
+ try {
178
+ const signed = await sign()
179
+ return (await client.request({
180
+ method: 'eth_sendRawTransaction' as never,
181
+ params: [signed],
182
+ })) as Hex.Hex
183
+ } catch (error) {
184
+ removeForError(error, selection, { store })
185
+ throw error
186
+ }
187
+ },
188
+ async sendSync() {
189
+ try {
190
+ const signed = await sign()
191
+ const result = await client.request({
192
+ method: 'eth_sendRawTransactionSync' as never,
193
+ params: [signed],
194
+ })
195
+ AccessKey.markPublished({
196
+ accessKey: selection.accessKey,
197
+ account: selection.record.access,
198
+ chainId: selection.record.chainId,
199
+ store,
200
+ })
201
+ return result as create.SendSyncReturnType
202
+ } catch (error) {
203
+ removeForError(error, selection, { store })
204
+ throw error
205
+ }
206
+ },
207
+ }
208
+ }
209
+
210
+ async function fillTransaction(
211
+ client: Client<Transport>,
212
+ parameters: create.FillParameters,
213
+ ): Promise<create.FillReturnType> {
214
+ const { keyAuthorization, ...rest } = parameters as create.FillParameters & {
215
+ keyAuthorization?: unknown
216
+ }
217
+ const formatter = client.chain?.formatters?.transactionRequest
218
+ const formatted = formatter ? formatter.format({ ...rest } as never, 'fillTransaction') : rest
219
+ return (await client.request({
220
+ method: 'eth_fillTransaction' as never,
221
+ params: [{ ...formatted, ...(keyAuthorization ? { keyAuthorization } : {}) } as never],
222
+ })) as create.FillReturnType
223
+ }
224
+
225
+ function removeForError(
226
+ error: unknown,
227
+ selection: Selection,
228
+ options: { store: Store.Store },
229
+ ): void {
230
+ if (!shouldRemoveForError(error)) return
231
+ AccessKey.remove({
232
+ accessKey: selection.accessKey,
233
+ account: selection.record.access,
234
+ chainId: selection.record.chainId,
235
+ store: options.store,
236
+ })
237
+ }
238
+
239
+ function shouldRemoveForError(error: unknown): boolean {
240
+ if (!(error instanceof Error)) return false
241
+ const parsed = ExecutionError.parse(error)
242
+ return removalErrorNames.has(parsed.errorName)
243
+ }
@@ -66,7 +66,7 @@ describe('mppx integration', () => {
66
66
  `)
67
67
  })
68
68
 
69
- test('pull mode publishes a pending access key on first charge', async () => {
69
+ test('pull mode publishes a pending access key authorization', async () => {
70
70
  const provider = Provider.create({
71
71
  adapter: headlessWebAuthn(),
72
72
  chains: [chain],
@@ -85,6 +85,9 @@ describe('mppx integration', () => {
85
85
 
86
86
  const res = await fetch(`${server.url}/fortune`)
87
87
  expect(res.status).toBe(200)
88
+
89
+ const status = await provider.getAccessKeyStatus({ accessKey: key.address })
90
+ expect(status).toMatchInlineSnapshot(`"published"`)
88
91
  expect(provider.store.getState().accessKeys[0]!.keyAuthorization).toBeUndefined()
89
92
 
90
93
  const metadata = await Actions.accessKey.getMetadata(client, {
@@ -94,7 +97,7 @@ describe('mppx integration', () => {
94
97
  expect(metadata.isRevoked).toMatchInlineSnapshot(`false`)
95
98
  })
96
99
 
97
- test('pull mode keeps pending access key after failed verification', async () => {
100
+ test('pull mode keeps access key authorization pending after failed verification', async () => {
98
101
  const failingServer = await createServer(async (req, res) => {
99
102
  if (req.headers.authorization) {
100
103
  res.writeHead(402, { 'Content-Type': 'application/json' })
@@ -122,14 +125,41 @@ describe('mppx integration', () => {
122
125
  method: 'wallet_authorizeAccessKey',
123
126
  params: [{ expiry: Expiry.days(1) }],
124
127
  })
128
+ const key = provider.store.getState().accessKeys[0]!
125
129
 
126
130
  const res = await fetch(`${failingServer.url}/fortune`)
127
131
  expect(res.status).toMatchInlineSnapshot(`402`)
128
132
  expect(provider.store.getState().accessKeys[0]!.keyAuthorization).toBeDefined()
133
+
134
+ const status = await provider.getAccessKeyStatus({ accessKey: key.address })
135
+ expect(status).toMatchInlineSnapshot(`"pending"`)
129
136
  } finally {
130
137
  await failingServer.closeAsync()
131
138
  }
132
139
  })
140
+
141
+ test('push mode publishes pending access key authorization', async () => {
142
+ const provider = Provider.create({
143
+ adapter: headlessWebAuthn(),
144
+ chains: [chain],
145
+ mpp: { mode: 'push' },
146
+ })
147
+ const address = await connect(provider)
148
+ await fund(address)
149
+
150
+ await provider.request({
151
+ method: 'wallet_authorizeAccessKey',
152
+ params: [{ expiry: Expiry.days(1) }],
153
+ })
154
+ const key = provider.store.getState().accessKeys[0]!
155
+
156
+ const res = await fetch(`${server.url}/fortune`)
157
+ expect(res.status).toMatchInlineSnapshot(`200`)
158
+
159
+ const status = await provider.getAccessKeyStatus({ accessKey: key.address })
160
+ expect(status).toMatchInlineSnapshot(`"published"`)
161
+ expect(provider.store.getState().accessKeys[0]!.keyAuthorization).toBeUndefined()
162
+ })
133
163
  })
134
164
 
135
165
  async function connect(provider: ReturnType<typeof Provider.create>) {
@@ -115,6 +115,42 @@ describe('validate', () => {
115
115
  `)
116
116
  })
117
117
 
118
+ test('default: validates wallet_deposit with amount and token symbol', () => {
119
+ const result = RpcRequest.validate(Schema.Request, {
120
+ method: 'wallet_deposit',
121
+ params: [
122
+ {
123
+ amount: '50',
124
+ displayName: 'DoorDash',
125
+ token: 'USDC',
126
+ },
127
+ ],
128
+ })
129
+ expect(result._decoded).toMatchInlineSnapshot(`
130
+ {
131
+ "method": "wallet_deposit",
132
+ "params": [
133
+ {
134
+ "amount": "50",
135
+ "displayName": "DoorDash",
136
+ "token": "USDC",
137
+ },
138
+ ],
139
+ }
140
+ `)
141
+ })
142
+
143
+ test('default: validates wallet_deposit without params', () => {
144
+ const result = RpcRequest.validate(Schema.Request, {
145
+ method: 'wallet_deposit',
146
+ })
147
+ expect(result._decoded).toMatchInlineSnapshot(`
148
+ {
149
+ "method": "wallet_deposit",
150
+ }
151
+ `)
152
+ })
153
+
118
154
  test('behavior: preserves original request properties', () => {
119
155
  const result = RpcRequest.validate(Schema.Request, {
120
156
  method: 'eth_accounts',
@@ -746,16 +746,23 @@ export namespace wallet_switchEthereumChain {
746
746
  export namespace wallet_deposit {
747
747
  export const schema = Schema.defineItem({
748
748
  method: z.literal('wallet_deposit'),
749
- params: z.readonly(
750
- z.tuple([
751
- z.object({
752
- address: z.optional(u.address()),
753
- chainId: z.optional(u.number()),
754
- displayName: z.optional(z.string()),
755
- token: z.optional(u.address()),
756
- value: z.optional(z.string()),
757
- }),
758
- ]),
749
+ params: z.optional(
750
+ z.readonly(
751
+ z.tuple([
752
+ z.object({
753
+ address: z.optional(u.address()),
754
+ /** Human-readable amount to pre-fill (e.g. `"50"`). */
755
+ amount: z.optional(z.string()),
756
+ chainId: z.optional(u.number()),
757
+ displayName: z.optional(z.string()),
758
+ /**
759
+ * Token to pre-fill, accepted as either a contract address or a
760
+ * supported deposit token symbol (case-insensitive, e.g. `"USDC"`).
761
+ */
762
+ token: z.optional(z.union([u.address(), z.string()])),
763
+ }),
764
+ ]),
765
+ ),
759
766
  ),
760
767
  returns: z.optional(
761
768
  z.object({
package/src/index.ts CHANGED
@@ -1,4 +1,3 @@
1
- export * as AccessKey from './core/AccessKey.js'
2
1
  export * as Adapter from './core/Adapter.js'
3
2
  export * as IntersectionObserver from './core/IntersectionObserver.js'
4
3
  export * as Dialog from './core/Dialog.js'
@@ -15,6 +14,7 @@ export * as TrustedHosts from './core/TrustedHosts.js'
15
14
  export * as WebAuthnCeremony from './core/WebAuthnCeremony.js'
16
15
  export { dialog, dialog as tempoWallet } from './core/adapters/dialog.js'
17
16
  export { local } from './core/adapters/local.js'
17
+ export { privy } from './core/adapters/privy.js'
18
18
  export { turnkey } from './core/adapters/turnkey.js'
19
19
  export { webAuthn } from './core/adapters/webAuthn.js'
20
20
  export {
@@ -8,11 +8,11 @@ import {
8
8
  RpcResponse,
9
9
  } from 'ox'
10
10
  import { KeyAuthorization } from 'ox/tempo'
11
- import { prepareTransactionRequest } from 'viem/actions'
12
11
  import { Actions, Account as TempoAccount, Secp256k1 } from 'viem/tempo'
13
12
 
14
13
  import * as AccessKey from '../core/AccessKey.js'
15
14
  import * as Adapter from '../core/Adapter.js'
15
+ import * as AccessKeyTransaction from '../core/internal/AccessKeyTransaction.js'
16
16
  import type * as Storage from '../core/Storage.js'
17
17
 
18
18
  /**
@@ -58,19 +58,19 @@ export function reactNative(options: reactNative.Options): Adapter.Adapter {
58
58
  const keyAuthorization = deserialized as KeyAuthorization.Signed
59
59
 
60
60
  if (keyAuthorization.address.toLowerCase() === keyAddress.toLowerCase())
61
- AccessKey.save({
62
- address,
63
- keyAuthorization,
61
+ AccessKey.add({
62
+ account: address,
63
+ authorization: keyAuthorization,
64
64
  privateKey: entry.key,
65
65
  store,
66
66
  })
67
67
  else
68
- store.setState((state) => ({
69
- accessKeys: state.accessKeys.filter(
70
- (accessKey) =>
71
- accessKey.address.toLowerCase() !== keyAuthorization.address.toLowerCase(),
72
- ),
73
- }))
68
+ AccessKey.remove({
69
+ account: address,
70
+ accessKey: keyAuthorization.address,
71
+ chainId: Number(keyAuthorization.chainId),
72
+ store,
73
+ })
74
74
 
75
75
  return {
76
76
  account,
@@ -127,9 +127,9 @@ export function reactNative(options: reactNative.Options): Adapter.Adapter {
127
127
  ) {
128
128
  if (!managedKey) return
129
129
 
130
- AccessKey.save({
131
- address,
132
- keyAuthorization,
130
+ AccessKey.add({
131
+ account: address,
132
+ authorization: keyAuthorization,
133
133
  privateKey: managedKey.key,
134
134
  store,
135
135
  })
@@ -189,27 +189,37 @@ export function reactNative(options: reactNative.Options): Adapter.Adapter {
189
189
  return result.keyAuthorization
190
190
  }
191
191
 
192
- async function withManagedAccessKey<result>(
193
- fn: (
194
- account: TempoAccount.Account,
195
- keyAuthorization?: KeyAuthorization.Signed | undefined,
196
- ) => Promise<result>,
192
+ async function prepareManagedTransaction(
193
+ client: ReturnType<typeof getClient>,
194
+ parameters: AccessKeyTransaction.create.PrepareParameters,
195
+ options: {
196
+ calls?: AccessKeyTransaction.create.Options['calls'] | undefined
197
+ chainId?: number | undefined
198
+ } = {},
197
199
  ) {
198
- const rootAddress = store.getState().accounts[store.getState().activeAccount]?.address
199
- const managedKey = rootAddress ? await loadManagedKey(rootAddress) : undefined
200
-
201
- const account = managedKey?.account ?? getAccount({ signable: true })
202
- let keyAuthorization = AccessKey.getPending(account, { store })
203
- if (rootAddress && managedKey && !keyAuthorization)
204
- if (!(await isManagedKeyAuthorized(rootAddress, managedKey)))
205
- keyAuthorization = await reauthorizeManagedKey(rootAddress, managedKey)
200
+ const state = store.getState()
201
+ const address = parameters.from ?? state.accounts[state.activeAccount]?.address
202
+ if (!address) throw new core_Provider.DisconnectedError({ message: 'No active account.' })
203
+ const managedKey = await loadManagedKey(address)
204
+ if (managedKey && !(await isManagedKeyAuthorized(address, managedKey)))
205
+ await reauthorizeManagedKey(address, managedKey)
206
+ const transaction = await AccessKeyTransaction.create({
207
+ address,
208
+ calls: options.calls,
209
+ chainId: options.chainId ?? state.chainId,
210
+ client,
211
+ store,
212
+ })
213
+ if (!transaction)
214
+ throw new core_Provider.UnauthorizedError({
215
+ message: `Account "${address}" cannot sign with an access key.`,
216
+ })
217
+ return await transaction.prepare(parameters)
218
+ }
206
219
 
207
- try {
208
- return await fn(account, keyAuthorization ?? undefined)
209
- } catch (error) {
210
- AccessKey.remove(account, { store })
211
- throw error
212
- }
220
+ async function loadManagedAccount(address: Adapter.signPersonalMessage.Parameters['address']) {
221
+ await loadManagedKey(address)
222
+ return getAccount({ address, signable: true })
213
223
  }
214
224
 
215
225
  async function authorize(request: {
@@ -333,74 +343,57 @@ export function reactNative(options: reactNative.Options): Adapter.Adapter {
333
343
  async sendTransaction(parameters) {
334
344
  const { feePayer, ...rest } = parameters
335
345
  const client = getClient(typeof feePayer === 'string' ? { feePayer } : {})
336
- const { account, prepared } = await withManagedAccessKey(
337
- async (account, keyAuthorization) => ({
338
- account,
339
- prepared: await prepareTransactionRequest(client, {
340
- account,
341
- ...rest,
342
- ...(feePayer ? { feePayer: true } : {}),
343
- ...(keyAuthorization ? { keyAuthorization } : {}),
344
- type: 'tempo',
345
- } as never),
346
- }),
346
+ const prepared = await prepareManagedTransaction(
347
+ client,
348
+ {
349
+ ...rest,
350
+ ...(feePayer ? { feePayer: true } : {}),
351
+ },
352
+ {
353
+ calls: parameters.calls as AccessKeyTransaction.create.Options['calls'],
354
+ chainId: parameters.chainId,
355
+ },
347
356
  )
348
- const signed = await account.signTransaction(prepared as never)
349
- const result = await client.request({
350
- method: 'eth_sendRawTransaction' as never,
351
- params: [signed],
352
- })
353
- AccessKey.removePending(account, { store })
354
- return result
357
+ return await prepared.send()
355
358
  },
356
359
  async sendTransactionSync(parameters) {
357
360
  const { feePayer, ...rest } = parameters
358
361
  const client = getClient(typeof feePayer === 'string' ? { feePayer } : {})
359
- const { account, prepared } = await withManagedAccessKey(
360
- async (account, keyAuthorization) => ({
361
- account,
362
- prepared: await prepareTransactionRequest(client, {
363
- account,
364
- ...rest,
365
- ...(feePayer ? { feePayer: true } : {}),
366
- ...(keyAuthorization ? { keyAuthorization } : {}),
367
- type: 'tempo',
368
- } as never),
369
- }),
362
+ const prepared = await prepareManagedTransaction(
363
+ client,
364
+ {
365
+ ...rest,
366
+ ...(feePayer ? { feePayer: true } : {}),
367
+ },
368
+ {
369
+ calls: parameters.calls as AccessKeyTransaction.create.Options['calls'],
370
+ chainId: parameters.chainId,
371
+ },
370
372
  )
371
- const signed = await account.signTransaction(prepared as never)
372
- const result = await client.request({
373
- method: 'eth_sendRawTransactionSync' as never,
374
- params: [signed],
375
- })
376
- AccessKey.removePending(account, { store })
377
- return result
373
+ return await prepared.sendSync()
378
374
  },
379
375
  async signPersonalMessage({ address, data }) {
380
- await loadManagedKey(address)
381
- const account = getAccount({ address, signable: true })
376
+ const account = await loadManagedAccount(address)
382
377
  return await account.signMessage({ message: { raw: data } })
383
378
  },
384
379
  async signTransaction(parameters) {
385
380
  const { feePayer, ...rest } = parameters
386
381
  const client = getClient(typeof feePayer === 'string' ? { feePayer } : {})
387
- const { account, prepared } = await withManagedAccessKey(
388
- async (account, keyAuthorization) => ({
389
- account,
390
- prepared: await prepareTransactionRequest(client, {
391
- account,
392
- ...rest,
393
- ...(feePayer ? { feePayer: true } : {}),
394
- ...(keyAuthorization ? { keyAuthorization } : {}),
395
- type: 'tempo',
396
- } as never),
397
- }),
382
+ const prepared = await prepareManagedTransaction(
383
+ client,
384
+ {
385
+ ...rest,
386
+ ...(feePayer ? { feePayer: true } : {}),
387
+ },
388
+ {
389
+ calls: parameters.calls as AccessKeyTransaction.create.Options['calls'],
390
+ chainId: parameters.chainId,
391
+ },
398
392
  )
399
- return await account.signTransaction(prepared as never)
393
+ return await prepared.sign()
400
394
  },
401
395
  async signTypedData({ address, data }) {
402
- await loadManagedKey(address)
403
- const account = getAccount({ address, signable: true })
396
+ const account = await loadManagedAccount(address)
404
397
  return await account.signTypedData(JSON.parse(data) as never)
405
398
  },
406
399
  },