@joeywallet/wallet-sdk 0.2.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 (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +705 -0
  3. package/dist/client.d.ts +55 -0
  4. package/dist/client.d.ts.map +1 -0
  5. package/dist/client.js +224 -0
  6. package/dist/client.js.map +1 -0
  7. package/dist/detect.d.ts +45 -0
  8. package/dist/detect.d.ts.map +1 -0
  9. package/dist/detect.js +238 -0
  10. package/dist/detect.js.map +1 -0
  11. package/dist/errors.d.ts +75 -0
  12. package/dist/errors.d.ts.map +1 -0
  13. package/dist/errors.js +120 -0
  14. package/dist/errors.js.map +1 -0
  15. package/dist/index.d.ts +13 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +13 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/mutation.d.ts +46 -0
  20. package/dist/mutation.d.ts.map +1 -0
  21. package/dist/mutation.js +67 -0
  22. package/dist/mutation.js.map +1 -0
  23. package/dist/provider.d.ts +159 -0
  24. package/dist/provider.d.ts.map +1 -0
  25. package/dist/provider.js +142 -0
  26. package/dist/provider.js.map +1 -0
  27. package/dist/react.d.ts +76 -0
  28. package/dist/react.d.ts.map +1 -0
  29. package/dist/react.js +225 -0
  30. package/dist/react.js.map +1 -0
  31. package/dist/types.d.ts +391 -0
  32. package/dist/types.d.ts.map +1 -0
  33. package/dist/types.js +38 -0
  34. package/dist/types.js.map +1 -0
  35. package/dist/vanilla.d.ts +58 -0
  36. package/dist/vanilla.d.ts.map +1 -0
  37. package/dist/vanilla.js +161 -0
  38. package/dist/vanilla.js.map +1 -0
  39. package/package.json +70 -0
  40. package/src/client.ts +342 -0
  41. package/src/detect.ts +278 -0
  42. package/src/errors.ts +133 -0
  43. package/src/index.ts +97 -0
  44. package/src/mutation.ts +109 -0
  45. package/src/provider.ts +229 -0
  46. package/src/react.ts +375 -0
  47. package/src/types.ts +440 -0
  48. package/src/vanilla.ts +239 -0
@@ -0,0 +1,229 @@
1
+ /**
2
+ * The object the extension injects into the page's MAIN world, as this SDK sees
3
+ * it.
4
+ *
5
+ * It is mounted at both `window.joey` and `window.xrpl.joey` (the latter by a
6
+ * non-destructive merge, so Crossmark's `window.xrpl` keeps working). The
7
+ * declaration below is a structural view of
8
+ * `apps/extension/src/provider/provider.ts`; anything the wallet adds later is
9
+ * still reachable through `request()`.
10
+ */
11
+ import type {
12
+ ConnectParams,
13
+ ConnectResult,
14
+ JoeyChain,
15
+ JoeyNetwork,
16
+ SignAndSubmitTransactionResult,
17
+ SignInParams,
18
+ SignInResult,
19
+ SignTransactionBulkParams,
20
+ SignTransactionForParams,
21
+ SignTransactionParams,
22
+ SignTransactionResult,
23
+ } from './types.js'
24
+
25
+ /**
26
+ * Wire method names.
27
+ *
28
+ * Short, unprefixed names: the injected provider is already a Joey-specific
29
+ * object, and the same table is what the content-script bridge and the
30
+ * background validate against. The three that also exist over WalletConnect
31
+ * (`signTransaction`, `signTransactionFor`, `signTransactionBulk`) carry the
32
+ * same parameter shapes as Joey mobile, minus the `xrpl_` namespace prefix that
33
+ * only WalletConnect needs.
34
+ */
35
+ export const JOEY_RPC_METHODS = {
36
+ connect: 'connect',
37
+ disconnect: 'disconnect',
38
+ getAccounts: 'getAccounts',
39
+ getNetwork: 'getNetwork',
40
+ signTransaction: 'signTransaction',
41
+ signAndSubmitTransaction: 'signAndSubmitTransaction',
42
+ signTransactionFor: 'signTransactionFor',
43
+ signTransactionBulk: 'signTransactionBulk',
44
+ signIn: 'signIn',
45
+ } as const
46
+
47
+ export type JoeyRpcMethod = (typeof JOEY_RPC_METHODS)[keyof typeof JOEY_RPC_METHODS]
48
+
49
+ export interface JoeyRequestArguments {
50
+ method: string
51
+ params?: unknown
52
+ }
53
+
54
+ /** Events the wallet pushes. Payloads are unnormalised at this layer. */
55
+ export type JoeyProviderEventName = 'connect' | 'disconnect' | 'accountsChanged' | 'networkChanged'
56
+
57
+ export interface JoeyInjectedProvider {
58
+ readonly isJoey?: boolean
59
+ /** Reverse-DNS identity, `xyz.joeywallet`. Used by CAIP-294 and aggregators. */
60
+ readonly rdns?: string
61
+ /** Version of the injected surface, not of the extension. */
62
+ readonly version?: string
63
+ /** Granted addresses for this origin. `[]` until the user connects. */
64
+ readonly accounts?: readonly string[]
65
+ readonly chain?: JoeyChain | null
66
+
67
+ isAvailable?(): boolean
68
+ isConnected?(): boolean
69
+
70
+ connect?(params?: ConnectParams): Promise<ConnectResult>
71
+ disconnect?(): Promise<void>
72
+ getAccounts?(): Promise<string[]>
73
+ getNetwork?(): Promise<JoeyNetwork>
74
+ signTransaction?(params: SignTransactionParams): Promise<SignTransactionResult>
75
+ signAndSubmitTransaction?(
76
+ params: SignTransactionParams,
77
+ ): Promise<SignAndSubmitTransactionResult>
78
+ signTransactionFor?(params: SignTransactionForParams): Promise<SignTransactionResult>
79
+ signTransactionBulk?(params: SignTransactionBulkParams): Promise<SignTransactionResult[]>
80
+ signIn?(params?: SignInParams): Promise<SignInResult>
81
+
82
+ /** EIP-1193-shaped escape hatch. Always present. */
83
+ request<TResult = unknown>(args: JoeyRequestArguments): Promise<TResult>
84
+
85
+ /** Returns an unsubscribe function. */
86
+ on(event: JoeyProviderEventName, listener: (payload: never) => void): (() => void) | void
87
+ removeListener?(event: JoeyProviderEventName, listener: (payload: never) => void): void
88
+ /** Not implemented by Joey, but common enough elsewhere to be worth trying. */
89
+ off?(event: JoeyProviderEventName, listener: (payload: never) => void): void
90
+ }
91
+
92
+ /** The name Joey registers under with the Wallet Standard. */
93
+ export const JOEY_WALLET_NAME = 'Joey'
94
+ export const JOEY_RDNS = 'xyz.joeywallet'
95
+
96
+ /**
97
+ * Events that mean "the provider just finished installing itself".
98
+ *
99
+ * Both are dispatched synchronously by the provider's install step, so a page
100
+ * whose bundle ran before `document_start` injection completed can wait on them
101
+ * instead of polling. There is no Joey-specific ready event on purpose: an
102
+ * extra global signal is one more thing a page can probe to fingerprint the
103
+ * extension, and these two already exist for discovery.
104
+ */
105
+ export const CAIP294_ANNOUNCE_EVENT = 'wallet_announce'
106
+ export const WALLET_STANDARD_REGISTER_EVENT = 'wallet-standard:register-wallet'
107
+
108
+ /**
109
+ * Events an *app* dispatches to make wallets announce themselves again.
110
+ *
111
+ * The counterparts to the two above, and the half that matters for a late
112
+ * bundle: a wallet that installed before your code ran has already dispatched
113
+ * its announcement into a page with nobody listening. Waiting for a second one
114
+ * that will never come is how a detection helper times out against a wallet
115
+ * that is sitting right there. {@link waitForJoey} dispatches both.
116
+ */
117
+ export const CAIP294_PROMPT_EVENT = 'wallet_prompt'
118
+ export const WALLET_STANDARD_APP_READY_EVENT = 'wallet-standard:app-ready'
119
+
120
+ /* ------------------------------------------------------------------ limits */
121
+
122
+ /**
123
+ * How long the wallet's own plumbing will wait before answering for it.
124
+ *
125
+ * Published because a dapp cannot otherwise size its own spinner or its own
126
+ * retry, and the two numbers are three orders of magnitude apart on purpose. A
127
+ * method that never touches the approval queue — `getAccounts`, `getNetwork`,
128
+ * `disconnect` — answers in milliseconds or the extension's worker is wedged,
129
+ * so it gets {@link REQUEST_TIMEOUT_MS}. A method that does touch the queue is
130
+ * waiting on a person reading a transaction, and its ceiling is
131
+ * {@link APPROVAL_TIMEOUT_MS}, which matches the approval's own expiry: a
132
+ * shorter one would fail a dapp for a signature the user did in fact give.
133
+ *
134
+ * So `signTransaction` can legitimately be pending for five minutes, and with
135
+ * the page's own backstop on top of it, a little over five. Do not put a
136
+ * thirty-second timeout around it.
137
+ */
138
+ export const REQUEST_TIMEOUT_MS = 30_000
139
+ export const APPROVAL_TIMEOUT_MS = 300_000
140
+
141
+ /**
142
+ * The most transactions `signTransactionBulk` will accept in one call.
143
+ *
144
+ * Exported so a dapp can split its own work rather than discover the rule by
145
+ * rejection. The wallet enforces it; this is the same constant, imported by the
146
+ * wallet from here.
147
+ */
148
+ export const MAX_BULK_TRANSACTIONS = 32
149
+
150
+ /**
151
+ * Transaction types Joey refuses to sign for a website, whatever the user
152
+ * clicks and whichever method carries them.
153
+ *
154
+ * Published so a dapp can check before it builds a flow around one, and so the
155
+ * refusal is a documented rule rather than a surprise `4100`. Every entry hands
156
+ * over or destroys the account itself:
157
+ *
158
+ * - `SetRegularKey`, `SignerListSet` and `DelegateSet` (XLS-75) each grant
159
+ * permanent authority to act as the account, by three separate mechanisms.
160
+ * - `AccountDelete` is irreversible.
161
+ * - `SetHook` installs code that runs on every future transaction.
162
+ * - `Batch` (XLS-56) carries other transactions inside `RawTransactions`, and
163
+ * Joey's approval screen renders the outer transaction. A user cannot consent
164
+ * to something they were never shown, so it is refused until the review
165
+ * screen can render inner transactions individually.
166
+ *
167
+ * Two rules are not expressible as a type name and are enforced anyway:
168
+ * `AccountSet` is refused when it sets or clears a flag that changes who
169
+ * controls the account (`asfDisableMaster`, `asfRequireAuth`, `asfNoFreeze` and
170
+ * the rest of that family) and permitted otherwise; and the ledger's
171
+ * pseudo-transactions — `EnableAmendment`, `SetFee`, `UNLModify` — are refused
172
+ * because no account signs one.
173
+ *
174
+ * The wallet checks at every nesting level, not just the top.
175
+ */
176
+ export const JOEY_DAPP_FORBIDDEN_TRANSACTION_TYPES: readonly string[] = Object.freeze([
177
+ 'SetRegularKey',
178
+ 'SignerListSet',
179
+ 'DelegateSet',
180
+ 'AccountDelete',
181
+ 'SetHook',
182
+ 'Batch',
183
+ ])
184
+
185
+ export function isJoeyInjectedProvider(value: unknown): value is JoeyInjectedProvider {
186
+ if (typeof value !== 'object' || value === null) return false
187
+ const candidate = value as Partial<JoeyInjectedProvider>
188
+ return typeof candidate.request === 'function' && typeof candidate.on === 'function'
189
+ }
190
+
191
+ /**
192
+ * Call a provider method by name, preferring the typed method over `request()`.
193
+ *
194
+ * The typed methods are not just sugar: `connect`, `disconnect` and
195
+ * `getAccounts` update the provider's own `accounts` and `chain` state, which a
196
+ * dapp reads synchronously through `joey.accounts` / `joey.isConnected()`.
197
+ * Routing those through `request()` would leave that state stale. `request()`
198
+ * remains the fallback for a provider older or newer than this SDK.
199
+ */
200
+ export async function invoke<TResult>(
201
+ provider: JoeyInjectedProvider,
202
+ method: JoeyRpcMethod,
203
+ params?: unknown,
204
+ ): Promise<TResult> {
205
+ const implementation = (provider as unknown as Record<string, unknown>)[method]
206
+ if (typeof implementation === 'function') {
207
+ return (await (implementation as (arg?: unknown) => Promise<unknown>).call(
208
+ provider,
209
+ params,
210
+ )) as TResult
211
+ }
212
+ return await provider.request<TResult>(
213
+ params === undefined ? { method } : { method, params },
214
+ )
215
+ }
216
+
217
+ /** Subscribe, tolerating a provider that reports removal three different ways. */
218
+ export function subscribe(
219
+ provider: JoeyInjectedProvider,
220
+ event: JoeyProviderEventName,
221
+ listener: (payload: never) => void,
222
+ ): () => void {
223
+ const returned = provider.on(event, listener)
224
+ if (typeof returned === 'function') return returned
225
+ return () => {
226
+ if (typeof provider.removeListener === 'function') provider.removeListener(event, listener)
227
+ else if (typeof provider.off === 'function') provider.off(event, listener)
228
+ }
229
+ }
package/src/react.ts ADDED
@@ -0,0 +1,375 @@
1
+ /**
2
+ * `@joeywallet/wallet-sdk/react`.
3
+ *
4
+ * The hooks are shaped like react-query mutations — `{ mutate, mutateAsync,
5
+ * isPending, error, data, reset }` — because that is the shape web3 developers
6
+ * already have muscle memory for. They do not depend on react-query: a wallet
7
+ * SDK that drags a cache library into every dapp is a tax on integration, and
8
+ * none of react-query's real features (caching, invalidation, retries) apply to
9
+ * a call whose side effect is a human clicking Approve.
10
+ *
11
+ * `react` is an optional peer dependency. Nothing in the core entry point
12
+ * imports this file.
13
+ */
14
+ import {
15
+ createContext,
16
+ createElement,
17
+ useCallback,
18
+ useContext,
19
+ useEffect,
20
+ useMemo,
21
+ useReducer,
22
+ useRef,
23
+ useState,
24
+ type ReactNode,
25
+ } from 'react'
26
+
27
+ import type { Joey } from './client.js'
28
+ import { getJoey, waitForJoey } from './detect.js'
29
+ import { JOEY_ERROR_CODES, JoeyRpcError } from './errors.js'
30
+ import {
31
+ initialMutationState,
32
+ mutationReducer,
33
+ toPublicState,
34
+ type MutationState,
35
+ } from './mutation.js'
36
+ import type {
37
+ AnyTransaction,
38
+ ConnectParams,
39
+ ConnectResult,
40
+ JoeyNetwork,
41
+ SignAndSubmitTransactionResult,
42
+ SignInParams,
43
+ SignInResult,
44
+ SignTransactionBulkParams,
45
+ SignTransactionForParams,
46
+ SignTransactionParams,
47
+ SignTransactionResult,
48
+ TransactionLike,
49
+ } from './types.js'
50
+
51
+ function notInstalled(): JoeyRpcError {
52
+ return new JoeyRpcError(
53
+ JOEY_ERROR_CODES.DISCONNECTED,
54
+ 'Joey Wallet is not installed, or its provider has not been injected into this page.',
55
+ )
56
+ }
57
+
58
+ /* ------------------------------------------------------------------ context */
59
+
60
+ export interface JoeyContextValue {
61
+ /** `null` until the provider is found, and forever if it never appears. */
62
+ joey: Joey | null
63
+ isAvailable: boolean
64
+ /** True once detection has finished, whether or not it found anything. */
65
+ isReady: boolean
66
+ /** Every address granted to this origin. */
67
+ accounts: string[]
68
+ /** The first granted address, which is what most dapps mean by "the account". */
69
+ account: string | null
70
+ network: JoeyNetwork | null
71
+ isConnected: boolean
72
+ connect(params?: ConnectParams): Promise<ConnectResult>
73
+ disconnect(): Promise<void>
74
+ /** Re-read the accounts and network from the wallet. */
75
+ refresh(): Promise<void>
76
+ }
77
+
78
+ const JoeyContext = createContext<JoeyContextValue | null>(null)
79
+
80
+ export interface JoeyProviderProps {
81
+ children?: ReactNode
82
+ /**
83
+ * Attempt a silent reconnect on mount. Only returns accounts the user already
84
+ * granted this origin, so it never opens an approval window.
85
+ */
86
+ autoConnect?: boolean
87
+ /** How long to wait for a late-injected provider. Default 3000ms. */
88
+ detectTimeoutMs?: number
89
+ }
90
+
91
+ export function JoeyProvider(props: JoeyProviderProps): ReactNode {
92
+ const { children, autoConnect = false, detectTimeoutMs = 3000 } = props
93
+
94
+ // Seeded synchronously: when the provider is already on the page the first
95
+ // render is already correct, and no dapp has to render a "detecting" state.
96
+ const [joey, setJoey] = useState<Joey | null>(() => getJoey())
97
+ const [isReady, setIsReady] = useState<boolean>(() => getJoey() !== null)
98
+ const [accounts, setAccounts] = useState<string[]>([])
99
+ const [network, setNetwork] = useState<JoeyNetwork | null>(null)
100
+
101
+ useEffect(() => {
102
+ if (joey !== null) return
103
+ let cancelled = false
104
+ const controller = new AbortController()
105
+ waitForJoey({ timeoutMs: detectTimeoutMs, signal: controller.signal })
106
+ .then((found) => {
107
+ if (!cancelled) setJoey(found)
108
+ })
109
+ .catch(() => {
110
+ /* absence is a state, not an error the dapp needs to handle here */
111
+ })
112
+ .finally(() => {
113
+ if (!cancelled) setIsReady(true)
114
+ })
115
+ return () => {
116
+ cancelled = true
117
+ controller.abort()
118
+ }
119
+ }, [joey, detectTimeoutMs])
120
+
121
+ const refresh = useCallback(async (): Promise<void> => {
122
+ if (joey === null) return
123
+ const [nextAccounts, nextNetwork] = await Promise.all([
124
+ joey.getAccounts().catch(() => [] as string[]),
125
+ joey.getNetwork().catch(() => null),
126
+ ])
127
+ setAccounts(nextAccounts)
128
+ setNetwork(nextNetwork)
129
+ }, [joey])
130
+
131
+ useEffect(() => {
132
+ if (joey === null) return
133
+ const offs = [
134
+ joey.on('accountsChanged', (next) => setAccounts(next.map((a) => a.address))),
135
+ joey.on('networkChanged', (next) => setNetwork(next)),
136
+ joey.on('connect', (result) => setAccounts(result.accounts.map((a) => a.address))),
137
+ joey.on('disconnect', () => setAccounts([])),
138
+ ]
139
+ return () => {
140
+ for (const off of offs) off()
141
+ }
142
+ }, [joey])
143
+
144
+ useEffect(() => {
145
+ if (joey === null) return
146
+ let cancelled = false
147
+
148
+ const run = async (): Promise<void> => {
149
+ if (autoConnect) {
150
+ try {
151
+ const result = await joey.connect({ silent: true })
152
+ if (cancelled) return
153
+ if (result.accounts.length > 0) {
154
+ setAccounts(result.accounts.map((account) => account.address))
155
+ const next = await joey.getNetwork().catch(() => null)
156
+ if (!cancelled) setNetwork(next)
157
+ return
158
+ }
159
+ } catch {
160
+ // A silent connect is best-effort. Fall through to the plain refresh.
161
+ }
162
+ }
163
+ if (!cancelled) await refresh()
164
+ }
165
+
166
+ void run()
167
+ return () => {
168
+ cancelled = true
169
+ }
170
+ }, [joey, autoConnect, refresh])
171
+
172
+ const connect = useCallback(
173
+ async (params?: ConnectParams): Promise<ConnectResult> => {
174
+ if (joey === null) throw notInstalled()
175
+ const result = await joey.connect(params)
176
+ setAccounts(result.accounts.map((account) => account.address))
177
+ if (result.chain !== null) {
178
+ setNetwork(await joey.getNetwork().catch(() => null))
179
+ }
180
+ return result
181
+ },
182
+ [joey],
183
+ )
184
+
185
+ const disconnect = useCallback(async (): Promise<void> => {
186
+ if (joey === null) return
187
+ await joey.disconnect()
188
+ setAccounts([])
189
+ }, [joey])
190
+
191
+ const value = useMemo<JoeyContextValue>(
192
+ () => ({
193
+ joey,
194
+ isAvailable: joey !== null,
195
+ isReady,
196
+ accounts,
197
+ account: accounts[0] ?? null,
198
+ network,
199
+ isConnected: accounts.length > 0,
200
+ connect,
201
+ disconnect,
202
+ refresh,
203
+ }),
204
+ [joey, isReady, accounts, network, connect, disconnect, refresh],
205
+ )
206
+
207
+ return createElement(JoeyContext.Provider, { value }, children)
208
+ }
209
+
210
+ export function useJoey(): JoeyContextValue {
211
+ const value = useContext(JoeyContext)
212
+ if (value === null) {
213
+ throw new Error('useJoey must be used inside a <JoeyProvider>.')
214
+ }
215
+ return value
216
+ }
217
+
218
+ /* ----------------------------------------------------------------- mutations */
219
+
220
+ export interface UseJoeyMutationOptions<TData, TVariables> {
221
+ onSuccess?(data: TData, variables: TVariables): void
222
+ onError?(error: JoeyRpcError, variables: TVariables): void
223
+ onSettled?(data: TData | undefined, error: JoeyRpcError | undefined, variables: TVariables): void
224
+ }
225
+
226
+ export interface UseJoeyMutationResult<TData, TVariables>
227
+ extends MutationState<TData, TVariables> {
228
+ /** Fire and forget. Never rejects — read `error` instead. */
229
+ mutate(variables: TVariables): void
230
+ /** Resolves the result, or rejects with `JoeyRpcError`. */
231
+ mutateAsync(variables: TVariables): Promise<TData>
232
+ reset(): void
233
+ }
234
+
235
+ /**
236
+ * Build a mutation hook over a Joey method.
237
+ *
238
+ * Exported so a dapp can wrap a method this SDK version does not model, using
239
+ * `joey.request()`, and still get the same `{ mutate, isPending, error }` shape
240
+ * as the built-in hooks.
241
+ */
242
+ export function useJoeyMutation<TData, TVariables>(
243
+ run: (joey: Joey, variables: TVariables) => Promise<TData>,
244
+ options: UseJoeyMutationOptions<TData, TVariables> = {},
245
+ ): UseJoeyMutationResult<TData, TVariables> {
246
+ const { joey } = useJoey()
247
+ const [state, dispatch] = useReducer(
248
+ mutationReducer<TData, TVariables>,
249
+ undefined,
250
+ initialMutationState<TData, TVariables>,
251
+ )
252
+
253
+ const runIdRef = useRef(0)
254
+ const mountedRef = useRef(true)
255
+ useEffect(() => {
256
+ mountedRef.current = true
257
+ return () => {
258
+ mountedRef.current = false
259
+ }
260
+ }, [])
261
+
262
+ // Held in refs so a callback identity that changes between renders does not
263
+ // change the identity of `mutate`, which dapps put in dependency arrays.
264
+ const runRef = useRef(run)
265
+ runRef.current = run
266
+ const optionsRef = useRef(options)
267
+ optionsRef.current = options
268
+
269
+ const mutateAsync = useCallback(
270
+ async (variables: TVariables): Promise<TData> => {
271
+ const runId = ++runIdRef.current
272
+ dispatch({ type: 'start', variables, runId })
273
+
274
+ try {
275
+ if (joey === null) throw notInstalled()
276
+ const data = await runRef.current(joey, variables)
277
+ if (mountedRef.current) dispatch({ type: 'success', data, runId })
278
+ optionsRef.current.onSuccess?.(data, variables)
279
+ optionsRef.current.onSettled?.(data, undefined, variables)
280
+ return data
281
+ } catch (cause) {
282
+ const error = JoeyRpcError.from(cause)
283
+ if (mountedRef.current) dispatch({ type: 'error', error, runId })
284
+ optionsRef.current.onError?.(error, variables)
285
+ optionsRef.current.onSettled?.(undefined, error, variables)
286
+ throw error
287
+ }
288
+ },
289
+ [joey],
290
+ )
291
+
292
+ const mutate = useCallback(
293
+ (variables: TVariables): void => {
294
+ void mutateAsync(variables).catch(() => {
295
+ /* surfaced through `error`; swallowing keeps `mutate` unrejectable */
296
+ })
297
+ },
298
+ [mutateAsync],
299
+ )
300
+
301
+ const reset = useCallback((): void => {
302
+ dispatch({ type: 'reset' })
303
+ }, [])
304
+
305
+ return { ...toPublicState(state), mutate, mutateAsync, reset }
306
+ }
307
+
308
+ export function useConnect(
309
+ options?: UseJoeyMutationOptions<ConnectResult, ConnectParams | undefined>,
310
+ ): UseJoeyMutationResult<ConnectResult, ConnectParams | undefined> {
311
+ const { connect } = useJoey()
312
+ return useJoeyMutation<ConnectResult, ConnectParams | undefined>(
313
+ // Routed through the context so the provider's account/network state is
314
+ // updated by the same call that resolves the mutation.
315
+ (_joey, params) => connect(params),
316
+ options,
317
+ )
318
+ }
319
+
320
+ export function useDisconnect(
321
+ options?: UseJoeyMutationOptions<void, void>,
322
+ ): UseJoeyMutationResult<void, void> {
323
+ const { disconnect } = useJoey()
324
+ return useJoeyMutation<void, void>(() => disconnect(), options)
325
+ }
326
+
327
+ export function useSignTransaction<TTx extends TransactionLike = AnyTransaction>(
328
+ options?: UseJoeyMutationOptions<SignTransactionResult, SignTransactionParams<TTx>>,
329
+ ): UseJoeyMutationResult<SignTransactionResult, SignTransactionParams<TTx>> {
330
+ return useJoeyMutation<SignTransactionResult, SignTransactionParams<TTx>>(
331
+ (joey, params) => joey.signTransaction(params),
332
+ options,
333
+ )
334
+ }
335
+
336
+ export function useSignAndSubmit<TTx extends TransactionLike = AnyTransaction>(
337
+ options?: UseJoeyMutationOptions<SignAndSubmitTransactionResult, SignTransactionParams<TTx>>,
338
+ ): UseJoeyMutationResult<SignAndSubmitTransactionResult, SignTransactionParams<TTx>> {
339
+ return useJoeyMutation<SignAndSubmitTransactionResult, SignTransactionParams<TTx>>(
340
+ (joey, params) => joey.signAndSubmitTransaction(params),
341
+ options,
342
+ )
343
+ }
344
+
345
+ export function useSignTransactionFor<TTx extends TransactionLike = AnyTransaction>(
346
+ options?: UseJoeyMutationOptions<SignTransactionResult, SignTransactionForParams<TTx>>,
347
+ ): UseJoeyMutationResult<SignTransactionResult, SignTransactionForParams<TTx>> {
348
+ return useJoeyMutation<SignTransactionResult, SignTransactionForParams<TTx>>(
349
+ (joey, params) => joey.signTransactionFor(params),
350
+ options,
351
+ )
352
+ }
353
+
354
+ export function useSignTransactionBulk<TTx extends TransactionLike = AnyTransaction>(
355
+ options?: UseJoeyMutationOptions<
356
+ SignAndSubmitTransactionResult[],
357
+ SignTransactionBulkParams<TTx>
358
+ >,
359
+ ): UseJoeyMutationResult<SignAndSubmitTransactionResult[], SignTransactionBulkParams<TTx>> {
360
+ return useJoeyMutation<SignAndSubmitTransactionResult[], SignTransactionBulkParams<TTx>>(
361
+ (joey, params) => joey.signTransactionBulk(params),
362
+ options,
363
+ )
364
+ }
365
+
366
+ export function useSignIn(
367
+ options?: UseJoeyMutationOptions<SignInResult, SignInParams | undefined>,
368
+ ): UseJoeyMutationResult<SignInResult, SignInParams | undefined> {
369
+ return useJoeyMutation<SignInResult, SignInParams | undefined>(
370
+ (joey, params) => joey.signIn(params),
371
+ options,
372
+ )
373
+ }
374
+
375
+ export type { MutationState, MutationStatus } from './mutation.js'