accounts 0.3.0 → 0.4.1

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 (168) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/LICENSE +21 -0
  3. package/README.md +97 -0
  4. package/dist/core/AccessKey.d.ts +55 -0
  5. package/dist/core/AccessKey.d.ts.map +1 -0
  6. package/dist/core/AccessKey.js +69 -0
  7. package/dist/core/AccessKey.js.map +1 -0
  8. package/dist/core/Account.d.ts +91 -0
  9. package/dist/core/Account.d.ts.map +1 -0
  10. package/dist/core/Account.js +64 -0
  11. package/dist/core/Account.js.map +1 -0
  12. package/dist/core/Adapter.d.ts +187 -0
  13. package/dist/core/Adapter.d.ts.map +1 -0
  14. package/dist/core/Adapter.js +7 -0
  15. package/dist/core/Adapter.js.map +1 -0
  16. package/dist/core/Ceremony.d.ts +109 -0
  17. package/dist/core/Ceremony.d.ts.map +1 -0
  18. package/dist/core/Ceremony.js +104 -0
  19. package/dist/core/Ceremony.js.map +1 -0
  20. package/dist/core/Client.d.ts +16 -0
  21. package/dist/core/Client.d.ts.map +1 -0
  22. package/dist/core/Client.js +18 -0
  23. package/dist/core/Client.js.map +1 -0
  24. package/dist/core/Dialog.d.ts +52 -0
  25. package/dist/core/Dialog.d.ts.map +1 -0
  26. package/dist/core/Dialog.js +342 -0
  27. package/dist/core/Dialog.js.map +1 -0
  28. package/dist/core/Expiry.d.ts +15 -0
  29. package/dist/core/Expiry.d.ts.map +1 -0
  30. package/dist/core/Expiry.js +29 -0
  31. package/dist/core/Expiry.js.map +1 -0
  32. package/dist/core/Messenger.d.ts +86 -0
  33. package/dist/core/Messenger.d.ts.map +1 -0
  34. package/dist/core/Messenger.js +127 -0
  35. package/dist/core/Messenger.js.map +1 -0
  36. package/dist/core/Provider.d.ts +69 -0
  37. package/dist/core/Provider.d.ts.map +1 -0
  38. package/dist/core/Provider.js +401 -0
  39. package/dist/core/Provider.js.map +1 -0
  40. package/dist/core/Remote.d.ts +114 -0
  41. package/dist/core/Remote.d.ts.map +1 -0
  42. package/dist/core/Remote.js +116 -0
  43. package/dist/core/Remote.js.map +1 -0
  44. package/dist/core/Schema.d.ts +805 -0
  45. package/dist/core/Schema.d.ts.map +1 -0
  46. package/dist/core/Schema.js +43 -0
  47. package/dist/core/Schema.js.map +1 -0
  48. package/dist/core/Storage.d.ts +42 -0
  49. package/dist/core/Storage.d.ts.map +1 -0
  50. package/dist/core/Storage.js +173 -0
  51. package/dist/core/Storage.js.map +1 -0
  52. package/dist/core/Store.d.ts +58 -0
  53. package/dist/core/Store.d.ts.map +1 -0
  54. package/dist/core/Store.js +58 -0
  55. package/dist/core/Store.js.map +1 -0
  56. package/dist/core/adapters/dangerous_secp256k1.d.ts +30 -0
  57. package/dist/core/adapters/dangerous_secp256k1.d.ts.map +1 -0
  58. package/dist/core/adapters/dangerous_secp256k1.js +39 -0
  59. package/dist/core/adapters/dangerous_secp256k1.js.map +1 -0
  60. package/dist/core/adapters/dialog.d.ts +31 -0
  61. package/dist/core/adapters/dialog.d.ts.map +1 -0
  62. package/dist/core/adapters/dialog.js +306 -0
  63. package/dist/core/adapters/dialog.js.map +1 -0
  64. package/dist/core/adapters/local.d.ts +33 -0
  65. package/dist/core/adapters/local.d.ts.map +1 -0
  66. package/dist/core/adapters/local.js +227 -0
  67. package/dist/core/adapters/local.js.map +1 -0
  68. package/dist/core/adapters/webAuthn.d.ts +36 -0
  69. package/dist/core/adapters/webAuthn.d.ts.map +1 -0
  70. package/dist/core/adapters/webAuthn.js +93 -0
  71. package/dist/core/adapters/webAuthn.js.map +1 -0
  72. package/dist/core/internal/withDedupe.d.ts +12 -0
  73. package/dist/core/internal/withDedupe.d.ts.map +1 -0
  74. package/dist/core/internal/withDedupe.js +12 -0
  75. package/dist/core/internal/withDedupe.js.map +1 -0
  76. package/dist/core/zod/request.d.ts +31 -0
  77. package/dist/core/zod/request.d.ts.map +1 -0
  78. package/dist/core/zod/request.js +41 -0
  79. package/dist/core/zod/request.js.map +1 -0
  80. package/dist/core/zod/rpc.d.ts +603 -0
  81. package/dist/core/zod/rpc.d.ts.map +1 -0
  82. package/dist/core/zod/rpc.js +293 -0
  83. package/dist/core/zod/rpc.js.map +1 -0
  84. package/dist/core/zod/utils.d.ts +18 -0
  85. package/dist/core/zod/utils.d.ts.map +1 -0
  86. package/dist/core/zod/utils.js +21 -0
  87. package/dist/core/zod/utils.js.map +1 -0
  88. package/dist/index.d.ts +15 -0
  89. package/dist/index.d.ts.map +1 -0
  90. package/dist/index.js +15 -0
  91. package/dist/index.js.map +1 -0
  92. package/dist/internal/types.d.ts +284 -0
  93. package/dist/internal/types.d.ts.map +1 -0
  94. package/dist/internal/types.js +2 -0
  95. package/dist/internal/types.js.map +1 -0
  96. package/dist/server/Handler.d.ts +257 -0
  97. package/dist/server/Handler.d.ts.map +1 -0
  98. package/dist/server/Handler.js +433 -0
  99. package/dist/server/Handler.js.map +1 -0
  100. package/dist/server/Kv.d.ts +16 -0
  101. package/dist/server/Kv.d.ts.map +1 -0
  102. package/dist/server/Kv.js +30 -0
  103. package/dist/server/Kv.js.map +1 -0
  104. package/dist/server/index.d.ts +3 -0
  105. package/dist/server/index.d.ts.map +1 -0
  106. package/dist/server/index.js +3 -0
  107. package/dist/server/index.js.map +1 -0
  108. package/dist/server/internal/requestListener.d.ts +124 -0
  109. package/dist/server/internal/requestListener.d.ts.map +1 -0
  110. package/dist/server/internal/requestListener.js +173 -0
  111. package/dist/server/internal/requestListener.js.map +1 -0
  112. package/dist/wagmi/Connector.d.ts +93 -0
  113. package/dist/wagmi/Connector.d.ts.map +1 -0
  114. package/dist/wagmi/Connector.js +238 -0
  115. package/dist/wagmi/Connector.js.map +1 -0
  116. package/dist/wagmi/index.d.ts +3 -0
  117. package/dist/wagmi/index.d.ts.map +1 -0
  118. package/dist/wagmi/index.js +3 -0
  119. package/dist/wagmi/index.js.map +1 -0
  120. package/package.json +56 -2
  121. package/src/core/AccessKey.test.ts +257 -0
  122. package/src/core/AccessKey.ts +123 -0
  123. package/src/core/Account.test.ts +309 -0
  124. package/src/core/Account.ts +152 -0
  125. package/src/core/Adapter.ts +238 -0
  126. package/src/core/Ceremony.browser.test.ts +239 -0
  127. package/src/core/Ceremony.test.ts +151 -0
  128. package/src/core/Ceremony.ts +203 -0
  129. package/src/core/Client.ts +36 -0
  130. package/src/core/Dialog.browser.test.ts +309 -0
  131. package/src/core/Dialog.test-d.ts +19 -0
  132. package/src/core/Dialog.ts +442 -0
  133. package/src/core/Expiry.ts +34 -0
  134. package/src/core/Messenger.ts +206 -0
  135. package/src/core/Provider.browser.test.ts +774 -0
  136. package/src/core/Provider.connect.browser.test.ts +415 -0
  137. package/src/core/Provider.test-d.ts +53 -0
  138. package/src/core/Provider.test.ts +1566 -0
  139. package/src/core/Provider.ts +559 -0
  140. package/src/core/Remote.ts +262 -0
  141. package/src/core/Schema.test-d.ts +211 -0
  142. package/src/core/Schema.ts +143 -0
  143. package/src/core/Storage.ts +213 -0
  144. package/src/core/Store.test.ts +287 -0
  145. package/src/core/Store.ts +129 -0
  146. package/src/core/adapters/dangerous_secp256k1.ts +53 -0
  147. package/src/core/adapters/dialog.ts +379 -0
  148. package/src/core/adapters/local.test.ts +97 -0
  149. package/src/core/adapters/local.ts +277 -0
  150. package/src/core/adapters/webAuthn.ts +129 -0
  151. package/src/core/internal/withDedupe.test.ts +116 -0
  152. package/src/core/internal/withDedupe.ts +20 -0
  153. package/src/core/mppx.test.ts +83 -0
  154. package/src/core/zod/request.test.ts +121 -0
  155. package/src/core/zod/request.ts +70 -0
  156. package/src/core/zod/rpc.ts +374 -0
  157. package/src/core/zod/utils.test.ts +69 -0
  158. package/src/core/zod/utils.ts +40 -0
  159. package/src/index.ts +14 -0
  160. package/src/internal/types.ts +378 -0
  161. package/src/server/Handler.test.ts +1014 -0
  162. package/src/server/Handler.ts +605 -0
  163. package/src/server/Kv.ts +46 -0
  164. package/src/server/index.ts +2 -0
  165. package/src/server/internal/requestListener.ts +273 -0
  166. package/src/tsconfig.json +9 -0
  167. package/src/wagmi/Connector.ts +287 -0
  168. package/src/wagmi/index.ts +2 -0
@@ -0,0 +1,379 @@
1
+ import { Address, Provider as ox_Provider, RpcRequest as ox_RpcRequest } from 'ox'
2
+ import { KeyAuthorization } from 'ox/tempo'
3
+ import { prepareTransactionRequest } from 'viem/actions'
4
+ import { Account as TempoAccount } from 'viem/tempo'
5
+ import { z } from 'zod/mini'
6
+
7
+ import * as AccessKey from '../AccessKey.js'
8
+ import * as Adapter from '../Adapter.js'
9
+ import * as Dialog from '../Dialog.js'
10
+ import * as Schema from '../Schema.js'
11
+ import type * as Store from '../Store.js'
12
+ import * as Rpc from '../zod/rpc.js'
13
+
14
+ /**
15
+ * Creates a dialog adapter that delegates signing to a remote embed app
16
+ * via an iframe or popup dialog.
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * import { dialog, Provider } from 'accounts'
21
+ *
22
+ * const provider = Provider.create({
23
+ * adapter: dialog(),
24
+ * })
25
+ * ```
26
+ */
27
+ export function dialog(options: dialog.Options = {}): Adapter.Adapter {
28
+ const {
29
+ dialog = Dialog.isSafari() ? Dialog.popup() : Dialog.iframe(),
30
+ host = 'https://wallet.tempo.xyz/embed',
31
+ icon,
32
+ name = 'Tempo',
33
+ rdns = 'xyz.tempo',
34
+ } = options
35
+
36
+ return Adapter.define({ icon, name, rdns }, ({ getAccount, getClient, store }) => {
37
+ const listeners = new Set<(requestQueue: readonly Store.QueuedRequest[]) => void>()
38
+ const requestStore = ox_RpcRequest.createStore()
39
+
40
+ /** Wait for a queued request to be resolved via the store. */
41
+ function waitForQueuedRequest(requestId: number) {
42
+ return new Promise((resolve, reject) => {
43
+ const listener = (requestQueue: readonly Store.QueuedRequest[]) => {
44
+ const queued = requestQueue.find((x) => x.request.id === requestId)
45
+
46
+ // Request removed and queue empty — cancelled or dialog closed.
47
+ if (!queued && requestQueue.length === 0) {
48
+ listeners.delete(listener)
49
+ reject(new ox_Provider.UserRejectedRequestError())
50
+ return
51
+ }
52
+
53
+ // Request not found but queue has other requests — wait.
54
+ if (!queued) return
55
+
56
+ // Request found but not yet resolved — wait.
57
+ if (queued.status !== 'success' && queued.status !== 'error') return
58
+
59
+ listeners.delete(listener)
60
+
61
+ if (queued.status === 'success') resolve(queued.result)
62
+ else reject(new ox_Provider.UserRejectedRequestError({ message: queued.error.message }))
63
+
64
+ // Remove the resolved request from the queue.
65
+ store.setState((x) => ({
66
+ ...x,
67
+ requestQueue: x.requestQueue.filter((x) => x.request.id !== requestId),
68
+ }))
69
+ }
70
+
71
+ listeners.add(listener)
72
+
73
+ // Notify immediately with current state so the store subscription
74
+ // picks up the request that was just added (setState fires
75
+ // synchronously before this listener is registered).
76
+ listener(store.getState().requestQueue)
77
+ })
78
+ }
79
+
80
+ /**
81
+ * An ox provider that queues RPC requests in the store. The store
82
+ * subscription syncs the pending queue to the dialog via `syncRequests`.
83
+ */
84
+ const provider = ox_Provider.from(
85
+ {
86
+ async request(r) {
87
+ const request = requestStore.prepare(r as never)
88
+
89
+ store.setState((x) => ({
90
+ ...x,
91
+ requestQueue: [...x.requestQueue, { request, status: 'pending' as const }],
92
+ }))
93
+
94
+ return waitForQueuedRequest(request.id)
95
+ },
96
+ },
97
+ { schema: Schema.ox },
98
+ )
99
+
100
+ /**
101
+ * Prepares a local key pair when `authorizeAccessKey` is requested without
102
+ * an external publicKey/address, and returns the params to inject into the
103
+ * RPC request so the dialog signs the authorization.
104
+ */
105
+ async function generateAccessKey(options: Adapter.authorizeAccessKey.Parameters | undefined) {
106
+ if (!options) return undefined
107
+ if (options.publicKey || options.address) return undefined
108
+
109
+ const { accessKey, keyPair } = await AccessKey.generate()
110
+ return {
111
+ accessKey,
112
+ keyPair,
113
+ request: {
114
+ ...options,
115
+ publicKey: accessKey.publicKey,
116
+ keyType: 'p256' as const,
117
+ },
118
+ }
119
+ }
120
+
121
+ /**
122
+ * After the dialog returns a signed key authorization, saves the local
123
+ * key pair + key authorization into the store.
124
+ */
125
+ function saveAccessKey(
126
+ address: Address.Address,
127
+ keyAuth: KeyAuthorization.Rpc,
128
+ keyPair: AccessKey.generate.ReturnType['keyPair'],
129
+ ) {
130
+ const keyAuthorization = KeyAuthorization.fromRpc(keyAuth)
131
+ AccessKey.save({ address, keyAuthorization, keyPair, store })
132
+ }
133
+
134
+ /**
135
+ * Tries to execute `fn` with the local access key. Returns `undefined`
136
+ * when no access key exists so the caller can fall through to the dialog.
137
+ * On access key errors, removes the stale key and also returns `undefined`.
138
+ */
139
+ async function withAccessKey<result>(
140
+ fn: (
141
+ account: TempoAccount.Account,
142
+ keyAuthorization?: KeyAuthorization.Signed,
143
+ ) => Promise<result>,
144
+ ): Promise<result | undefined> {
145
+ const account = (() => {
146
+ try {
147
+ return getAccount({ signable: true })
148
+ } catch {
149
+ return undefined
150
+ }
151
+ })()
152
+ if (!account) return undefined
153
+ if (account.source !== 'accessKey') return undefined
154
+ const keyAuthorization = AccessKey.getPending(account, { store })
155
+ try {
156
+ const result = await fn(account, keyAuthorization ?? undefined)
157
+ AccessKey.removePending(account, { store })
158
+ return result
159
+ } catch {
160
+ AccessKey.remove(account, { store })
161
+ return undefined
162
+ }
163
+ }
164
+
165
+ const dialogInstance = dialog({ host, store })
166
+
167
+ // Sync store → dialog: whenever the request queue changes, notify
168
+ // listeners and sync pending requests to the dialog.
169
+ const unsubscribe = store.subscribe(
170
+ (x) => x.requestQueue,
171
+ (requestQueue) => {
172
+ for (const listener of listeners) listener(requestQueue)
173
+
174
+ const pending = requestQueue.filter(
175
+ (x): x is Store.QueuedRequest & { status: 'pending' } => x.status === 'pending',
176
+ )
177
+
178
+ dialogInstance?.syncRequests(pending)
179
+ if (pending.length === 0) dialogInstance?.close()
180
+ },
181
+ )
182
+
183
+ return {
184
+ cleanup() {
185
+ unsubscribe()
186
+ dialogInstance?.destroy()
187
+ },
188
+ actions: {
189
+ async createAccount(parameters, request) {
190
+ const accessKey = await generateAccessKey(parameters.authorizeAccessKey)
191
+
192
+ const { accounts } = await provider.request({
193
+ ...request,
194
+ params: [
195
+ {
196
+ ...request.params?.[0],
197
+ capabilities: {
198
+ ...request.params?.[0]?.capabilities,
199
+ ...(accessKey
200
+ ? {
201
+ authorizeAccessKey: z.encode(
202
+ Rpc.wallet_connect.authorizeAccessKey,
203
+ accessKey.request,
204
+ ),
205
+ }
206
+ : {}),
207
+ },
208
+ },
209
+ ] as const,
210
+ })
211
+
212
+ const address = accounts[0]?.address
213
+ const keyAuthorization = accounts[0]?.capabilities.keyAuthorization
214
+
215
+ if (accessKey && address && keyAuthorization)
216
+ saveAccessKey(address, keyAuthorization, accessKey.keyPair)
217
+
218
+ return {
219
+ accounts: accounts.map((a) => ({ address: a.address })),
220
+ ...(keyAuthorization ? { keyAuthorization } : {}),
221
+ ...(accounts[0]?.capabilities.signature
222
+ ? { signature: accounts[0].capabilities.signature }
223
+ : {}),
224
+ }
225
+ },
226
+
227
+ async loadAccounts(parameters, request) {
228
+ const accessKey = await generateAccessKey(parameters?.authorizeAccessKey)
229
+
230
+ const { accounts } = await provider.request({
231
+ ...request,
232
+ params: [
233
+ {
234
+ ...request.params?.[0],
235
+ capabilities: {
236
+ ...request.params?.[0]?.capabilities,
237
+ ...(accessKey
238
+ ? {
239
+ authorizeAccessKey: z.encode(
240
+ Rpc.wallet_connect.authorizeAccessKey,
241
+ accessKey.request,
242
+ ),
243
+ }
244
+ : {}),
245
+ },
246
+ },
247
+ ] as const,
248
+ })
249
+
250
+ const address = accounts[0]?.address
251
+ const keyAuthorization = accounts[0]?.capabilities.keyAuthorization
252
+
253
+ if (accessKey && address && keyAuthorization)
254
+ saveAccessKey(address, keyAuthorization, accessKey.keyPair)
255
+
256
+ return {
257
+ accounts: accounts.map((a) => ({ address: a.address })),
258
+ ...(keyAuthorization ? { keyAuthorization } : {}),
259
+ ...(accounts[0]?.capabilities.signature
260
+ ? { signature: accounts[0].capabilities.signature }
261
+ : {}),
262
+ }
263
+ },
264
+
265
+ async signPersonalMessage(_params, request) {
266
+ return await provider.request(request)
267
+ },
268
+
269
+ async signTransaction(parameters, request) {
270
+ const result = await withAccessKey(async (account, keyAuthorization) => {
271
+ const { feePayer, ...rest } = parameters
272
+ const client = getClient({
273
+ feePayer: typeof feePayer === 'string' ? feePayer : undefined,
274
+ })
275
+ const prepared = await prepareTransactionRequest(client, {
276
+ account,
277
+ ...rest,
278
+ ...(feePayer ? { feePayer: true } : {}),
279
+ keyAuthorization,
280
+ type: 'tempo',
281
+ })
282
+ return await account.signTransaction(prepared as never)
283
+ })
284
+ if (result !== undefined) return result
285
+ return await provider.request(request)
286
+ },
287
+
288
+ async signTypedData(_params, request) {
289
+ return await provider.request(request)
290
+ },
291
+
292
+ async sendTransaction(parameters, request) {
293
+ const result = await withAccessKey(async (account, keyAuthorization) => {
294
+ const { feePayer, ...rest } = parameters
295
+ const client = getClient({
296
+ feePayer: typeof feePayer === 'string' ? feePayer : undefined,
297
+ })
298
+ const prepared = await prepareTransactionRequest(client, {
299
+ account,
300
+ ...rest,
301
+ ...(feePayer ? { feePayer: true } : {}),
302
+ keyAuthorization,
303
+ type: 'tempo',
304
+ })
305
+ const signed = await account.signTransaction(prepared as never)
306
+ return await client.request({
307
+ method: 'eth_sendRawTransaction' as never,
308
+ params: [signed],
309
+ })
310
+ })
311
+ if (result !== undefined) return result
312
+ return await provider.request(request)
313
+ },
314
+
315
+ async sendTransactionSync(parameters, request) {
316
+ const result = await withAccessKey(async (account, keyAuthorization) => {
317
+ const { feePayer, ...rest } = parameters
318
+ const client = getClient({
319
+ feePayer: typeof feePayer === 'string' ? feePayer : undefined,
320
+ })
321
+ const prepared = await prepareTransactionRequest(client, {
322
+ account,
323
+ ...rest,
324
+ ...(feePayer ? { feePayer: true } : {}),
325
+ keyAuthorization,
326
+ type: 'tempo',
327
+ })
328
+ const signed = await account.signTransaction(prepared as never)
329
+ return await client.request({
330
+ method: 'eth_sendRawTransactionSync' as never,
331
+ params: [signed],
332
+ })
333
+ })
334
+ if (result !== undefined) return result
335
+ return await provider.request(request)
336
+ },
337
+
338
+ async authorizeAccessKey(parameters, request) {
339
+ const accessKey = await generateAccessKey(parameters)
340
+
341
+ const result = await provider.request({
342
+ ...request,
343
+ params: [z.encode(Rpc.wallet_connect.authorizeAccessKey, accessKey!.request)!],
344
+ })
345
+
346
+ if (accessKey) {
347
+ const account = getAccount({ accessKey: false, signable: false })
348
+ saveAccessKey(account.address, result, accessKey.keyPair)
349
+ }
350
+
351
+ return result
352
+ },
353
+
354
+ async revokeAccessKey(_params, request) {
355
+ await provider.request(request)
356
+ },
357
+
358
+ async disconnect() {
359
+ store.setState({ accessKeys: [], accounts: [], activeAccount: 0 })
360
+ },
361
+ },
362
+ }
363
+ })
364
+ }
365
+
366
+ export declare namespace dialog {
367
+ type Options = {
368
+ /** Dialog to use for the embed app. @default `Dialog.iframe()` (or `Dialog.popup()` in Safari) */
369
+ dialog?: Dialog.Dialog | undefined
370
+ /** URL of the embed app. @default `'https://wallet.tempo.xyz/embed'` */
371
+ host?: string | undefined
372
+ /** Data URI of the provider icon. */
373
+ icon?: `data:image/${string}` | undefined
374
+ /** Display name of the provider. @default `'Tempo'` */
375
+ name?: string | undefined
376
+ /** Reverse DNS identifier. @default `'xyz.tempo'` */
377
+ rdns?: string | undefined
378
+ }
379
+ }
@@ -0,0 +1,97 @@
1
+ import { tempoLocalnet } from 'viem/chains'
2
+ import { describe, expect, test } from 'vp/test'
3
+
4
+ import {
5
+ accounts as core_accounts,
6
+ getClient,
7
+ privateKeys,
8
+ webAuthnAccounts,
9
+ } from '../../../test/config.js'
10
+ import * as Account from '../Account.js'
11
+ import * as Storage from '../Storage.js'
12
+ import * as Store from '../Store.js'
13
+ import { local } from './local.js'
14
+
15
+ describe('local', () => {
16
+ describe('loadAccounts', () => {
17
+ test('default: loads accounts', async () => {
18
+ const { adapter } = setup()
19
+
20
+ const { accounts } = await adapter.actions.loadAccounts(undefined, {
21
+ method: 'wallet_connect',
22
+ params: undefined,
23
+ })
24
+
25
+ expect(accounts.map((a) => a.address)).toMatchInlineSnapshot(`
26
+ [
27
+ "0x1ecBa262e4510F333FB5051743e2a53a765deBD0",
28
+ ]
29
+ `)
30
+ })
31
+ })
32
+
33
+ describe('createAccount', () => {
34
+ test('default: creates account', async () => {
35
+ const { adapter } = setup({
36
+ createAccount: async () => ({
37
+ accounts: [
38
+ {
39
+ address: core_accounts[1].address,
40
+ keyType: 'secp256k1',
41
+ privateKey: privateKeys[1],
42
+ },
43
+ ],
44
+ }),
45
+ })
46
+
47
+ const { accounts } = await adapter.actions.createAccount(
48
+ { name: 'test' },
49
+ { method: 'wallet_connect', params: undefined },
50
+ )
51
+
52
+ expect(accounts.map((a) => a.address)).toMatchInlineSnapshot(`
53
+ [
54
+ "0x8C8d35429F74ec245F8Ef2f4Fd1e551cFF97d650",
55
+ ]
56
+ `)
57
+ })
58
+
59
+ test('error: throws when createAccount not configured', async () => {
60
+ const { adapter } = setup()
61
+
62
+ await expect(
63
+ adapter.actions.createAccount(
64
+ { name: 'test' },
65
+ { method: 'wallet_connect', params: undefined },
66
+ ),
67
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
68
+ `[Provider.UnsupportedMethodError: \`createAccount\` not configured on adapter.]`,
69
+ )
70
+ })
71
+ })
72
+ })
73
+
74
+ function setup(overrides: Partial<local.Options> = {}) {
75
+ const storage = Storage.memory()
76
+ const store = Store.create({ chainId: tempoLocalnet.id, storage })
77
+ const adapter = local({
78
+ loadAccounts: async () => ({
79
+ accounts: [
80
+ {
81
+ address: webAuthnAccounts[0]!.address,
82
+ keyType: 'webAuthn_headless' as const,
83
+ privateKey: privateKeys[0]!,
84
+ rpId: 'example.com',
85
+ origin: 'https://example.com',
86
+ },
87
+ ],
88
+ }),
89
+ ...overrides,
90
+ })({
91
+ getAccount: (options) => Account.find({ ...options, signable: true, store }),
92
+ getClient: () => getClient({ chain: tempoLocalnet }) as never,
93
+ storage,
94
+ store,
95
+ })
96
+ return { adapter, store }
97
+ }