@naculus/connect-appkit-core 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.
@@ -0,0 +1,325 @@
1
+ import { Namespace, UniversalWalletSession, TokenConfig, WalletError, WalletErrorCode, NameResolverConfig, NameResolver } from '@naculus/connect-core';
2
+
3
+ /**
4
+ * The types the framework-neutral half of appkit is built on.
5
+ *
6
+ * They live here rather than in the React package because a Vue composable
7
+ * needs to name the same things, and importing them from a package called
8
+ * `-react` would be both odd and a lie about what the dependency is.
9
+ */
10
+
11
+ type WalletChain = {
12
+ /**
13
+ * CAIP-2, and the chain's identity.
14
+ *
15
+ * This was `id: number` alongside a separate `namespace`, which could only
16
+ * ever describe an EIP-155 chain: a Solana reference is base58
17
+ * (`solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp`) and an XRPL one is an
18
+ * unsigned network id. The registry structurally could not hold a non-EVM
19
+ * chain, so every non-EVM path fell out to "unknown".
20
+ *
21
+ * `namespace` is gone rather than kept alongside: two fields that must
22
+ * agree are two fields that eventually do not.
23
+ */
24
+ caip2: string;
25
+ name: string;
26
+ rpcUrl?: string;
27
+ explorerUrl?: string;
28
+ token?: string;
29
+ };
30
+ interface ChainInfo {
31
+ namespace: Namespace;
32
+ chainId: string;
33
+ name: string;
34
+ selected: boolean;
35
+ }
36
+ type ConnectionStatus = "disconnected" | "connecting" | "authenticating" | "connected" | "reconnecting";
37
+ interface Web3State {
38
+ status: ConnectionStatus;
39
+ session: UniversalWalletSession | null;
40
+ accounts: string[];
41
+ chainId: string | null;
42
+ error: Error | null;
43
+ }
44
+
45
+ /**
46
+ * Which chains a session can actually switch to, and what the current one is.
47
+ *
48
+ * Framework-neutral, and deliberately built on `parseChainId` from
49
+ * `@naculus/connect-core` rather than on another `startsWith("eip155:")`.
50
+ */
51
+
52
+ /**
53
+ * The EIP-155 chain number, or null when this is not an EVM chain.
54
+ *
55
+ * Callers that need a number — a viem client, an ERC-20 read — must handle
56
+ * the null rather than defaulting, because there is no number that means
57
+ * "Solana" and any stand-in would address a real EVM chain.
58
+ */
59
+ declare function chainNumber(chain: WalletChain): number | null;
60
+ /** The namespace a chain belongs to, read from its CAIP-2 id. */
61
+ declare function chainNamespace(chain: WalletChain): string | null;
62
+ /**
63
+ * The configured chain for a CAIP-2 id, or null.
64
+ *
65
+ * A string comparison now that `WalletChain` is keyed by CAIP-2. It used to
66
+ * pull an integer out of the id and compare that, which no non-EVM chain
67
+ * could ever match.
68
+ */
69
+ declare function resolveChain(chains: WalletChain[], chainId: string | null | undefined): WalletChain | null;
70
+ /**
71
+ * The chains worth offering for the session's namespace.
72
+ *
73
+ * This used to return every configured chain regardless of what was
74
+ * connected, so a Solana wallet was offered Ethereum and Polygon in the
75
+ * switcher. Clicking one asked a wallet that has no concept of EIP-155 to
76
+ * switch to chain 137 — an action the interface presented as available and
77
+ * that could never work.
78
+ *
79
+ * With no connected chain, everything is offered: nothing has been ruled out
80
+ * yet, and hiding the list would leave a user unable to pick.
81
+ */
82
+ declare function chainsForNamespace(chains: WalletChain[], chainId: string | null | undefined): WalletChain[];
83
+ /**
84
+ * A description of the connected chain for display.
85
+ *
86
+ * Returns something for every namespace. It used to return null for anything
87
+ * that was not EIP-155, so a connected Solana wallet had no chain to show at
88
+ * all and the interface fell back to "Unknown Chain".
89
+ */
90
+ declare function describeChain(chains: WalletChain[], chainId: string | null | undefined): ChainInfo | null;
91
+
92
+ /**
93
+ * The viem `Chain` shape for a configured EVM chain.
94
+ *
95
+ * Three hooks each built this inline — `useBalance`, `useTokenBalance` and
96
+ * `useViemClient` — with identical bodies and the same `?? "ETH"` in each.
97
+ * Three copies of a fallback is three places for it to be wrong.
98
+ *
99
+ * Returns a plain object rather than importing viem, so this package stays
100
+ * free of a dependency only the React bindings need. The shape satisfies
101
+ * viem's `Chain` structurally.
102
+ */
103
+
104
+ interface ViemChainShape {
105
+ id: number;
106
+ name: string;
107
+ nativeCurrency: {
108
+ name: string;
109
+ symbol: string;
110
+ decimals: number;
111
+ };
112
+ rpcUrls: {
113
+ default: {
114
+ http: string[];
115
+ };
116
+ public: {
117
+ http: string[];
118
+ };
119
+ };
120
+ }
121
+ /**
122
+ * Build the chain descriptor a viem client needs, or null when this chain is
123
+ * not one viem can talk to.
124
+ *
125
+ * Null for a non-EVM chain rather than a stand-in number: viem speaks to EVM
126
+ * nodes, and any number chosen to fill the gap addresses a real chain that is
127
+ * not the one connected.
128
+ *
129
+ * The native symbol comes from the chain's own `token`, then from the shared
130
+ * chain registry. For an unlisted chain it stays empty rather than claiming
131
+ * the asset is ETH. viem requires a string here; the user-facing symbol comes
132
+ * from `useBalance().symbol`, which stays null when it is unknown.
133
+ */
134
+ declare function toViemChain(chain: WalletChain, chainNumber: number | null): ViemChainShape | null;
135
+
136
+ /**
137
+ * Which EVM chain does an account-abstraction operation belong to?
138
+ *
139
+ * The smart-account hooks each resolved this as
140
+ * `options.chainId ?? connectedChainId ?? "eip155:1"`, and each then guarded
141
+ * with `if (connectedChainId && connectedChainId !== target)`. The guard is
142
+ * skipped precisely when the mainnet default is used, so the one branch that
143
+ * assumes a chain is the one branch that never checks it.
144
+ *
145
+ * That branch is reachable: a caller passing its own `signer` needs no
146
+ * session, so `connectedChainId` is legitimately absent. It would then build
147
+ * and sign a UserOperation for chain 1 — with chain 1's EntryPoint — while its
148
+ * configured RPC and bundler point somewhere else. The chain ID is part of the
149
+ * UserOperation hash, so the user approves a commitment to a chain they were
150
+ * never shown.
151
+ *
152
+ * Resolving to undefined instead lets each caller refuse in whatever way suits
153
+ * it. Framework-agnostic on purpose: it must not import React.
154
+ */
155
+ declare function isEvmChainId(value: string | null | undefined): boolean;
156
+ /**
157
+ * The chain to operate on, or undefined when it cannot be known.
158
+ *
159
+ * An explicitly requested chain wins over the connected one; a malformed value
160
+ * from either source resolves to undefined rather than being replaced by a
161
+ * guess, because the caller asked for something specific and got it wrong.
162
+ */
163
+ declare function resolveEvmChainId(requested: string | null | undefined, connected: string | null | undefined): string | undefined;
164
+
165
+ /**
166
+ * Does this token belong to the chain the wallet is pointed at?
167
+ *
168
+ * `TokenConfig.chainId` existed but was only ever read as a display label and
169
+ * a cache key. Nothing compared it to the connected chain, so a hook handed a
170
+ * mainnet token config while the wallet sat on Polygon would read — or
171
+ * approve — whatever contract happens to occupy that address on Polygon.
172
+ * Usually that address holds nothing and the call reverts, which is merely
173
+ * confusing. When it holds a different token, `allowance()` returns a
174
+ * plausible number for the wrong asset, and `approve()` grants a spender
175
+ * rights over something the user never looked at.
176
+ *
177
+ * Framework-agnostic on purpose: it must not import React.
178
+ */
179
+
180
+ /**
181
+ * A mismatch, or undefined when the token is on the active chain — or when
182
+ * there is not enough information to tell.
183
+ *
184
+ * Silence on "cannot tell" is deliberate. A token config without a chainId, or
185
+ * a session with no resolved chain, is a gap in what the caller supplied; a
186
+ * guess in either direction would be worse than letting the call proceed and
187
+ * fail on its own terms.
188
+ */
189
+ declare function tokenChainMismatch(token: Pick<TokenConfig, "address" | "chainId">, activeChainId: number | undefined): WalletError | undefined;
190
+
191
+ /**
192
+ * Decimals cache keyed by the token it belongs to.
193
+ *
194
+ * The ERC-20 hooks cached decimals in a bare `useRef` seeded from the token
195
+ * prop. `useRef` only takes its initial value on mount, and nothing reset it,
196
+ * so a component that lets the user switch tokens kept the first token's
197
+ * precision: picking USDC (6) and then DAI (18) converted "100" with 6
198
+ * decimals and transferred 0.0000000001 DAI. The transaction succeeded, so
199
+ * nothing surfaced the mistake.
200
+ *
201
+ * Keying the cache by the token makes a stale hit impossible without needing
202
+ * an effect to fire first.
203
+ */
204
+ interface TokenIdentity {
205
+ chainId?: number;
206
+ address: string;
207
+ }
208
+ interface DecimalsCache {
209
+ key: string;
210
+ value: number | undefined;
211
+ }
212
+ declare function tokenKey(token: TokenIdentity): string;
213
+ declare function createDecimalsCache(token: TokenIdentity, known?: number): DecimalsCache;
214
+ /**
215
+ * Cached decimals for `token`, or undefined when the cache holds another
216
+ * token's value. Resets the cache as a side effect so the next write lands
217
+ * against the right key.
218
+ */
219
+ declare function readDecimals(cache: DecimalsCache, token: TokenIdentity, known?: number): number | undefined;
220
+ declare function writeDecimals(cache: DecimalsCache, token: TokenIdentity, value: number): void;
221
+
222
+ /**
223
+ * Normalize an EIP-1193 provider error into a WalletError.
224
+ *
225
+ * Hooks were collapsing every non-WalletError into one code — `useSwitchChain`
226
+ * labelled a user pressing Cancel as `chain_unsupported`, so the UI told them
227
+ * the chain was unavailable when they had simply declined. The codes below are
228
+ * the ones wallets actually send, and they mean different things to a caller:
229
+ * one is retryable by asking again, the other is not retryable at all until
230
+ * the chain is added.
231
+ *
232
+ * Framework-agnostic on purpose: it must not import React.
233
+ */
234
+
235
+ /**
236
+ * Map a chain-switch failure onto the code that describes it.
237
+ *
238
+ * `fallback` is used when the wallet gave nothing to go on, so the caller
239
+ * still decides what an unexplained failure means in its own context.
240
+ */
241
+ declare function toChainSwitchError(error: unknown, fallback?: WalletErrorCode): WalletError;
242
+
243
+ /**
244
+ * Decode an EVM revert payload into something a user can act on.
245
+ *
246
+ * This text is what a wallet shows when a simulation says a transaction would
247
+ * fail, so it is worth getting right and worth sharing: the logic is pure and
248
+ * every binding needs the same answer.
249
+ *
250
+ * Two things are handled that the inlined version did not. The ABI length
251
+ * word is attacker-controlled, so a payload can declare a longer string than
252
+ * it carries; decoding that produced a run of NUL bytes rather than an honest
253
+ * "could not decode". And a Panic was reported as "Built-in failure" with the
254
+ * code discarded, which is the one piece of information that distinguishes an
255
+ * overflow from a division by zero.
256
+ */
257
+ /**
258
+ * @param data Raw revert data from the node, with or without the 0x prefix.
259
+ * @returns The decoded reason, or undefined when the payload is not a shape
260
+ * this understands — callers should fall back to the RPC error text rather
261
+ * than presenting a guess.
262
+ */
263
+ declare function decodeRevertReason(data: unknown): string | undefined;
264
+ /** Decoded reason if available, otherwise the node's own message. */
265
+ declare function resolveRevertReason(data: unknown, fallbackMessage: string): string;
266
+
267
+ /**
268
+ * Shared NameResolver instance for the name-resolution hooks.
269
+ *
270
+ * useResolveName and useLookupAddress each carried an identical getResolver
271
+ * with its own module-level singleton, so the two directions of the same
272
+ * lookup ran against separate resolvers and separate caches. One definition,
273
+ * one instance.
274
+ */
275
+
276
+ /**
277
+ * @param config When supplied, returns a dedicated resolver rather than the
278
+ * shared one — a caller asking for specific settings must not be handed an
279
+ * instance configured by someone else.
280
+ */
281
+ declare function getResolver(config?: NameResolverConfig): NameResolver;
282
+ /** Test seam: drop the shared instance. */
283
+ declare function __resetResolverForTests(): void;
284
+
285
+ declare function withTimeout<T>(promise: Promise<T>, ms: number, label?: string): Promise<T>;
286
+ interface RetryOptions {
287
+ maxRetries: number;
288
+ baseDelay: number;
289
+ label?: string;
290
+ }
291
+ declare function withRetry<T>(fn: () => Promise<T>, options: RetryOptions): Promise<T>;
292
+ /** Convert EIP-1193 chainChanged values into the CAIP-2 form used by sessions. */
293
+ declare function normalizeEip155ChainId(value: unknown): string | undefined;
294
+ declare function toEip155Accounts(accounts: unknown[], chainId: string): string[];
295
+ /**
296
+ * Numeric EIP-155 chain id, or undefined for any other namespace.
297
+ *
298
+ * The previous implementation took the last CAIP segment and ran parseInt over
299
+ * it, which stops at the first non-digit: `solana:5eykt4Us…` became 5 — an
300
+ * EVM chain id — so asking for Solana mainnet's token list returned Goerli's,
301
+ * and `xrpl:0` became chain 0. Anchor the whole reference instead.
302
+ */
303
+ declare function eip155ChainIdToNumber(chainId: string): number | undefined;
304
+ type Web3Action = {
305
+ type: "SET_STATUS";
306
+ payload: ConnectionStatus;
307
+ } | {
308
+ type: "SET_SESSION";
309
+ payload: Web3State["session"];
310
+ } | {
311
+ type: "SET_ACCOUNTS";
312
+ payload: string[];
313
+ } | {
314
+ type: "SET_CHAIN";
315
+ payload: string | null;
316
+ } | {
317
+ type: "SET_ERROR";
318
+ payload: Error | null;
319
+ } | {
320
+ type: "RESET";
321
+ };
322
+ declare const initialWeb3State: Web3State;
323
+ declare function web3Reducer(state: Web3State, action: Web3Action): Web3State;
324
+
325
+ export { type ChainInfo, type ConnectionStatus, type DecimalsCache, type RetryOptions, type TokenIdentity, type ViemChainShape, type WalletChain, type Web3Action, type Web3State, __resetResolverForTests, chainNamespace, chainNumber, chainsForNamespace, createDecimalsCache, decodeRevertReason, describeChain, eip155ChainIdToNumber, getResolver, initialWeb3State, isEvmChainId, normalizeEip155ChainId, readDecimals, resolveChain, resolveEvmChainId, resolveRevertReason, toChainSwitchError, toEip155Accounts, toViemChain, tokenChainMismatch, tokenKey, web3Reducer, withRetry, withTimeout, writeDecimals };
@@ -0,0 +1,325 @@
1
+ import { Namespace, UniversalWalletSession, TokenConfig, WalletError, WalletErrorCode, NameResolverConfig, NameResolver } from '@naculus/connect-core';
2
+
3
+ /**
4
+ * The types the framework-neutral half of appkit is built on.
5
+ *
6
+ * They live here rather than in the React package because a Vue composable
7
+ * needs to name the same things, and importing them from a package called
8
+ * `-react` would be both odd and a lie about what the dependency is.
9
+ */
10
+
11
+ type WalletChain = {
12
+ /**
13
+ * CAIP-2, and the chain's identity.
14
+ *
15
+ * This was `id: number` alongside a separate `namespace`, which could only
16
+ * ever describe an EIP-155 chain: a Solana reference is base58
17
+ * (`solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp`) and an XRPL one is an
18
+ * unsigned network id. The registry structurally could not hold a non-EVM
19
+ * chain, so every non-EVM path fell out to "unknown".
20
+ *
21
+ * `namespace` is gone rather than kept alongside: two fields that must
22
+ * agree are two fields that eventually do not.
23
+ */
24
+ caip2: string;
25
+ name: string;
26
+ rpcUrl?: string;
27
+ explorerUrl?: string;
28
+ token?: string;
29
+ };
30
+ interface ChainInfo {
31
+ namespace: Namespace;
32
+ chainId: string;
33
+ name: string;
34
+ selected: boolean;
35
+ }
36
+ type ConnectionStatus = "disconnected" | "connecting" | "authenticating" | "connected" | "reconnecting";
37
+ interface Web3State {
38
+ status: ConnectionStatus;
39
+ session: UniversalWalletSession | null;
40
+ accounts: string[];
41
+ chainId: string | null;
42
+ error: Error | null;
43
+ }
44
+
45
+ /**
46
+ * Which chains a session can actually switch to, and what the current one is.
47
+ *
48
+ * Framework-neutral, and deliberately built on `parseChainId` from
49
+ * `@naculus/connect-core` rather than on another `startsWith("eip155:")`.
50
+ */
51
+
52
+ /**
53
+ * The EIP-155 chain number, or null when this is not an EVM chain.
54
+ *
55
+ * Callers that need a number — a viem client, an ERC-20 read — must handle
56
+ * the null rather than defaulting, because there is no number that means
57
+ * "Solana" and any stand-in would address a real EVM chain.
58
+ */
59
+ declare function chainNumber(chain: WalletChain): number | null;
60
+ /** The namespace a chain belongs to, read from its CAIP-2 id. */
61
+ declare function chainNamespace(chain: WalletChain): string | null;
62
+ /**
63
+ * The configured chain for a CAIP-2 id, or null.
64
+ *
65
+ * A string comparison now that `WalletChain` is keyed by CAIP-2. It used to
66
+ * pull an integer out of the id and compare that, which no non-EVM chain
67
+ * could ever match.
68
+ */
69
+ declare function resolveChain(chains: WalletChain[], chainId: string | null | undefined): WalletChain | null;
70
+ /**
71
+ * The chains worth offering for the session's namespace.
72
+ *
73
+ * This used to return every configured chain regardless of what was
74
+ * connected, so a Solana wallet was offered Ethereum and Polygon in the
75
+ * switcher. Clicking one asked a wallet that has no concept of EIP-155 to
76
+ * switch to chain 137 — an action the interface presented as available and
77
+ * that could never work.
78
+ *
79
+ * With no connected chain, everything is offered: nothing has been ruled out
80
+ * yet, and hiding the list would leave a user unable to pick.
81
+ */
82
+ declare function chainsForNamespace(chains: WalletChain[], chainId: string | null | undefined): WalletChain[];
83
+ /**
84
+ * A description of the connected chain for display.
85
+ *
86
+ * Returns something for every namespace. It used to return null for anything
87
+ * that was not EIP-155, so a connected Solana wallet had no chain to show at
88
+ * all and the interface fell back to "Unknown Chain".
89
+ */
90
+ declare function describeChain(chains: WalletChain[], chainId: string | null | undefined): ChainInfo | null;
91
+
92
+ /**
93
+ * The viem `Chain` shape for a configured EVM chain.
94
+ *
95
+ * Three hooks each built this inline — `useBalance`, `useTokenBalance` and
96
+ * `useViemClient` — with identical bodies and the same `?? "ETH"` in each.
97
+ * Three copies of a fallback is three places for it to be wrong.
98
+ *
99
+ * Returns a plain object rather than importing viem, so this package stays
100
+ * free of a dependency only the React bindings need. The shape satisfies
101
+ * viem's `Chain` structurally.
102
+ */
103
+
104
+ interface ViemChainShape {
105
+ id: number;
106
+ name: string;
107
+ nativeCurrency: {
108
+ name: string;
109
+ symbol: string;
110
+ decimals: number;
111
+ };
112
+ rpcUrls: {
113
+ default: {
114
+ http: string[];
115
+ };
116
+ public: {
117
+ http: string[];
118
+ };
119
+ };
120
+ }
121
+ /**
122
+ * Build the chain descriptor a viem client needs, or null when this chain is
123
+ * not one viem can talk to.
124
+ *
125
+ * Null for a non-EVM chain rather than a stand-in number: viem speaks to EVM
126
+ * nodes, and any number chosen to fill the gap addresses a real chain that is
127
+ * not the one connected.
128
+ *
129
+ * The native symbol comes from the chain's own `token`, then from the shared
130
+ * chain registry. For an unlisted chain it stays empty rather than claiming
131
+ * the asset is ETH. viem requires a string here; the user-facing symbol comes
132
+ * from `useBalance().symbol`, which stays null when it is unknown.
133
+ */
134
+ declare function toViemChain(chain: WalletChain, chainNumber: number | null): ViemChainShape | null;
135
+
136
+ /**
137
+ * Which EVM chain does an account-abstraction operation belong to?
138
+ *
139
+ * The smart-account hooks each resolved this as
140
+ * `options.chainId ?? connectedChainId ?? "eip155:1"`, and each then guarded
141
+ * with `if (connectedChainId && connectedChainId !== target)`. The guard is
142
+ * skipped precisely when the mainnet default is used, so the one branch that
143
+ * assumes a chain is the one branch that never checks it.
144
+ *
145
+ * That branch is reachable: a caller passing its own `signer` needs no
146
+ * session, so `connectedChainId` is legitimately absent. It would then build
147
+ * and sign a UserOperation for chain 1 — with chain 1's EntryPoint — while its
148
+ * configured RPC and bundler point somewhere else. The chain ID is part of the
149
+ * UserOperation hash, so the user approves a commitment to a chain they were
150
+ * never shown.
151
+ *
152
+ * Resolving to undefined instead lets each caller refuse in whatever way suits
153
+ * it. Framework-agnostic on purpose: it must not import React.
154
+ */
155
+ declare function isEvmChainId(value: string | null | undefined): boolean;
156
+ /**
157
+ * The chain to operate on, or undefined when it cannot be known.
158
+ *
159
+ * An explicitly requested chain wins over the connected one; a malformed value
160
+ * from either source resolves to undefined rather than being replaced by a
161
+ * guess, because the caller asked for something specific and got it wrong.
162
+ */
163
+ declare function resolveEvmChainId(requested: string | null | undefined, connected: string | null | undefined): string | undefined;
164
+
165
+ /**
166
+ * Does this token belong to the chain the wallet is pointed at?
167
+ *
168
+ * `TokenConfig.chainId` existed but was only ever read as a display label and
169
+ * a cache key. Nothing compared it to the connected chain, so a hook handed a
170
+ * mainnet token config while the wallet sat on Polygon would read — or
171
+ * approve — whatever contract happens to occupy that address on Polygon.
172
+ * Usually that address holds nothing and the call reverts, which is merely
173
+ * confusing. When it holds a different token, `allowance()` returns a
174
+ * plausible number for the wrong asset, and `approve()` grants a spender
175
+ * rights over something the user never looked at.
176
+ *
177
+ * Framework-agnostic on purpose: it must not import React.
178
+ */
179
+
180
+ /**
181
+ * A mismatch, or undefined when the token is on the active chain — or when
182
+ * there is not enough information to tell.
183
+ *
184
+ * Silence on "cannot tell" is deliberate. A token config without a chainId, or
185
+ * a session with no resolved chain, is a gap in what the caller supplied; a
186
+ * guess in either direction would be worse than letting the call proceed and
187
+ * fail on its own terms.
188
+ */
189
+ declare function tokenChainMismatch(token: Pick<TokenConfig, "address" | "chainId">, activeChainId: number | undefined): WalletError | undefined;
190
+
191
+ /**
192
+ * Decimals cache keyed by the token it belongs to.
193
+ *
194
+ * The ERC-20 hooks cached decimals in a bare `useRef` seeded from the token
195
+ * prop. `useRef` only takes its initial value on mount, and nothing reset it,
196
+ * so a component that lets the user switch tokens kept the first token's
197
+ * precision: picking USDC (6) and then DAI (18) converted "100" with 6
198
+ * decimals and transferred 0.0000000001 DAI. The transaction succeeded, so
199
+ * nothing surfaced the mistake.
200
+ *
201
+ * Keying the cache by the token makes a stale hit impossible without needing
202
+ * an effect to fire first.
203
+ */
204
+ interface TokenIdentity {
205
+ chainId?: number;
206
+ address: string;
207
+ }
208
+ interface DecimalsCache {
209
+ key: string;
210
+ value: number | undefined;
211
+ }
212
+ declare function tokenKey(token: TokenIdentity): string;
213
+ declare function createDecimalsCache(token: TokenIdentity, known?: number): DecimalsCache;
214
+ /**
215
+ * Cached decimals for `token`, or undefined when the cache holds another
216
+ * token's value. Resets the cache as a side effect so the next write lands
217
+ * against the right key.
218
+ */
219
+ declare function readDecimals(cache: DecimalsCache, token: TokenIdentity, known?: number): number | undefined;
220
+ declare function writeDecimals(cache: DecimalsCache, token: TokenIdentity, value: number): void;
221
+
222
+ /**
223
+ * Normalize an EIP-1193 provider error into a WalletError.
224
+ *
225
+ * Hooks were collapsing every non-WalletError into one code — `useSwitchChain`
226
+ * labelled a user pressing Cancel as `chain_unsupported`, so the UI told them
227
+ * the chain was unavailable when they had simply declined. The codes below are
228
+ * the ones wallets actually send, and they mean different things to a caller:
229
+ * one is retryable by asking again, the other is not retryable at all until
230
+ * the chain is added.
231
+ *
232
+ * Framework-agnostic on purpose: it must not import React.
233
+ */
234
+
235
+ /**
236
+ * Map a chain-switch failure onto the code that describes it.
237
+ *
238
+ * `fallback` is used when the wallet gave nothing to go on, so the caller
239
+ * still decides what an unexplained failure means in its own context.
240
+ */
241
+ declare function toChainSwitchError(error: unknown, fallback?: WalletErrorCode): WalletError;
242
+
243
+ /**
244
+ * Decode an EVM revert payload into something a user can act on.
245
+ *
246
+ * This text is what a wallet shows when a simulation says a transaction would
247
+ * fail, so it is worth getting right and worth sharing: the logic is pure and
248
+ * every binding needs the same answer.
249
+ *
250
+ * Two things are handled that the inlined version did not. The ABI length
251
+ * word is attacker-controlled, so a payload can declare a longer string than
252
+ * it carries; decoding that produced a run of NUL bytes rather than an honest
253
+ * "could not decode". And a Panic was reported as "Built-in failure" with the
254
+ * code discarded, which is the one piece of information that distinguishes an
255
+ * overflow from a division by zero.
256
+ */
257
+ /**
258
+ * @param data Raw revert data from the node, with or without the 0x prefix.
259
+ * @returns The decoded reason, or undefined when the payload is not a shape
260
+ * this understands — callers should fall back to the RPC error text rather
261
+ * than presenting a guess.
262
+ */
263
+ declare function decodeRevertReason(data: unknown): string | undefined;
264
+ /** Decoded reason if available, otherwise the node's own message. */
265
+ declare function resolveRevertReason(data: unknown, fallbackMessage: string): string;
266
+
267
+ /**
268
+ * Shared NameResolver instance for the name-resolution hooks.
269
+ *
270
+ * useResolveName and useLookupAddress each carried an identical getResolver
271
+ * with its own module-level singleton, so the two directions of the same
272
+ * lookup ran against separate resolvers and separate caches. One definition,
273
+ * one instance.
274
+ */
275
+
276
+ /**
277
+ * @param config When supplied, returns a dedicated resolver rather than the
278
+ * shared one — a caller asking for specific settings must not be handed an
279
+ * instance configured by someone else.
280
+ */
281
+ declare function getResolver(config?: NameResolverConfig): NameResolver;
282
+ /** Test seam: drop the shared instance. */
283
+ declare function __resetResolverForTests(): void;
284
+
285
+ declare function withTimeout<T>(promise: Promise<T>, ms: number, label?: string): Promise<T>;
286
+ interface RetryOptions {
287
+ maxRetries: number;
288
+ baseDelay: number;
289
+ label?: string;
290
+ }
291
+ declare function withRetry<T>(fn: () => Promise<T>, options: RetryOptions): Promise<T>;
292
+ /** Convert EIP-1193 chainChanged values into the CAIP-2 form used by sessions. */
293
+ declare function normalizeEip155ChainId(value: unknown): string | undefined;
294
+ declare function toEip155Accounts(accounts: unknown[], chainId: string): string[];
295
+ /**
296
+ * Numeric EIP-155 chain id, or undefined for any other namespace.
297
+ *
298
+ * The previous implementation took the last CAIP segment and ran parseInt over
299
+ * it, which stops at the first non-digit: `solana:5eykt4Us…` became 5 — an
300
+ * EVM chain id — so asking for Solana mainnet's token list returned Goerli's,
301
+ * and `xrpl:0` became chain 0. Anchor the whole reference instead.
302
+ */
303
+ declare function eip155ChainIdToNumber(chainId: string): number | undefined;
304
+ type Web3Action = {
305
+ type: "SET_STATUS";
306
+ payload: ConnectionStatus;
307
+ } | {
308
+ type: "SET_SESSION";
309
+ payload: Web3State["session"];
310
+ } | {
311
+ type: "SET_ACCOUNTS";
312
+ payload: string[];
313
+ } | {
314
+ type: "SET_CHAIN";
315
+ payload: string | null;
316
+ } | {
317
+ type: "SET_ERROR";
318
+ payload: Error | null;
319
+ } | {
320
+ type: "RESET";
321
+ };
322
+ declare const initialWeb3State: Web3State;
323
+ declare function web3Reducer(state: Web3State, action: Web3Action): Web3State;
324
+
325
+ export { type ChainInfo, type ConnectionStatus, type DecimalsCache, type RetryOptions, type TokenIdentity, type ViemChainShape, type WalletChain, type Web3Action, type Web3State, __resetResolverForTests, chainNamespace, chainNumber, chainsForNamespace, createDecimalsCache, decodeRevertReason, describeChain, eip155ChainIdToNumber, getResolver, initialWeb3State, isEvmChainId, normalizeEip155ChainId, readDecimals, resolveChain, resolveEvmChainId, resolveRevertReason, toChainSwitchError, toEip155Accounts, toViemChain, tokenChainMismatch, tokenKey, web3Reducer, withRetry, withTimeout, writeDecimals };