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,262 @@
1
+ import { Hex } from 'ox'
2
+ import * as Provider from 'ox/Provider'
3
+ import * as RpcResponse from 'ox/RpcResponse'
4
+ import { useStore } from 'zustand'
5
+ import type { StoreApi } from 'zustand/vanilla'
6
+ import { createStore } from 'zustand/vanilla'
7
+
8
+ import type * as Messenger from '../core/Messenger.js'
9
+ import type * as CoreProvider from '../core/Provider.js'
10
+ import type * as Store from '../core/Store.js'
11
+
12
+ /** State managed by the remote (dialog) side. */
13
+ export type State = {
14
+ /** Whether the dialog is rendered in an iframe or popup. */
15
+ mode: 'iframe' | 'popup' | undefined
16
+ /** Trusted host origin from MessageEvent. */
17
+ origin: string | undefined
18
+ /** Whether the dialog is ready to display content. */
19
+ ready: boolean
20
+ /** Queued RPC requests received from the host. */
21
+ requests: readonly Store.QueuedRequest[]
22
+ }
23
+
24
+ /** Remote context — bundles messenger, provider, and remote store. */
25
+ export type Remote = {
26
+ /**
27
+ * Messenger for remote communication.
28
+ */
29
+ messenger: Messenger.Bridge
30
+ /**
31
+ * Provider instance for executing RPC methods.
32
+ */
33
+ provider: CoreProvider.Provider
34
+ /**
35
+ * Remote context store.
36
+ */
37
+ store: StoreApi<State>
38
+ /**
39
+ * Subscribes to user-facing RPC requests from the parent context.
40
+ *
41
+ * Syncs the host's active chain, updates the remote store, and invokes
42
+ * the callback with the first pending request (or `null` when the queue
43
+ * is cleared, signalling the UI should close).
44
+ *
45
+ * @param cb - Callback receiving the request payload.
46
+ * @returns Unsubscribe function.
47
+ */
48
+ onUserRequest: (cb: (payload: onUserRequest.Payload) => void | Promise<void>) => () => void
49
+ /**
50
+ * Subscribes to incoming RPC requests from the parent context.
51
+ * Updates the remote store with the received requests and syncs the
52
+ * host's active chain to the remote provider.
53
+ *
54
+ * @param cb - Callback receiving the full queued request list.
55
+ * @returns Unsubscribe function.
56
+ */
57
+ onRequests: (
58
+ cb: (
59
+ requests: readonly Store.QueuedRequest[],
60
+ event: MessageEvent,
61
+ extra: { account: { address: string } | undefined },
62
+ ) => void,
63
+ ) => () => void
64
+ /**
65
+ * Signals readiness to the host and begins accepting requests.
66
+ * Call this after the remote context is fully initialized.
67
+ */
68
+ ready: () => void
69
+ /**
70
+ * Reject an RPC request.
71
+ */
72
+ reject: (
73
+ request: Store.QueuedRequest['request'],
74
+ error?: Provider.ProviderRpcError | RpcResponse.BaseError | undefined,
75
+ ) => void
76
+ /** Reject all pending RPC requests. */
77
+ rejectAll: (error?: Provider.ProviderRpcError | RpcResponse.BaseError | undefined) => void
78
+ /**
79
+ * Respond to an RPC request.
80
+ *
81
+ * When `options.result` is provided, sends it directly.
82
+ * When `options.error` is provided, sends an error response.
83
+ * Otherwise, executes `provider.request(request)` and sends the result.
84
+ */
85
+ respond: (request: Store.QueuedRequest['request'], options?: respond.Options) => Promise<unknown>
86
+ }
87
+
88
+ export declare namespace onUserRequest {
89
+ type Payload = {
90
+ /** Active account on the host side. */
91
+ account: { address: string } | undefined
92
+ /** Origin of the host that opened this dialog. */
93
+ origin: string
94
+ /** The pending request to display, or `null` when the dialog should close. */
95
+ request: Store.QueuedRequest['request'] | null
96
+ }
97
+ }
98
+
99
+ export declare namespace respond {
100
+ type Options = {
101
+ /** Error to respond with (takes precedence over result). */
102
+ error?: { code: number; message: string } | undefined
103
+ /** Explicit result — if omitted, calls `provider.request(request)`. */
104
+ result?: unknown | undefined
105
+ /** Transform the result before sending. */
106
+ selector?: ((result: any) => unknown) | undefined
107
+ }
108
+ }
109
+
110
+ /** Creates a remote context for the dialog app. */
111
+ export function create(options: create.Options): Remote {
112
+ const { messenger, provider } = options
113
+ const ready =
114
+ typeof window !== 'undefined' && !new URLSearchParams(window.location.search).get('mode')
115
+ const store = createStore<State>(() => ({
116
+ mode: undefined,
117
+ origin: undefined,
118
+ ready,
119
+ requests: [],
120
+ }))
121
+
122
+ return {
123
+ messenger,
124
+ provider,
125
+ store,
126
+
127
+ onUserRequest(cb) {
128
+ return this.onRequests(async (requests, event, { account }) => {
129
+ // Sync the active account with the host.
130
+ if (account) {
131
+ const state = provider.store.getState()
132
+ const index = state.accounts.findIndex(
133
+ (a) => a.address.toLowerCase() === account.address.toLowerCase(),
134
+ )
135
+ if (index >= 0 && index !== state.activeAccount)
136
+ provider.store.setState({ activeAccount: index })
137
+ }
138
+
139
+ const pending = requests.find((r) => r.status === 'pending')
140
+ store.setState({
141
+ origin: event.origin,
142
+ ready: false,
143
+ })
144
+ await cb({
145
+ account,
146
+ origin: event.origin,
147
+ request: pending?.request ?? null,
148
+ })
149
+ if (pending) store.setState({ ready: true })
150
+ })
151
+ },
152
+
153
+ onRequests(cb) {
154
+ return messenger.on('rpc-requests', async (payload, event) => {
155
+ const { account, chainId, requests } = payload
156
+
157
+ // Rehydrate persisted state so the iframe picks up accounts
158
+ // created in a popup (e.g. Safari WebAuthn fallback).
159
+ await provider.store.persist?.rehydrate()
160
+
161
+ store.setState({ requests })
162
+
163
+ if (provider.store.getState().chainId !== chainId)
164
+ provider.request({
165
+ method: 'wallet_switchEthereumChain',
166
+ params: [{ chainId: Hex.fromNumber(chainId) }],
167
+ })
168
+
169
+ cb(requests, event, { account })
170
+ })
171
+ },
172
+
173
+ ready() {
174
+ messenger.ready()
175
+
176
+ if (typeof window !== 'undefined') {
177
+ const params = new URLSearchParams(window.location.search)
178
+ const mode = params.get('mode') as State['mode']
179
+ const chainId = Number(params.get('chainId'))
180
+
181
+ if (mode) store.setState({ mode })
182
+
183
+ if (chainId && provider.store.getState().chainId !== chainId)
184
+ provider.request({
185
+ method: 'wallet_switchEthereumChain',
186
+ params: [{ chainId: Hex.fromNumber(chainId) }],
187
+ })
188
+ }
189
+ },
190
+
191
+ reject(request, error) {
192
+ const error_ = error ?? new Provider.UserRejectedRequestError()
193
+ messenger.send(
194
+ 'rpc-response',
195
+ Object.assign(
196
+ RpcResponse.from({
197
+ error: { code: error_.code, message: error_.message },
198
+ id: request.id,
199
+ jsonrpc: '2.0',
200
+ }),
201
+ { _request: request },
202
+ ),
203
+ )
204
+ },
205
+
206
+ rejectAll(error) {
207
+ store.setState({ ready: false })
208
+ const requests = store.getState().requests
209
+ for (const queued of requests) this.reject(queued.request, error)
210
+ },
211
+
212
+ async respond(request, options = {}) {
213
+ const { error, selector } = options
214
+ const shared = { id: request.id, jsonrpc: '2.0' } as const
215
+
216
+ if (error) {
217
+ messenger.send(
218
+ 'rpc-response',
219
+ Object.assign(RpcResponse.from({ ...shared, error, status: 'error' }), {
220
+ _request: request,
221
+ }),
222
+ )
223
+ return
224
+ }
225
+
226
+ try {
227
+ let result = options.result ?? (await provider?.request(request as never))
228
+ if (selector) result = selector(result)
229
+ messenger.send(
230
+ 'rpc-response',
231
+ Object.assign(RpcResponse.from({ ...shared, result }), { _request: request }),
232
+ )
233
+ return result
234
+ } catch (e) {
235
+ const err = e as RpcResponse.BaseError
236
+ messenger.send(
237
+ 'rpc-response',
238
+ Object.assign(RpcResponse.from({ ...shared, error: err, status: 'error' }), {
239
+ _request: request,
240
+ }),
241
+ )
242
+ throw err
243
+ }
244
+ },
245
+ }
246
+ }
247
+
248
+ export declare namespace create {
249
+ type Options = {
250
+ /** Bridge messenger for cross-frame communication. */
251
+ messenger: Messenger.Bridge
252
+ /** Provider to execute RPC requests against. */
253
+ provider: CoreProvider.Provider
254
+ }
255
+ }
256
+
257
+ /** React hook to select state from a remote context's store. */
258
+ export function useState(remote: Remote): State
259
+ export function useState<selected>(remote: Remote, selector: (state: State) => selected): selected
260
+ export function useState(remote: Remote, selector?: (state: State) => unknown) {
261
+ return useStore(remote.store, selector as never)
262
+ }
@@ -0,0 +1,211 @@
1
+ import type { RpcSchema } from 'ox'
2
+ import type { Hex } from 'viem'
3
+ import { describe, expectTypeOf, test } from 'vp/test'
4
+
5
+ import type * as Schema from './Schema.js'
6
+ import type * as Rpc from './zod/rpc.js'
7
+
8
+ describe('Encoded', () => {
9
+ test('eth_accounts', () => {
10
+ expectTypeOf<Rpc.eth_accounts.Encoded>().toEqualTypeOf<{
11
+ method: 'eth_accounts'
12
+ params: undefined
13
+ returns: readonly Hex[]
14
+ }>()
15
+ })
16
+
17
+ test('eth_chainId', () => {
18
+ expectTypeOf<Rpc.eth_chainId.Encoded>().toEqualTypeOf<{
19
+ method: 'eth_chainId'
20
+ params: undefined
21
+ returns: Hex
22
+ }>()
23
+ })
24
+
25
+ test('eth_requestAccounts', () => {
26
+ expectTypeOf<Rpc.eth_requestAccounts.Encoded>().toEqualTypeOf<{
27
+ method: 'eth_requestAccounts'
28
+ params: undefined
29
+ returns: readonly Hex[]
30
+ }>()
31
+ })
32
+
33
+ test('eth_sendTransaction', () => {
34
+ expectTypeOf<Rpc.eth_sendTransaction.Encoded>().toMatchTypeOf<{
35
+ method: 'eth_sendTransaction'
36
+ params: readonly [
37
+ {
38
+ accessList?: { address: Hex; storageKeys: Hex[] }[] | undefined
39
+ calls?: readonly { data?: Hex | undefined; to?: Hex | undefined }[] | undefined
40
+ chainId?: Hex | undefined
41
+ feePayer?: boolean | string | undefined
42
+ feeToken?: Hex | undefined
43
+ from?: Hex | undefined
44
+ gas?: Hex | undefined
45
+ maxFeePerGas?: Hex | undefined
46
+ maxPriorityFeePerGas?: Hex | undefined
47
+ nonce?: Hex | undefined
48
+ nonceKey?: Hex | undefined
49
+ validAfter?: Hex | undefined
50
+ validBefore?: Hex | undefined
51
+ value?: Hex | undefined
52
+ },
53
+ ]
54
+ returns: Hex
55
+ }>()
56
+ })
57
+
58
+ test('wallet_connect: params', () => {
59
+ expectTypeOf<Rpc.wallet_connect.Encoded['params']>().toExtend<
60
+ | readonly [
61
+ {
62
+ capabilities?:
63
+ | {
64
+ digest?: Hex | undefined
65
+ authorizeAccessKey?:
66
+ | {
67
+ address?: Hex | undefined
68
+ expiry: number
69
+ keyType?: 'secp256k1' | 'p256' | 'webAuthn' | undefined
70
+ limits?: readonly { token: Hex; limit: Hex }[] | undefined
71
+ publicKey?: Hex | undefined
72
+ }
73
+ | undefined
74
+ method: 'register'
75
+ name?: string | undefined
76
+ userId?: string | undefined
77
+ }
78
+ | {
79
+ digest?: Hex | undefined
80
+ credentialId?: string | undefined
81
+ authorizeAccessKey?:
82
+ | {
83
+ address?: Hex | undefined
84
+ expiry: number
85
+ keyType?: 'secp256k1' | 'p256' | 'webAuthn' | undefined
86
+ limits?: readonly { token: Hex; limit: Hex }[] | undefined
87
+ publicKey?: Hex | undefined
88
+ }
89
+ | undefined
90
+ method?: 'login' | undefined
91
+ selectAccount?: boolean | undefined
92
+ }
93
+ | undefined
94
+ version?: string | undefined
95
+ },
96
+ ]
97
+ | undefined
98
+ >()
99
+ })
100
+
101
+ test('wallet_connect: returns', () => {
102
+ expectTypeOf<Rpc.wallet_connect.Encoded['returns']>().toHaveProperty('accounts')
103
+ expectTypeOf<
104
+ Rpc.wallet_connect.Encoded['returns']['accounts'][number]['address']
105
+ >().toEqualTypeOf<Hex>()
106
+ })
107
+
108
+ test('wallet_authorizeAccessKey', () => {
109
+ expectTypeOf<Rpc.wallet_authorizeAccessKey.Encoded>().toMatchTypeOf<{
110
+ method: 'wallet_authorizeAccessKey'
111
+ params: readonly [
112
+ {
113
+ address?: Hex | undefined
114
+ expiry: number
115
+ keyType?: 'secp256k1' | 'p256' | 'webAuthn' | undefined
116
+ limits?: readonly { token: Hex; limit: Hex }[] | undefined
117
+ publicKey?: Hex | undefined
118
+ },
119
+ ]
120
+ }>()
121
+ })
122
+
123
+ test('wallet_disconnect', () => {
124
+ expectTypeOf<Rpc.wallet_disconnect.Encoded>().toEqualTypeOf<{
125
+ method: 'wallet_disconnect'
126
+ params: undefined
127
+ returns: undefined
128
+ }>()
129
+ })
130
+
131
+ test('wallet_switchEthereumChain', () => {
132
+ expectTypeOf<Rpc.wallet_switchEthereumChain.Encoded>().toEqualTypeOf<{
133
+ method: 'wallet_switchEthereumChain'
134
+ params: readonly [{ chainId: Hex }]
135
+ returns: undefined
136
+ }>()
137
+ })
138
+ })
139
+
140
+ describe('ToOx', () => {
141
+ test('produces RpcSchema.Generic members', () => {
142
+ type OxSchema = Schema.ToOx<typeof Schema.schema>
143
+ expectTypeOf<OxSchema>().toMatchTypeOf<RpcSchema.Generic>()
144
+ })
145
+ })
146
+
147
+ describe('ToViem', () => {
148
+ test('produces tuple with Method/Parameters/ReturnType', () => {
149
+ type ViemSchema = Schema.ToViem<typeof Schema.schema>
150
+ expectTypeOf<ViemSchema[0]['Method']>().toEqualTypeOf<'eth_accounts'>()
151
+ expectTypeOf<ViemSchema[0]['Parameters']>().toEqualTypeOf<undefined>()
152
+ expectTypeOf<ViemSchema[0]['ReturnType']>().toEqualTypeOf<readonly Hex[]>()
153
+ })
154
+ })
155
+
156
+ describe('Ox', () => {
157
+ test('includes RpcSchema.Eth', () => {
158
+ expectTypeOf<RpcSchema.Eth>().toMatchTypeOf<Schema.Ox>()
159
+ })
160
+
161
+ test('includes provider methods', () => {
162
+ expectTypeOf<Schema.ToOx<typeof Schema.schema>>().toMatchTypeOf<Schema.Ox>()
163
+ })
164
+ })
165
+
166
+ describe('Viem', () => {
167
+ test('is a tuple of all provider methods', () => {
168
+ expectTypeOf<Schema.Viem[0]['Method']>().toEqualTypeOf<'eth_accounts'>()
169
+ expectTypeOf<Schema.Viem[16]['Method']>().toEqualTypeOf<'wallet_switchEthereumChain'>()
170
+ })
171
+ })
172
+
173
+ describe('Request', () => {
174
+ test('is a discriminated union of all methods', () => {
175
+ type Methods = Schema.Request['method']
176
+ expectTypeOf<Methods>().toEqualTypeOf<
177
+ | 'eth_accounts'
178
+ | 'eth_chainId'
179
+ | 'eth_requestAccounts'
180
+ | 'eth_sendTransaction'
181
+ | 'eth_signTransaction'
182
+ | 'eth_sendTransactionSync'
183
+ | 'eth_signTypedData_v4'
184
+ | 'personal_sign'
185
+ | 'wallet_sendCalls'
186
+ | 'wallet_getBalances'
187
+ | 'wallet_getCallsStatus'
188
+ | 'wallet_getCapabilities'
189
+ | 'wallet_connect'
190
+ | 'wallet_disconnect'
191
+ | 'wallet_authorizeAccessKey'
192
+ | 'wallet_revokeAccessKey'
193
+ | 'wallet_switchEthereumChain'
194
+ >()
195
+ })
196
+
197
+ test('wallet_switchEthereumChain has decoded params', () => {
198
+ type SwitchChain = Extract<Schema.Request, { method: 'wallet_switchEthereumChain' }>
199
+ expectTypeOf<SwitchChain['params']>().toEqualTypeOf<readonly [{ chainId: number }]>()
200
+ })
201
+
202
+ test('eth_accounts has no params', () => {
203
+ type EthAccounts = Extract<Schema.Request, { method: 'eth_accounts' }>
204
+ expectTypeOf<EthAccounts>().not.toHaveProperty('params')
205
+ })
206
+
207
+ test('wallet_connect params are optional', () => {
208
+ type WalletConnect = Extract<Schema.Request, { method: 'wallet_connect' }>
209
+ expectTypeOf<WalletConnect>().toMatchTypeOf<{ method: 'wallet_connect' }>()
210
+ })
211
+ })
@@ -0,0 +1,143 @@
1
+ import { RpcSchema } from 'ox'
2
+ import { rpcSchema } from 'viem'
3
+ import * as z from 'zod/mini'
4
+
5
+ import * as Rpc from './zod/rpc.js'
6
+
7
+ /** All provider-handled RPC method definitions. */
8
+ export const schema = from([
9
+ Rpc.eth_accounts.schema,
10
+ Rpc.eth_chainId.schema,
11
+ Rpc.eth_requestAccounts.schema,
12
+ Rpc.eth_sendTransaction.schema,
13
+ Rpc.eth_signTransaction.schema,
14
+ Rpc.eth_sendTransactionSync.schema,
15
+ Rpc.eth_signTypedData_v4.schema,
16
+ Rpc.personal_sign.schema,
17
+ Rpc.wallet_sendCalls.schema,
18
+ Rpc.wallet_getBalances.schema,
19
+ Rpc.wallet_getCallsStatus.schema,
20
+ Rpc.wallet_getCapabilities.schema,
21
+ Rpc.wallet_connect.schema,
22
+ Rpc.wallet_disconnect.schema,
23
+ Rpc.wallet_authorizeAccessKey.schema,
24
+ Rpc.wallet_revokeAccessKey.schema,
25
+ Rpc.wallet_switchEthereumChain.schema,
26
+ ])
27
+
28
+ /**
29
+ * A single JSON-RPC method definition with Zod schemas for
30
+ * the method name, parameters, and return type.
31
+ */
32
+ export type Item = {
33
+ /** Method name as a Zod literal. */
34
+ method: z.ZodMiniLiteral<string>
35
+ /** Parameters schema, or `undefined` if the method takes no params. */
36
+ params: z.ZodMiniType | undefined
37
+ /** Return type schema, or `undefined` if the method returns nothing. */
38
+ returns: z.ZodMiniType | undefined
39
+ }
40
+
41
+ /** An array of JSON-RPC method definitions. */
42
+ export type Schema = readonly Item[]
43
+
44
+ /** Inferred wire-format type for a schema item — raw JSON-RPC `{ method, params, returns }`. */
45
+ export type Encoded<item extends Item> = {
46
+ method: z.input<item['method']>
47
+ params: item['params'] extends z.ZodMiniType ? z.input<item['params']> : undefined
48
+ returns: item['returns'] extends z.ZodMiniType ? z.input<item['returns']> : undefined
49
+ }
50
+
51
+ /** Inferred decoded type for a schema item — after codec transforms are applied. */
52
+ export type Decoded<item extends Item> = {
53
+ method: z.input<item['method']>
54
+ params: item['params'] extends z.ZodMiniType ? z.output<item['params']> : undefined
55
+ returns: item['returns'] extends z.ZodMiniType ? z.output<item['returns']> : undefined
56
+ }
57
+
58
+ /**
59
+ * Transforms a {@link Schema} into an Ox-compatible `RpcSchema.Generic` union.
60
+ *
61
+ * Uses `z.input` (the wire/encoded form — hex strings) since Ox operates
62
+ * on the raw JSON-RPC wire format.
63
+ */
64
+ export type ToOx<schema extends Schema> = {
65
+ [key in keyof schema]: RpcSchema.From<{
66
+ Request: schema[key]['params'] extends z.ZodMiniType
67
+ ? undefined extends z.input<schema[key]['params']>
68
+ ? { method: z.input<schema[key]['method']>; params?: z.input<schema[key]['params']> }
69
+ : { method: z.input<schema[key]['method']>; params: z.input<schema[key]['params']> }
70
+ : { method: z.input<schema[key]['method']> }
71
+ ReturnType: schema[key]['returns'] extends z.ZodMiniType
72
+ ? z.input<schema[key]['returns']>
73
+ : undefined
74
+ }>
75
+ }[number]
76
+
77
+ /**
78
+ * Transforms a {@link Schema} into a Viem-compatible `RpcSchema` tuple.
79
+ *
80
+ * Uses `z.input` (the wire/encoded form — hex strings) since Viem's
81
+ * RPC schema types operate on the raw JSON-RPC wire format.
82
+ */
83
+ export type ToViem<schema extends Schema> = {
84
+ [key in keyof schema]: {
85
+ Method: z.input<schema[key]['method']>
86
+ Parameters: schema[key]['params'] extends z.ZodMiniType
87
+ ? z.input<schema[key]['params']>
88
+ : undefined
89
+ ReturnType: schema[key]['returns'] extends z.ZodMiniType
90
+ ? z.input<schema[key]['returns']>
91
+ : undefined
92
+ }
93
+ }
94
+
95
+ /** Ox-compatible RPC schema union for the provider. */
96
+ export type Ox = RpcSchema.Eth | ToOx<typeof schema>
97
+ export const ox = RpcSchema.from<Ox>()
98
+
99
+ /** Viem-compatible RPC schema tuple for the provider. */
100
+ export type Viem = ToViem<typeof schema>
101
+ export const viem = rpcSchema<Viem>()
102
+
103
+ /** Derives a `z.object({ method, params? })` from an {@link Item}. */
104
+ type ToRequestSchema<item extends Item> = item['params'] extends z.ZodMiniType
105
+ ? ReturnType<typeof z.object<{ method: item['method']; params: item['params'] }>>
106
+ : ReturnType<typeof z.object<{ method: item['method'] }>>
107
+
108
+ /** Builds a request `z.object` from a schema item at runtime. */
109
+ function toRequestSchema<const item extends Item>(item: item): ToRequestSchema<item> {
110
+ if (item.params) return z.object({ method: item.method, params: item.params }) as never
111
+ return z.object({ method: item.method }) as never
112
+ }
113
+
114
+ /** Derives a union of wire-format request shapes from a {@link Schema}. */
115
+ type ToRequestInput<schema extends Schema> = {
116
+ [key in keyof schema]: schema[key]['params'] extends z.ZodMiniType
117
+ ? { method: z.input<schema[key]['method']>; params: z.input<schema[key]['params']> }
118
+ : { method: z.input<schema[key]['method']> }
119
+ }[number]
120
+
121
+ /** Derives a union of decoded request shapes from a {@link Schema}. */
122
+ type ToRequestOutput<schema extends Schema> = {
123
+ [key in keyof schema]: schema[key]['params'] extends z.ZodMiniType
124
+ ? { method: z.output<schema[key]['method']>; params: z.output<schema[key]['params']> }
125
+ : { method: z.output<schema[key]['method']> }
126
+ }[number]
127
+
128
+ /** Discriminated union of all provider-handled RPC requests. */
129
+ export const Request: z.ZodMiniType<
130
+ ToRequestOutput<typeof schema>,
131
+ ToRequestInput<typeof schema>
132
+ > = z.discriminatedUnion('method', schema.map(toRequestSchema) as never)
133
+ export type Request = ToRequestOutput<typeof schema>
134
+
135
+ /** Defines a JSON-RPC method schema item. */
136
+ export function defineItem<const item extends Item>(item: item): item {
137
+ return item
138
+ }
139
+
140
+ /** Creates a {@link Schema}. */
141
+ export function from<const schema extends Schema>(schema: schema): schema {
142
+ return schema
143
+ }