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.
- package/CHANGELOG.md +13 -0
- package/LICENSE +21 -0
- package/README.md +97 -0
- package/dist/core/AccessKey.d.ts +55 -0
- package/dist/core/AccessKey.d.ts.map +1 -0
- package/dist/core/AccessKey.js +69 -0
- package/dist/core/AccessKey.js.map +1 -0
- package/dist/core/Account.d.ts +91 -0
- package/dist/core/Account.d.ts.map +1 -0
- package/dist/core/Account.js +64 -0
- package/dist/core/Account.js.map +1 -0
- package/dist/core/Adapter.d.ts +187 -0
- package/dist/core/Adapter.d.ts.map +1 -0
- package/dist/core/Adapter.js +7 -0
- package/dist/core/Adapter.js.map +1 -0
- package/dist/core/Ceremony.d.ts +109 -0
- package/dist/core/Ceremony.d.ts.map +1 -0
- package/dist/core/Ceremony.js +104 -0
- package/dist/core/Ceremony.js.map +1 -0
- package/dist/core/Client.d.ts +16 -0
- package/dist/core/Client.d.ts.map +1 -0
- package/dist/core/Client.js +18 -0
- package/dist/core/Client.js.map +1 -0
- package/dist/core/Dialog.d.ts +52 -0
- package/dist/core/Dialog.d.ts.map +1 -0
- package/dist/core/Dialog.js +342 -0
- package/dist/core/Dialog.js.map +1 -0
- package/dist/core/Expiry.d.ts +15 -0
- package/dist/core/Expiry.d.ts.map +1 -0
- package/dist/core/Expiry.js +29 -0
- package/dist/core/Expiry.js.map +1 -0
- package/dist/core/Messenger.d.ts +86 -0
- package/dist/core/Messenger.d.ts.map +1 -0
- package/dist/core/Messenger.js +127 -0
- package/dist/core/Messenger.js.map +1 -0
- package/dist/core/Provider.d.ts +69 -0
- package/dist/core/Provider.d.ts.map +1 -0
- package/dist/core/Provider.js +401 -0
- package/dist/core/Provider.js.map +1 -0
- package/dist/core/Remote.d.ts +114 -0
- package/dist/core/Remote.d.ts.map +1 -0
- package/dist/core/Remote.js +116 -0
- package/dist/core/Remote.js.map +1 -0
- package/dist/core/Schema.d.ts +805 -0
- package/dist/core/Schema.d.ts.map +1 -0
- package/dist/core/Schema.js +43 -0
- package/dist/core/Schema.js.map +1 -0
- package/dist/core/Storage.d.ts +42 -0
- package/dist/core/Storage.d.ts.map +1 -0
- package/dist/core/Storage.js +173 -0
- package/dist/core/Storage.js.map +1 -0
- package/dist/core/Store.d.ts +58 -0
- package/dist/core/Store.d.ts.map +1 -0
- package/dist/core/Store.js +58 -0
- package/dist/core/Store.js.map +1 -0
- package/dist/core/adapters/dangerous_secp256k1.d.ts +30 -0
- package/dist/core/adapters/dangerous_secp256k1.d.ts.map +1 -0
- package/dist/core/adapters/dangerous_secp256k1.js +39 -0
- package/dist/core/adapters/dangerous_secp256k1.js.map +1 -0
- package/dist/core/adapters/dialog.d.ts +31 -0
- package/dist/core/adapters/dialog.d.ts.map +1 -0
- package/dist/core/adapters/dialog.js +306 -0
- package/dist/core/adapters/dialog.js.map +1 -0
- package/dist/core/adapters/local.d.ts +33 -0
- package/dist/core/adapters/local.d.ts.map +1 -0
- package/dist/core/adapters/local.js +227 -0
- package/dist/core/adapters/local.js.map +1 -0
- package/dist/core/adapters/webAuthn.d.ts +36 -0
- package/dist/core/adapters/webAuthn.d.ts.map +1 -0
- package/dist/core/adapters/webAuthn.js +93 -0
- package/dist/core/adapters/webAuthn.js.map +1 -0
- package/dist/core/internal/withDedupe.d.ts +12 -0
- package/dist/core/internal/withDedupe.d.ts.map +1 -0
- package/dist/core/internal/withDedupe.js +12 -0
- package/dist/core/internal/withDedupe.js.map +1 -0
- package/dist/core/zod/request.d.ts +31 -0
- package/dist/core/zod/request.d.ts.map +1 -0
- package/dist/core/zod/request.js +41 -0
- package/dist/core/zod/request.js.map +1 -0
- package/dist/core/zod/rpc.d.ts +603 -0
- package/dist/core/zod/rpc.d.ts.map +1 -0
- package/dist/core/zod/rpc.js +293 -0
- package/dist/core/zod/rpc.js.map +1 -0
- package/dist/core/zod/utils.d.ts +18 -0
- package/dist/core/zod/utils.d.ts.map +1 -0
- package/dist/core/zod/utils.js +21 -0
- package/dist/core/zod/utils.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/types.d.ts +284 -0
- package/dist/internal/types.d.ts.map +1 -0
- package/dist/internal/types.js +2 -0
- package/dist/internal/types.js.map +1 -0
- package/dist/server/Handler.d.ts +257 -0
- package/dist/server/Handler.d.ts.map +1 -0
- package/dist/server/Handler.js +433 -0
- package/dist/server/Handler.js.map +1 -0
- package/dist/server/Kv.d.ts +16 -0
- package/dist/server/Kv.d.ts.map +1 -0
- package/dist/server/Kv.js +30 -0
- package/dist/server/Kv.js.map +1 -0
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +3 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/internal/requestListener.d.ts +124 -0
- package/dist/server/internal/requestListener.d.ts.map +1 -0
- package/dist/server/internal/requestListener.js +173 -0
- package/dist/server/internal/requestListener.js.map +1 -0
- package/dist/wagmi/Connector.d.ts +93 -0
- package/dist/wagmi/Connector.d.ts.map +1 -0
- package/dist/wagmi/Connector.js +238 -0
- package/dist/wagmi/Connector.js.map +1 -0
- package/dist/wagmi/index.d.ts +3 -0
- package/dist/wagmi/index.d.ts.map +1 -0
- package/dist/wagmi/index.js +3 -0
- package/dist/wagmi/index.js.map +1 -0
- package/package.json +56 -2
- package/src/core/AccessKey.test.ts +257 -0
- package/src/core/AccessKey.ts +123 -0
- package/src/core/Account.test.ts +309 -0
- package/src/core/Account.ts +152 -0
- package/src/core/Adapter.ts +238 -0
- package/src/core/Ceremony.browser.test.ts +239 -0
- package/src/core/Ceremony.test.ts +151 -0
- package/src/core/Ceremony.ts +203 -0
- package/src/core/Client.ts +36 -0
- package/src/core/Dialog.browser.test.ts +309 -0
- package/src/core/Dialog.test-d.ts +19 -0
- package/src/core/Dialog.ts +442 -0
- package/src/core/Expiry.ts +34 -0
- package/src/core/Messenger.ts +206 -0
- package/src/core/Provider.browser.test.ts +774 -0
- package/src/core/Provider.connect.browser.test.ts +415 -0
- package/src/core/Provider.test-d.ts +53 -0
- package/src/core/Provider.test.ts +1566 -0
- package/src/core/Provider.ts +559 -0
- package/src/core/Remote.ts +262 -0
- package/src/core/Schema.test-d.ts +211 -0
- package/src/core/Schema.ts +143 -0
- package/src/core/Storage.ts +213 -0
- package/src/core/Store.test.ts +287 -0
- package/src/core/Store.ts +129 -0
- package/src/core/adapters/dangerous_secp256k1.ts +53 -0
- package/src/core/adapters/dialog.ts +379 -0
- package/src/core/adapters/local.test.ts +97 -0
- package/src/core/adapters/local.ts +277 -0
- package/src/core/adapters/webAuthn.ts +129 -0
- package/src/core/internal/withDedupe.test.ts +116 -0
- package/src/core/internal/withDedupe.ts +20 -0
- package/src/core/mppx.test.ts +83 -0
- package/src/core/zod/request.test.ts +121 -0
- package/src/core/zod/request.ts +70 -0
- package/src/core/zod/rpc.ts +374 -0
- package/src/core/zod/utils.test.ts +69 -0
- package/src/core/zod/utils.ts +40 -0
- package/src/index.ts +14 -0
- package/src/internal/types.ts +378 -0
- package/src/server/Handler.test.ts +1014 -0
- package/src/server/Handler.ts +605 -0
- package/src/server/Kv.ts +46 -0
- package/src/server/index.ts +2 -0
- package/src/server/internal/requestListener.ts +273 -0
- package/src/tsconfig.json +9 -0
- package/src/wagmi/Connector.ts +287 -0
- 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
|
+
}
|