@naculus/connect-appkit-core 0.2.2 → 0.2.4

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/dist/index.cjs CHANGED
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var src_exports = {};
22
22
  __export(src_exports, {
23
23
  __resetResolverForTests: () => __resetResolverForTests,
24
+ atomicSupportFor: () => atomicSupportFor,
24
25
  chainNamespace: () => chainNamespace,
25
26
  chainNumber: () => chainNumber,
26
27
  chainsForNamespace: () => chainsForNamespace,
@@ -31,11 +32,14 @@ __export(src_exports, {
31
32
  getResolver: () => getResolver,
32
33
  initialWeb3State: () => initialWeb3State,
33
34
  isEvmChainId: () => isEvmChainId,
35
+ normalizeCapabilities: () => normalizeCapabilities,
34
36
  normalizeEip155ChainId: () => normalizeEip155ChainId,
35
37
  readDecimals: () => readDecimals,
38
+ readSolanaRoles: () => readSolanaRoles,
36
39
  resolveChain: () => resolveChain,
37
40
  resolveEvmChainId: () => resolveEvmChainId,
38
41
  resolveRevertReason: () => resolveRevertReason,
42
+ selectChainCapabilities: () => selectChainCapabilities,
39
43
  toChainSwitchError: () => toChainSwitchError,
40
44
  toEip155Accounts: () => toEip155Accounts,
41
45
  toViemChain: () => toViemChain,
@@ -48,13 +52,53 @@ __export(src_exports, {
48
52
  });
49
53
  module.exports = __toCommonJS(src_exports);
50
54
 
51
- // src/chain-selection.ts
55
+ // src/capabilities.ts
52
56
  var import_connect_core = require("@naculus/connect-core");
57
+ function normalizeCapabilities(raw) {
58
+ const out = {};
59
+ for (const [key, entry] of Object.entries(raw ?? {})) {
60
+ if (!entry || typeof entry !== "object") continue;
61
+ const { supported, maxBatchSize } = (0, import_connect_core.readAtomicSupport)(
62
+ entry
63
+ );
64
+ out[key] = {
65
+ chainId: key,
66
+ atomic: supported ? "supported" : "unsupported",
67
+ ...maxBatchSize === void 0 ? {} : { maxBatchSize },
68
+ raw: entry
69
+ };
70
+ }
71
+ return out;
72
+ }
73
+ function selectChainCapabilities(capabilities, chainId) {
74
+ if (!capabilities || !chainId) return null;
75
+ const hex = (0, import_connect_core.caip2ToHexChain)(chainId);
76
+ return capabilities[chainId] ?? (hex ? capabilities[hex] : void 0) ?? null;
77
+ }
78
+ function atomicSupportFor(capabilities, chainId) {
79
+ return selectChainCapabilities(capabilities, chainId)?.atomic ?? "unknown";
80
+ }
81
+
82
+ // src/solana-roles.ts
83
+ function readSolanaRoles(connector, session) {
84
+ if (!session) return { roles: null, absence: "no_session" };
85
+ if (session.walletType !== "solana") {
86
+ return { roles: null, absence: "not_solana" };
87
+ }
88
+ if (typeof connector?.getRoles !== "function") {
89
+ return { roles: null, absence: "unreported" };
90
+ }
91
+ const roles = connector.getRoles(session);
92
+ return roles ? { roles, absence: null } : { roles: null, absence: "no_session" };
93
+ }
94
+
95
+ // src/chain-selection.ts
96
+ var import_connect_core2 = require("@naculus/connect-core");
53
97
  function chainNumber(chain) {
54
- return (0, import_connect_core.eip155Reference)(chain.caip2);
98
+ return (0, import_connect_core2.eip155Reference)(chain.caip2);
55
99
  }
56
100
  function chainNamespace(chain) {
57
- return (0, import_connect_core.namespaceOf)(chain.caip2);
101
+ return (0, import_connect_core2.namespaceOf)(chain.caip2);
58
102
  }
59
103
  function resolveChain(chains, chainId) {
60
104
  if (!chainId) return null;
@@ -62,13 +106,13 @@ function resolveChain(chains, chainId) {
62
106
  }
63
107
  function chainsForNamespace(chains, chainId) {
64
108
  if (!chainId) return chains;
65
- const namespace = (0, import_connect_core.namespaceOf)(chainId);
109
+ const namespace = (0, import_connect_core2.namespaceOf)(chainId);
66
110
  if (!namespace) return chains;
67
111
  return chains.filter((c) => chainNamespace(c) === namespace);
68
112
  }
69
113
  function describeChain(chains, chainId) {
70
114
  if (!chainId) return null;
71
- const namespace = (0, import_connect_core.namespaceOf)(chainId);
115
+ const namespace = (0, import_connect_core2.namespaceOf)(chainId);
72
116
  if (!namespace) return null;
73
117
  const known = resolveChain(chains, chainId);
74
118
  if (known) {
@@ -84,10 +128,10 @@ function describeChain(chains, chainId) {
84
128
  }
85
129
 
86
130
  // src/viem-chain.ts
87
- var import_connect_core2 = require("@naculus/connect-core");
131
+ var import_connect_core3 = require("@naculus/connect-core");
88
132
  function toViemChain(chain, chainNumber2) {
89
133
  if (chainNumber2 === null || !chain.rpcUrl) return null;
90
- const symbol = chain.token ?? import_connect_core2.CHAINS[chainNumber2]?.nativeCurrency?.symbol ?? "";
134
+ const symbol = chain.token ?? import_connect_core3.CHAINS[chainNumber2]?.nativeCurrency?.symbol ?? "";
91
135
  return {
92
136
  id: chainNumber2,
93
137
  name: chain.name,
@@ -113,7 +157,7 @@ function resolveEvmChainId(requested, connected) {
113
157
  }
114
158
 
115
159
  // src/token-chain.ts
116
- var import_connect_core3 = require("@naculus/connect-core");
160
+ var import_connect_core4 = require("@naculus/connect-core");
117
161
  function tokenChainMismatch(token, activeChainId) {
118
162
  if (typeof token?.chainId !== "number" || !Number.isFinite(token.chainId)) {
119
163
  return void 0;
@@ -122,7 +166,7 @@ function tokenChainMismatch(token, activeChainId) {
122
166
  return void 0;
123
167
  }
124
168
  if (token.chainId === activeChainId) return void 0;
125
- return new import_connect_core3.WalletError(
169
+ return new import_connect_core4.WalletError(
126
170
  "chain_mismatch",
127
171
  `Token ${token.address} is configured for chain ${token.chainId}, but the wallet is on chain ${activeChainId}. Switch chains before continuing \u2014 the same address holds a different contract on each chain.`
128
172
  );
@@ -149,7 +193,7 @@ function writeDecimals(cache, token, value) {
149
193
  }
150
194
 
151
195
  // src/provider-errors.ts
152
- var import_connect_core4 = require("@naculus/connect-core");
196
+ var import_connect_core5 = require("@naculus/connect-core");
153
197
  var USER_REJECTED = 4001;
154
198
  var UNRECOGNIZED_CHAIN = 4902;
155
199
  function providerCode(error) {
@@ -161,19 +205,19 @@ function providerCode(error) {
161
205
  return nested ? providerCode(nested) : void 0;
162
206
  }
163
207
  function toChainSwitchError(error, fallback = "chain_unsupported") {
164
- if (error instanceof import_connect_core4.WalletError) return error;
208
+ if (error instanceof import_connect_core5.WalletError) return error;
165
209
  const message = error instanceof Error ? error.message : "Unknown error during chain switch";
166
210
  switch (providerCode(error)) {
167
211
  case USER_REJECTED:
168
- return new import_connect_core4.WalletError("chain_switch_rejected", message, error);
212
+ return new import_connect_core5.WalletError("chain_switch_rejected", message, error);
169
213
  case UNRECOGNIZED_CHAIN:
170
- return new import_connect_core4.WalletError(
214
+ return new import_connect_core5.WalletError(
171
215
  "chain_unsupported",
172
216
  `${message} (the wallet does not have this chain configured)`,
173
217
  error
174
218
  );
175
219
  default:
176
- return new import_connect_core4.WalletError(fallback, message, error);
220
+ return new import_connect_core5.WalletError(fallback, message, error);
177
221
  }
178
222
  }
179
223
 
@@ -222,11 +266,11 @@ function resolveRevertReason(data, fallbackMessage) {
222
266
  }
223
267
 
224
268
  // src/name-resolver.ts
225
- var import_connect_core5 = require("@naculus/connect-core");
269
+ var import_connect_core6 = require("@naculus/connect-core");
226
270
  var sharedResolver = null;
227
271
  function getResolver(config) {
228
- if (config) return new import_connect_core5.NameResolver(config);
229
- if (!sharedResolver) sharedResolver = new import_connect_core5.NameResolver();
272
+ if (config) return new import_connect_core6.NameResolver(config);
273
+ if (!sharedResolver) sharedResolver = new import_connect_core6.NameResolver();
230
274
  return sharedResolver;
231
275
  }
232
276
  function __resetResolverForTests() {
@@ -234,7 +278,7 @@ function __resetResolverForTests() {
234
278
  }
235
279
 
236
280
  // src/web3-store.ts
237
- var import_connect_core6 = require("@naculus/connect-core");
281
+ var import_connect_core7 = require("@naculus/connect-core");
238
282
  function withTimeout(promise, ms, label = "Operation") {
239
283
  let timeoutId;
240
284
  const timeout = new Promise((_, reject) => {
@@ -256,7 +300,7 @@ async function withRetry(fn, options) {
256
300
  lastError = error;
257
301
  if (attempt < options.maxRetries) {
258
302
  const delay = options.baseDelay * 2 ** attempt;
259
- import_connect_core6.logger.warn(
303
+ import_connect_core7.logger.warn(
260
304
  "appkit/core",
261
305
  `Retry attempt ${attempt + 1}/${options.maxRetries} after ${delay}ms`
262
306
  );
@@ -324,6 +368,7 @@ function web3Reducer(state, action) {
324
368
  // Annotate the CommonJS export names for ESM import in node:
325
369
  0 && (module.exports = {
326
370
  __resetResolverForTests,
371
+ atomicSupportFor,
327
372
  chainNamespace,
328
373
  chainNumber,
329
374
  chainsForNamespace,
@@ -334,11 +379,14 @@ function web3Reducer(state, action) {
334
379
  getResolver,
335
380
  initialWeb3State,
336
381
  isEvmChainId,
382
+ normalizeCapabilities,
337
383
  normalizeEip155ChainId,
338
384
  readDecimals,
385
+ readSolanaRoles,
339
386
  resolveChain,
340
387
  resolveEvmChainId,
341
388
  resolveRevertReason,
389
+ selectChainCapabilities,
342
390
  toChainSwitchError,
343
391
  toEip155Accounts,
344
392
  toViemChain,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/chain-selection.ts","../src/viem-chain.ts","../src/aa-chain.ts","../src/token-chain.ts","../src/token-decimals.ts","../src/provider-errors.ts","../src/revert-reason.ts","../src/name-resolver.ts","../src/web3-store.ts"],"sourcesContent":["/**\n * `@naculus/connect-appkit-core` — the half of appkit that is not about React.\n *\n * appkit's shape is \"write the Stencil components once, generate the React and\n * Vue wrappers\", and that works for anything visual. It never covered the\n * logic: the React package carried ~8.5k hand-written lines against Vue's\n * ~280, and only two files in it contain JSX. The rest was logic wearing React\n * clothes, reachable from exactly one framework.\n *\n * Everything exported here imports no framework. A React hook and a Vue\n * composable over it are each about ten lines, and they share every decision\n * that took thought rather than each making it again.\n */\n\nexport * from \"./types\";\n\n// ── Chains ────────────────────────────────────────────────────────\nexport {\n chainNamespace,\n chainNumber,\n chainsForNamespace,\n describeChain,\n resolveChain,\n} from \"./chain-selection\";\n\nexport { toViemChain } from \"./viem-chain\";\nexport type { ViemChainShape } from \"./viem-chain\";\n\n// ── Account abstraction ───────────────────────────────────────────\nexport * from \"./aa-chain\";\n\n// ── Tokens ────────────────────────────────────────────────────────\nexport * from \"./token-chain\";\nexport * from \"./token-decimals\";\n\n// ── Errors ────────────────────────────────────────────────────────\nexport * from \"./provider-errors\";\nexport * from \"./revert-reason\";\n\n// ── Names ─────────────────────────────────────────────────────────\nexport * from \"./name-resolver\";\n\n// ── Connection state machine ──────────────────────────────────────\nexport * from \"./web3-store\";\n","/**\n * Which chains a session can actually switch to, and what the current one is.\n *\n * Framework-neutral, and deliberately built on `parseChainId` from\n * `@naculus/connect-core` rather than on another `startsWith(\"eip155:\")`.\n */\n\nimport { eip155Reference, namespaceOf } from \"@naculus/connect-core\";\nimport type { ChainInfo, WalletChain } from \"./types\";\n\n/**\n * The EIP-155 chain number, or null when this is not an EVM chain.\n *\n * Callers that need a number — a viem client, an ERC-20 read — must handle\n * the null rather than defaulting, because there is no number that means\n * \"Solana\" and any stand-in would address a real EVM chain.\n */\nexport function chainNumber(chain: WalletChain): number | null {\n return eip155Reference(chain.caip2);\n}\n\n/** The namespace a chain belongs to, read from its CAIP-2 id. */\nexport function chainNamespace(chain: WalletChain): string | null {\n return namespaceOf(chain.caip2);\n}\n\n/**\n * The configured chain for a CAIP-2 id, or null.\n *\n * A string comparison now that `WalletChain` is keyed by CAIP-2. It used to\n * pull an integer out of the id and compare that, which no non-EVM chain\n * could ever match.\n */\nexport function resolveChain(\n chains: WalletChain[],\n chainId: string | null | undefined,\n): WalletChain | null {\n if (!chainId) return null;\n return chains.find((c) => c.caip2 === chainId) ?? null;\n}\n\n/**\n * The chains worth offering for the session's namespace.\n *\n * This used to return every configured chain regardless of what was\n * connected, so a Solana wallet was offered Ethereum and Polygon in the\n * switcher. Clicking one asked a wallet that has no concept of EIP-155 to\n * switch to chain 137 — an action the interface presented as available and\n * that could never work.\n *\n * With no connected chain, everything is offered: nothing has been ruled out\n * yet, and hiding the list would leave a user unable to pick.\n */\nexport function chainsForNamespace(\n chains: WalletChain[],\n chainId: string | null | undefined,\n): WalletChain[] {\n if (!chainId) return chains;\n const namespace = namespaceOf(chainId);\n if (!namespace) return chains;\n return chains.filter((c) => chainNamespace(c) === namespace);\n}\n\n/**\n * A description of the connected chain for display.\n *\n * Returns something for every namespace. It used to return null for anything\n * that was not EIP-155, so a connected Solana wallet had no chain to show at\n * all and the interface fell back to \"Unknown Chain\".\n */\nexport function describeChain(\n chains: WalletChain[],\n chainId: string | null | undefined,\n): ChainInfo | null {\n if (!chainId) return null;\n const namespace = namespaceOf(chainId);\n if (!namespace) return null;\n\n const known = resolveChain(chains, chainId);\n if (known) {\n return { namespace, chainId, name: known.name, selected: true };\n }\n\n // No configured entry. Name it by its CAIP-2 reference rather than\n // inventing one: \"Chain 5\" for a Solana cluster would be a fabrication, and\n // the reference is at least something a user can look up.\n const reference = chainId.slice(namespace.length + 1);\n return {\n namespace,\n chainId,\n name: namespace === \"eip155\" ? `Chain ${reference}` : reference,\n selected: true,\n };\n}\n","/**\n * The viem `Chain` shape for a configured EVM chain.\n *\n * Three hooks each built this inline — `useBalance`, `useTokenBalance` and\n * `useViemClient` — with identical bodies and the same `?? \"ETH\"` in each.\n * Three copies of a fallback is three places for it to be wrong.\n *\n * Returns a plain object rather than importing viem, so this package stays\n * free of a dependency only the React bindings need. The shape satisfies\n * viem's `Chain` structurally.\n */\n\nimport { CHAINS } from \"@naculus/connect-core\";\nimport type { WalletChain } from \"./types\";\n\nexport interface ViemChainShape {\n id: number;\n name: string;\n nativeCurrency: { name: string; symbol: string; decimals: number };\n rpcUrls: {\n default: { http: string[] };\n public: { http: string[] };\n };\n}\n\n/**\n * Build the chain descriptor a viem client needs, or null when this chain is\n * not one viem can talk to.\n *\n * Null for a non-EVM chain rather than a stand-in number: viem speaks to EVM\n * nodes, and any number chosen to fill the gap addresses a real chain that is\n * not the one connected.\n *\n * The native symbol comes from the chain's own `token`, then from the shared\n * chain registry. For an unlisted chain it stays empty rather than claiming\n * the asset is ETH. viem requires a string here; the user-facing symbol comes\n * from `useBalance().symbol`, which stays null when it is unknown.\n */\nexport function toViemChain(\n chain: WalletChain,\n chainNumber: number | null,\n): ViemChainShape | null {\n if (chainNumber === null || !chain.rpcUrl) return null;\n\n const symbol =\n chain.token ?? CHAINS[chainNumber]?.nativeCurrency?.symbol ?? \"\";\n\n return {\n id: chainNumber,\n name: chain.name,\n // Every EIP-155 native currency is 18 decimals by protocol, so this is a\n // constant rather than an assumption. ERC-20 decimals are per-token and\n // are never guessed anywhere in this codebase.\n nativeCurrency: { name: symbol, symbol, decimals: 18 },\n rpcUrls: {\n default: { http: [chain.rpcUrl] },\n public: { http: [chain.rpcUrl] },\n },\n };\n}\n","/**\n * Which EVM chain does an account-abstraction operation belong to?\n *\n * The smart-account hooks each resolved this as\n * `options.chainId ?? connectedChainId ?? \"eip155:1\"`, and each then guarded\n * with `if (connectedChainId && connectedChainId !== target)`. The guard is\n * skipped precisely when the mainnet default is used, so the one branch that\n * assumes a chain is the one branch that never checks it.\n *\n * That branch is reachable: a caller passing its own `signer` needs no\n * session, so `connectedChainId` is legitimately absent. It would then build\n * and sign a UserOperation for chain 1 — with chain 1's EntryPoint — while its\n * configured RPC and bundler point somewhere else. The chain ID is part of the\n * UserOperation hash, so the user approves a commitment to a chain they were\n * never shown.\n *\n * Resolving to undefined instead lets each caller refuse in whatever way suits\n * it. Framework-agnostic on purpose: it must not import React.\n */\n\n/** CAIP-2 for an EVM chain. The reference is a decimal chain ID with no leading zero. */\nconst EVM_CHAIN_ID = /^eip155:[1-9][0-9]*$/;\n\nexport function isEvmChainId(value: string | null | undefined): boolean {\n return typeof value === \"string\" && EVM_CHAIN_ID.test(value);\n}\n\n/**\n * The chain to operate on, or undefined when it cannot be known.\n *\n * An explicitly requested chain wins over the connected one; a malformed value\n * from either source resolves to undefined rather than being replaced by a\n * guess, because the caller asked for something specific and got it wrong.\n */\nexport function resolveEvmChainId(\n requested: string | null | undefined,\n connected: string | null | undefined,\n): string | undefined {\n const candidate = requested ?? connected ?? undefined;\n return isEvmChainId(candidate) ? candidate : undefined;\n}\n","/**\n * Does this token belong to the chain the wallet is pointed at?\n *\n * `TokenConfig.chainId` existed but was only ever read as a display label and\n * a cache key. Nothing compared it to the connected chain, so a hook handed a\n * mainnet token config while the wallet sat on Polygon would read — or\n * approve — whatever contract happens to occupy that address on Polygon.\n * Usually that address holds nothing and the call reverts, which is merely\n * confusing. When it holds a different token, `allowance()` returns a\n * plausible number for the wrong asset, and `approve()` grants a spender\n * rights over something the user never looked at.\n *\n * Framework-agnostic on purpose: it must not import React.\n */\n\nimport type { TokenConfig } from \"@naculus/connect-core\";\nimport { WalletError } from \"@naculus/connect-core\";\n\n/**\n * A mismatch, or undefined when the token is on the active chain — or when\n * there is not enough information to tell.\n *\n * Silence on \"cannot tell\" is deliberate. A token config without a chainId, or\n * a session with no resolved chain, is a gap in what the caller supplied; a\n * guess in either direction would be worse than letting the call proceed and\n * fail on its own terms.\n */\nexport function tokenChainMismatch(\n token: Pick<TokenConfig, \"address\" | \"chainId\">,\n activeChainId: number | undefined,\n): WalletError | undefined {\n if (typeof token?.chainId !== \"number\" || !Number.isFinite(token.chainId)) {\n return undefined;\n }\n if (typeof activeChainId !== \"number\" || !Number.isFinite(activeChainId)) {\n return undefined;\n }\n if (token.chainId === activeChainId) return undefined;\n\n return new WalletError(\n \"chain_mismatch\",\n `Token ${token.address} is configured for chain ${token.chainId}, but the ` +\n `wallet is on chain ${activeChainId}. Switch chains before continuing — ` +\n `the same address holds a different contract on each chain.`,\n );\n}\n","/**\n * Decimals cache keyed by the token it belongs to.\n *\n * The ERC-20 hooks cached decimals in a bare `useRef` seeded from the token\n * prop. `useRef` only takes its initial value on mount, and nothing reset it,\n * so a component that lets the user switch tokens kept the first token's\n * precision: picking USDC (6) and then DAI (18) converted \"100\" with 6\n * decimals and transferred 0.0000000001 DAI. The transaction succeeded, so\n * nothing surfaced the mistake.\n *\n * Keying the cache by the token makes a stale hit impossible without needing\n * an effect to fire first.\n */\n\nexport interface TokenIdentity {\n chainId?: number;\n address: string;\n}\n\nexport interface DecimalsCache {\n key: string;\n value: number | undefined;\n}\n\nexport function tokenKey(token: TokenIdentity): string {\n return `${token.chainId ?? \"?\"}:${token.address.toLowerCase()}`;\n}\n\nexport function createDecimalsCache(\n token: TokenIdentity,\n known?: number,\n): DecimalsCache {\n return { key: tokenKey(token), value: known };\n}\n\n/**\n * Cached decimals for `token`, or undefined when the cache holds another\n * token's value. Resets the cache as a side effect so the next write lands\n * against the right key.\n */\nexport function readDecimals(\n cache: DecimalsCache,\n token: TokenIdentity,\n known?: number,\n): number | undefined {\n const key = tokenKey(token);\n if (cache.key !== key) {\n cache.key = key;\n cache.value = known;\n }\n return cache.value;\n}\n\nexport function writeDecimals(\n cache: DecimalsCache,\n token: TokenIdentity,\n value: number,\n): void {\n cache.key = tokenKey(token);\n cache.value = value;\n}\n","/**\n * Normalize an EIP-1193 provider error into a WalletError.\n *\n * Hooks were collapsing every non-WalletError into one code — `useSwitchChain`\n * labelled a user pressing Cancel as `chain_unsupported`, so the UI told them\n * the chain was unavailable when they had simply declined. The codes below are\n * the ones wallets actually send, and they mean different things to a caller:\n * one is retryable by asking again, the other is not retryable at all until\n * the chain is added.\n *\n * Framework-agnostic on purpose: it must not import React.\n */\n\nimport type { WalletErrorCode } from \"@naculus/connect-core\";\nimport { WalletError } from \"@naculus/connect-core\";\n\n/** EIP-1193: the user rejected the request. */\nconst USER_REJECTED = 4001;\n/** EIP-3326: the wallet does not recognize the chain and must add it first. */\nconst UNRECOGNIZED_CHAIN = 4902;\n\nfunction providerCode(error: unknown): number | undefined {\n if (!error || typeof error !== \"object\") return undefined;\n const code = (error as { code?: unknown }).code;\n if (typeof code === \"number\") return code;\n // Some wallets stringify it, and some nest the original under `data`.\n if (typeof code === \"string\" && /^-?\\d+$/.test(code)) return Number(code);\n const nested = (error as { data?: { originalError?: unknown } }).data\n ?.originalError;\n return nested ? providerCode(nested) : undefined;\n}\n\n/**\n * Map a chain-switch failure onto the code that describes it.\n *\n * `fallback` is used when the wallet gave nothing to go on, so the caller\n * still decides what an unexplained failure means in its own context.\n */\nexport function toChainSwitchError(\n error: unknown,\n fallback: WalletErrorCode = \"chain_unsupported\",\n): WalletError {\n if (error instanceof WalletError) return error;\n\n const message =\n error instanceof Error\n ? error.message\n : \"Unknown error during chain switch\";\n\n switch (providerCode(error)) {\n case USER_REJECTED:\n return new WalletError(\"chain_switch_rejected\", message, error);\n case UNRECOGNIZED_CHAIN:\n return new WalletError(\n \"chain_unsupported\",\n `${message} (the wallet does not have this chain configured)`,\n error,\n );\n default:\n return new WalletError(fallback, message, error);\n }\n}\n","/**\n * Decode an EVM revert payload into something a user can act on.\n *\n * This text is what a wallet shows when a simulation says a transaction would\n * fail, so it is worth getting right and worth sharing: the logic is pure and\n * every binding needs the same answer.\n *\n * Two things are handled that the inlined version did not. The ABI length\n * word is attacker-controlled, so a payload can declare a longer string than\n * it carries; decoding that produced a run of NUL bytes rather than an honest\n * \"could not decode\". And a Panic was reported as \"Built-in failure\" with the\n * code discarded, which is the one piece of information that distinguishes an\n * overflow from a division by zero.\n */\n\n/** keccak256(\"Error(string)\")[0:4] */\nconst ERROR_STRING_SELECTOR = \"08c379a0\";\n/** keccak256(\"Panic(uint256)\")[0:4] */\nconst PANIC_SELECTOR = \"4e487b71\";\n\n/**\n * Panic codes defined by Solidity. Unlisted codes still surface their raw\n * value rather than being flattened into a generic message.\n */\nconst PANIC_REASONS: Record<number, string> = {\n 0x01: \"assertion failed\",\n 0x11: \"arithmetic overflow or underflow\",\n 0x12: \"division or modulo by zero\",\n 0x21: \"invalid enum value\",\n 0x31: \"pop() on an empty array\",\n 0x32: \"array index out of bounds\",\n};\n\nfunction hexToUtf8(hex: string): string {\n const bytes = new Uint8Array(hex.length / 2);\n for (let i = 0; i < bytes.length; i++) {\n bytes[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n }\n return new TextDecoder().decode(bytes);\n}\n\n/**\n * @param data Raw revert data from the node, with or without the 0x prefix.\n * @returns The decoded reason, or undefined when the payload is not a shape\n * this understands — callers should fall back to the RPC error text rather\n * than presenting a guess.\n */\nexport function decodeRevertReason(data: unknown): string | undefined {\n if (typeof data !== \"string\" || data.length === 0) return undefined;\n const clean = data.startsWith(\"0x\") ? data.slice(2) : data;\n if (!/^[0-9a-fA-F]*$/.test(clean)) return undefined;\n\n if (clean.startsWith(ERROR_STRING_SELECTOR)) {\n // selector(8) + offset(64) + length(64) + payload\n if (clean.length < 136) return undefined;\n const declared = Number.parseInt(clean.slice(72, 136), 16);\n if (!Number.isSafeInteger(declared) || declared <= 0) return undefined;\n const available = (clean.length - 136) / 2;\n // Refuse a payload that promises more than it carries instead of padding\n // the difference with NULs and calling it a revert reason.\n if (declared > available) return undefined;\n return hexToUtf8(clean.slice(136, 136 + declared * 2));\n }\n\n if (clean.startsWith(PANIC_SELECTOR)) {\n if (clean.length < 8 + 64) return \"Panic\";\n const code = Number.parseInt(clean.slice(8, 72), 16);\n if (!Number.isSafeInteger(code)) return \"Panic\";\n const known = PANIC_REASONS[code];\n const hex = `0x${code.toString(16).padStart(2, \"0\")}`;\n return known ? `Panic ${hex}: ${known}` : `Panic ${hex}`;\n }\n\n return undefined;\n}\n\n/** Decoded reason if available, otherwise the node's own message. */\nexport function resolveRevertReason(\n data: unknown,\n fallbackMessage: string,\n): string {\n return (\n decodeRevertReason(data) ??\n (fallbackMessage.includes(\"revert\")\n ? fallbackMessage\n : \"Transaction reverted\")\n );\n}\n","/**\n * Shared NameResolver instance for the name-resolution hooks.\n *\n * useResolveName and useLookupAddress each carried an identical getResolver\n * with its own module-level singleton, so the two directions of the same\n * lookup ran against separate resolvers and separate caches. One definition,\n * one instance.\n */\nimport { NameResolver } from \"@naculus/connect-core\";\nimport type { NameResolverConfig } from \"@naculus/connect-core\";\n\nlet sharedResolver: NameResolver | null = null;\n\n/**\n * @param config When supplied, returns a dedicated resolver rather than the\n * shared one — a caller asking for specific settings must not be handed an\n * instance configured by someone else.\n */\nexport function getResolver(config?: NameResolverConfig): NameResolver {\n if (config) return new NameResolver(config);\n if (!sharedResolver) sharedResolver = new NameResolver();\n return sharedResolver;\n}\n\n/** Test seam: drop the shared instance. */\nexport function __resetResolverForTests(): void {\n sharedResolver = null;\n}\n","/**\n * Framework-agnostic core of the connect provider.\n *\n * appkit's stated shape is \"write the Stencil components once, generate the\n * React and Vue wrappers\". In practice `react/` carries ~8.5k hand-written\n * lines against `vue/`'s ~280, and only two files in `react/src` contain JSX —\n * the rest is logic wearing React clothes. Web Components also cannot serve\n * React Native or Flutter, so the presentation layer was never going to be the\n * thing worth sharing.\n *\n * This module is the first piece moved out: the state machine and the pure\n * helpers around it, with no React import. Everything here is consumable from\n * Vue, Svelte, or a native binding as-is, and `web3-store.test.ts` asserts the\n * absence of a framework import so the boundary cannot rot back.\n */\nimport { logger } from \"@naculus/connect-core\";\nimport type { ConnectionStatus, Web3State } from \"./types\";\n\n// ── Async helpers ──────────────────────────────────────────────────────\n\nexport function withTimeout<T>(\n promise: Promise<T>,\n ms: number,\n label = \"Operation\",\n): Promise<T> {\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<T>((_, reject) => {\n timeoutId = setTimeout(\n () => reject(new Error(`${label} timed out after ${ms}ms`)),\n ms,\n );\n });\n return Promise.race([promise, timeout]).finally(() => {\n if (timeoutId !== undefined) clearTimeout(timeoutId);\n });\n}\n\nexport interface RetryOptions {\n maxRetries: number;\n baseDelay: number;\n label?: string;\n}\n\nexport async function withRetry<T>(\n fn: () => Promise<T>,\n options: RetryOptions,\n): Promise<T> {\n let lastError: unknown;\n for (let attempt = 0; attempt <= options.maxRetries; attempt++) {\n try {\n return await fn();\n } catch (error) {\n lastError = error;\n if (attempt < options.maxRetries) {\n const delay = options.baseDelay * 2 ** attempt;\n logger.warn(\n \"appkit/core\",\n `Retry attempt ${attempt + 1}/${options.maxRetries} after ${delay}ms`,\n );\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n }\n throw lastError;\n}\n\n// ── CAIP normalisation ─────────────────────────────────────────────────\n\n/** Convert EIP-1193 chainChanged values into the CAIP-2 form used by sessions. */\nexport function normalizeEip155ChainId(value: unknown): string | undefined {\n if (typeof value !== \"string\") return undefined;\n try {\n if (value.startsWith(\"eip155:\")) {\n const reference = value.slice(\"eip155:\".length);\n if (!/^\\d+$/.test(reference)) return undefined;\n return `eip155:${BigInt(reference).toString(10)}`;\n }\n if (/^0x[0-9a-f]+$/i.test(value) || /^\\d+$/.test(value)) {\n return `eip155:${BigInt(value).toString(10)}`;\n }\n } catch {\n // Ignore malformed wallet events rather than corrupting session state.\n }\n return undefined;\n}\n\nexport function toEip155Accounts(\n accounts: unknown[],\n chainId: string,\n): string[] {\n const reference = chainId.split(\":\")[1];\n if (!reference) return [];\n return accounts\n .filter(\n (account): account is string =>\n typeof account === \"string\" && account.length > 0,\n )\n .map((account) =>\n account.includes(\":\") ? (account.split(\":\").pop() as string) : account,\n )\n .map((account) => `eip155:${reference}:${account}`);\n}\n\n/**\n * Numeric EIP-155 chain id, or undefined for any other namespace.\n *\n * The previous implementation took the last CAIP segment and ran parseInt over\n * it, which stops at the first non-digit: `solana:5eykt4Us…` became 5 — an\n * EVM chain id — so asking for Solana mainnet's token list returned Goerli's,\n * and `xrpl:0` became chain 0. Anchor the whole reference instead.\n */\nexport function eip155ChainIdToNumber(chainId: string): number | undefined {\n const match = /^eip155:([1-9][0-9]*)$/.exec(chainId);\n if (!match) return undefined;\n const numeric = Number(match[1]);\n return Number.isSafeInteger(numeric) ? numeric : undefined;\n}\n\n// ── State machine ──────────────────────────────────────────────────────\n\nexport type Web3Action =\n | { type: \"SET_STATUS\"; payload: ConnectionStatus }\n | { type: \"SET_SESSION\"; payload: Web3State[\"session\"] }\n | { type: \"SET_ACCOUNTS\"; payload: string[] }\n | { type: \"SET_CHAIN\"; payload: string | null }\n | { type: \"SET_ERROR\"; payload: Error | null }\n | { type: \"RESET\" };\n\nexport const initialWeb3State: Web3State = {\n status: \"disconnected\",\n session: null,\n accounts: [],\n chainId: null,\n error: null,\n};\n\nexport function web3Reducer(state: Web3State, action: Web3Action): Web3State {\n switch (action.type) {\n case \"SET_STATUS\":\n return { ...state, status: action.payload };\n case \"SET_SESSION\":\n return { ...state, session: action.payload };\n case \"SET_ACCOUNTS\":\n return { ...state, accounts: action.payload };\n case \"SET_CHAIN\":\n return { ...state, chainId: action.payload };\n case \"SET_ERROR\":\n return { ...state, error: action.payload };\n case \"RESET\":\n return initialWeb3State;\n default:\n return state;\n }\n}\n"],"mappings":"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","names":["import_connect_core","chainNumber","import_connect_core","import_connect_core","import_connect_core","import_connect_core"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/capabilities.ts","../src/solana-roles.ts","../src/chain-selection.ts","../src/viem-chain.ts","../src/aa-chain.ts","../src/token-chain.ts","../src/token-decimals.ts","../src/provider-errors.ts","../src/revert-reason.ts","../src/name-resolver.ts","../src/web3-store.ts"],"sourcesContent":["/**\n * `@naculus/connect-appkit-core` — the half of appkit that is not about React.\n *\n * appkit's shape is \"write the Stencil components once, generate the React and\n * Vue wrappers\", and that works for anything visual. It never covered the\n * logic: the React package carried ~8.5k hand-written lines against Vue's\n * ~280, and only two files in it contain JSX. The rest was logic wearing React\n * clothes, reachable from exactly one framework.\n *\n * Everything exported here imports no framework. A React hook and a Vue\n * composable over it are each about ten lines, and they share every decision\n * that took thought rather than each making it again.\n */\n\nexport * from \"./types\";\n\n// ── EIP-5792 capabilities ─────────────────────────────────────────\nexport {\n atomicSupportFor,\n normalizeCapabilities,\n selectChainCapabilities,\n type AtomicSupport,\n type ChainCapabilities,\n} from \"./capabilities\";\n\n// ── Solana signer roles ───────────────────────────────────────────\nexport {\n readSolanaRoles,\n type SolanaIdentity,\n type SolanaPayer,\n type SolanaRoles,\n type SolanaRolesAbsence,\n type SolanaRolesSource,\n type SolanaRolesState,\n type SolanaSigner,\n type SolanaWalletFeatures,\n} from \"./solana-roles\";\n\n// ── Chains ────────────────────────────────────────────────────────\nexport {\n chainNamespace,\n chainNumber,\n chainsForNamespace,\n describeChain,\n resolveChain,\n} from \"./chain-selection\";\n\nexport { toViemChain } from \"./viem-chain\";\nexport type { ViemChainShape } from \"./viem-chain\";\n\n// ── Account abstraction ───────────────────────────────────────────\nexport * from \"./aa-chain\";\n\n// ── Tokens ────────────────────────────────────────────────────────\nexport * from \"./token-chain\";\nexport * from \"./token-decimals\";\n\n// ── Errors ────────────────────────────────────────────────────────\nexport * from \"./provider-errors\";\nexport * from \"./revert-reason\";\n\n// ── Names ─────────────────────────────────────────────────────────\nexport * from \"./name-resolver\";\n\n// ── Connection state machine ──────────────────────────────────────\nexport * from \"./web3-store\";\n","/**\n * EIP-5792 capability answers, normalised once.\n *\n * Deciding how to execute before sending is the whole point of\n * `wallet_getCapabilities`, and the reasoning that turns a raw answer into\n * that decision is not React's. It lived inside the React hook, which meant a\n * Vue composable could only get it by copying — and a second copy of \"absence\n * is not a denial\" is exactly the copy that drifts back into `false`.\n */\n\nimport {\n caip2ToHexChain,\n readAtomicSupport,\n type WalletCapabilities,\n} from \"@naculus/connect-core\";\n\n/**\n * Whether a wallet can execute a batch atomically.\n *\n * Three values, not two. A wallet that does not implement\n * `wallet_getCapabilities` has not said no — it has said nothing, and EIP-5792\n * is explicit that absence is not a denial. Collapsing that into `false` sends\n * every such wallet down the sequential path, which is the one where an\n * approve can land and the swap it was for can fail.\n */\nexport type AtomicSupport = \"supported\" | \"unsupported\" | \"unknown\";\n\nexport interface ChainCapabilities {\n /** CAIP-2 chain this describes. */\n chainId: string;\n atomic: AtomicSupport;\n /** Largest batch the wallet will accept, when it says. */\n maxBatchSize?: number;\n /** The raw entry, for capabilities this SDK does not model. */\n raw: WalletCapabilities;\n}\n\n/**\n * Turn a wallet's raw answer into per-chain entries.\n *\n * Entries that are not objects are dropped rather than coerced: a wallet\n * answering `null` for a chain has said nothing about it, and inventing an\n * `\"unsupported\"` there would be the same mistake as reading absence as denial.\n */\nexport function normalizeCapabilities(\n raw: Record<string, unknown> | null | undefined,\n): Record<string, ChainCapabilities> {\n const out: Record<string, ChainCapabilities> = {};\n for (const [key, entry] of Object.entries(raw ?? {})) {\n if (!entry || typeof entry !== \"object\") continue;\n const { supported, maxBatchSize } = readAtomicSupport(\n entry as Record<string, unknown>,\n );\n out[key] = {\n chainId: key,\n atomic: supported ? \"supported\" : \"unsupported\",\n ...(maxBatchSize === undefined ? {} : { maxBatchSize }),\n raw: entry as WalletCapabilities,\n };\n }\n return out;\n}\n\n/**\n * Find the entry for a chain, by CAIP-2 or by the hex form.\n *\n * Connectors normalise keys to CAIP-2, but a wallet answering in raw hex\n * through a custom connector should still be found rather than reported as\n * \"unknown\" next to an entry that is sitting right there.\n */\nexport function selectChainCapabilities(\n capabilities: Record<string, ChainCapabilities> | null | undefined,\n chainId: string | null | undefined,\n): ChainCapabilities | null {\n if (!capabilities || !chainId) return null;\n const hex = caip2ToHexChain(chainId);\n return capabilities[chainId] ?? (hex ? capabilities[hex] : undefined) ?? null;\n}\n\n/**\n * Atomic support for a chain.\n *\n * `\"unknown\"` covers every case where we have not been told: no query yet, a\n * wallet without the method, a failed query, one still in flight. A caller\n * choosing between an atomic and a sequential path needs that distinction, not\n * a confident `false`.\n */\nexport function atomicSupportFor(\n capabilities: Record<string, ChainCapabilities> | null | undefined,\n chainId: string | null | undefined,\n): AtomicSupport {\n return selectChainCapabilities(capabilities, chainId)?.atomic ?? \"unknown\";\n}\n","/**\n * Solana signer roles, read once for both frameworks.\n *\n * `@naculus/connector-solana` splits a connected wallet into `identity`,\n * `signer` and `payer`, each `null` when the wallet declared it cannot fill\n * that job. That answer is the connector's. What this module owns is the part\n * a UI has to decide before it can show anything: *why* there is no answer —\n * because nothing is connected, because the connected wallet is not Solana,\n * or because the connector predates the roles API and has not said. Those are\n * three different screens, and a hook that hands back one `null` for all of\n * them makes every consumer rediscover the distinction.\n */\n\n/** Which account this is, with no ability to sign for it. */\nexport interface SolanaIdentity {\n /** Base58 public key. */\n readonly address: string;\n /** CAIP-2 chain this account is being used on. */\n readonly chain: string;\n}\n\n/** An account that will contribute a signature without submitting anything. */\nexport interface SolanaSigner extends SolanaIdentity {\n signMessage?: (message: Uint8Array) => Promise<Uint8Array>;\n signTransaction: (transaction: Uint8Array) => Promise<Uint8Array>;\n /** Absent, not throwing, when the wallet has no batch feature. */\n signAllTransactions?: (transactions: Uint8Array[]) => Promise<Uint8Array[]>;\n}\n\n/** An account that signs and broadcasts through the wallet's own RPC. */\nexport interface SolanaPayer extends SolanaIdentity {\n /** Returns the base58 transaction signature the cluster accepted. */\n signAndSendTransaction: (transaction: Uint8Array) => Promise<string>;\n}\n\n/** What the wallet declared, recorded at discovery. Two-state on purpose. */\nexport interface SolanaWalletFeatures {\n signMessage: boolean;\n signTransaction: boolean;\n signAllTransactions: boolean;\n signAndSendTransaction: boolean;\n}\n\n/**\n * The roles a connected account can fill.\n *\n * Structurally identical to `SolanaRoles` in `@naculus/connector-solana`,\n * declared here rather than imported because this package depends only on\n * `@naculus/connect-core`, and the connector's own declaration is what its\n * `getRoles` returns — a value of that type satisfies this one unchanged.\n */\nexport interface SolanaRoles {\n identity: SolanaIdentity;\n signer: SolanaSigner | null;\n payer: SolanaPayer | null;\n features: SolanaWalletFeatures;\n}\n\n/** The part of a connector this needs. `getRoles` is optional on purpose. */\nexport interface SolanaRolesSource {\n getRoles?: (session: unknown) => SolanaRoles | null;\n}\n\n/** The part of a session this needs. */\nexport interface SessionLike {\n walletType?: string;\n}\n\n/**\n * Why `roles` is null.\n *\n * - `no_session` — nothing connected, or the connector has no live session\n * for the one the app holds (a restored session before reconnect).\n * - `not_solana` — the connected wallet is on another namespace. Not an\n * error; a Solana panel simply does not apply.\n * - `unreported` — the connector does not expose `getRoles`. An older\n * `@naculus/connector-solana`, or a custom one. The wallet has not said\n * what it can do, which is not the same as saying it can do nothing.\n */\nexport type SolanaRolesAbsence = \"no_session\" | \"not_solana\" | \"unreported\";\n\nexport type SolanaRolesState =\n | { roles: SolanaRoles; absence: null }\n | { roles: null; absence: SolanaRolesAbsence };\n\n/**\n * Read the roles for the session the app holds.\n *\n * Synchronous and pure: the connector already recorded what the wallet\n * declared, so there is nothing to await and no prompt to trigger. Reading\n * roles must never cost the user an approval dialog.\n */\nexport function readSolanaRoles(\n connector: SolanaRolesSource | null | undefined,\n session: SessionLike | null | undefined,\n): SolanaRolesState {\n if (!session) return { roles: null, absence: \"no_session\" };\n if (session.walletType !== \"solana\") {\n return { roles: null, absence: \"not_solana\" };\n }\n if (typeof connector?.getRoles !== \"function\") {\n return { roles: null, absence: \"unreported\" };\n }\n const roles = connector.getRoles(session);\n return roles\n ? { roles, absence: null }\n : { roles: null, absence: \"no_session\" };\n}\n","/**\n * Which chains a session can actually switch to, and what the current one is.\n *\n * Framework-neutral, and deliberately built on `parseChainId` from\n * `@naculus/connect-core` rather than on another `startsWith(\"eip155:\")`.\n */\n\nimport { eip155Reference, namespaceOf } from \"@naculus/connect-core\";\nimport type { ChainInfo, WalletChain } from \"./types\";\n\n/**\n * The EIP-155 chain number, or null when this is not an EVM chain.\n *\n * Callers that need a number — a viem client, an ERC-20 read — must handle\n * the null rather than defaulting, because there is no number that means\n * \"Solana\" and any stand-in would address a real EVM chain.\n */\nexport function chainNumber(chain: WalletChain): number | null {\n return eip155Reference(chain.caip2);\n}\n\n/** The namespace a chain belongs to, read from its CAIP-2 id. */\nexport function chainNamespace(chain: WalletChain): string | null {\n return namespaceOf(chain.caip2);\n}\n\n/**\n * The configured chain for a CAIP-2 id, or null.\n *\n * A string comparison now that `WalletChain` is keyed by CAIP-2. It used to\n * pull an integer out of the id and compare that, which no non-EVM chain\n * could ever match.\n */\nexport function resolveChain(\n chains: WalletChain[],\n chainId: string | null | undefined,\n): WalletChain | null {\n if (!chainId) return null;\n return chains.find((c) => c.caip2 === chainId) ?? null;\n}\n\n/**\n * The chains worth offering for the session's namespace.\n *\n * This used to return every configured chain regardless of what was\n * connected, so a Solana wallet was offered Ethereum and Polygon in the\n * switcher. Clicking one asked a wallet that has no concept of EIP-155 to\n * switch to chain 137 — an action the interface presented as available and\n * that could never work.\n *\n * With no connected chain, everything is offered: nothing has been ruled out\n * yet, and hiding the list would leave a user unable to pick.\n */\nexport function chainsForNamespace(\n chains: WalletChain[],\n chainId: string | null | undefined,\n): WalletChain[] {\n if (!chainId) return chains;\n const namespace = namespaceOf(chainId);\n if (!namespace) return chains;\n return chains.filter((c) => chainNamespace(c) === namespace);\n}\n\n/**\n * A description of the connected chain for display.\n *\n * Returns something for every namespace. It used to return null for anything\n * that was not EIP-155, so a connected Solana wallet had no chain to show at\n * all and the interface fell back to \"Unknown Chain\".\n */\nexport function describeChain(\n chains: WalletChain[],\n chainId: string | null | undefined,\n): ChainInfo | null {\n if (!chainId) return null;\n const namespace = namespaceOf(chainId);\n if (!namespace) return null;\n\n const known = resolveChain(chains, chainId);\n if (known) {\n return { namespace, chainId, name: known.name, selected: true };\n }\n\n // No configured entry. Name it by its CAIP-2 reference rather than\n // inventing one: \"Chain 5\" for a Solana cluster would be a fabrication, and\n // the reference is at least something a user can look up.\n const reference = chainId.slice(namespace.length + 1);\n return {\n namespace,\n chainId,\n name: namespace === \"eip155\" ? `Chain ${reference}` : reference,\n selected: true,\n };\n}\n","/**\n * The viem `Chain` shape for a configured EVM chain.\n *\n * Three hooks each built this inline — `useBalance`, `useTokenBalance` and\n * `useViemClient` — with identical bodies and the same `?? \"ETH\"` in each.\n * Three copies of a fallback is three places for it to be wrong.\n *\n * Returns a plain object rather than importing viem, so this package stays\n * free of a dependency only the React bindings need. The shape satisfies\n * viem's `Chain` structurally.\n */\n\nimport { CHAINS } from \"@naculus/connect-core\";\nimport type { WalletChain } from \"./types\";\n\nexport interface ViemChainShape {\n id: number;\n name: string;\n nativeCurrency: { name: string; symbol: string; decimals: number };\n rpcUrls: {\n default: { http: string[] };\n public: { http: string[] };\n };\n}\n\n/**\n * Build the chain descriptor a viem client needs, or null when this chain is\n * not one viem can talk to.\n *\n * Null for a non-EVM chain rather than a stand-in number: viem speaks to EVM\n * nodes, and any number chosen to fill the gap addresses a real chain that is\n * not the one connected.\n *\n * The native symbol comes from the chain's own `token`, then from the shared\n * chain registry. For an unlisted chain it stays empty rather than claiming\n * the asset is ETH. viem requires a string here; the user-facing symbol comes\n * from `useBalance().symbol`, which stays null when it is unknown.\n */\nexport function toViemChain(\n chain: WalletChain,\n chainNumber: number | null,\n): ViemChainShape | null {\n if (chainNumber === null || !chain.rpcUrl) return null;\n\n const symbol =\n chain.token ?? CHAINS[chainNumber]?.nativeCurrency?.symbol ?? \"\";\n\n return {\n id: chainNumber,\n name: chain.name,\n // Every EIP-155 native currency is 18 decimals by protocol, so this is a\n // constant rather than an assumption. ERC-20 decimals are per-token and\n // are never guessed anywhere in this codebase.\n nativeCurrency: { name: symbol, symbol, decimals: 18 },\n rpcUrls: {\n default: { http: [chain.rpcUrl] },\n public: { http: [chain.rpcUrl] },\n },\n };\n}\n","/**\n * Which EVM chain does an account-abstraction operation belong to?\n *\n * The smart-account hooks each resolved this as\n * `options.chainId ?? connectedChainId ?? \"eip155:1\"`, and each then guarded\n * with `if (connectedChainId && connectedChainId !== target)`. The guard is\n * skipped precisely when the mainnet default is used, so the one branch that\n * assumes a chain is the one branch that never checks it.\n *\n * That branch is reachable: a caller passing its own `signer` needs no\n * session, so `connectedChainId` is legitimately absent. It would then build\n * and sign a UserOperation for chain 1 — with chain 1's EntryPoint — while its\n * configured RPC and bundler point somewhere else. The chain ID is part of the\n * UserOperation hash, so the user approves a commitment to a chain they were\n * never shown.\n *\n * Resolving to undefined instead lets each caller refuse in whatever way suits\n * it. Framework-agnostic on purpose: it must not import React.\n */\n\n/** CAIP-2 for an EVM chain. The reference is a decimal chain ID with no leading zero. */\nconst EVM_CHAIN_ID = /^eip155:[1-9][0-9]*$/;\n\nexport function isEvmChainId(value: string | null | undefined): boolean {\n return typeof value === \"string\" && EVM_CHAIN_ID.test(value);\n}\n\n/**\n * The chain to operate on, or undefined when it cannot be known.\n *\n * An explicitly requested chain wins over the connected one; a malformed value\n * from either source resolves to undefined rather than being replaced by a\n * guess, because the caller asked for something specific and got it wrong.\n */\nexport function resolveEvmChainId(\n requested: string | null | undefined,\n connected: string | null | undefined,\n): string | undefined {\n const candidate = requested ?? connected ?? undefined;\n return isEvmChainId(candidate) ? candidate : undefined;\n}\n","/**\n * Does this token belong to the chain the wallet is pointed at?\n *\n * `TokenConfig.chainId` existed but was only ever read as a display label and\n * a cache key. Nothing compared it to the connected chain, so a hook handed a\n * mainnet token config while the wallet sat on Polygon would read — or\n * approve — whatever contract happens to occupy that address on Polygon.\n * Usually that address holds nothing and the call reverts, which is merely\n * confusing. When it holds a different token, `allowance()` returns a\n * plausible number for the wrong asset, and `approve()` grants a spender\n * rights over something the user never looked at.\n *\n * Framework-agnostic on purpose: it must not import React.\n */\n\nimport type { TokenConfig } from \"@naculus/connect-core\";\nimport { WalletError } from \"@naculus/connect-core\";\n\n/**\n * A mismatch, or undefined when the token is on the active chain — or when\n * there is not enough information to tell.\n *\n * Silence on \"cannot tell\" is deliberate. A token config without a chainId, or\n * a session with no resolved chain, is a gap in what the caller supplied; a\n * guess in either direction would be worse than letting the call proceed and\n * fail on its own terms.\n */\nexport function tokenChainMismatch(\n token: Pick<TokenConfig, \"address\" | \"chainId\">,\n activeChainId: number | undefined,\n): WalletError | undefined {\n if (typeof token?.chainId !== \"number\" || !Number.isFinite(token.chainId)) {\n return undefined;\n }\n if (typeof activeChainId !== \"number\" || !Number.isFinite(activeChainId)) {\n return undefined;\n }\n if (token.chainId === activeChainId) return undefined;\n\n return new WalletError(\n \"chain_mismatch\",\n `Token ${token.address} is configured for chain ${token.chainId}, but the ` +\n `wallet is on chain ${activeChainId}. Switch chains before continuing — ` +\n `the same address holds a different contract on each chain.`,\n );\n}\n","/**\n * Decimals cache keyed by the token it belongs to.\n *\n * The ERC-20 hooks cached decimals in a bare `useRef` seeded from the token\n * prop. `useRef` only takes its initial value on mount, and nothing reset it,\n * so a component that lets the user switch tokens kept the first token's\n * precision: picking USDC (6) and then DAI (18) converted \"100\" with 6\n * decimals and transferred 0.0000000001 DAI. The transaction succeeded, so\n * nothing surfaced the mistake.\n *\n * Keying the cache by the token makes a stale hit impossible without needing\n * an effect to fire first.\n */\n\nexport interface TokenIdentity {\n chainId?: number;\n address: string;\n}\n\nexport interface DecimalsCache {\n key: string;\n value: number | undefined;\n}\n\nexport function tokenKey(token: TokenIdentity): string {\n return `${token.chainId ?? \"?\"}:${token.address.toLowerCase()}`;\n}\n\nexport function createDecimalsCache(\n token: TokenIdentity,\n known?: number,\n): DecimalsCache {\n return { key: tokenKey(token), value: known };\n}\n\n/**\n * Cached decimals for `token`, or undefined when the cache holds another\n * token's value. Resets the cache as a side effect so the next write lands\n * against the right key.\n */\nexport function readDecimals(\n cache: DecimalsCache,\n token: TokenIdentity,\n known?: number,\n): number | undefined {\n const key = tokenKey(token);\n if (cache.key !== key) {\n cache.key = key;\n cache.value = known;\n }\n return cache.value;\n}\n\nexport function writeDecimals(\n cache: DecimalsCache,\n token: TokenIdentity,\n value: number,\n): void {\n cache.key = tokenKey(token);\n cache.value = value;\n}\n","/**\n * Normalize an EIP-1193 provider error into a WalletError.\n *\n * Hooks were collapsing every non-WalletError into one code — `useSwitchChain`\n * labelled a user pressing Cancel as `chain_unsupported`, so the UI told them\n * the chain was unavailable when they had simply declined. The codes below are\n * the ones wallets actually send, and they mean different things to a caller:\n * one is retryable by asking again, the other is not retryable at all until\n * the chain is added.\n *\n * Framework-agnostic on purpose: it must not import React.\n */\n\nimport type { WalletErrorCode } from \"@naculus/connect-core\";\nimport { WalletError } from \"@naculus/connect-core\";\n\n/** EIP-1193: the user rejected the request. */\nconst USER_REJECTED = 4001;\n/** EIP-3326: the wallet does not recognize the chain and must add it first. */\nconst UNRECOGNIZED_CHAIN = 4902;\n\nfunction providerCode(error: unknown): number | undefined {\n if (!error || typeof error !== \"object\") return undefined;\n const code = (error as { code?: unknown }).code;\n if (typeof code === \"number\") return code;\n // Some wallets stringify it, and some nest the original under `data`.\n if (typeof code === \"string\" && /^-?\\d+$/.test(code)) return Number(code);\n const nested = (error as { data?: { originalError?: unknown } }).data\n ?.originalError;\n return nested ? providerCode(nested) : undefined;\n}\n\n/**\n * Map a chain-switch failure onto the code that describes it.\n *\n * `fallback` is used when the wallet gave nothing to go on, so the caller\n * still decides what an unexplained failure means in its own context.\n */\nexport function toChainSwitchError(\n error: unknown,\n fallback: WalletErrorCode = \"chain_unsupported\",\n): WalletError {\n if (error instanceof WalletError) return error;\n\n const message =\n error instanceof Error\n ? error.message\n : \"Unknown error during chain switch\";\n\n switch (providerCode(error)) {\n case USER_REJECTED:\n return new WalletError(\"chain_switch_rejected\", message, error);\n case UNRECOGNIZED_CHAIN:\n return new WalletError(\n \"chain_unsupported\",\n `${message} (the wallet does not have this chain configured)`,\n error,\n );\n default:\n return new WalletError(fallback, message, error);\n }\n}\n","/**\n * Decode an EVM revert payload into something a user can act on.\n *\n * This text is what a wallet shows when a simulation says a transaction would\n * fail, so it is worth getting right and worth sharing: the logic is pure and\n * every binding needs the same answer.\n *\n * Two things are handled that the inlined version did not. The ABI length\n * word is attacker-controlled, so a payload can declare a longer string than\n * it carries; decoding that produced a run of NUL bytes rather than an honest\n * \"could not decode\". And a Panic was reported as \"Built-in failure\" with the\n * code discarded, which is the one piece of information that distinguishes an\n * overflow from a division by zero.\n */\n\n/** keccak256(\"Error(string)\")[0:4] */\nconst ERROR_STRING_SELECTOR = \"08c379a0\";\n/** keccak256(\"Panic(uint256)\")[0:4] */\nconst PANIC_SELECTOR = \"4e487b71\";\n\n/**\n * Panic codes defined by Solidity. Unlisted codes still surface their raw\n * value rather than being flattened into a generic message.\n */\nconst PANIC_REASONS: Record<number, string> = {\n 0x01: \"assertion failed\",\n 0x11: \"arithmetic overflow or underflow\",\n 0x12: \"division or modulo by zero\",\n 0x21: \"invalid enum value\",\n 0x31: \"pop() on an empty array\",\n 0x32: \"array index out of bounds\",\n};\n\nfunction hexToUtf8(hex: string): string {\n const bytes = new Uint8Array(hex.length / 2);\n for (let i = 0; i < bytes.length; i++) {\n bytes[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n }\n return new TextDecoder().decode(bytes);\n}\n\n/**\n * @param data Raw revert data from the node, with or without the 0x prefix.\n * @returns The decoded reason, or undefined when the payload is not a shape\n * this understands — callers should fall back to the RPC error text rather\n * than presenting a guess.\n */\nexport function decodeRevertReason(data: unknown): string | undefined {\n if (typeof data !== \"string\" || data.length === 0) return undefined;\n const clean = data.startsWith(\"0x\") ? data.slice(2) : data;\n if (!/^[0-9a-fA-F]*$/.test(clean)) return undefined;\n\n if (clean.startsWith(ERROR_STRING_SELECTOR)) {\n // selector(8) + offset(64) + length(64) + payload\n if (clean.length < 136) return undefined;\n const declared = Number.parseInt(clean.slice(72, 136), 16);\n if (!Number.isSafeInteger(declared) || declared <= 0) return undefined;\n const available = (clean.length - 136) / 2;\n // Refuse a payload that promises more than it carries instead of padding\n // the difference with NULs and calling it a revert reason.\n if (declared > available) return undefined;\n return hexToUtf8(clean.slice(136, 136 + declared * 2));\n }\n\n if (clean.startsWith(PANIC_SELECTOR)) {\n if (clean.length < 8 + 64) return \"Panic\";\n const code = Number.parseInt(clean.slice(8, 72), 16);\n if (!Number.isSafeInteger(code)) return \"Panic\";\n const known = PANIC_REASONS[code];\n const hex = `0x${code.toString(16).padStart(2, \"0\")}`;\n return known ? `Panic ${hex}: ${known}` : `Panic ${hex}`;\n }\n\n return undefined;\n}\n\n/** Decoded reason if available, otherwise the node's own message. */\nexport function resolveRevertReason(\n data: unknown,\n fallbackMessage: string,\n): string {\n return (\n decodeRevertReason(data) ??\n (fallbackMessage.includes(\"revert\")\n ? fallbackMessage\n : \"Transaction reverted\")\n );\n}\n","/**\n * Shared NameResolver instance for the name-resolution hooks.\n *\n * useResolveName and useLookupAddress each carried an identical getResolver\n * with its own module-level singleton, so the two directions of the same\n * lookup ran against separate resolvers and separate caches. One definition,\n * one instance.\n */\nimport { NameResolver } from \"@naculus/connect-core\";\nimport type { NameResolverConfig } from \"@naculus/connect-core\";\n\nlet sharedResolver: NameResolver | null = null;\n\n/**\n * @param config When supplied, returns a dedicated resolver rather than the\n * shared one — a caller asking for specific settings must not be handed an\n * instance configured by someone else.\n */\nexport function getResolver(config?: NameResolverConfig): NameResolver {\n if (config) return new NameResolver(config);\n if (!sharedResolver) sharedResolver = new NameResolver();\n return sharedResolver;\n}\n\n/** Test seam: drop the shared instance. */\nexport function __resetResolverForTests(): void {\n sharedResolver = null;\n}\n","/**\n * Framework-agnostic core of the connect provider.\n *\n * appkit's stated shape is \"write the Stencil components once, generate the\n * React and Vue wrappers\". In practice `react/` carries ~8.5k hand-written\n * lines against `vue/`'s ~280, and only two files in `react/src` contain JSX —\n * the rest is logic wearing React clothes. Web Components also cannot serve\n * React Native or Flutter, so the presentation layer was never going to be the\n * thing worth sharing.\n *\n * This module is the first piece moved out: the state machine and the pure\n * helpers around it, with no React import. Everything here is consumable from\n * Vue, Svelte, or a native binding as-is, and `web3-store.test.ts` asserts the\n * absence of a framework import so the boundary cannot rot back.\n */\nimport { logger } from \"@naculus/connect-core\";\nimport type { ConnectionStatus, Web3State } from \"./types\";\n\n// ── Async helpers ──────────────────────────────────────────────────────\n\nexport function withTimeout<T>(\n promise: Promise<T>,\n ms: number,\n label = \"Operation\",\n): Promise<T> {\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<T>((_, reject) => {\n timeoutId = setTimeout(\n () => reject(new Error(`${label} timed out after ${ms}ms`)),\n ms,\n );\n });\n return Promise.race([promise, timeout]).finally(() => {\n if (timeoutId !== undefined) clearTimeout(timeoutId);\n });\n}\n\nexport interface RetryOptions {\n maxRetries: number;\n baseDelay: number;\n label?: string;\n}\n\nexport async function withRetry<T>(\n fn: () => Promise<T>,\n options: RetryOptions,\n): Promise<T> {\n let lastError: unknown;\n for (let attempt = 0; attempt <= options.maxRetries; attempt++) {\n try {\n return await fn();\n } catch (error) {\n lastError = error;\n if (attempt < options.maxRetries) {\n const delay = options.baseDelay * 2 ** attempt;\n logger.warn(\n \"appkit/core\",\n `Retry attempt ${attempt + 1}/${options.maxRetries} after ${delay}ms`,\n );\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n }\n throw lastError;\n}\n\n// ── CAIP normalisation ─────────────────────────────────────────────────\n\n/** Convert EIP-1193 chainChanged values into the CAIP-2 form used by sessions. */\nexport function normalizeEip155ChainId(value: unknown): string | undefined {\n if (typeof value !== \"string\") return undefined;\n try {\n if (value.startsWith(\"eip155:\")) {\n const reference = value.slice(\"eip155:\".length);\n if (!/^\\d+$/.test(reference)) return undefined;\n return `eip155:${BigInt(reference).toString(10)}`;\n }\n if (/^0x[0-9a-f]+$/i.test(value) || /^\\d+$/.test(value)) {\n return `eip155:${BigInt(value).toString(10)}`;\n }\n } catch {\n // Ignore malformed wallet events rather than corrupting session state.\n }\n return undefined;\n}\n\nexport function toEip155Accounts(\n accounts: unknown[],\n chainId: string,\n): string[] {\n const reference = chainId.split(\":\")[1];\n if (!reference) return [];\n return accounts\n .filter(\n (account): account is string =>\n typeof account === \"string\" && account.length > 0,\n )\n .map((account) =>\n account.includes(\":\") ? (account.split(\":\").pop() as string) : account,\n )\n .map((account) => `eip155:${reference}:${account}`);\n}\n\n/**\n * Numeric EIP-155 chain id, or undefined for any other namespace.\n *\n * The previous implementation took the last CAIP segment and ran parseInt over\n * it, which stops at the first non-digit: `solana:5eykt4Us…` became 5 — an\n * EVM chain id — so asking for Solana mainnet's token list returned Goerli's,\n * and `xrpl:0` became chain 0. Anchor the whole reference instead.\n */\nexport function eip155ChainIdToNumber(chainId: string): number | undefined {\n const match = /^eip155:([1-9][0-9]*)$/.exec(chainId);\n if (!match) return undefined;\n const numeric = Number(match[1]);\n return Number.isSafeInteger(numeric) ? numeric : undefined;\n}\n\n// ── State machine ──────────────────────────────────────────────────────\n\nexport type Web3Action =\n | { type: \"SET_STATUS\"; payload: ConnectionStatus }\n | { type: \"SET_SESSION\"; payload: Web3State[\"session\"] }\n | { type: \"SET_ACCOUNTS\"; payload: string[] }\n | { type: \"SET_CHAIN\"; payload: string | null }\n | { type: \"SET_ERROR\"; payload: Error | null }\n | { type: \"RESET\" };\n\nexport const initialWeb3State: Web3State = {\n status: \"disconnected\",\n session: null,\n accounts: [],\n chainId: null,\n error: null,\n};\n\nexport function web3Reducer(state: Web3State, action: Web3Action): Web3State {\n switch (action.type) {\n case \"SET_STATUS\":\n return { ...state, status: action.payload };\n case \"SET_SESSION\":\n return { ...state, session: action.payload };\n case \"SET_ACCOUNTS\":\n return { ...state, accounts: action.payload };\n case \"SET_CHAIN\":\n return { ...state, chainId: action.payload };\n case \"SET_ERROR\":\n return { ...state, error: action.payload };\n case \"RESET\":\n return initialWeb3State;\n default:\n return state;\n }\n}\n"],"mappings":"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","names":["import_connect_core","import_connect_core","chainNumber","import_connect_core","import_connect_core","import_connect_core","import_connect_core"]}
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { Namespace, UniversalWalletSession, TokenConfig, WalletError, WalletErrorCode, NameResolverConfig, NameResolver } from '@naculus/connect-core';
1
+ import { Namespace, UniversalWalletSession, WalletCapabilities, TokenConfig, WalletError, WalletErrorCode, NameResolverConfig, NameResolver } from '@naculus/connect-core';
2
2
 
3
3
  /**
4
4
  * The types the framework-neutral half of appkit is built on.
@@ -42,6 +42,149 @@ interface Web3State {
42
42
  error: Error | null;
43
43
  }
44
44
 
45
+ /**
46
+ * EIP-5792 capability answers, normalised once.
47
+ *
48
+ * Deciding how to execute before sending is the whole point of
49
+ * `wallet_getCapabilities`, and the reasoning that turns a raw answer into
50
+ * that decision is not React's. It lived inside the React hook, which meant a
51
+ * Vue composable could only get it by copying — and a second copy of "absence
52
+ * is not a denial" is exactly the copy that drifts back into `false`.
53
+ */
54
+
55
+ /**
56
+ * Whether a wallet can execute a batch atomically.
57
+ *
58
+ * Three values, not two. A wallet that does not implement
59
+ * `wallet_getCapabilities` has not said no — it has said nothing, and EIP-5792
60
+ * is explicit that absence is not a denial. Collapsing that into `false` sends
61
+ * every such wallet down the sequential path, which is the one where an
62
+ * approve can land and the swap it was for can fail.
63
+ */
64
+ type AtomicSupport = "supported" | "unsupported" | "unknown";
65
+ interface ChainCapabilities {
66
+ /** CAIP-2 chain this describes. */
67
+ chainId: string;
68
+ atomic: AtomicSupport;
69
+ /** Largest batch the wallet will accept, when it says. */
70
+ maxBatchSize?: number;
71
+ /** The raw entry, for capabilities this SDK does not model. */
72
+ raw: WalletCapabilities;
73
+ }
74
+ /**
75
+ * Turn a wallet's raw answer into per-chain entries.
76
+ *
77
+ * Entries that are not objects are dropped rather than coerced: a wallet
78
+ * answering `null` for a chain has said nothing about it, and inventing an
79
+ * `"unsupported"` there would be the same mistake as reading absence as denial.
80
+ */
81
+ declare function normalizeCapabilities(raw: Record<string, unknown> | null | undefined): Record<string, ChainCapabilities>;
82
+ /**
83
+ * Find the entry for a chain, by CAIP-2 or by the hex form.
84
+ *
85
+ * Connectors normalise keys to CAIP-2, but a wallet answering in raw hex
86
+ * through a custom connector should still be found rather than reported as
87
+ * "unknown" next to an entry that is sitting right there.
88
+ */
89
+ declare function selectChainCapabilities(capabilities: Record<string, ChainCapabilities> | null | undefined, chainId: string | null | undefined): ChainCapabilities | null;
90
+ /**
91
+ * Atomic support for a chain.
92
+ *
93
+ * `"unknown"` covers every case where we have not been told: no query yet, a
94
+ * wallet without the method, a failed query, one still in flight. A caller
95
+ * choosing between an atomic and a sequential path needs that distinction, not
96
+ * a confident `false`.
97
+ */
98
+ declare function atomicSupportFor(capabilities: Record<string, ChainCapabilities> | null | undefined, chainId: string | null | undefined): AtomicSupport;
99
+
100
+ /**
101
+ * Solana signer roles, read once for both frameworks.
102
+ *
103
+ * `@naculus/connector-solana` splits a connected wallet into `identity`,
104
+ * `signer` and `payer`, each `null` when the wallet declared it cannot fill
105
+ * that job. That answer is the connector's. What this module owns is the part
106
+ * a UI has to decide before it can show anything: *why* there is no answer —
107
+ * because nothing is connected, because the connected wallet is not Solana,
108
+ * or because the connector predates the roles API and has not said. Those are
109
+ * three different screens, and a hook that hands back one `null` for all of
110
+ * them makes every consumer rediscover the distinction.
111
+ */
112
+ /** Which account this is, with no ability to sign for it. */
113
+ interface SolanaIdentity {
114
+ /** Base58 public key. */
115
+ readonly address: string;
116
+ /** CAIP-2 chain this account is being used on. */
117
+ readonly chain: string;
118
+ }
119
+ /** An account that will contribute a signature without submitting anything. */
120
+ interface SolanaSigner extends SolanaIdentity {
121
+ signMessage?: (message: Uint8Array) => Promise<Uint8Array>;
122
+ signTransaction: (transaction: Uint8Array) => Promise<Uint8Array>;
123
+ /** Absent, not throwing, when the wallet has no batch feature. */
124
+ signAllTransactions?: (transactions: Uint8Array[]) => Promise<Uint8Array[]>;
125
+ }
126
+ /** An account that signs and broadcasts through the wallet's own RPC. */
127
+ interface SolanaPayer extends SolanaIdentity {
128
+ /** Returns the base58 transaction signature the cluster accepted. */
129
+ signAndSendTransaction: (transaction: Uint8Array) => Promise<string>;
130
+ }
131
+ /** What the wallet declared, recorded at discovery. Two-state on purpose. */
132
+ interface SolanaWalletFeatures {
133
+ signMessage: boolean;
134
+ signTransaction: boolean;
135
+ signAllTransactions: boolean;
136
+ signAndSendTransaction: boolean;
137
+ }
138
+ /**
139
+ * The roles a connected account can fill.
140
+ *
141
+ * Structurally identical to `SolanaRoles` in `@naculus/connector-solana`,
142
+ * declared here rather than imported because this package depends only on
143
+ * `@naculus/connect-core`, and the connector's own declaration is what its
144
+ * `getRoles` returns — a value of that type satisfies this one unchanged.
145
+ */
146
+ interface SolanaRoles {
147
+ identity: SolanaIdentity;
148
+ signer: SolanaSigner | null;
149
+ payer: SolanaPayer | null;
150
+ features: SolanaWalletFeatures;
151
+ }
152
+ /** The part of a connector this needs. `getRoles` is optional on purpose. */
153
+ interface SolanaRolesSource {
154
+ getRoles?: (session: unknown) => SolanaRoles | null;
155
+ }
156
+ /** The part of a session this needs. */
157
+ interface SessionLike {
158
+ walletType?: string;
159
+ }
160
+ /**
161
+ * Why `roles` is null.
162
+ *
163
+ * - `no_session` — nothing connected, or the connector has no live session
164
+ * for the one the app holds (a restored session before reconnect).
165
+ * - `not_solana` — the connected wallet is on another namespace. Not an
166
+ * error; a Solana panel simply does not apply.
167
+ * - `unreported` — the connector does not expose `getRoles`. An older
168
+ * `@naculus/connector-solana`, or a custom one. The wallet has not said
169
+ * what it can do, which is not the same as saying it can do nothing.
170
+ */
171
+ type SolanaRolesAbsence = "no_session" | "not_solana" | "unreported";
172
+ type SolanaRolesState = {
173
+ roles: SolanaRoles;
174
+ absence: null;
175
+ } | {
176
+ roles: null;
177
+ absence: SolanaRolesAbsence;
178
+ };
179
+ /**
180
+ * Read the roles for the session the app holds.
181
+ *
182
+ * Synchronous and pure: the connector already recorded what the wallet
183
+ * declared, so there is nothing to await and no prompt to trigger. Reading
184
+ * roles must never cost the user an approval dialog.
185
+ */
186
+ declare function readSolanaRoles(connector: SolanaRolesSource | null | undefined, session: SessionLike | null | undefined): SolanaRolesState;
187
+
45
188
  /**
46
189
  * Which chains a session can actually switch to, and what the current one is.
47
190
  *
@@ -322,4 +465,4 @@ type Web3Action = {
322
465
  declare const initialWeb3State: Web3State;
323
466
  declare function web3Reducer(state: Web3State, action: Web3Action): Web3State;
324
467
 
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 };
468
+ export { type AtomicSupport, type ChainCapabilities, type ChainInfo, type ConnectionStatus, type DecimalsCache, type RetryOptions, type SolanaIdentity, type SolanaPayer, type SolanaRoles, type SolanaRolesAbsence, type SolanaRolesSource, type SolanaRolesState, type SolanaSigner, type SolanaWalletFeatures, type TokenIdentity, type ViemChainShape, type WalletChain, type Web3Action, type Web3State, __resetResolverForTests, atomicSupportFor, chainNamespace, chainNumber, chainsForNamespace, createDecimalsCache, decodeRevertReason, describeChain, eip155ChainIdToNumber, getResolver, initialWeb3State, isEvmChainId, normalizeCapabilities, normalizeEip155ChainId, readDecimals, readSolanaRoles, resolveChain, resolveEvmChainId, resolveRevertReason, selectChainCapabilities, toChainSwitchError, toEip155Accounts, toViemChain, tokenChainMismatch, tokenKey, web3Reducer, withRetry, withTimeout, writeDecimals };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Namespace, UniversalWalletSession, TokenConfig, WalletError, WalletErrorCode, NameResolverConfig, NameResolver } from '@naculus/connect-core';
1
+ import { Namespace, UniversalWalletSession, WalletCapabilities, TokenConfig, WalletError, WalletErrorCode, NameResolverConfig, NameResolver } from '@naculus/connect-core';
2
2
 
3
3
  /**
4
4
  * The types the framework-neutral half of appkit is built on.
@@ -42,6 +42,149 @@ interface Web3State {
42
42
  error: Error | null;
43
43
  }
44
44
 
45
+ /**
46
+ * EIP-5792 capability answers, normalised once.
47
+ *
48
+ * Deciding how to execute before sending is the whole point of
49
+ * `wallet_getCapabilities`, and the reasoning that turns a raw answer into
50
+ * that decision is not React's. It lived inside the React hook, which meant a
51
+ * Vue composable could only get it by copying — and a second copy of "absence
52
+ * is not a denial" is exactly the copy that drifts back into `false`.
53
+ */
54
+
55
+ /**
56
+ * Whether a wallet can execute a batch atomically.
57
+ *
58
+ * Three values, not two. A wallet that does not implement
59
+ * `wallet_getCapabilities` has not said no — it has said nothing, and EIP-5792
60
+ * is explicit that absence is not a denial. Collapsing that into `false` sends
61
+ * every such wallet down the sequential path, which is the one where an
62
+ * approve can land and the swap it was for can fail.
63
+ */
64
+ type AtomicSupport = "supported" | "unsupported" | "unknown";
65
+ interface ChainCapabilities {
66
+ /** CAIP-2 chain this describes. */
67
+ chainId: string;
68
+ atomic: AtomicSupport;
69
+ /** Largest batch the wallet will accept, when it says. */
70
+ maxBatchSize?: number;
71
+ /** The raw entry, for capabilities this SDK does not model. */
72
+ raw: WalletCapabilities;
73
+ }
74
+ /**
75
+ * Turn a wallet's raw answer into per-chain entries.
76
+ *
77
+ * Entries that are not objects are dropped rather than coerced: a wallet
78
+ * answering `null` for a chain has said nothing about it, and inventing an
79
+ * `"unsupported"` there would be the same mistake as reading absence as denial.
80
+ */
81
+ declare function normalizeCapabilities(raw: Record<string, unknown> | null | undefined): Record<string, ChainCapabilities>;
82
+ /**
83
+ * Find the entry for a chain, by CAIP-2 or by the hex form.
84
+ *
85
+ * Connectors normalise keys to CAIP-2, but a wallet answering in raw hex
86
+ * through a custom connector should still be found rather than reported as
87
+ * "unknown" next to an entry that is sitting right there.
88
+ */
89
+ declare function selectChainCapabilities(capabilities: Record<string, ChainCapabilities> | null | undefined, chainId: string | null | undefined): ChainCapabilities | null;
90
+ /**
91
+ * Atomic support for a chain.
92
+ *
93
+ * `"unknown"` covers every case where we have not been told: no query yet, a
94
+ * wallet without the method, a failed query, one still in flight. A caller
95
+ * choosing between an atomic and a sequential path needs that distinction, not
96
+ * a confident `false`.
97
+ */
98
+ declare function atomicSupportFor(capabilities: Record<string, ChainCapabilities> | null | undefined, chainId: string | null | undefined): AtomicSupport;
99
+
100
+ /**
101
+ * Solana signer roles, read once for both frameworks.
102
+ *
103
+ * `@naculus/connector-solana` splits a connected wallet into `identity`,
104
+ * `signer` and `payer`, each `null` when the wallet declared it cannot fill
105
+ * that job. That answer is the connector's. What this module owns is the part
106
+ * a UI has to decide before it can show anything: *why* there is no answer —
107
+ * because nothing is connected, because the connected wallet is not Solana,
108
+ * or because the connector predates the roles API and has not said. Those are
109
+ * three different screens, and a hook that hands back one `null` for all of
110
+ * them makes every consumer rediscover the distinction.
111
+ */
112
+ /** Which account this is, with no ability to sign for it. */
113
+ interface SolanaIdentity {
114
+ /** Base58 public key. */
115
+ readonly address: string;
116
+ /** CAIP-2 chain this account is being used on. */
117
+ readonly chain: string;
118
+ }
119
+ /** An account that will contribute a signature without submitting anything. */
120
+ interface SolanaSigner extends SolanaIdentity {
121
+ signMessage?: (message: Uint8Array) => Promise<Uint8Array>;
122
+ signTransaction: (transaction: Uint8Array) => Promise<Uint8Array>;
123
+ /** Absent, not throwing, when the wallet has no batch feature. */
124
+ signAllTransactions?: (transactions: Uint8Array[]) => Promise<Uint8Array[]>;
125
+ }
126
+ /** An account that signs and broadcasts through the wallet's own RPC. */
127
+ interface SolanaPayer extends SolanaIdentity {
128
+ /** Returns the base58 transaction signature the cluster accepted. */
129
+ signAndSendTransaction: (transaction: Uint8Array) => Promise<string>;
130
+ }
131
+ /** What the wallet declared, recorded at discovery. Two-state on purpose. */
132
+ interface SolanaWalletFeatures {
133
+ signMessage: boolean;
134
+ signTransaction: boolean;
135
+ signAllTransactions: boolean;
136
+ signAndSendTransaction: boolean;
137
+ }
138
+ /**
139
+ * The roles a connected account can fill.
140
+ *
141
+ * Structurally identical to `SolanaRoles` in `@naculus/connector-solana`,
142
+ * declared here rather than imported because this package depends only on
143
+ * `@naculus/connect-core`, and the connector's own declaration is what its
144
+ * `getRoles` returns — a value of that type satisfies this one unchanged.
145
+ */
146
+ interface SolanaRoles {
147
+ identity: SolanaIdentity;
148
+ signer: SolanaSigner | null;
149
+ payer: SolanaPayer | null;
150
+ features: SolanaWalletFeatures;
151
+ }
152
+ /** The part of a connector this needs. `getRoles` is optional on purpose. */
153
+ interface SolanaRolesSource {
154
+ getRoles?: (session: unknown) => SolanaRoles | null;
155
+ }
156
+ /** The part of a session this needs. */
157
+ interface SessionLike {
158
+ walletType?: string;
159
+ }
160
+ /**
161
+ * Why `roles` is null.
162
+ *
163
+ * - `no_session` — nothing connected, or the connector has no live session
164
+ * for the one the app holds (a restored session before reconnect).
165
+ * - `not_solana` — the connected wallet is on another namespace. Not an
166
+ * error; a Solana panel simply does not apply.
167
+ * - `unreported` — the connector does not expose `getRoles`. An older
168
+ * `@naculus/connector-solana`, or a custom one. The wallet has not said
169
+ * what it can do, which is not the same as saying it can do nothing.
170
+ */
171
+ type SolanaRolesAbsence = "no_session" | "not_solana" | "unreported";
172
+ type SolanaRolesState = {
173
+ roles: SolanaRoles;
174
+ absence: null;
175
+ } | {
176
+ roles: null;
177
+ absence: SolanaRolesAbsence;
178
+ };
179
+ /**
180
+ * Read the roles for the session the app holds.
181
+ *
182
+ * Synchronous and pure: the connector already recorded what the wallet
183
+ * declared, so there is nothing to await and no prompt to trigger. Reading
184
+ * roles must never cost the user an approval dialog.
185
+ */
186
+ declare function readSolanaRoles(connector: SolanaRolesSource | null | undefined, session: SessionLike | null | undefined): SolanaRolesState;
187
+
45
188
  /**
46
189
  * Which chains a session can actually switch to, and what the current one is.
47
190
  *
@@ -322,4 +465,4 @@ type Web3Action = {
322
465
  declare const initialWeb3State: Web3State;
323
466
  declare function web3Reducer(state: Web3State, action: Web3Action): Web3State;
324
467
 
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 };
468
+ export { type AtomicSupport, type ChainCapabilities, type ChainInfo, type ConnectionStatus, type DecimalsCache, type RetryOptions, type SolanaIdentity, type SolanaPayer, type SolanaRoles, type SolanaRolesAbsence, type SolanaRolesSource, type SolanaRolesState, type SolanaSigner, type SolanaWalletFeatures, type TokenIdentity, type ViemChainShape, type WalletChain, type Web3Action, type Web3State, __resetResolverForTests, atomicSupportFor, chainNamespace, chainNumber, chainsForNamespace, createDecimalsCache, decodeRevertReason, describeChain, eip155ChainIdToNumber, getResolver, initialWeb3State, isEvmChainId, normalizeCapabilities, normalizeEip155ChainId, readDecimals, readSolanaRoles, resolveChain, resolveEvmChainId, resolveRevertReason, selectChainCapabilities, toChainSwitchError, toEip155Accounts, toViemChain, tokenChainMismatch, tokenKey, web3Reducer, withRetry, withTimeout, writeDecimals };
package/dist/index.js CHANGED
@@ -1,3 +1,46 @@
1
+ // src/capabilities.ts
2
+ import {
3
+ caip2ToHexChain,
4
+ readAtomicSupport
5
+ } from "@naculus/connect-core";
6
+ function normalizeCapabilities(raw) {
7
+ const out = {};
8
+ for (const [key, entry] of Object.entries(raw ?? {})) {
9
+ if (!entry || typeof entry !== "object") continue;
10
+ const { supported, maxBatchSize } = readAtomicSupport(
11
+ entry
12
+ );
13
+ out[key] = {
14
+ chainId: key,
15
+ atomic: supported ? "supported" : "unsupported",
16
+ ...maxBatchSize === void 0 ? {} : { maxBatchSize },
17
+ raw: entry
18
+ };
19
+ }
20
+ return out;
21
+ }
22
+ function selectChainCapabilities(capabilities, chainId) {
23
+ if (!capabilities || !chainId) return null;
24
+ const hex = caip2ToHexChain(chainId);
25
+ return capabilities[chainId] ?? (hex ? capabilities[hex] : void 0) ?? null;
26
+ }
27
+ function atomicSupportFor(capabilities, chainId) {
28
+ return selectChainCapabilities(capabilities, chainId)?.atomic ?? "unknown";
29
+ }
30
+
31
+ // src/solana-roles.ts
32
+ function readSolanaRoles(connector, session) {
33
+ if (!session) return { roles: null, absence: "no_session" };
34
+ if (session.walletType !== "solana") {
35
+ return { roles: null, absence: "not_solana" };
36
+ }
37
+ if (typeof connector?.getRoles !== "function") {
38
+ return { roles: null, absence: "unreported" };
39
+ }
40
+ const roles = connector.getRoles(session);
41
+ return roles ? { roles, absence: null } : { roles: null, absence: "no_session" };
42
+ }
43
+
1
44
  // src/chain-selection.ts
2
45
  import { eip155Reference, namespaceOf } from "@naculus/connect-core";
3
46
  function chainNumber(chain) {
@@ -273,6 +316,7 @@ function web3Reducer(state, action) {
273
316
  }
274
317
  export {
275
318
  __resetResolverForTests,
319
+ atomicSupportFor,
276
320
  chainNamespace,
277
321
  chainNumber,
278
322
  chainsForNamespace,
@@ -283,11 +327,14 @@ export {
283
327
  getResolver,
284
328
  initialWeb3State,
285
329
  isEvmChainId,
330
+ normalizeCapabilities,
286
331
  normalizeEip155ChainId,
287
332
  readDecimals,
333
+ readSolanaRoles,
288
334
  resolveChain,
289
335
  resolveEvmChainId,
290
336
  resolveRevertReason,
337
+ selectChainCapabilities,
291
338
  toChainSwitchError,
292
339
  toEip155Accounts,
293
340
  toViemChain,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/chain-selection.ts","../src/viem-chain.ts","../src/aa-chain.ts","../src/token-chain.ts","../src/token-decimals.ts","../src/provider-errors.ts","../src/revert-reason.ts","../src/name-resolver.ts","../src/web3-store.ts"],"sourcesContent":["/**\n * Which chains a session can actually switch to, and what the current one is.\n *\n * Framework-neutral, and deliberately built on `parseChainId` from\n * `@naculus/connect-core` rather than on another `startsWith(\"eip155:\")`.\n */\n\nimport { eip155Reference, namespaceOf } from \"@naculus/connect-core\";\nimport type { ChainInfo, WalletChain } from \"./types\";\n\n/**\n * The EIP-155 chain number, or null when this is not an EVM chain.\n *\n * Callers that need a number — a viem client, an ERC-20 read — must handle\n * the null rather than defaulting, because there is no number that means\n * \"Solana\" and any stand-in would address a real EVM chain.\n */\nexport function chainNumber(chain: WalletChain): number | null {\n return eip155Reference(chain.caip2);\n}\n\n/** The namespace a chain belongs to, read from its CAIP-2 id. */\nexport function chainNamespace(chain: WalletChain): string | null {\n return namespaceOf(chain.caip2);\n}\n\n/**\n * The configured chain for a CAIP-2 id, or null.\n *\n * A string comparison now that `WalletChain` is keyed by CAIP-2. It used to\n * pull an integer out of the id and compare that, which no non-EVM chain\n * could ever match.\n */\nexport function resolveChain(\n chains: WalletChain[],\n chainId: string | null | undefined,\n): WalletChain | null {\n if (!chainId) return null;\n return chains.find((c) => c.caip2 === chainId) ?? null;\n}\n\n/**\n * The chains worth offering for the session's namespace.\n *\n * This used to return every configured chain regardless of what was\n * connected, so a Solana wallet was offered Ethereum and Polygon in the\n * switcher. Clicking one asked a wallet that has no concept of EIP-155 to\n * switch to chain 137 — an action the interface presented as available and\n * that could never work.\n *\n * With no connected chain, everything is offered: nothing has been ruled out\n * yet, and hiding the list would leave a user unable to pick.\n */\nexport function chainsForNamespace(\n chains: WalletChain[],\n chainId: string | null | undefined,\n): WalletChain[] {\n if (!chainId) return chains;\n const namespace = namespaceOf(chainId);\n if (!namespace) return chains;\n return chains.filter((c) => chainNamespace(c) === namespace);\n}\n\n/**\n * A description of the connected chain for display.\n *\n * Returns something for every namespace. It used to return null for anything\n * that was not EIP-155, so a connected Solana wallet had no chain to show at\n * all and the interface fell back to \"Unknown Chain\".\n */\nexport function describeChain(\n chains: WalletChain[],\n chainId: string | null | undefined,\n): ChainInfo | null {\n if (!chainId) return null;\n const namespace = namespaceOf(chainId);\n if (!namespace) return null;\n\n const known = resolveChain(chains, chainId);\n if (known) {\n return { namespace, chainId, name: known.name, selected: true };\n }\n\n // No configured entry. Name it by its CAIP-2 reference rather than\n // inventing one: \"Chain 5\" for a Solana cluster would be a fabrication, and\n // the reference is at least something a user can look up.\n const reference = chainId.slice(namespace.length + 1);\n return {\n namespace,\n chainId,\n name: namespace === \"eip155\" ? `Chain ${reference}` : reference,\n selected: true,\n };\n}\n","/**\n * The viem `Chain` shape for a configured EVM chain.\n *\n * Three hooks each built this inline — `useBalance`, `useTokenBalance` and\n * `useViemClient` — with identical bodies and the same `?? \"ETH\"` in each.\n * Three copies of a fallback is three places for it to be wrong.\n *\n * Returns a plain object rather than importing viem, so this package stays\n * free of a dependency only the React bindings need. The shape satisfies\n * viem's `Chain` structurally.\n */\n\nimport { CHAINS } from \"@naculus/connect-core\";\nimport type { WalletChain } from \"./types\";\n\nexport interface ViemChainShape {\n id: number;\n name: string;\n nativeCurrency: { name: string; symbol: string; decimals: number };\n rpcUrls: {\n default: { http: string[] };\n public: { http: string[] };\n };\n}\n\n/**\n * Build the chain descriptor a viem client needs, or null when this chain is\n * not one viem can talk to.\n *\n * Null for a non-EVM chain rather than a stand-in number: viem speaks to EVM\n * nodes, and any number chosen to fill the gap addresses a real chain that is\n * not the one connected.\n *\n * The native symbol comes from the chain's own `token`, then from the shared\n * chain registry. For an unlisted chain it stays empty rather than claiming\n * the asset is ETH. viem requires a string here; the user-facing symbol comes\n * from `useBalance().symbol`, which stays null when it is unknown.\n */\nexport function toViemChain(\n chain: WalletChain,\n chainNumber: number | null,\n): ViemChainShape | null {\n if (chainNumber === null || !chain.rpcUrl) return null;\n\n const symbol =\n chain.token ?? CHAINS[chainNumber]?.nativeCurrency?.symbol ?? \"\";\n\n return {\n id: chainNumber,\n name: chain.name,\n // Every EIP-155 native currency is 18 decimals by protocol, so this is a\n // constant rather than an assumption. ERC-20 decimals are per-token and\n // are never guessed anywhere in this codebase.\n nativeCurrency: { name: symbol, symbol, decimals: 18 },\n rpcUrls: {\n default: { http: [chain.rpcUrl] },\n public: { http: [chain.rpcUrl] },\n },\n };\n}\n","/**\n * Which EVM chain does an account-abstraction operation belong to?\n *\n * The smart-account hooks each resolved this as\n * `options.chainId ?? connectedChainId ?? \"eip155:1\"`, and each then guarded\n * with `if (connectedChainId && connectedChainId !== target)`. The guard is\n * skipped precisely when the mainnet default is used, so the one branch that\n * assumes a chain is the one branch that never checks it.\n *\n * That branch is reachable: a caller passing its own `signer` needs no\n * session, so `connectedChainId` is legitimately absent. It would then build\n * and sign a UserOperation for chain 1 — with chain 1's EntryPoint — while its\n * configured RPC and bundler point somewhere else. The chain ID is part of the\n * UserOperation hash, so the user approves a commitment to a chain they were\n * never shown.\n *\n * Resolving to undefined instead lets each caller refuse in whatever way suits\n * it. Framework-agnostic on purpose: it must not import React.\n */\n\n/** CAIP-2 for an EVM chain. The reference is a decimal chain ID with no leading zero. */\nconst EVM_CHAIN_ID = /^eip155:[1-9][0-9]*$/;\n\nexport function isEvmChainId(value: string | null | undefined): boolean {\n return typeof value === \"string\" && EVM_CHAIN_ID.test(value);\n}\n\n/**\n * The chain to operate on, or undefined when it cannot be known.\n *\n * An explicitly requested chain wins over the connected one; a malformed value\n * from either source resolves to undefined rather than being replaced by a\n * guess, because the caller asked for something specific and got it wrong.\n */\nexport function resolveEvmChainId(\n requested: string | null | undefined,\n connected: string | null | undefined,\n): string | undefined {\n const candidate = requested ?? connected ?? undefined;\n return isEvmChainId(candidate) ? candidate : undefined;\n}\n","/**\n * Does this token belong to the chain the wallet is pointed at?\n *\n * `TokenConfig.chainId` existed but was only ever read as a display label and\n * a cache key. Nothing compared it to the connected chain, so a hook handed a\n * mainnet token config while the wallet sat on Polygon would read — or\n * approve — whatever contract happens to occupy that address on Polygon.\n * Usually that address holds nothing and the call reverts, which is merely\n * confusing. When it holds a different token, `allowance()` returns a\n * plausible number for the wrong asset, and `approve()` grants a spender\n * rights over something the user never looked at.\n *\n * Framework-agnostic on purpose: it must not import React.\n */\n\nimport type { TokenConfig } from \"@naculus/connect-core\";\nimport { WalletError } from \"@naculus/connect-core\";\n\n/**\n * A mismatch, or undefined when the token is on the active chain — or when\n * there is not enough information to tell.\n *\n * Silence on \"cannot tell\" is deliberate. A token config without a chainId, or\n * a session with no resolved chain, is a gap in what the caller supplied; a\n * guess in either direction would be worse than letting the call proceed and\n * fail on its own terms.\n */\nexport function tokenChainMismatch(\n token: Pick<TokenConfig, \"address\" | \"chainId\">,\n activeChainId: number | undefined,\n): WalletError | undefined {\n if (typeof token?.chainId !== \"number\" || !Number.isFinite(token.chainId)) {\n return undefined;\n }\n if (typeof activeChainId !== \"number\" || !Number.isFinite(activeChainId)) {\n return undefined;\n }\n if (token.chainId === activeChainId) return undefined;\n\n return new WalletError(\n \"chain_mismatch\",\n `Token ${token.address} is configured for chain ${token.chainId}, but the ` +\n `wallet is on chain ${activeChainId}. Switch chains before continuing — ` +\n `the same address holds a different contract on each chain.`,\n );\n}\n","/**\n * Decimals cache keyed by the token it belongs to.\n *\n * The ERC-20 hooks cached decimals in a bare `useRef` seeded from the token\n * prop. `useRef` only takes its initial value on mount, and nothing reset it,\n * so a component that lets the user switch tokens kept the first token's\n * precision: picking USDC (6) and then DAI (18) converted \"100\" with 6\n * decimals and transferred 0.0000000001 DAI. The transaction succeeded, so\n * nothing surfaced the mistake.\n *\n * Keying the cache by the token makes a stale hit impossible without needing\n * an effect to fire first.\n */\n\nexport interface TokenIdentity {\n chainId?: number;\n address: string;\n}\n\nexport interface DecimalsCache {\n key: string;\n value: number | undefined;\n}\n\nexport function tokenKey(token: TokenIdentity): string {\n return `${token.chainId ?? \"?\"}:${token.address.toLowerCase()}`;\n}\n\nexport function createDecimalsCache(\n token: TokenIdentity,\n known?: number,\n): DecimalsCache {\n return { key: tokenKey(token), value: known };\n}\n\n/**\n * Cached decimals for `token`, or undefined when the cache holds another\n * token's value. Resets the cache as a side effect so the next write lands\n * against the right key.\n */\nexport function readDecimals(\n cache: DecimalsCache,\n token: TokenIdentity,\n known?: number,\n): number | undefined {\n const key = tokenKey(token);\n if (cache.key !== key) {\n cache.key = key;\n cache.value = known;\n }\n return cache.value;\n}\n\nexport function writeDecimals(\n cache: DecimalsCache,\n token: TokenIdentity,\n value: number,\n): void {\n cache.key = tokenKey(token);\n cache.value = value;\n}\n","/**\n * Normalize an EIP-1193 provider error into a WalletError.\n *\n * Hooks were collapsing every non-WalletError into one code — `useSwitchChain`\n * labelled a user pressing Cancel as `chain_unsupported`, so the UI told them\n * the chain was unavailable when they had simply declined. The codes below are\n * the ones wallets actually send, and they mean different things to a caller:\n * one is retryable by asking again, the other is not retryable at all until\n * the chain is added.\n *\n * Framework-agnostic on purpose: it must not import React.\n */\n\nimport type { WalletErrorCode } from \"@naculus/connect-core\";\nimport { WalletError } from \"@naculus/connect-core\";\n\n/** EIP-1193: the user rejected the request. */\nconst USER_REJECTED = 4001;\n/** EIP-3326: the wallet does not recognize the chain and must add it first. */\nconst UNRECOGNIZED_CHAIN = 4902;\n\nfunction providerCode(error: unknown): number | undefined {\n if (!error || typeof error !== \"object\") return undefined;\n const code = (error as { code?: unknown }).code;\n if (typeof code === \"number\") return code;\n // Some wallets stringify it, and some nest the original under `data`.\n if (typeof code === \"string\" && /^-?\\d+$/.test(code)) return Number(code);\n const nested = (error as { data?: { originalError?: unknown } }).data\n ?.originalError;\n return nested ? providerCode(nested) : undefined;\n}\n\n/**\n * Map a chain-switch failure onto the code that describes it.\n *\n * `fallback` is used when the wallet gave nothing to go on, so the caller\n * still decides what an unexplained failure means in its own context.\n */\nexport function toChainSwitchError(\n error: unknown,\n fallback: WalletErrorCode = \"chain_unsupported\",\n): WalletError {\n if (error instanceof WalletError) return error;\n\n const message =\n error instanceof Error\n ? error.message\n : \"Unknown error during chain switch\";\n\n switch (providerCode(error)) {\n case USER_REJECTED:\n return new WalletError(\"chain_switch_rejected\", message, error);\n case UNRECOGNIZED_CHAIN:\n return new WalletError(\n \"chain_unsupported\",\n `${message} (the wallet does not have this chain configured)`,\n error,\n );\n default:\n return new WalletError(fallback, message, error);\n }\n}\n","/**\n * Decode an EVM revert payload into something a user can act on.\n *\n * This text is what a wallet shows when a simulation says a transaction would\n * fail, so it is worth getting right and worth sharing: the logic is pure and\n * every binding needs the same answer.\n *\n * Two things are handled that the inlined version did not. The ABI length\n * word is attacker-controlled, so a payload can declare a longer string than\n * it carries; decoding that produced a run of NUL bytes rather than an honest\n * \"could not decode\". And a Panic was reported as \"Built-in failure\" with the\n * code discarded, which is the one piece of information that distinguishes an\n * overflow from a division by zero.\n */\n\n/** keccak256(\"Error(string)\")[0:4] */\nconst ERROR_STRING_SELECTOR = \"08c379a0\";\n/** keccak256(\"Panic(uint256)\")[0:4] */\nconst PANIC_SELECTOR = \"4e487b71\";\n\n/**\n * Panic codes defined by Solidity. Unlisted codes still surface their raw\n * value rather than being flattened into a generic message.\n */\nconst PANIC_REASONS: Record<number, string> = {\n 0x01: \"assertion failed\",\n 0x11: \"arithmetic overflow or underflow\",\n 0x12: \"division or modulo by zero\",\n 0x21: \"invalid enum value\",\n 0x31: \"pop() on an empty array\",\n 0x32: \"array index out of bounds\",\n};\n\nfunction hexToUtf8(hex: string): string {\n const bytes = new Uint8Array(hex.length / 2);\n for (let i = 0; i < bytes.length; i++) {\n bytes[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n }\n return new TextDecoder().decode(bytes);\n}\n\n/**\n * @param data Raw revert data from the node, with or without the 0x prefix.\n * @returns The decoded reason, or undefined when the payload is not a shape\n * this understands — callers should fall back to the RPC error text rather\n * than presenting a guess.\n */\nexport function decodeRevertReason(data: unknown): string | undefined {\n if (typeof data !== \"string\" || data.length === 0) return undefined;\n const clean = data.startsWith(\"0x\") ? data.slice(2) : data;\n if (!/^[0-9a-fA-F]*$/.test(clean)) return undefined;\n\n if (clean.startsWith(ERROR_STRING_SELECTOR)) {\n // selector(8) + offset(64) + length(64) + payload\n if (clean.length < 136) return undefined;\n const declared = Number.parseInt(clean.slice(72, 136), 16);\n if (!Number.isSafeInteger(declared) || declared <= 0) return undefined;\n const available = (clean.length - 136) / 2;\n // Refuse a payload that promises more than it carries instead of padding\n // the difference with NULs and calling it a revert reason.\n if (declared > available) return undefined;\n return hexToUtf8(clean.slice(136, 136 + declared * 2));\n }\n\n if (clean.startsWith(PANIC_SELECTOR)) {\n if (clean.length < 8 + 64) return \"Panic\";\n const code = Number.parseInt(clean.slice(8, 72), 16);\n if (!Number.isSafeInteger(code)) return \"Panic\";\n const known = PANIC_REASONS[code];\n const hex = `0x${code.toString(16).padStart(2, \"0\")}`;\n return known ? `Panic ${hex}: ${known}` : `Panic ${hex}`;\n }\n\n return undefined;\n}\n\n/** Decoded reason if available, otherwise the node's own message. */\nexport function resolveRevertReason(\n data: unknown,\n fallbackMessage: string,\n): string {\n return (\n decodeRevertReason(data) ??\n (fallbackMessage.includes(\"revert\")\n ? fallbackMessage\n : \"Transaction reverted\")\n );\n}\n","/**\n * Shared NameResolver instance for the name-resolution hooks.\n *\n * useResolveName and useLookupAddress each carried an identical getResolver\n * with its own module-level singleton, so the two directions of the same\n * lookup ran against separate resolvers and separate caches. One definition,\n * one instance.\n */\nimport { NameResolver } from \"@naculus/connect-core\";\nimport type { NameResolverConfig } from \"@naculus/connect-core\";\n\nlet sharedResolver: NameResolver | null = null;\n\n/**\n * @param config When supplied, returns a dedicated resolver rather than the\n * shared one — a caller asking for specific settings must not be handed an\n * instance configured by someone else.\n */\nexport function getResolver(config?: NameResolverConfig): NameResolver {\n if (config) return new NameResolver(config);\n if (!sharedResolver) sharedResolver = new NameResolver();\n return sharedResolver;\n}\n\n/** Test seam: drop the shared instance. */\nexport function __resetResolverForTests(): void {\n sharedResolver = null;\n}\n","/**\n * Framework-agnostic core of the connect provider.\n *\n * appkit's stated shape is \"write the Stencil components once, generate the\n * React and Vue wrappers\". In practice `react/` carries ~8.5k hand-written\n * lines against `vue/`'s ~280, and only two files in `react/src` contain JSX —\n * the rest is logic wearing React clothes. Web Components also cannot serve\n * React Native or Flutter, so the presentation layer was never going to be the\n * thing worth sharing.\n *\n * This module is the first piece moved out: the state machine and the pure\n * helpers around it, with no React import. Everything here is consumable from\n * Vue, Svelte, or a native binding as-is, and `web3-store.test.ts` asserts the\n * absence of a framework import so the boundary cannot rot back.\n */\nimport { logger } from \"@naculus/connect-core\";\nimport type { ConnectionStatus, Web3State } from \"./types\";\n\n// ── Async helpers ──────────────────────────────────────────────────────\n\nexport function withTimeout<T>(\n promise: Promise<T>,\n ms: number,\n label = \"Operation\",\n): Promise<T> {\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<T>((_, reject) => {\n timeoutId = setTimeout(\n () => reject(new Error(`${label} timed out after ${ms}ms`)),\n ms,\n );\n });\n return Promise.race([promise, timeout]).finally(() => {\n if (timeoutId !== undefined) clearTimeout(timeoutId);\n });\n}\n\nexport interface RetryOptions {\n maxRetries: number;\n baseDelay: number;\n label?: string;\n}\n\nexport async function withRetry<T>(\n fn: () => Promise<T>,\n options: RetryOptions,\n): Promise<T> {\n let lastError: unknown;\n for (let attempt = 0; attempt <= options.maxRetries; attempt++) {\n try {\n return await fn();\n } catch (error) {\n lastError = error;\n if (attempt < options.maxRetries) {\n const delay = options.baseDelay * 2 ** attempt;\n logger.warn(\n \"appkit/core\",\n `Retry attempt ${attempt + 1}/${options.maxRetries} after ${delay}ms`,\n );\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n }\n throw lastError;\n}\n\n// ── CAIP normalisation ─────────────────────────────────────────────────\n\n/** Convert EIP-1193 chainChanged values into the CAIP-2 form used by sessions. */\nexport function normalizeEip155ChainId(value: unknown): string | undefined {\n if (typeof value !== \"string\") return undefined;\n try {\n if (value.startsWith(\"eip155:\")) {\n const reference = value.slice(\"eip155:\".length);\n if (!/^\\d+$/.test(reference)) return undefined;\n return `eip155:${BigInt(reference).toString(10)}`;\n }\n if (/^0x[0-9a-f]+$/i.test(value) || /^\\d+$/.test(value)) {\n return `eip155:${BigInt(value).toString(10)}`;\n }\n } catch {\n // Ignore malformed wallet events rather than corrupting session state.\n }\n return undefined;\n}\n\nexport function toEip155Accounts(\n accounts: unknown[],\n chainId: string,\n): string[] {\n const reference = chainId.split(\":\")[1];\n if (!reference) return [];\n return accounts\n .filter(\n (account): account is string =>\n typeof account === \"string\" && account.length > 0,\n )\n .map((account) =>\n account.includes(\":\") ? (account.split(\":\").pop() as string) : account,\n )\n .map((account) => `eip155:${reference}:${account}`);\n}\n\n/**\n * Numeric EIP-155 chain id, or undefined for any other namespace.\n *\n * The previous implementation took the last CAIP segment and ran parseInt over\n * it, which stops at the first non-digit: `solana:5eykt4Us…` became 5 — an\n * EVM chain id — so asking for Solana mainnet's token list returned Goerli's,\n * and `xrpl:0` became chain 0. Anchor the whole reference instead.\n */\nexport function eip155ChainIdToNumber(chainId: string): number | undefined {\n const match = /^eip155:([1-9][0-9]*)$/.exec(chainId);\n if (!match) return undefined;\n const numeric = Number(match[1]);\n return Number.isSafeInteger(numeric) ? numeric : undefined;\n}\n\n// ── State machine ──────────────────────────────────────────────────────\n\nexport type Web3Action =\n | { type: \"SET_STATUS\"; payload: ConnectionStatus }\n | { type: \"SET_SESSION\"; payload: Web3State[\"session\"] }\n | { type: \"SET_ACCOUNTS\"; payload: string[] }\n | { type: \"SET_CHAIN\"; payload: string | null }\n | { type: \"SET_ERROR\"; payload: Error | null }\n | { type: \"RESET\" };\n\nexport const initialWeb3State: Web3State = {\n status: \"disconnected\",\n session: null,\n accounts: [],\n chainId: null,\n error: null,\n};\n\nexport function web3Reducer(state: Web3State, action: Web3Action): Web3State {\n switch (action.type) {\n case \"SET_STATUS\":\n return { ...state, status: action.payload };\n case \"SET_SESSION\":\n return { ...state, session: action.payload };\n case \"SET_ACCOUNTS\":\n return { ...state, accounts: action.payload };\n case \"SET_CHAIN\":\n return { ...state, chainId: action.payload };\n case \"SET_ERROR\":\n return { ...state, error: action.payload };\n case \"RESET\":\n return initialWeb3State;\n default:\n return state;\n }\n}\n"],"mappings":"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","names":["chainNumber","WalletError"]}
1
+ {"version":3,"sources":["../src/capabilities.ts","../src/solana-roles.ts","../src/chain-selection.ts","../src/viem-chain.ts","../src/aa-chain.ts","../src/token-chain.ts","../src/token-decimals.ts","../src/provider-errors.ts","../src/revert-reason.ts","../src/name-resolver.ts","../src/web3-store.ts"],"sourcesContent":["/**\n * EIP-5792 capability answers, normalised once.\n *\n * Deciding how to execute before sending is the whole point of\n * `wallet_getCapabilities`, and the reasoning that turns a raw answer into\n * that decision is not React's. It lived inside the React hook, which meant a\n * Vue composable could only get it by copying — and a second copy of \"absence\n * is not a denial\" is exactly the copy that drifts back into `false`.\n */\n\nimport {\n caip2ToHexChain,\n readAtomicSupport,\n type WalletCapabilities,\n} from \"@naculus/connect-core\";\n\n/**\n * Whether a wallet can execute a batch atomically.\n *\n * Three values, not two. A wallet that does not implement\n * `wallet_getCapabilities` has not said no — it has said nothing, and EIP-5792\n * is explicit that absence is not a denial. Collapsing that into `false` sends\n * every such wallet down the sequential path, which is the one where an\n * approve can land and the swap it was for can fail.\n */\nexport type AtomicSupport = \"supported\" | \"unsupported\" | \"unknown\";\n\nexport interface ChainCapabilities {\n /** CAIP-2 chain this describes. */\n chainId: string;\n atomic: AtomicSupport;\n /** Largest batch the wallet will accept, when it says. */\n maxBatchSize?: number;\n /** The raw entry, for capabilities this SDK does not model. */\n raw: WalletCapabilities;\n}\n\n/**\n * Turn a wallet's raw answer into per-chain entries.\n *\n * Entries that are not objects are dropped rather than coerced: a wallet\n * answering `null` for a chain has said nothing about it, and inventing an\n * `\"unsupported\"` there would be the same mistake as reading absence as denial.\n */\nexport function normalizeCapabilities(\n raw: Record<string, unknown> | null | undefined,\n): Record<string, ChainCapabilities> {\n const out: Record<string, ChainCapabilities> = {};\n for (const [key, entry] of Object.entries(raw ?? {})) {\n if (!entry || typeof entry !== \"object\") continue;\n const { supported, maxBatchSize } = readAtomicSupport(\n entry as Record<string, unknown>,\n );\n out[key] = {\n chainId: key,\n atomic: supported ? \"supported\" : \"unsupported\",\n ...(maxBatchSize === undefined ? {} : { maxBatchSize }),\n raw: entry as WalletCapabilities,\n };\n }\n return out;\n}\n\n/**\n * Find the entry for a chain, by CAIP-2 or by the hex form.\n *\n * Connectors normalise keys to CAIP-2, but a wallet answering in raw hex\n * through a custom connector should still be found rather than reported as\n * \"unknown\" next to an entry that is sitting right there.\n */\nexport function selectChainCapabilities(\n capabilities: Record<string, ChainCapabilities> | null | undefined,\n chainId: string | null | undefined,\n): ChainCapabilities | null {\n if (!capabilities || !chainId) return null;\n const hex = caip2ToHexChain(chainId);\n return capabilities[chainId] ?? (hex ? capabilities[hex] : undefined) ?? null;\n}\n\n/**\n * Atomic support for a chain.\n *\n * `\"unknown\"` covers every case where we have not been told: no query yet, a\n * wallet without the method, a failed query, one still in flight. A caller\n * choosing between an atomic and a sequential path needs that distinction, not\n * a confident `false`.\n */\nexport function atomicSupportFor(\n capabilities: Record<string, ChainCapabilities> | null | undefined,\n chainId: string | null | undefined,\n): AtomicSupport {\n return selectChainCapabilities(capabilities, chainId)?.atomic ?? \"unknown\";\n}\n","/**\n * Solana signer roles, read once for both frameworks.\n *\n * `@naculus/connector-solana` splits a connected wallet into `identity`,\n * `signer` and `payer`, each `null` when the wallet declared it cannot fill\n * that job. That answer is the connector's. What this module owns is the part\n * a UI has to decide before it can show anything: *why* there is no answer —\n * because nothing is connected, because the connected wallet is not Solana,\n * or because the connector predates the roles API and has not said. Those are\n * three different screens, and a hook that hands back one `null` for all of\n * them makes every consumer rediscover the distinction.\n */\n\n/** Which account this is, with no ability to sign for it. */\nexport interface SolanaIdentity {\n /** Base58 public key. */\n readonly address: string;\n /** CAIP-2 chain this account is being used on. */\n readonly chain: string;\n}\n\n/** An account that will contribute a signature without submitting anything. */\nexport interface SolanaSigner extends SolanaIdentity {\n signMessage?: (message: Uint8Array) => Promise<Uint8Array>;\n signTransaction: (transaction: Uint8Array) => Promise<Uint8Array>;\n /** Absent, not throwing, when the wallet has no batch feature. */\n signAllTransactions?: (transactions: Uint8Array[]) => Promise<Uint8Array[]>;\n}\n\n/** An account that signs and broadcasts through the wallet's own RPC. */\nexport interface SolanaPayer extends SolanaIdentity {\n /** Returns the base58 transaction signature the cluster accepted. */\n signAndSendTransaction: (transaction: Uint8Array) => Promise<string>;\n}\n\n/** What the wallet declared, recorded at discovery. Two-state on purpose. */\nexport interface SolanaWalletFeatures {\n signMessage: boolean;\n signTransaction: boolean;\n signAllTransactions: boolean;\n signAndSendTransaction: boolean;\n}\n\n/**\n * The roles a connected account can fill.\n *\n * Structurally identical to `SolanaRoles` in `@naculus/connector-solana`,\n * declared here rather than imported because this package depends only on\n * `@naculus/connect-core`, and the connector's own declaration is what its\n * `getRoles` returns — a value of that type satisfies this one unchanged.\n */\nexport interface SolanaRoles {\n identity: SolanaIdentity;\n signer: SolanaSigner | null;\n payer: SolanaPayer | null;\n features: SolanaWalletFeatures;\n}\n\n/** The part of a connector this needs. `getRoles` is optional on purpose. */\nexport interface SolanaRolesSource {\n getRoles?: (session: unknown) => SolanaRoles | null;\n}\n\n/** The part of a session this needs. */\nexport interface SessionLike {\n walletType?: string;\n}\n\n/**\n * Why `roles` is null.\n *\n * - `no_session` — nothing connected, or the connector has no live session\n * for the one the app holds (a restored session before reconnect).\n * - `not_solana` — the connected wallet is on another namespace. Not an\n * error; a Solana panel simply does not apply.\n * - `unreported` — the connector does not expose `getRoles`. An older\n * `@naculus/connector-solana`, or a custom one. The wallet has not said\n * what it can do, which is not the same as saying it can do nothing.\n */\nexport type SolanaRolesAbsence = \"no_session\" | \"not_solana\" | \"unreported\";\n\nexport type SolanaRolesState =\n | { roles: SolanaRoles; absence: null }\n | { roles: null; absence: SolanaRolesAbsence };\n\n/**\n * Read the roles for the session the app holds.\n *\n * Synchronous and pure: the connector already recorded what the wallet\n * declared, so there is nothing to await and no prompt to trigger. Reading\n * roles must never cost the user an approval dialog.\n */\nexport function readSolanaRoles(\n connector: SolanaRolesSource | null | undefined,\n session: SessionLike | null | undefined,\n): SolanaRolesState {\n if (!session) return { roles: null, absence: \"no_session\" };\n if (session.walletType !== \"solana\") {\n return { roles: null, absence: \"not_solana\" };\n }\n if (typeof connector?.getRoles !== \"function\") {\n return { roles: null, absence: \"unreported\" };\n }\n const roles = connector.getRoles(session);\n return roles\n ? { roles, absence: null }\n : { roles: null, absence: \"no_session\" };\n}\n","/**\n * Which chains a session can actually switch to, and what the current one is.\n *\n * Framework-neutral, and deliberately built on `parseChainId` from\n * `@naculus/connect-core` rather than on another `startsWith(\"eip155:\")`.\n */\n\nimport { eip155Reference, namespaceOf } from \"@naculus/connect-core\";\nimport type { ChainInfo, WalletChain } from \"./types\";\n\n/**\n * The EIP-155 chain number, or null when this is not an EVM chain.\n *\n * Callers that need a number — a viem client, an ERC-20 read — must handle\n * the null rather than defaulting, because there is no number that means\n * \"Solana\" and any stand-in would address a real EVM chain.\n */\nexport function chainNumber(chain: WalletChain): number | null {\n return eip155Reference(chain.caip2);\n}\n\n/** The namespace a chain belongs to, read from its CAIP-2 id. */\nexport function chainNamespace(chain: WalletChain): string | null {\n return namespaceOf(chain.caip2);\n}\n\n/**\n * The configured chain for a CAIP-2 id, or null.\n *\n * A string comparison now that `WalletChain` is keyed by CAIP-2. It used to\n * pull an integer out of the id and compare that, which no non-EVM chain\n * could ever match.\n */\nexport function resolveChain(\n chains: WalletChain[],\n chainId: string | null | undefined,\n): WalletChain | null {\n if (!chainId) return null;\n return chains.find((c) => c.caip2 === chainId) ?? null;\n}\n\n/**\n * The chains worth offering for the session's namespace.\n *\n * This used to return every configured chain regardless of what was\n * connected, so a Solana wallet was offered Ethereum and Polygon in the\n * switcher. Clicking one asked a wallet that has no concept of EIP-155 to\n * switch to chain 137 — an action the interface presented as available and\n * that could never work.\n *\n * With no connected chain, everything is offered: nothing has been ruled out\n * yet, and hiding the list would leave a user unable to pick.\n */\nexport function chainsForNamespace(\n chains: WalletChain[],\n chainId: string | null | undefined,\n): WalletChain[] {\n if (!chainId) return chains;\n const namespace = namespaceOf(chainId);\n if (!namespace) return chains;\n return chains.filter((c) => chainNamespace(c) === namespace);\n}\n\n/**\n * A description of the connected chain for display.\n *\n * Returns something for every namespace. It used to return null for anything\n * that was not EIP-155, so a connected Solana wallet had no chain to show at\n * all and the interface fell back to \"Unknown Chain\".\n */\nexport function describeChain(\n chains: WalletChain[],\n chainId: string | null | undefined,\n): ChainInfo | null {\n if (!chainId) return null;\n const namespace = namespaceOf(chainId);\n if (!namespace) return null;\n\n const known = resolveChain(chains, chainId);\n if (known) {\n return { namespace, chainId, name: known.name, selected: true };\n }\n\n // No configured entry. Name it by its CAIP-2 reference rather than\n // inventing one: \"Chain 5\" for a Solana cluster would be a fabrication, and\n // the reference is at least something a user can look up.\n const reference = chainId.slice(namespace.length + 1);\n return {\n namespace,\n chainId,\n name: namespace === \"eip155\" ? `Chain ${reference}` : reference,\n selected: true,\n };\n}\n","/**\n * The viem `Chain` shape for a configured EVM chain.\n *\n * Three hooks each built this inline — `useBalance`, `useTokenBalance` and\n * `useViemClient` — with identical bodies and the same `?? \"ETH\"` in each.\n * Three copies of a fallback is three places for it to be wrong.\n *\n * Returns a plain object rather than importing viem, so this package stays\n * free of a dependency only the React bindings need. The shape satisfies\n * viem's `Chain` structurally.\n */\n\nimport { CHAINS } from \"@naculus/connect-core\";\nimport type { WalletChain } from \"./types\";\n\nexport interface ViemChainShape {\n id: number;\n name: string;\n nativeCurrency: { name: string; symbol: string; decimals: number };\n rpcUrls: {\n default: { http: string[] };\n public: { http: string[] };\n };\n}\n\n/**\n * Build the chain descriptor a viem client needs, or null when this chain is\n * not one viem can talk to.\n *\n * Null for a non-EVM chain rather than a stand-in number: viem speaks to EVM\n * nodes, and any number chosen to fill the gap addresses a real chain that is\n * not the one connected.\n *\n * The native symbol comes from the chain's own `token`, then from the shared\n * chain registry. For an unlisted chain it stays empty rather than claiming\n * the asset is ETH. viem requires a string here; the user-facing symbol comes\n * from `useBalance().symbol`, which stays null when it is unknown.\n */\nexport function toViemChain(\n chain: WalletChain,\n chainNumber: number | null,\n): ViemChainShape | null {\n if (chainNumber === null || !chain.rpcUrl) return null;\n\n const symbol =\n chain.token ?? CHAINS[chainNumber]?.nativeCurrency?.symbol ?? \"\";\n\n return {\n id: chainNumber,\n name: chain.name,\n // Every EIP-155 native currency is 18 decimals by protocol, so this is a\n // constant rather than an assumption. ERC-20 decimals are per-token and\n // are never guessed anywhere in this codebase.\n nativeCurrency: { name: symbol, symbol, decimals: 18 },\n rpcUrls: {\n default: { http: [chain.rpcUrl] },\n public: { http: [chain.rpcUrl] },\n },\n };\n}\n","/**\n * Which EVM chain does an account-abstraction operation belong to?\n *\n * The smart-account hooks each resolved this as\n * `options.chainId ?? connectedChainId ?? \"eip155:1\"`, and each then guarded\n * with `if (connectedChainId && connectedChainId !== target)`. The guard is\n * skipped precisely when the mainnet default is used, so the one branch that\n * assumes a chain is the one branch that never checks it.\n *\n * That branch is reachable: a caller passing its own `signer` needs no\n * session, so `connectedChainId` is legitimately absent. It would then build\n * and sign a UserOperation for chain 1 — with chain 1's EntryPoint — while its\n * configured RPC and bundler point somewhere else. The chain ID is part of the\n * UserOperation hash, so the user approves a commitment to a chain they were\n * never shown.\n *\n * Resolving to undefined instead lets each caller refuse in whatever way suits\n * it. Framework-agnostic on purpose: it must not import React.\n */\n\n/** CAIP-2 for an EVM chain. The reference is a decimal chain ID with no leading zero. */\nconst EVM_CHAIN_ID = /^eip155:[1-9][0-9]*$/;\n\nexport function isEvmChainId(value: string | null | undefined): boolean {\n return typeof value === \"string\" && EVM_CHAIN_ID.test(value);\n}\n\n/**\n * The chain to operate on, or undefined when it cannot be known.\n *\n * An explicitly requested chain wins over the connected one; a malformed value\n * from either source resolves to undefined rather than being replaced by a\n * guess, because the caller asked for something specific and got it wrong.\n */\nexport function resolveEvmChainId(\n requested: string | null | undefined,\n connected: string | null | undefined,\n): string | undefined {\n const candidate = requested ?? connected ?? undefined;\n return isEvmChainId(candidate) ? candidate : undefined;\n}\n","/**\n * Does this token belong to the chain the wallet is pointed at?\n *\n * `TokenConfig.chainId` existed but was only ever read as a display label and\n * a cache key. Nothing compared it to the connected chain, so a hook handed a\n * mainnet token config while the wallet sat on Polygon would read — or\n * approve — whatever contract happens to occupy that address on Polygon.\n * Usually that address holds nothing and the call reverts, which is merely\n * confusing. When it holds a different token, `allowance()` returns a\n * plausible number for the wrong asset, and `approve()` grants a spender\n * rights over something the user never looked at.\n *\n * Framework-agnostic on purpose: it must not import React.\n */\n\nimport type { TokenConfig } from \"@naculus/connect-core\";\nimport { WalletError } from \"@naculus/connect-core\";\n\n/**\n * A mismatch, or undefined when the token is on the active chain — or when\n * there is not enough information to tell.\n *\n * Silence on \"cannot tell\" is deliberate. A token config without a chainId, or\n * a session with no resolved chain, is a gap in what the caller supplied; a\n * guess in either direction would be worse than letting the call proceed and\n * fail on its own terms.\n */\nexport function tokenChainMismatch(\n token: Pick<TokenConfig, \"address\" | \"chainId\">,\n activeChainId: number | undefined,\n): WalletError | undefined {\n if (typeof token?.chainId !== \"number\" || !Number.isFinite(token.chainId)) {\n return undefined;\n }\n if (typeof activeChainId !== \"number\" || !Number.isFinite(activeChainId)) {\n return undefined;\n }\n if (token.chainId === activeChainId) return undefined;\n\n return new WalletError(\n \"chain_mismatch\",\n `Token ${token.address} is configured for chain ${token.chainId}, but the ` +\n `wallet is on chain ${activeChainId}. Switch chains before continuing — ` +\n `the same address holds a different contract on each chain.`,\n );\n}\n","/**\n * Decimals cache keyed by the token it belongs to.\n *\n * The ERC-20 hooks cached decimals in a bare `useRef` seeded from the token\n * prop. `useRef` only takes its initial value on mount, and nothing reset it,\n * so a component that lets the user switch tokens kept the first token's\n * precision: picking USDC (6) and then DAI (18) converted \"100\" with 6\n * decimals and transferred 0.0000000001 DAI. The transaction succeeded, so\n * nothing surfaced the mistake.\n *\n * Keying the cache by the token makes a stale hit impossible without needing\n * an effect to fire first.\n */\n\nexport interface TokenIdentity {\n chainId?: number;\n address: string;\n}\n\nexport interface DecimalsCache {\n key: string;\n value: number | undefined;\n}\n\nexport function tokenKey(token: TokenIdentity): string {\n return `${token.chainId ?? \"?\"}:${token.address.toLowerCase()}`;\n}\n\nexport function createDecimalsCache(\n token: TokenIdentity,\n known?: number,\n): DecimalsCache {\n return { key: tokenKey(token), value: known };\n}\n\n/**\n * Cached decimals for `token`, or undefined when the cache holds another\n * token's value. Resets the cache as a side effect so the next write lands\n * against the right key.\n */\nexport function readDecimals(\n cache: DecimalsCache,\n token: TokenIdentity,\n known?: number,\n): number | undefined {\n const key = tokenKey(token);\n if (cache.key !== key) {\n cache.key = key;\n cache.value = known;\n }\n return cache.value;\n}\n\nexport function writeDecimals(\n cache: DecimalsCache,\n token: TokenIdentity,\n value: number,\n): void {\n cache.key = tokenKey(token);\n cache.value = value;\n}\n","/**\n * Normalize an EIP-1193 provider error into a WalletError.\n *\n * Hooks were collapsing every non-WalletError into one code — `useSwitchChain`\n * labelled a user pressing Cancel as `chain_unsupported`, so the UI told them\n * the chain was unavailable when they had simply declined. The codes below are\n * the ones wallets actually send, and they mean different things to a caller:\n * one is retryable by asking again, the other is not retryable at all until\n * the chain is added.\n *\n * Framework-agnostic on purpose: it must not import React.\n */\n\nimport type { WalletErrorCode } from \"@naculus/connect-core\";\nimport { WalletError } from \"@naculus/connect-core\";\n\n/** EIP-1193: the user rejected the request. */\nconst USER_REJECTED = 4001;\n/** EIP-3326: the wallet does not recognize the chain and must add it first. */\nconst UNRECOGNIZED_CHAIN = 4902;\n\nfunction providerCode(error: unknown): number | undefined {\n if (!error || typeof error !== \"object\") return undefined;\n const code = (error as { code?: unknown }).code;\n if (typeof code === \"number\") return code;\n // Some wallets stringify it, and some nest the original under `data`.\n if (typeof code === \"string\" && /^-?\\d+$/.test(code)) return Number(code);\n const nested = (error as { data?: { originalError?: unknown } }).data\n ?.originalError;\n return nested ? providerCode(nested) : undefined;\n}\n\n/**\n * Map a chain-switch failure onto the code that describes it.\n *\n * `fallback` is used when the wallet gave nothing to go on, so the caller\n * still decides what an unexplained failure means in its own context.\n */\nexport function toChainSwitchError(\n error: unknown,\n fallback: WalletErrorCode = \"chain_unsupported\",\n): WalletError {\n if (error instanceof WalletError) return error;\n\n const message =\n error instanceof Error\n ? error.message\n : \"Unknown error during chain switch\";\n\n switch (providerCode(error)) {\n case USER_REJECTED:\n return new WalletError(\"chain_switch_rejected\", message, error);\n case UNRECOGNIZED_CHAIN:\n return new WalletError(\n \"chain_unsupported\",\n `${message} (the wallet does not have this chain configured)`,\n error,\n );\n default:\n return new WalletError(fallback, message, error);\n }\n}\n","/**\n * Decode an EVM revert payload into something a user can act on.\n *\n * This text is what a wallet shows when a simulation says a transaction would\n * fail, so it is worth getting right and worth sharing: the logic is pure and\n * every binding needs the same answer.\n *\n * Two things are handled that the inlined version did not. The ABI length\n * word is attacker-controlled, so a payload can declare a longer string than\n * it carries; decoding that produced a run of NUL bytes rather than an honest\n * \"could not decode\". And a Panic was reported as \"Built-in failure\" with the\n * code discarded, which is the one piece of information that distinguishes an\n * overflow from a division by zero.\n */\n\n/** keccak256(\"Error(string)\")[0:4] */\nconst ERROR_STRING_SELECTOR = \"08c379a0\";\n/** keccak256(\"Panic(uint256)\")[0:4] */\nconst PANIC_SELECTOR = \"4e487b71\";\n\n/**\n * Panic codes defined by Solidity. Unlisted codes still surface their raw\n * value rather than being flattened into a generic message.\n */\nconst PANIC_REASONS: Record<number, string> = {\n 0x01: \"assertion failed\",\n 0x11: \"arithmetic overflow or underflow\",\n 0x12: \"division or modulo by zero\",\n 0x21: \"invalid enum value\",\n 0x31: \"pop() on an empty array\",\n 0x32: \"array index out of bounds\",\n};\n\nfunction hexToUtf8(hex: string): string {\n const bytes = new Uint8Array(hex.length / 2);\n for (let i = 0; i < bytes.length; i++) {\n bytes[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n }\n return new TextDecoder().decode(bytes);\n}\n\n/**\n * @param data Raw revert data from the node, with or without the 0x prefix.\n * @returns The decoded reason, or undefined when the payload is not a shape\n * this understands — callers should fall back to the RPC error text rather\n * than presenting a guess.\n */\nexport function decodeRevertReason(data: unknown): string | undefined {\n if (typeof data !== \"string\" || data.length === 0) return undefined;\n const clean = data.startsWith(\"0x\") ? data.slice(2) : data;\n if (!/^[0-9a-fA-F]*$/.test(clean)) return undefined;\n\n if (clean.startsWith(ERROR_STRING_SELECTOR)) {\n // selector(8) + offset(64) + length(64) + payload\n if (clean.length < 136) return undefined;\n const declared = Number.parseInt(clean.slice(72, 136), 16);\n if (!Number.isSafeInteger(declared) || declared <= 0) return undefined;\n const available = (clean.length - 136) / 2;\n // Refuse a payload that promises more than it carries instead of padding\n // the difference with NULs and calling it a revert reason.\n if (declared > available) return undefined;\n return hexToUtf8(clean.slice(136, 136 + declared * 2));\n }\n\n if (clean.startsWith(PANIC_SELECTOR)) {\n if (clean.length < 8 + 64) return \"Panic\";\n const code = Number.parseInt(clean.slice(8, 72), 16);\n if (!Number.isSafeInteger(code)) return \"Panic\";\n const known = PANIC_REASONS[code];\n const hex = `0x${code.toString(16).padStart(2, \"0\")}`;\n return known ? `Panic ${hex}: ${known}` : `Panic ${hex}`;\n }\n\n return undefined;\n}\n\n/** Decoded reason if available, otherwise the node's own message. */\nexport function resolveRevertReason(\n data: unknown,\n fallbackMessage: string,\n): string {\n return (\n decodeRevertReason(data) ??\n (fallbackMessage.includes(\"revert\")\n ? fallbackMessage\n : \"Transaction reverted\")\n );\n}\n","/**\n * Shared NameResolver instance for the name-resolution hooks.\n *\n * useResolveName and useLookupAddress each carried an identical getResolver\n * with its own module-level singleton, so the two directions of the same\n * lookup ran against separate resolvers and separate caches. One definition,\n * one instance.\n */\nimport { NameResolver } from \"@naculus/connect-core\";\nimport type { NameResolverConfig } from \"@naculus/connect-core\";\n\nlet sharedResolver: NameResolver | null = null;\n\n/**\n * @param config When supplied, returns a dedicated resolver rather than the\n * shared one — a caller asking for specific settings must not be handed an\n * instance configured by someone else.\n */\nexport function getResolver(config?: NameResolverConfig): NameResolver {\n if (config) return new NameResolver(config);\n if (!sharedResolver) sharedResolver = new NameResolver();\n return sharedResolver;\n}\n\n/** Test seam: drop the shared instance. */\nexport function __resetResolverForTests(): void {\n sharedResolver = null;\n}\n","/**\n * Framework-agnostic core of the connect provider.\n *\n * appkit's stated shape is \"write the Stencil components once, generate the\n * React and Vue wrappers\". In practice `react/` carries ~8.5k hand-written\n * lines against `vue/`'s ~280, and only two files in `react/src` contain JSX —\n * the rest is logic wearing React clothes. Web Components also cannot serve\n * React Native or Flutter, so the presentation layer was never going to be the\n * thing worth sharing.\n *\n * This module is the first piece moved out: the state machine and the pure\n * helpers around it, with no React import. Everything here is consumable from\n * Vue, Svelte, or a native binding as-is, and `web3-store.test.ts` asserts the\n * absence of a framework import so the boundary cannot rot back.\n */\nimport { logger } from \"@naculus/connect-core\";\nimport type { ConnectionStatus, Web3State } from \"./types\";\n\n// ── Async helpers ──────────────────────────────────────────────────────\n\nexport function withTimeout<T>(\n promise: Promise<T>,\n ms: number,\n label = \"Operation\",\n): Promise<T> {\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<T>((_, reject) => {\n timeoutId = setTimeout(\n () => reject(new Error(`${label} timed out after ${ms}ms`)),\n ms,\n );\n });\n return Promise.race([promise, timeout]).finally(() => {\n if (timeoutId !== undefined) clearTimeout(timeoutId);\n });\n}\n\nexport interface RetryOptions {\n maxRetries: number;\n baseDelay: number;\n label?: string;\n}\n\nexport async function withRetry<T>(\n fn: () => Promise<T>,\n options: RetryOptions,\n): Promise<T> {\n let lastError: unknown;\n for (let attempt = 0; attempt <= options.maxRetries; attempt++) {\n try {\n return await fn();\n } catch (error) {\n lastError = error;\n if (attempt < options.maxRetries) {\n const delay = options.baseDelay * 2 ** attempt;\n logger.warn(\n \"appkit/core\",\n `Retry attempt ${attempt + 1}/${options.maxRetries} after ${delay}ms`,\n );\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n }\n throw lastError;\n}\n\n// ── CAIP normalisation ─────────────────────────────────────────────────\n\n/** Convert EIP-1193 chainChanged values into the CAIP-2 form used by sessions. */\nexport function normalizeEip155ChainId(value: unknown): string | undefined {\n if (typeof value !== \"string\") return undefined;\n try {\n if (value.startsWith(\"eip155:\")) {\n const reference = value.slice(\"eip155:\".length);\n if (!/^\\d+$/.test(reference)) return undefined;\n return `eip155:${BigInt(reference).toString(10)}`;\n }\n if (/^0x[0-9a-f]+$/i.test(value) || /^\\d+$/.test(value)) {\n return `eip155:${BigInt(value).toString(10)}`;\n }\n } catch {\n // Ignore malformed wallet events rather than corrupting session state.\n }\n return undefined;\n}\n\nexport function toEip155Accounts(\n accounts: unknown[],\n chainId: string,\n): string[] {\n const reference = chainId.split(\":\")[1];\n if (!reference) return [];\n return accounts\n .filter(\n (account): account is string =>\n typeof account === \"string\" && account.length > 0,\n )\n .map((account) =>\n account.includes(\":\") ? (account.split(\":\").pop() as string) : account,\n )\n .map((account) => `eip155:${reference}:${account}`);\n}\n\n/**\n * Numeric EIP-155 chain id, or undefined for any other namespace.\n *\n * The previous implementation took the last CAIP segment and ran parseInt over\n * it, which stops at the first non-digit: `solana:5eykt4Us…` became 5 — an\n * EVM chain id — so asking for Solana mainnet's token list returned Goerli's,\n * and `xrpl:0` became chain 0. Anchor the whole reference instead.\n */\nexport function eip155ChainIdToNumber(chainId: string): number | undefined {\n const match = /^eip155:([1-9][0-9]*)$/.exec(chainId);\n if (!match) return undefined;\n const numeric = Number(match[1]);\n return Number.isSafeInteger(numeric) ? numeric : undefined;\n}\n\n// ── State machine ──────────────────────────────────────────────────────\n\nexport type Web3Action =\n | { type: \"SET_STATUS\"; payload: ConnectionStatus }\n | { type: \"SET_SESSION\"; payload: Web3State[\"session\"] }\n | { type: \"SET_ACCOUNTS\"; payload: string[] }\n | { type: \"SET_CHAIN\"; payload: string | null }\n | { type: \"SET_ERROR\"; payload: Error | null }\n | { type: \"RESET\" };\n\nexport const initialWeb3State: Web3State = {\n status: \"disconnected\",\n session: null,\n accounts: [],\n chainId: null,\n error: null,\n};\n\nexport function web3Reducer(state: Web3State, action: Web3Action): Web3State {\n switch (action.type) {\n case \"SET_STATUS\":\n return { ...state, status: action.payload };\n case \"SET_SESSION\":\n return { ...state, session: action.payload };\n case \"SET_ACCOUNTS\":\n return { ...state, accounts: action.payload };\n case \"SET_CHAIN\":\n return { ...state, chainId: action.payload };\n case \"SET_ERROR\":\n return { ...state, error: action.payload };\n case \"RESET\":\n return initialWeb3State;\n default:\n return state;\n }\n}\n"],"mappings":"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","names":["chainNumber","WalletError"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naculus/connect-appkit-core",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "type": "module",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",