accounts 0.16.2 → 0.17.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 (65) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +3 -4
  3. package/dist/cli/Provider.d.ts +1 -1
  4. package/dist/cli/Provider.d.ts.map +1 -1
  5. package/dist/cli/Provider.js +3 -3
  6. package/dist/cli/Provider.js.map +1 -1
  7. package/dist/cli/adapter.d.ts +13 -8
  8. package/dist/cli/adapter.d.ts.map +1 -1
  9. package/dist/cli/adapter.js +92 -303
  10. package/dist/cli/adapter.js.map +1 -1
  11. package/dist/core/adapters/deviceCode/deviceCode.d.ts +59 -0
  12. package/dist/core/adapters/deviceCode/deviceCode.d.ts.map +1 -0
  13. package/dist/core/adapters/deviceCode/deviceCode.js +93 -0
  14. package/dist/core/adapters/deviceCode/deviceCode.js.map +1 -0
  15. package/dist/core/adapters/deviceCode/index.d.ts +3 -0
  16. package/dist/core/adapters/deviceCode/index.d.ts.map +1 -0
  17. package/dist/core/adapters/deviceCode/index.js +3 -0
  18. package/dist/core/adapters/deviceCode/index.js.map +1 -0
  19. package/dist/core/adapters/deviceCode/tempoWallet.d.ts +9 -0
  20. package/dist/core/adapters/deviceCode/tempoWallet.d.ts.map +1 -0
  21. package/dist/core/adapters/deviceCode/tempoWallet.js +11 -0
  22. package/dist/core/adapters/deviceCode/tempoWallet.js.map +1 -0
  23. package/dist/core/adapters/internal/fromRequest.d.ts +10 -0
  24. package/dist/core/adapters/internal/fromRequest.d.ts.map +1 -1
  25. package/dist/core/adapters/internal/fromRequest.js +4 -2
  26. package/dist/core/adapters/internal/fromRequest.js.map +1 -1
  27. package/dist/server/CliAuth.d.ts +69 -1
  28. package/dist/server/CliAuth.d.ts.map +1 -1
  29. package/dist/server/CliAuth.js +6 -1
  30. package/dist/server/CliAuth.js.map +1 -1
  31. package/dist/server/Handler.d.ts +2 -1
  32. package/dist/server/Handler.d.ts.map +1 -1
  33. package/dist/server/Handler.js +2 -1
  34. package/dist/server/Handler.js.map +1 -1
  35. package/dist/server/index.d.ts +1 -0
  36. package/dist/server/index.d.ts.map +1 -1
  37. package/dist/server/index.js +1 -0
  38. package/dist/server/index.js.map +1 -1
  39. package/dist/server/internal/handlers/codeAuth.d.ts +1 -0
  40. package/dist/server/internal/handlers/codeAuth.d.ts.map +1 -1
  41. package/dist/server/internal/handlers/codeAuth.js +1 -0
  42. package/dist/server/internal/handlers/codeAuth.js.map +1 -1
  43. package/dist/server/internal/handlers/deviceCode.d.ts +60 -0
  44. package/dist/server/internal/handlers/deviceCode.d.ts.map +1 -0
  45. package/dist/server/internal/handlers/deviceCode.js +218 -0
  46. package/dist/server/internal/handlers/deviceCode.js.map +1 -0
  47. package/package.json +7 -1
  48. package/src/cli/Provider.localnet.test.ts +414 -470
  49. package/src/cli/Provider.test-d.ts +4 -3
  50. package/src/cli/Provider.ts +6 -6
  51. package/src/cli/adapter.ts +111 -380
  52. package/src/core/adapters/deviceCode/deviceCode.localnet.test.ts +226 -0
  53. package/src/core/adapters/deviceCode/deviceCode.test-d.ts +19 -0
  54. package/src/core/adapters/deviceCode/deviceCode.ts +157 -0
  55. package/src/core/adapters/deviceCode/index.ts +2 -0
  56. package/src/core/adapters/deviceCode/tempoWallet.ts +17 -0
  57. package/src/core/adapters/internal/fromRequest.ts +115 -88
  58. package/src/server/CliAuth.test.ts +29 -18
  59. package/src/server/CliAuth.ts +10 -2
  60. package/src/server/Handler.test-d.ts +19 -33
  61. package/src/server/Handler.ts +2 -1
  62. package/src/server/index.ts +1 -0
  63. package/src/server/internal/handlers/codeAuth.ts +1 -0
  64. package/src/server/internal/handlers/deviceCode.test.ts +140 -0
  65. package/src/server/internal/handlers/deviceCode.ts +323 -0
@@ -0,0 +1,226 @@
1
+ import { Hex } from 'ox'
2
+ import { describe, expect, test } from 'vp/test'
3
+ import type { DeviceCode } from 'wata'
4
+
5
+ import { accounts, chain } from '../../../../test/config.js'
6
+ import { createDeviceCodeHost, submitVerify } from '../../../../test/deviceCode.js'
7
+ import { createServer } from '../../../../test/utils.js'
8
+ import * as Provider from '../../Provider.js'
9
+ import { deviceCode } from './deviceCode.js'
10
+
11
+ const root = accounts[0]!
12
+ const accessKey = accounts[1]!
13
+ const expiry = Math.floor(Date.now() / 1000) + 3_600
14
+
15
+ function connectRequest() {
16
+ return {
17
+ method: 'wallet_connect',
18
+ params: [
19
+ {
20
+ capabilities: {
21
+ authorizeAccessKey: {
22
+ expiry,
23
+ keyType: accessKey.keyType,
24
+ publicKey: accessKey.publicKey,
25
+ },
26
+ },
27
+ },
28
+ ],
29
+ } as const
30
+ }
31
+
32
+ function createProvider(options: Partial<deviceCode.Options> & { url: string }) {
33
+ return Provider.create({
34
+ adapter: deviceCode({
35
+ name: 'Accounts Test CLI',
36
+ onPrompt: (prompt) => void submitVerify(prompt),
37
+ rdns: 'xyz.tempo.accounts.test',
38
+ ...options,
39
+ }),
40
+ chains: [chain],
41
+ })
42
+ }
43
+
44
+ describe('deviceCode', () => {
45
+ test('default: bootstraps wallet_connect through the device-code flow', async () => {
46
+ const host = createDeviceCodeHost()
47
+ const server = await createServer(host.listener)
48
+ const prompts: DeviceCode.Prompt[] = []
49
+
50
+ try {
51
+ const provider = createProvider({
52
+ onPrompt: (prompt) => {
53
+ prompts.push(prompt)
54
+ void submitVerify(prompt)
55
+ },
56
+ url: `${server.url}/auth/device`,
57
+ })
58
+
59
+ const result = await provider.request(connectRequest())
60
+ const account = result.accounts[0]!
61
+ const keyAuthorization = account.capabilities.keyAuthorization
62
+ ? {
63
+ ...account.capabilities.keyAuthorization,
64
+ signature: { type: account.capabilities.keyAuthorization.signature.type },
65
+ }
66
+ : undefined
67
+
68
+ expect(prompts).toHaveLength(1)
69
+ expect(prompts[0]!.userCode).toMatch(/^[BCDFGHJKLMNPQRSTVWXZ]{4}-[BCDFGHJKLMNPQRSTVWXZ]{4}$/)
70
+ expect(prompts[0]!.verificationUri).toBe(`${server.url}/auth/device/verify`)
71
+ expect(prompts[0]!.verificationUriFull).toBe(
72
+ `${server.url}/auth/device/verify?user_code=${prompts[0]!.userCode}`,
73
+ )
74
+ expect({
75
+ account: {
76
+ address: account.address,
77
+ capabilities: keyAuthorization ? { keyAuthorization } : {},
78
+ },
79
+ }).toMatchInlineSnapshot(`
80
+ {
81
+ "account": {
82
+ "address": "${root.address}",
83
+ "capabilities": {
84
+ "keyAuthorization": {
85
+ "address": "${accessKey.address.toLowerCase()}",
86
+ "chainId": "${Hex.fromNumber(chain.id)}",
87
+ "expiry": "${Hex.fromNumber(expiry)}",
88
+ "keyId": "${accessKey.address.toLowerCase()}",
89
+ "keyType": "secp256k1",
90
+ "signature": {
91
+ "type": "secp256k1",
92
+ },
93
+ },
94
+ },
95
+ },
96
+ }
97
+ `)
98
+ } finally {
99
+ await server.closeAsync()
100
+ }
101
+ })
102
+
103
+ test('behavior: forwards request context to the host', async () => {
104
+ const host = createDeviceCodeHost()
105
+ const server = await createServer(host.listener)
106
+
107
+ try {
108
+ const provider = createProvider({ url: `${server.url}/auth/device` })
109
+ await provider.request(connectRequest())
110
+
111
+ expect(host.requests()).toHaveLength(1)
112
+ expect(host.requests()[0]!.method).toBe('wallet_connect')
113
+ } finally {
114
+ await server.closeAsync()
115
+ }
116
+ })
117
+
118
+ test('behavior: preserves a null username capability', async () => {
119
+ const host = createDeviceCodeHost({ username: null })
120
+ const server = await createServer(host.listener)
121
+
122
+ try {
123
+ const provider = createProvider({ url: `${server.url}/auth/device` })
124
+ const result = await provider.request(connectRequest())
125
+
126
+ expect(result.accounts[0]!.capabilities.username).toBeNull()
127
+ } finally {
128
+ await server.closeAsync()
129
+ }
130
+ })
131
+
132
+ test('behavior: denial rejects with a user-rejected error', async () => {
133
+ const host = createDeviceCodeHost()
134
+ const server = await createServer(host.listener)
135
+
136
+ try {
137
+ const provider = createProvider({
138
+ onPrompt: (prompt) => void submitVerify(prompt, { action: 'deny' }),
139
+ url: `${server.url}/auth/device`,
140
+ })
141
+
142
+ await expect(
143
+ provider.request(connectRequest()).catch((error: { code: number; message: string }) => ({
144
+ code: error.code,
145
+ message: error.message,
146
+ })),
147
+ ).resolves.toMatchInlineSnapshot(`
148
+ {
149
+ "code": 4001,
150
+ "message": "User denied the device-code request.",
151
+ }
152
+ `)
153
+ } finally {
154
+ await server.closeAsync()
155
+ }
156
+ })
157
+
158
+ test('behavior: times out while waiting for authorization', async () => {
159
+ const host = createDeviceCodeHost({ pollingInterval: 10 })
160
+ const server = await createServer(host.listener)
161
+
162
+ try {
163
+ const provider = createProvider({
164
+ onPrompt: () => {},
165
+ timeout: 100,
166
+ url: `${server.url}/auth/device`,
167
+ })
168
+
169
+ const error = await provider.request(connectRequest()).then(
170
+ () => undefined,
171
+ (error: Error) => error,
172
+ )
173
+ expect(error?.message).toMatch(/^Timed out waiting for device code [A-Z-]+\. Continue at /)
174
+ } finally {
175
+ await server.closeAsync()
176
+ }
177
+ })
178
+
179
+ test('behavior: onPrompt failures surface the URL and code', async () => {
180
+ const host = createDeviceCodeHost()
181
+ const server = await createServer(host.listener)
182
+
183
+ try {
184
+ const provider = createProvider({
185
+ onPrompt: () => {
186
+ throw new Error('browser unavailable')
187
+ },
188
+ url: `${server.url}/auth/device`,
189
+ })
190
+
191
+ const error = await provider.request(connectRequest()).then(
192
+ () => undefined,
193
+ (error: Error) => error,
194
+ )
195
+ expect(error?.message).toMatch(
196
+ /^Failed to surface device code [A-Z-]+\. Open .*\/auth\/device\/verify\?user_code=[A-Z-]+ manually\.$/,
197
+ )
198
+ } finally {
199
+ await server.closeAsync()
200
+ }
201
+ })
202
+
203
+ test('behavior: rejects methods outside the allowlist without a ceremony', async () => {
204
+ const host = createDeviceCodeHost()
205
+ const server = await createServer(host.listener)
206
+ const prompts: DeviceCode.Prompt[] = []
207
+
208
+ try {
209
+ const provider = createProvider({
210
+ methods: ['wallet_authorizeAccessKey'],
211
+ onPrompt: (prompt) => {
212
+ prompts.push(prompt)
213
+ void submitVerify(prompt)
214
+ },
215
+ url: `${server.url}/auth/device`,
216
+ })
217
+
218
+ await expect(provider.request(connectRequest())).rejects.toThrowErrorMatchingInlineSnapshot(
219
+ `[Provider.UnsupportedMethodError: \`wallet_connect\` not supported by device-code adapter.]`,
220
+ )
221
+ expect(prompts).toHaveLength(0)
222
+ } finally {
223
+ await server.closeAsync()
224
+ }
225
+ })
226
+ })
@@ -0,0 +1,19 @@
1
+ import { describe, test } from 'vp/test'
2
+
3
+ import { deviceCode } from './deviceCode.js'
4
+
5
+ describe('deviceCode', () => {
6
+ test('accepts explicitly undefined optional metadata', () => {
7
+ const description: string | undefined = undefined
8
+ const icon: string | undefined = undefined
9
+ const websiteUrl: string | undefined = undefined
10
+
11
+ deviceCode({
12
+ meta: { name: 'Example', description, icon, websiteUrl },
13
+ name: 'Example',
14
+ onPrompt() {},
15
+ rdns: 'com.example',
16
+ url: 'https://example.com/auth/device',
17
+ })
18
+ })
19
+ })
@@ -0,0 +1,157 @@
1
+ import { Provider as core_Provider } from 'ox'
2
+ import { DeviceCode, Transport, Wata, deviceCode as core_deviceCode } from 'wata'
3
+
4
+ import type * as Adapter from '../../Adapter.js'
5
+ import type * as Keystore from '../../Keystore.js'
6
+ import { fromRequest } from '../internal/fromRequest.js'
7
+
8
+ /**
9
+ * Creates a device-code adapter that forwards wallet RPC through Wata.
10
+ *
11
+ * Each request runs one OAuth 2.0 Device Authorization Grant exchange
12
+ * (RFC 8628 with PKCE): the host issues a user code surfaced via
13
+ * `onPrompt`, the user approves on the host's verification page, and the
14
+ * adapter polls until the response arrives.
15
+ */
16
+ export function deviceCode(options: deviceCode.Options): Adapter.Adapter {
17
+ const {
18
+ actions,
19
+ fetch,
20
+ icon,
21
+ keystores,
22
+ meta,
23
+ methods,
24
+ name,
25
+ onPrompt,
26
+ pollingInterval,
27
+ rdns,
28
+ timeout = 300_000,
29
+ url,
30
+ } = options
31
+
32
+ return fromRequest({
33
+ ...(actions ? { actions } : {}),
34
+ ...(icon ? { icon } : {}),
35
+ ...(keystores ? { keystores } : {}),
36
+ name,
37
+ rdns,
38
+ async request(request) {
39
+ if (methods && !methods.includes(request.method))
40
+ throw new core_Provider.UnsupportedMethodError({
41
+ message: `\`${request.method}\` not supported by device-code adapter.`,
42
+ })
43
+
44
+ // Device code is single-exchange: one register + poll cycle carries one
45
+ // RPC request envelope, so a fresh Wata instance backs every request.
46
+ const wata = Wata.create({
47
+ transports: [
48
+ core_deviceCode({
49
+ ...(fetch !== undefined ? { fetch } : {}),
50
+ ...(meta ? { meta } : {}),
51
+ ...(pollingInterval !== undefined ? { pollingInterval } : {}),
52
+ url,
53
+ }),
54
+ ],
55
+ })
56
+ const session = wata.start()
57
+
58
+ // A failed prompt surface (e.g. browser open) fails the exchange — the
59
+ // user can never learn the code, so polling would only run out the clock.
60
+ session.onPrompt((prompt) => {
61
+ void (async () => onPrompt(prompt))().catch((error) => {
62
+ void session.close(new PromptError(prompt, error))
63
+ })
64
+ })
65
+
66
+ const timer = setTimeout(() => {
67
+ void session.close(new TimeoutError(session.prompt))
68
+ }, timeout)
69
+
70
+ try {
71
+ const { result } = await session.send({
72
+ method: request.method,
73
+ params: request.params ?? [],
74
+ ...(request.context ? { context: request.context } : {}),
75
+ })
76
+ return result
77
+ } catch (error) {
78
+ if (error instanceof DeviceCode.UserRejectedError)
79
+ throw new core_Provider.UserRejectedRequestError({
80
+ message: 'User denied the device-code request.',
81
+ })
82
+ if (error instanceof Transport.ClosedError)
83
+ throw new Error('Device code expired before authorization completed.', { cause: error })
84
+ throw error
85
+ } finally {
86
+ clearTimeout(timer)
87
+ }
88
+ },
89
+ })
90
+ }
91
+
92
+ export declare namespace deviceCode {
93
+ /** Options for {@link deviceCode}. */
94
+ export type Options = {
95
+ /** Adapter-specific action overrides layered over the request-forwarding defaults. */
96
+ actions?: fromRequest.Options['actions'] | undefined
97
+ /** Override the transport `fetch` implementation. */
98
+ fetch?: typeof globalThis.fetch | undefined
99
+ /** Data URI of the provider icon, announced via EIP-6963. */
100
+ icon?: Adapter.Meta['icon'] | undefined
101
+ /** Keystores backing locally generated access keys (SDK default otherwise). */
102
+ keystores?: Keystore.Keystores | undefined
103
+ /** Consumer metadata shown on the host's approval page. */
104
+ meta?:
105
+ | {
106
+ name: string
107
+ description?: string | undefined
108
+ icon?: string | undefined
109
+ websiteUrl?: string | undefined
110
+ }
111
+ | undefined
112
+ /** Allowlist of RPC methods forwarded over the transport. Forwards all when omitted. */
113
+ methods?: readonly string[] | undefined
114
+ /** Provider display name. */
115
+ name: string
116
+ /** Surfaces the pairing prompt (user code + verification URL) to the user. */
117
+ onPrompt: (prompt: DeviceCode.Prompt) => Promise<void> | void
118
+ /** Poll cadence override in milliseconds. Defaults to the host-advertised interval. */
119
+ pollingInterval?: number | undefined
120
+ /** Reverse-DNS provider identifier. */
121
+ rdns: string
122
+ /** Overall wait for user approval in milliseconds. @default 300000 */
123
+ timeout?: number | undefined
124
+ /** Base URL of the host's device-code endpoints (derives `/register` and `/token`). */
125
+ url: string
126
+ }
127
+ }
128
+
129
+ /** Thrown when the `onPrompt` callback fails (e.g. no browser could be opened). */
130
+ export class PromptError extends Error {
131
+ override cause?: unknown | undefined
132
+ prompt: DeviceCode.Prompt
133
+
134
+ constructor(prompt: DeviceCode.Prompt, cause?: unknown) {
135
+ super(
136
+ `Failed to surface device code ${prompt.userCode}. Open ${prompt.verificationUriFull ?? prompt.verificationUri} manually.`,
137
+ )
138
+ this.name = 'PromptError'
139
+ this.cause = cause
140
+ this.prompt = prompt
141
+ }
142
+ }
143
+
144
+ /** Thrown when the user did not approve within {@link deviceCode.Options.timeout}. */
145
+ export class TimeoutError extends Error {
146
+ prompt: DeviceCode.Prompt | undefined
147
+
148
+ constructor(prompt: DeviceCode.Prompt | undefined) {
149
+ super(
150
+ prompt
151
+ ? `Timed out waiting for device code ${prompt.userCode}. Continue at ${prompt.verificationUriFull ?? prompt.verificationUri}.`
152
+ : 'Timed out waiting for device code approval.',
153
+ )
154
+ this.name = 'TimeoutError'
155
+ this.prompt = prompt
156
+ }
157
+ }
@@ -0,0 +1,2 @@
1
+ export { PromptError, TimeoutError, deviceCode } from './deviceCode.js'
2
+ export { tempoWallet } from './tempoWallet.js'
@@ -0,0 +1,17 @@
1
+ import type * as Adapter from '../../Adapter.js'
2
+ import { deviceCode } from './deviceCode.js'
3
+
4
+ /** Creates the Tempo Wallet adapter using the Wata device-code transport. */
5
+ export function tempoWallet(options: tempoWallet.Options): Adapter.Adapter {
6
+ return deviceCode({
7
+ ...options,
8
+ name: 'Tempo Wallet',
9
+ rdns: 'xyz.tempo',
10
+ url: 'https://wallet.tempo.xyz/auth/device',
11
+ })
12
+ }
13
+
14
+ export declare namespace tempoWallet {
15
+ /** Options for {@link tempoWallet}. */
16
+ export type Options = Omit<deviceCode.Options, 'name' | 'rdns' | 'url'>
17
+ }
@@ -16,106 +16,133 @@ import * as Rpc from '../../zod/rpc.js'
16
16
  * `request`.
17
17
  */
18
18
  export function fromRequest(options: fromRequest.Options): Adapter.Adapter {
19
- const { bind, cleanup, close, icon, keystores, name, rdns, request: requestRemote } = options
19
+ const {
20
+ actions,
21
+ bind,
22
+ cleanup,
23
+ close,
24
+ icon,
25
+ keystores,
26
+ name,
27
+ rdns,
28
+ request: requestRemote,
29
+ } = options
20
30
 
21
- return Adapter.define({ ...(icon ? { icon } : {}), name, rdns }, ({ getAccount, store }) => {
22
- // Late-bind the store so the request channel can reconcile local
23
- // connection state (e.g. a wallet `accountsChanged` notification).
24
- bind?.({ store })
31
+ return Adapter.define(
32
+ { ...(icon ? { icon } : {}), name, rdns },
33
+ ({ getAccount, getClient, store }) => {
34
+ // Late-bind the store so the request channel can reconcile local
35
+ // connection state (e.g. a wallet `accountsChanged` notification).
36
+ bind?.({ store })
25
37
 
26
- const provider = core_Provider.from({
27
- async request(request) {
28
- const { accounts, activeAccount, chainId } = store.getState()
29
- if (request.method === 'eth_chainId') return Hex.fromNumber(chainId)
30
- // Forward the active account + chain as request context so the wallet
31
- // acts against the session the app believes it is using rather than
32
- // whatever the wallet last selected for itself.
33
- const account = accounts[activeAccount]?.address
34
- return await requestRemote({
35
- method: request.method,
36
- params: request.params as readonly unknown[] | undefined,
37
- context: { chainId, ...(account ? { account } : {}) },
38
- })
39
- },
40
- })
38
+ const provider = core_Provider.from({
39
+ async request(request) {
40
+ const { accounts, activeAccount, chainId } = store.getState()
41
+ if (request.method === 'eth_chainId') return Hex.fromNumber(chainId)
42
+ // Forward the active account + chain as request context so the wallet
43
+ // acts against the session the app believes it is using rather than
44
+ // whatever the wallet last selected for itself.
45
+ const account = accounts[activeAccount]?.address
46
+ return await requestRemote({
47
+ method: request.method,
48
+ params: request.params as readonly unknown[] | undefined,
49
+ context: { chainId, ...(account ? { account } : {}) },
50
+ })
51
+ },
52
+ })
41
53
 
42
- // Connect capabilities are read from the first account only: the remote
43
- // wallet connects a single account per exchange.
44
- function toConnectReturn(result: Rpc.wallet_connect.Encoded['returns']) {
45
- const capabilities = result.accounts[0]?.capabilities
46
- return {
47
- accounts: result.accounts.map((account) => ({ address: account.address })),
48
- ...(capabilities?.auth ? { auth: capabilities.auth } : {}),
49
- ...(capabilities?.identity ? { identity: capabilities.identity } : {}),
50
- ...(capabilities?.keyAuthorization
51
- ? { keyAuthorization: capabilities.keyAuthorization }
52
- : {}),
53
- ...(capabilities?.personalSign ? { personalSign: capabilities.personalSign } : {}),
54
- ...(capabilities?.signature ? { signature: capabilities.signature } : {}),
54
+ // Connect capabilities are read from the first account only: the remote
55
+ // wallet connects a single account per exchange.
56
+ function toConnectReturn(result: Rpc.wallet_connect.Encoded['returns']) {
57
+ const capabilities = result.accounts[0]?.capabilities
58
+ return {
59
+ accounts: result.accounts.map((account) => ({ address: account.address })),
60
+ ...(capabilities?.auth ? { auth: capabilities.auth } : {}),
61
+ ...(capabilities?.identity ? { identity: capabilities.identity } : {}),
62
+ ...(capabilities?.keyAuthorization
63
+ ? { keyAuthorization: capabilities.keyAuthorization }
64
+ : {}),
65
+ ...(capabilities?.personalSign ? { personalSign: capabilities.personalSign } : {}),
66
+ ...(capabilities?.signature ? { signature: capabilities.signature } : {}),
67
+ ...(capabilities?.username !== undefined ? { username: capabilities.username } : {}),
68
+ }
55
69
  }
56
- }
57
70
 
58
- return {
59
- forwardsAuth: true,
60
- ...(keystores ? { accessKey: { keystores } } : {}),
61
- actions: {
62
- async createAccount(_parameters, request) {
63
- return toConnectReturn(
64
- (await provider.request(request)) as Rpc.wallet_connect.Encoded['returns'],
65
- )
66
- },
67
- async loadAccounts(_parameters, request) {
68
- return toConnectReturn(
69
- (await provider.request(request)) as Rpc.wallet_connect.Encoded['returns'],
70
- )
71
- },
72
- async deposit(_parameters, request) {
73
- return (await provider.request(request)) as Rpc.wallet_deposit.Encoded['returns']
74
- },
75
- async depositZone(parameters, request) {
76
- return (await provider.request({
77
- ...request,
78
- params: [z.encode(Rpc.wallet_depositZone.parameters, parameters)] as const,
79
- })) as Rpc.wallet_depositZone.Encoded['returns']
80
- },
81
- // `disconnect` (when `close` is set) only tears down the local
82
- // wallet session — it is never forwarded. No `switchChain` action:
83
- // chain selection is local state the provider handles itself, and
84
- // forwarding it would open a wallet session for nothing.
85
- ...(close ? { disconnect: close } : {}),
86
- async swap(_parameters, request) {
87
- return (await provider.request(request)) as Rpc.wallet_swap.Encoded['returns']
88
- },
89
- async transfer(parameters, request) {
90
- return (await provider.request({
91
- ...request,
92
- params: [z.encode(Rpc.wallet_transfer.parameters, parameters)] as const,
93
- })) as Rpc.wallet_transfer.Encoded['returns']
71
+ return {
72
+ forwardsAuth: true,
73
+ ...(keystores ? { accessKey: { keystores } } : {}),
74
+ actions: {
75
+ async createAccount(_parameters, request) {
76
+ return toConnectReturn(
77
+ (await provider.request(request)) as Rpc.wallet_connect.Encoded['returns'],
78
+ )
79
+ },
80
+ async loadAccounts(_parameters, request) {
81
+ return toConnectReturn(
82
+ (await provider.request(request)) as Rpc.wallet_connect.Encoded['returns'],
83
+ )
84
+ },
85
+ async deposit(_parameters, request) {
86
+ return (await provider.request(request)) as Rpc.wallet_deposit.Encoded['returns']
87
+ },
88
+ async depositZone(parameters, request) {
89
+ return (await provider.request({
90
+ ...request,
91
+ params: [z.encode(Rpc.wallet_depositZone.parameters, parameters)] as const,
92
+ })) as Rpc.wallet_depositZone.Encoded['returns']
93
+ },
94
+ // `disconnect` (when `close` is set) only tears down the local
95
+ // wallet session — it is never forwarded. No `switchChain` action:
96
+ // chain selection is local state the provider handles itself, and
97
+ // forwarding it would open a wallet session for nothing.
98
+ ...(close ? { disconnect: close } : {}),
99
+ async swap(_parameters, request) {
100
+ return (await provider.request(request)) as Rpc.wallet_swap.Encoded['returns']
101
+ },
102
+ async transfer(parameters, request) {
103
+ return (await provider.request({
104
+ ...request,
105
+ params: [z.encode(Rpc.wallet_transfer.parameters, parameters)] as const,
106
+ })) as Rpc.wallet_transfer.Encoded['returns']
107
+ },
108
+ async withdrawZone(parameters, request) {
109
+ return (await provider.request({
110
+ ...request,
111
+ params: [z.encode(Rpc.wallet_withdrawZone.parameters, parameters)] as const,
112
+ })) as Rpc.wallet_withdrawZone.Encoded['returns']
113
+ },
114
+ ...actions?.({ getClient, request: requestRemote, store }),
94
115
  },
95
- async withdrawZone(parameters, request) {
96
- return (await provider.request({
97
- ...request,
98
- params: [z.encode(Rpc.wallet_withdrawZone.parameters, parameters)] as const,
99
- })) as Rpc.wallet_withdrawZone.Encoded['returns']
116
+ ...(cleanup ? { cleanup } : close ? { cleanup: () => void close() } : {}),
117
+ getAccount(parameters = {}) {
118
+ return {
119
+ account: {
120
+ address: parameters.address ?? getAccount({ signable: false }).address,
121
+ type: 'json-rpc' as const,
122
+ },
123
+ transport: custom(provider),
124
+ }
100
125
  },
101
- },
102
- ...(cleanup ? { cleanup } : close ? { cleanup: () => void close() } : {}),
103
- getAccount(parameters = {}) {
104
- return {
105
- account: {
106
- address: parameters.address ?? getAccount({ signable: false }).address,
107
- type: 'json-rpc' as const,
108
- },
109
- transport: custom(provider),
110
- }
111
- },
112
- }
113
- })
126
+ }
127
+ },
128
+ )
114
129
  }
115
130
 
116
131
  export declare namespace fromRequest {
117
132
  /** Options for {@link fromRequest}. */
118
133
  export type Options = {
134
+ /**
135
+ * Adapter-specific action overrides layered over the request-forwarding
136
+ * defaults. Receives the request channel plus the adapter's client and
137
+ * store for actions that need local bookkeeping around an exchange.
138
+ */
139
+ actions?:
140
+ | ((context: {
141
+ getClient: Adapter.SetupFn.Parameters['getClient']
142
+ request: Options['request']
143
+ store: Adapter.SetupFn.Parameters['store']
144
+ }) => Partial<Adapter.Instance['actions']>)
145
+ | undefined
119
146
  /**
120
147
  * Called once during setup with the adapter's store, for late wiring
121
148
  * that needs to mutate local state (e.g. reconciling connection state