accounts 0.12.2 → 0.14.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 (128) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +6 -5
  3. package/dist/cli/adapter.d.ts.map +1 -1
  4. package/dist/cli/adapter.js +76 -69
  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 +230 -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 +24 -4
  15. package/dist/core/Adapter.d.ts.map +1 -1
  16. package/dist/core/Client.d.ts +1 -1
  17. package/dist/core/Client.d.ts.map +1 -1
  18. package/dist/core/Client.js.map +1 -1
  19. package/dist/core/Provider.d.ts +6 -7
  20. package/dist/core/Provider.d.ts.map +1 -1
  21. package/dist/core/Provider.js +73 -77
  22. package/dist/core/Provider.js.map +1 -1
  23. package/dist/core/Remote.d.ts +2 -0
  24. package/dist/core/Remote.d.ts.map +1 -1
  25. package/dist/core/Remote.js +3 -1
  26. package/dist/core/Remote.js.map +1 -1
  27. package/dist/core/Schema.d.ts +17 -7
  28. package/dist/core/Schema.d.ts.map +1 -1
  29. package/dist/core/adapters/dialog.d.ts.map +1 -1
  30. package/dist/core/adapters/dialog.js +126 -131
  31. package/dist/core/adapters/dialog.js.map +1 -1
  32. package/dist/core/adapters/local.d.ts.map +1 -1
  33. package/dist/core/adapters/local.js +92 -105
  34. package/dist/core/adapters/local.js.map +1 -1
  35. package/dist/core/adapters/privy.d.ts +164 -0
  36. package/dist/core/adapters/privy.d.ts.map +1 -0
  37. package/dist/core/adapters/privy.js +578 -0
  38. package/dist/core/adapters/privy.js.map +1 -0
  39. package/dist/core/adapters/turnkey.d.ts.map +1 -1
  40. package/dist/core/adapters/turnkey.js +136 -133
  41. package/dist/core/adapters/turnkey.js.map +1 -1
  42. package/dist/core/internal/AccessKeyTransaction.d.ts +59 -0
  43. package/dist/core/internal/AccessKeyTransaction.d.ts.map +1 -0
  44. package/dist/core/internal/AccessKeyTransaction.js +155 -0
  45. package/dist/core/internal/AccessKeyTransaction.js.map +1 -0
  46. package/dist/core/zod/rpc.d.ts +71 -4
  47. package/dist/core/zod/rpc.d.ts.map +1 -1
  48. package/dist/core/zod/rpc.js +33 -4
  49. package/dist/core/zod/rpc.js.map +1 -1
  50. package/dist/index.d.ts +1 -1
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +1 -1
  53. package/dist/index.js.map +1 -1
  54. package/dist/react-native/adapter.d.ts.map +1 -1
  55. package/dist/react-native/adapter.js +86 -75
  56. package/dist/react-native/adapter.js.map +1 -1
  57. package/dist/server/CliAuth.d.ts +55 -0
  58. package/dist/server/CliAuth.d.ts.map +1 -1
  59. package/dist/server/CliAuth.js +18 -1
  60. package/dist/server/CliAuth.js.map +1 -1
  61. package/dist/server/Kv.d.ts +19 -9
  62. package/dist/server/Kv.d.ts.map +1 -1
  63. package/dist/server/Kv.js +32 -9
  64. package/dist/server/Kv.js.map +1 -1
  65. package/dist/server/internal/handlers/auth.d.ts.map +1 -1
  66. package/dist/server/internal/handlers/auth.js +3 -1
  67. package/dist/server/internal/handlers/auth.js.map +1 -1
  68. package/dist/server/internal/handlers/codeAuth.js +1 -1
  69. package/dist/server/internal/handlers/codeAuth.js.map +1 -1
  70. package/dist/server/internal/handlers/exchange.js +1 -1
  71. package/dist/server/internal/handlers/exchange.js.map +1 -1
  72. package/dist/server/internal/handlers/relay.d.ts +18 -12
  73. package/dist/server/internal/handlers/relay.d.ts.map +1 -1
  74. package/dist/server/internal/handlers/relay.js +116 -3
  75. package/dist/server/internal/handlers/relay.js.map +1 -1
  76. package/dist/server/internal/handlers/webAuthn.d.ts +6 -1
  77. package/dist/server/internal/handlers/webAuthn.d.ts.map +1 -1
  78. package/dist/server/internal/handlers/webAuthn.js +16 -9
  79. package/dist/server/internal/handlers/webAuthn.js.map +1 -1
  80. package/package.json +3 -3
  81. package/src/cli/{Provider.test.ts → Provider.localnet.test.ts} +49 -1
  82. package/src/cli/adapter.ts +95 -76
  83. package/src/core/AccessKey.test.ts +563 -560
  84. package/src/core/AccessKey.ts +327 -327
  85. package/src/core/Account.test.ts +6 -51
  86. package/src/core/Account.ts +1 -19
  87. package/src/core/Adapter.ts +22 -4
  88. package/src/core/Client.test.ts +1 -1
  89. package/src/core/Client.ts +1 -1
  90. package/src/core/Provider.connect.browser.test.ts +1 -1
  91. package/src/core/{Provider.test.ts → Provider.localnet.test.ts} +98 -32
  92. package/src/core/Provider.ts +118 -112
  93. package/src/core/Remote.test.ts +28 -0
  94. package/src/core/Remote.ts +4 -1
  95. package/src/core/Schema.test-d.ts +41 -0
  96. package/src/core/adapters/dialog.test.ts +335 -15
  97. package/src/core/adapters/dialog.ts +120 -127
  98. package/src/core/adapters/local.test.ts +1 -1
  99. package/src/core/adapters/local.ts +95 -119
  100. package/src/core/adapters/privy.test-d.ts +128 -0
  101. package/src/core/adapters/privy.test.ts +648 -0
  102. package/src/core/adapters/privy.ts +811 -0
  103. package/src/core/adapters/turnkey.test.ts +117 -13
  104. package/src/core/adapters/turnkey.ts +132 -149
  105. package/src/core/internal/AccessKeyTransaction.ts +243 -0
  106. package/src/core/{mppx.test.ts → mppx.localnet.test.ts} +32 -2
  107. package/src/core/zod/request.test.ts +36 -0
  108. package/src/core/zod/rpc.test.ts +139 -0
  109. package/src/core/zod/rpc.ts +44 -10
  110. package/src/index.ts +1 -1
  111. package/src/react-native/{Provider.test.ts → Provider.localnet.test.ts} +65 -0
  112. package/src/react-native/adapter.ts +107 -87
  113. package/src/server/CliAuth.test-d.ts +23 -0
  114. package/src/server/CliAuth.test.ts +47 -0
  115. package/src/server/CliAuth.ts +20 -1
  116. package/src/server/Handler.test-d.ts +1 -1
  117. package/src/server/Kv.test.ts +51 -0
  118. package/src/server/Kv.ts +43 -9
  119. package/src/server/internal/handlers/auth.test.ts +10 -5
  120. package/src/server/internal/handlers/auth.ts +4 -1
  121. package/src/server/internal/handlers/codeAuth.ts +1 -1
  122. package/src/server/internal/handlers/exchange.ts +1 -1
  123. package/src/server/internal/handlers/{relay.test.ts → relay.localnet.test.ts} +4 -1
  124. package/src/server/internal/handlers/relay.ts +139 -14
  125. package/src/server/internal/handlers/session.test.ts +1 -1
  126. package/src/server/internal/handlers/webAuthn.test.ts +20 -0
  127. package/src/server/internal/handlers/webAuthn.ts +24 -12
  128. /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',
@@ -200,6 +200,72 @@ describe('wallet_connect.capabilities.result: auth + personalSign', () => {
200
200
  })
201
201
  })
202
202
 
203
+ describe('wallet_connect.capabilities.request: showDeposit', () => {
204
+ test('accepts true on the register branch', () => {
205
+ expect(
206
+ z.parse(Rpc.wallet_connect.capabilities.request, {
207
+ method: 'register',
208
+ showDeposit: true,
209
+ }),
210
+ ).toEqual({
211
+ method: 'register',
212
+ showDeposit: true,
213
+ })
214
+ })
215
+
216
+ test('accepts false on the register branch', () => {
217
+ expect(
218
+ z.parse(Rpc.wallet_connect.capabilities.request, {
219
+ method: 'register',
220
+ showDeposit: false,
221
+ }),
222
+ ).toEqual({
223
+ method: 'register',
224
+ showDeposit: false,
225
+ })
226
+ })
227
+
228
+ test('accepts deposit parameters on the register branch', () => {
229
+ expect(
230
+ z.parse(Rpc.wallet_connect.capabilities.request, {
231
+ method: 'register',
232
+ showDeposit: {
233
+ amount: '50',
234
+ displayName: 'DoorDash',
235
+ token: 'USDC',
236
+ },
237
+ }),
238
+ ).toEqual({
239
+ method: 'register',
240
+ showDeposit: {
241
+ amount: '50',
242
+ displayName: 'DoorDash',
243
+ token: 'USDC',
244
+ },
245
+ })
246
+ })
247
+
248
+ test('ignores showDeposit on the login branch', () => {
249
+ expect(
250
+ z.parse(Rpc.wallet_connect.capabilities.request, {
251
+ method: 'login',
252
+ showDeposit: true,
253
+ }),
254
+ ).toEqual({
255
+ method: 'login',
256
+ })
257
+ })
258
+
259
+ test('rejects invalid deposit parameters', () => {
260
+ expect(() =>
261
+ z.parse(Rpc.wallet_connect.capabilities.request, {
262
+ method: 'register',
263
+ showDeposit: { amount: 50 },
264
+ }),
265
+ ).toThrow()
266
+ })
267
+ })
268
+
203
269
  describe('wallet_connect_strict.parameters: auth', () => {
204
270
  test('accepts string shorthand', () => {
205
271
  expect(
@@ -240,3 +306,76 @@ describe('wallet_connect_strict.parameters: auth', () => {
240
306
  `)
241
307
  })
242
308
  })
309
+
310
+ describe('wallet_connect_strict.parameters: showDeposit', () => {
311
+ test('accepts true on the register branch', () => {
312
+ expect(
313
+ z.parse(Rpc.wallet_connect_strict.parameters, {
314
+ capabilities: {
315
+ method: 'register',
316
+ showDeposit: true,
317
+ },
318
+ }),
319
+ ).toEqual({
320
+ capabilities: {
321
+ method: 'register',
322
+ showDeposit: true,
323
+ },
324
+ })
325
+ })
326
+
327
+ test('accepts false on the register branch', () => {
328
+ expect(
329
+ z.parse(Rpc.wallet_connect_strict.parameters, {
330
+ capabilities: {
331
+ method: 'register',
332
+ showDeposit: false,
333
+ },
334
+ }),
335
+ ).toEqual({
336
+ capabilities: {
337
+ method: 'register',
338
+ showDeposit: false,
339
+ },
340
+ })
341
+ })
342
+
343
+ test('accepts deposit parameters on the register branch', () => {
344
+ expect(
345
+ z.parse(Rpc.wallet_connect_strict.parameters, {
346
+ capabilities: {
347
+ method: 'register',
348
+ showDeposit: {
349
+ amount: '100',
350
+ displayName: 'DoorDash',
351
+ token: 'USDC',
352
+ },
353
+ },
354
+ }),
355
+ ).toEqual({
356
+ capabilities: {
357
+ method: 'register',
358
+ showDeposit: {
359
+ amount: '100',
360
+ displayName: 'DoorDash',
361
+ token: 'USDC',
362
+ },
363
+ },
364
+ })
365
+ })
366
+
367
+ test('ignores showDeposit on the login branch', () => {
368
+ expect(
369
+ z.parse(Rpc.wallet_connect_strict.parameters, {
370
+ capabilities: {
371
+ method: 'login',
372
+ showDeposit: true,
373
+ },
374
+ }),
375
+ ).toEqual({
376
+ capabilities: {
377
+ method: 'login',
378
+ },
379
+ })
380
+ })
381
+ })
@@ -427,6 +427,30 @@ export namespace wallet_revokeAccessKey {
427
427
  export namespace wallet_connect {
428
428
  export const authorizeAccessKey = z.optional(wallet_authorizeAccessKey.parameters)
429
429
 
430
+ /**
431
+ * Shows an optional funding prompt after `wallet_connect` registration succeeds.
432
+ *
433
+ * `true` uses the connected account as the deposit target. Object form
434
+ * pre-fills deposit UI hints. The deposit chain comes from the surrounding
435
+ * `wallet_connect` chain context.
436
+ */
437
+ export const showDeposit = z.optional(
438
+ z.union([
439
+ z.boolean(),
440
+ z.object({
441
+ /** Human-readable amount to pre-fill (e.g. `"50"`). */
442
+ amount: z.optional(z.string()),
443
+ /** Display name shown in the deposit UI (e.g. the app name). */
444
+ displayName: z.optional(z.string()),
445
+ /**
446
+ * Token to pre-fill, accepted as either a contract address or a
447
+ * supported deposit token symbol (case-insensitive, e.g. `"USDC"`).
448
+ */
449
+ token: z.optional(z.union([u.address(), z.string()])),
450
+ }),
451
+ ]),
452
+ )
453
+
430
454
  /**
431
455
  * SIWE round-trip configuration. Bare string is shorthand for `{ url }`.
432
456
  *
@@ -484,6 +508,7 @@ export namespace wallet_connect {
484
508
  method: z.literal('register'),
485
509
  name: z.optional(z.string()),
486
510
  personalSign,
511
+ showDeposit,
487
512
  userId: z.optional(z.string()),
488
513
  }),
489
514
  z.object({
@@ -558,6 +583,7 @@ export namespace wallet_connect_strict {
558
583
  const authorizeAccessKey = z.optional(wallet_authorizeAccessKey_strict.parameters)
559
584
  const auth = wallet_connect.auth
560
585
  const personalSign = wallet_connect.personalSign
586
+ const showDeposit = wallet_connect.showDeposit
561
587
 
562
588
  export const parameters = z.object({
563
589
  capabilities: z.optional(
@@ -569,6 +595,7 @@ export namespace wallet_connect_strict {
569
595
  method: z.literal('register'),
570
596
  name: z.optional(z.string()),
571
597
  personalSign,
598
+ showDeposit,
572
599
  userId: z.optional(z.string()),
573
600
  }),
574
601
  z.object({
@@ -746,16 +773,23 @@ export namespace wallet_switchEthereumChain {
746
773
  export namespace wallet_deposit {
747
774
  export const schema = Schema.defineItem({
748
775
  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
- ]),
776
+ params: z.optional(
777
+ z.readonly(
778
+ z.tuple([
779
+ z.object({
780
+ address: z.optional(u.address()),
781
+ /** Human-readable amount to pre-fill (e.g. `"50"`). */
782
+ amount: z.optional(z.string()),
783
+ chainId: z.optional(u.number()),
784
+ displayName: z.optional(z.string()),
785
+ /**
786
+ * Token to pre-fill, accepted as either a contract address or a
787
+ * supported deposit token symbol (case-insensitive, e.g. `"USDC"`).
788
+ */
789
+ token: z.optional(z.union([u.address(), z.string()])),
790
+ }),
791
+ ]),
792
+ ),
759
793
  ),
760
794
  returns: z.optional(
761
795
  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 {
@@ -27,11 +27,14 @@ async function fund(address: viem_Address) {
27
27
 
28
28
  function createOpen(options: { mismatchFirstCall?: boolean | undefined } = {}) {
29
29
  let calls = 0
30
+ const urls: string[] = []
30
31
 
31
32
  return {
32
33
  calls: () => calls,
34
+ urls: () => urls,
33
35
  open: async (url: string) => {
34
36
  calls += 1
37
+ urls.push(url)
35
38
 
36
39
  const authUrl = new URL(url)
37
40
  const callback = authUrl.searchParams.get('callback')
@@ -112,4 +115,66 @@ describe('create', () => {
112
115
  expect(receipt.status).toMatchInlineSnapshot(`"0x1"`)
113
116
  expect(browser.calls()).toMatchInlineSnapshot(`2`)
114
117
  })
118
+
119
+ test('behavior: forwards showDeposit boolean to the mobile auth URL for registration', async () => {
120
+ const browser = createOpen()
121
+ const provider = Provider.create({
122
+ authorizeAccessKey: () => ({
123
+ expiry: Math.floor(Date.now() / 1000) + 3600,
124
+ }),
125
+ chains: [chain],
126
+ host: 'https://wallet-next.tempo.xyz',
127
+ open: browser.open,
128
+ redirectUri: 'accounts-playground://auth',
129
+ secureStorage: Storage.memory(),
130
+ })
131
+
132
+ await provider.request({
133
+ method: 'wallet_connect',
134
+ params: [{ capabilities: { method: 'register', showDeposit: true } }],
135
+ })
136
+
137
+ expect(new URL(browser.urls()[0]!).searchParams.get('showDeposit')).toMatchInlineSnapshot(
138
+ `"true"`,
139
+ )
140
+ })
141
+
142
+ test('behavior: forwards showDeposit params to the mobile auth URL for registration', async () => {
143
+ const browser = createOpen()
144
+ const provider = Provider.create({
145
+ authorizeAccessKey: () => ({
146
+ expiry: Math.floor(Date.now() / 1000) + 3600,
147
+ }),
148
+ chains: [chain],
149
+ host: 'https://wallet-next.tempo.xyz',
150
+ open: browser.open,
151
+ redirectUri: 'accounts-playground://auth',
152
+ secureStorage: Storage.memory(),
153
+ })
154
+
155
+ await provider.request({
156
+ method: 'wallet_connect',
157
+ params: [
158
+ {
159
+ capabilities: {
160
+ method: 'register',
161
+ showDeposit: {
162
+ amount: '50',
163
+ displayName: 'DoorDash',
164
+ token: 'USDC',
165
+ },
166
+ },
167
+ },
168
+ ],
169
+ })
170
+
171
+ expect(JSON.parse(new URL(browser.urls()[0]!).searchParams.get('showDeposit')!))
172
+ .toMatchInlineSnapshot(`
173
+ {
174
+ "amount": "50",
175
+ "displayName": "DoorDash",
176
+ "token": "USDC",
177
+ }
178
+ `)
179
+ })
115
180
  })