@naculus/connect-appkit-core 0.2.3 → 0.2.5

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.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, NotificationSettings, InAppChannel, NotificationItem } 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
  *
@@ -282,6 +425,148 @@ declare function getResolver(config?: NameResolverConfig): NameResolver;
282
425
  /** Test seam: drop the shared instance. */
283
426
  declare function __resetResolverForTests(): void;
284
427
 
428
+ /** Framework-neutral in-app notification actions and settings persistence. */
429
+
430
+ interface NotificationSettingsStorage {
431
+ getItem<T>(key: string): Promise<T | null>;
432
+ setItem<T>(key: string, value: T): Promise<void>;
433
+ removeItem(key: string): Promise<void>;
434
+ }
435
+ declare const NOTIFICATION_SETTINGS_KEY = "naculus_notif_react_settings";
436
+ declare const DEFAULT_NOTIFICATION_SETTINGS: NotificationSettings;
437
+ /** Preserve the existing localStorage adapter and fail-open UI fallback. */
438
+ declare function createNotificationSettingsStorage(): NotificationSettingsStorage;
439
+ declare function loadNotificationSettings(storage: NotificationSettingsStorage): Promise<NotificationSettings | null>;
440
+ declare function saveNotificationSettings(storage: NotificationSettingsStorage, settings: NotificationSettings): void;
441
+ declare function updateNotificationSettings(current: NotificationSettings, update: Partial<NotificationSettings>): NotificationSettings;
442
+ declare function channelsUpdate(channels: string[]): Partial<NotificationSettings>;
443
+ declare function mutedChainSettings(settings: NotificationSettings, chainId: string, muted: boolean): Partial<NotificationSettings>;
444
+ declare function createNotification(item: Omit<NotificationItem, "id" | "timestamp">): NotificationItem;
445
+ declare function addInAppNotification(channel: InAppChannel, item: Omit<NotificationItem, "id" | "timestamp">): void;
446
+ /** InAppChannel has no remove-one method; preserve its clear/replay behavior. */
447
+ declare function clearOneInAppNotification(channel: InAppChannel, id: string): Promise<NotificationItem[]>;
448
+
449
+ /**
450
+ * Destination-address checks shared by the React hook and the Vue composable.
451
+ *
452
+ * Syntactic only. `"ok"` means none of these checks found anything wrong —
453
+ * not that the recipient is who the user thinks. This cannot see an
454
+ * address-poisoning lookalike, a contract that will not release the funds,
455
+ * or a known-malicious destination. Labelling that "safe" once put a green
456
+ * tick beside a scammer's address; the level is deliberately not "safe".
457
+ */
458
+ type AddressValidationLevel = "ok" | "warning" | "blocked";
459
+ /** Machine-readable reason; the framework layer maps it to a user string. */
460
+ type DestinationIssue = "empty" | "invalid_format" | "zero_address" | "burn_address";
461
+ interface DestinationValidation {
462
+ isValid: boolean;
463
+ level: AddressValidationLevel;
464
+ issue: DestinationIssue | null;
465
+ }
466
+ /**
467
+ * Burn heuristic kept from the React hook: known sinks plus any well-formed
468
+ * address containing "dead". Stricter than connect-core's prefix-only
469
+ * `isBurnAddress`; unifying the two is a connect-lib change.
470
+ */
471
+ declare function isBurnDestination(address: string): boolean;
472
+ declare function validateDestination(address: string): DestinationValidation;
473
+ /**
474
+ * The bare `0x…` account from either a CAIP-10 id (`eip155:1:0x…`) or a plain
475
+ * hex address; null when it is neither. Hooks and composables should not each
476
+ * carry their own copy of this split.
477
+ */
478
+ declare function bareEvmAddress(account: string | null | undefined): `0x${string}` | null;
479
+
480
+ /**
481
+ * Chain-abstraction (routing) domain types and the few decisions the React
482
+ * hooks and Vue composables share. The hooks inject the actual quote /
483
+ * compare / execute functions; nothing here talks to a network.
484
+ */
485
+ interface RouteQuote {
486
+ routeId: string;
487
+ provider: string;
488
+ netReceiveFormatted: string;
489
+ toTokenSymbol: string;
490
+ }
491
+ interface RouteQuoteOptions {
492
+ sortBy?: "cost" | "time" | "balance";
493
+ maxSlippage?: number;
494
+ }
495
+ interface RouteQuoteInput {
496
+ fromChain: string;
497
+ toChain: string;
498
+ fromToken: string;
499
+ toToken?: string;
500
+ amount: string;
501
+ options?: RouteQuoteOptions;
502
+ }
503
+ type GetRouteQuotes = (from: string, to: string, token: string, amount: string, options?: RouteQuoteOptions, toToken?: string) => Promise<RouteQuote[]>;
504
+ /**
505
+ * `BigInt()` throws on anything that is not a decimal integer — "1.5", "abc",
506
+ * "1e18" — so a user typing into an amount field must not reach it. Only a
507
+ * positive base-unit integer is a quotable amount.
508
+ */
509
+ declare function isPositiveIntegerAmount(amount: string): boolean;
510
+ /** True when every field a quote needs is present and the amount is quotable. */
511
+ declare function isQuotableInput(input: RouteQuoteInput): boolean;
512
+ interface CostComparison {
513
+ chain: string;
514
+ chainName: string;
515
+ totalCost: string;
516
+ gasCost: string;
517
+ estimatedTimeMs: number;
518
+ }
519
+ type CostComparisonOperation = "send_erc20" | "swap" | "bridge" | (string & {});
520
+ interface CostComparisonOptions {
521
+ amount?: string;
522
+ token?: string;
523
+ [key: string]: unknown;
524
+ }
525
+ interface CompareCostsInput {
526
+ operation: CostComparisonOperation;
527
+ chains: string[];
528
+ options?: CostComparisonOptions;
529
+ }
530
+ type CompareCosts = (operation: string, chains: string[], options?: CostComparisonOptions) => Promise<CostComparison[]>;
531
+ /**
532
+ * Value key for a compare-costs input. `chains` is an array and `options` an
533
+ * object; keying on identity re-fired the fetch on every render/reactive tick
534
+ * for a caller writing the natural inline literal — an unbounded loop against
535
+ * a cost API.
536
+ */
537
+ declare function compareCostsKey(input: CompareCostsInput): string;
538
+ interface ExecutableQuote {
539
+ routeId: string;
540
+ provider: string;
541
+ totalCost?: bigint;
542
+ estimatedTimeMs?: number;
543
+ toChain?: string;
544
+ }
545
+ interface ExecuteRouteResult {
546
+ fromTxHash: string;
547
+ toTxHash?: string;
548
+ }
549
+ interface ExecuteRouteOptions {
550
+ timeoutMs?: number;
551
+ gasLimit?: bigint;
552
+ }
553
+ interface ExecuteRouteError {
554
+ code: string;
555
+ message: string;
556
+ details?: Record<string, unknown>;
557
+ }
558
+ type ExecuteRoute = (quote: ExecutableQuote, recipient: string, options?: ExecuteRouteOptions) => Promise<ExecuteRouteResult>;
559
+ /**
560
+ * Recipient check before a cross-chain execution. An empty recipient is wrong
561
+ * on every chain. Beyond that the destination namespace decides: when the
562
+ * quote names `toChain`, the address is validated for that namespace; when it
563
+ * does not, this returns null and the executor decides, rather than rejecting
564
+ * a valid non-EVM address on a guess.
565
+ */
566
+ declare function validateRouteRecipient(quote: ExecutableQuote | null | undefined, recipient: string): ExecuteRouteError | null;
567
+ /** Normalise whatever an executor threw into the hook's error shape. */
568
+ declare function toExecuteRouteError(cause: unknown): ExecuteRouteError;
569
+
285
570
  declare function withTimeout<T>(promise: Promise<T>, ms: number, label?: string): Promise<T>;
286
571
  interface RetryOptions {
287
572
  maxRetries: number;
@@ -322,4 +607,4 @@ type Web3Action = {
322
607
  declare const initialWeb3State: Web3State;
323
608
  declare function web3Reducer(state: Web3State, action: Web3Action): Web3State;
324
609
 
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 };
610
+ export { type AddressValidationLevel, type AtomicSupport, type ChainCapabilities, type ChainInfo, type CompareCosts, type CompareCostsInput, type ConnectionStatus, type CostComparison, type CostComparisonOperation, type CostComparisonOptions, DEFAULT_NOTIFICATION_SETTINGS, type DecimalsCache, type DestinationIssue, type DestinationValidation, type ExecutableQuote, type ExecuteRoute, type ExecuteRouteError, type ExecuteRouteOptions, type ExecuteRouteResult, type GetRouteQuotes, NOTIFICATION_SETTINGS_KEY, type NotificationSettingsStorage, type RetryOptions, type RouteQuote, type RouteQuoteInput, type RouteQuoteOptions, 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, addInAppNotification, atomicSupportFor, bareEvmAddress, chainNamespace, chainNumber, chainsForNamespace, channelsUpdate, clearOneInAppNotification, compareCostsKey, createDecimalsCache, createNotification, createNotificationSettingsStorage, decodeRevertReason, describeChain, eip155ChainIdToNumber, getResolver, initialWeb3State, isBurnDestination, isEvmChainId, isPositiveIntegerAmount, isQuotableInput, loadNotificationSettings, mutedChainSettings, normalizeCapabilities, normalizeEip155ChainId, readDecimals, readSolanaRoles, resolveChain, resolveEvmChainId, resolveRevertReason, saveNotificationSettings, selectChainCapabilities, toChainSwitchError, toEip155Accounts, toExecuteRouteError, toViemChain, tokenChainMismatch, tokenKey, updateNotificationSettings, validateDestination, validateRouteRecipient, 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, NotificationSettings, InAppChannel, NotificationItem } 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
  *
@@ -282,6 +425,148 @@ declare function getResolver(config?: NameResolverConfig): NameResolver;
282
425
  /** Test seam: drop the shared instance. */
283
426
  declare function __resetResolverForTests(): void;
284
427
 
428
+ /** Framework-neutral in-app notification actions and settings persistence. */
429
+
430
+ interface NotificationSettingsStorage {
431
+ getItem<T>(key: string): Promise<T | null>;
432
+ setItem<T>(key: string, value: T): Promise<void>;
433
+ removeItem(key: string): Promise<void>;
434
+ }
435
+ declare const NOTIFICATION_SETTINGS_KEY = "naculus_notif_react_settings";
436
+ declare const DEFAULT_NOTIFICATION_SETTINGS: NotificationSettings;
437
+ /** Preserve the existing localStorage adapter and fail-open UI fallback. */
438
+ declare function createNotificationSettingsStorage(): NotificationSettingsStorage;
439
+ declare function loadNotificationSettings(storage: NotificationSettingsStorage): Promise<NotificationSettings | null>;
440
+ declare function saveNotificationSettings(storage: NotificationSettingsStorage, settings: NotificationSettings): void;
441
+ declare function updateNotificationSettings(current: NotificationSettings, update: Partial<NotificationSettings>): NotificationSettings;
442
+ declare function channelsUpdate(channels: string[]): Partial<NotificationSettings>;
443
+ declare function mutedChainSettings(settings: NotificationSettings, chainId: string, muted: boolean): Partial<NotificationSettings>;
444
+ declare function createNotification(item: Omit<NotificationItem, "id" | "timestamp">): NotificationItem;
445
+ declare function addInAppNotification(channel: InAppChannel, item: Omit<NotificationItem, "id" | "timestamp">): void;
446
+ /** InAppChannel has no remove-one method; preserve its clear/replay behavior. */
447
+ declare function clearOneInAppNotification(channel: InAppChannel, id: string): Promise<NotificationItem[]>;
448
+
449
+ /**
450
+ * Destination-address checks shared by the React hook and the Vue composable.
451
+ *
452
+ * Syntactic only. `"ok"` means none of these checks found anything wrong —
453
+ * not that the recipient is who the user thinks. This cannot see an
454
+ * address-poisoning lookalike, a contract that will not release the funds,
455
+ * or a known-malicious destination. Labelling that "safe" once put a green
456
+ * tick beside a scammer's address; the level is deliberately not "safe".
457
+ */
458
+ type AddressValidationLevel = "ok" | "warning" | "blocked";
459
+ /** Machine-readable reason; the framework layer maps it to a user string. */
460
+ type DestinationIssue = "empty" | "invalid_format" | "zero_address" | "burn_address";
461
+ interface DestinationValidation {
462
+ isValid: boolean;
463
+ level: AddressValidationLevel;
464
+ issue: DestinationIssue | null;
465
+ }
466
+ /**
467
+ * Burn heuristic kept from the React hook: known sinks plus any well-formed
468
+ * address containing "dead". Stricter than connect-core's prefix-only
469
+ * `isBurnAddress`; unifying the two is a connect-lib change.
470
+ */
471
+ declare function isBurnDestination(address: string): boolean;
472
+ declare function validateDestination(address: string): DestinationValidation;
473
+ /**
474
+ * The bare `0x…` account from either a CAIP-10 id (`eip155:1:0x…`) or a plain
475
+ * hex address; null when it is neither. Hooks and composables should not each
476
+ * carry their own copy of this split.
477
+ */
478
+ declare function bareEvmAddress(account: string | null | undefined): `0x${string}` | null;
479
+
480
+ /**
481
+ * Chain-abstraction (routing) domain types and the few decisions the React
482
+ * hooks and Vue composables share. The hooks inject the actual quote /
483
+ * compare / execute functions; nothing here talks to a network.
484
+ */
485
+ interface RouteQuote {
486
+ routeId: string;
487
+ provider: string;
488
+ netReceiveFormatted: string;
489
+ toTokenSymbol: string;
490
+ }
491
+ interface RouteQuoteOptions {
492
+ sortBy?: "cost" | "time" | "balance";
493
+ maxSlippage?: number;
494
+ }
495
+ interface RouteQuoteInput {
496
+ fromChain: string;
497
+ toChain: string;
498
+ fromToken: string;
499
+ toToken?: string;
500
+ amount: string;
501
+ options?: RouteQuoteOptions;
502
+ }
503
+ type GetRouteQuotes = (from: string, to: string, token: string, amount: string, options?: RouteQuoteOptions, toToken?: string) => Promise<RouteQuote[]>;
504
+ /**
505
+ * `BigInt()` throws on anything that is not a decimal integer — "1.5", "abc",
506
+ * "1e18" — so a user typing into an amount field must not reach it. Only a
507
+ * positive base-unit integer is a quotable amount.
508
+ */
509
+ declare function isPositiveIntegerAmount(amount: string): boolean;
510
+ /** True when every field a quote needs is present and the amount is quotable. */
511
+ declare function isQuotableInput(input: RouteQuoteInput): boolean;
512
+ interface CostComparison {
513
+ chain: string;
514
+ chainName: string;
515
+ totalCost: string;
516
+ gasCost: string;
517
+ estimatedTimeMs: number;
518
+ }
519
+ type CostComparisonOperation = "send_erc20" | "swap" | "bridge" | (string & {});
520
+ interface CostComparisonOptions {
521
+ amount?: string;
522
+ token?: string;
523
+ [key: string]: unknown;
524
+ }
525
+ interface CompareCostsInput {
526
+ operation: CostComparisonOperation;
527
+ chains: string[];
528
+ options?: CostComparisonOptions;
529
+ }
530
+ type CompareCosts = (operation: string, chains: string[], options?: CostComparisonOptions) => Promise<CostComparison[]>;
531
+ /**
532
+ * Value key for a compare-costs input. `chains` is an array and `options` an
533
+ * object; keying on identity re-fired the fetch on every render/reactive tick
534
+ * for a caller writing the natural inline literal — an unbounded loop against
535
+ * a cost API.
536
+ */
537
+ declare function compareCostsKey(input: CompareCostsInput): string;
538
+ interface ExecutableQuote {
539
+ routeId: string;
540
+ provider: string;
541
+ totalCost?: bigint;
542
+ estimatedTimeMs?: number;
543
+ toChain?: string;
544
+ }
545
+ interface ExecuteRouteResult {
546
+ fromTxHash: string;
547
+ toTxHash?: string;
548
+ }
549
+ interface ExecuteRouteOptions {
550
+ timeoutMs?: number;
551
+ gasLimit?: bigint;
552
+ }
553
+ interface ExecuteRouteError {
554
+ code: string;
555
+ message: string;
556
+ details?: Record<string, unknown>;
557
+ }
558
+ type ExecuteRoute = (quote: ExecutableQuote, recipient: string, options?: ExecuteRouteOptions) => Promise<ExecuteRouteResult>;
559
+ /**
560
+ * Recipient check before a cross-chain execution. An empty recipient is wrong
561
+ * on every chain. Beyond that the destination namespace decides: when the
562
+ * quote names `toChain`, the address is validated for that namespace; when it
563
+ * does not, this returns null and the executor decides, rather than rejecting
564
+ * a valid non-EVM address on a guess.
565
+ */
566
+ declare function validateRouteRecipient(quote: ExecutableQuote | null | undefined, recipient: string): ExecuteRouteError | null;
567
+ /** Normalise whatever an executor threw into the hook's error shape. */
568
+ declare function toExecuteRouteError(cause: unknown): ExecuteRouteError;
569
+
285
570
  declare function withTimeout<T>(promise: Promise<T>, ms: number, label?: string): Promise<T>;
286
571
  interface RetryOptions {
287
572
  maxRetries: number;
@@ -322,4 +607,4 @@ type Web3Action = {
322
607
  declare const initialWeb3State: Web3State;
323
608
  declare function web3Reducer(state: Web3State, action: Web3Action): Web3State;
324
609
 
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 };
610
+ export { type AddressValidationLevel, type AtomicSupport, type ChainCapabilities, type ChainInfo, type CompareCosts, type CompareCostsInput, type ConnectionStatus, type CostComparison, type CostComparisonOperation, type CostComparisonOptions, DEFAULT_NOTIFICATION_SETTINGS, type DecimalsCache, type DestinationIssue, type DestinationValidation, type ExecutableQuote, type ExecuteRoute, type ExecuteRouteError, type ExecuteRouteOptions, type ExecuteRouteResult, type GetRouteQuotes, NOTIFICATION_SETTINGS_KEY, type NotificationSettingsStorage, type RetryOptions, type RouteQuote, type RouteQuoteInput, type RouteQuoteOptions, 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, addInAppNotification, atomicSupportFor, bareEvmAddress, chainNamespace, chainNumber, chainsForNamespace, channelsUpdate, clearOneInAppNotification, compareCostsKey, createDecimalsCache, createNotification, createNotificationSettingsStorage, decodeRevertReason, describeChain, eip155ChainIdToNumber, getResolver, initialWeb3State, isBurnDestination, isEvmChainId, isPositiveIntegerAmount, isQuotableInput, loadNotificationSettings, mutedChainSettings, normalizeCapabilities, normalizeEip155ChainId, readDecimals, readSolanaRoles, resolveChain, resolveEvmChainId, resolveRevertReason, saveNotificationSettings, selectChainCapabilities, toChainSwitchError, toEip155Accounts, toExecuteRouteError, toViemChain, tokenChainMismatch, tokenKey, updateNotificationSettings, validateDestination, validateRouteRecipient, web3Reducer, withRetry, withTimeout, writeDecimals };