@naculus/connect-appkit-core 0.2.4 → 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, WalletCapabilities, 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.
@@ -425,6 +425,148 @@ declare function getResolver(config?: NameResolverConfig): NameResolver;
425
425
  /** Test seam: drop the shared instance. */
426
426
  declare function __resetResolverForTests(): void;
427
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
+
428
570
  declare function withTimeout<T>(promise: Promise<T>, ms: number, label?: string): Promise<T>;
429
571
  interface RetryOptions {
430
572
  maxRetries: number;
@@ -465,4 +607,4 @@ type Web3Action = {
465
607
  declare const initialWeb3State: Web3State;
466
608
  declare function web3Reducer(state: Web3State, action: Web3Action): Web3State;
467
609
 
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 };
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, WalletCapabilities, 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.
@@ -425,6 +425,148 @@ declare function getResolver(config?: NameResolverConfig): NameResolver;
425
425
  /** Test seam: drop the shared instance. */
426
426
  declare function __resetResolverForTests(): void;
427
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
+
428
570
  declare function withTimeout<T>(promise: Promise<T>, ms: number, label?: string): Promise<T>;
429
571
  interface RetryOptions {
430
572
  maxRetries: number;
@@ -465,4 +607,4 @@ type Web3Action = {
465
607
  declare const initialWeb3State: Web3State;
466
608
  declare function web3Reducer(state: Web3State, action: Web3Action): Web3State;
467
609
 
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 };
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.js CHANGED
@@ -226,6 +226,172 @@ function __resetResolverForTests() {
226
226
  sharedResolver = null;
227
227
  }
228
228
 
229
+ // src/notification.ts
230
+ import {
231
+ createStorageAdapter
232
+ } from "@naculus/connect-core";
233
+ var NOTIFICATION_SETTINGS_KEY = "naculus_notif_react_settings";
234
+ var DEFAULT_NOTIFICATION_SETTINGS = {
235
+ telegram: false,
236
+ webpush: false,
237
+ inapp: true,
238
+ frequency: "final-only",
239
+ mutedChains: [],
240
+ mutedTypes: []
241
+ };
242
+ function createNotificationSettingsStorage() {
243
+ const storage = createStorageAdapter("local", "naculus_notif_");
244
+ return {
245
+ getItem: async (key) => {
246
+ try {
247
+ return await storage.get(key);
248
+ } catch (cause) {
249
+ console.warn("useNotification: failed to read from storage:", cause);
250
+ return null;
251
+ }
252
+ },
253
+ setItem: async (key, value) => {
254
+ try {
255
+ await storage.set(key, value);
256
+ } catch (cause) {
257
+ console.warn("useNotification: failed to write to storage:", cause);
258
+ }
259
+ },
260
+ removeItem: async (key) => {
261
+ try {
262
+ await storage.remove(key);
263
+ } catch (cause) {
264
+ console.warn("useNotification: failed to remove from storage:", cause);
265
+ }
266
+ }
267
+ };
268
+ }
269
+ async function loadNotificationSettings(storage) {
270
+ return storage.getItem(NOTIFICATION_SETTINGS_KEY);
271
+ }
272
+ function saveNotificationSettings(storage, settings) {
273
+ storage.setItem(NOTIFICATION_SETTINGS_KEY, settings).catch(() => {
274
+ });
275
+ }
276
+ function updateNotificationSettings(current, update) {
277
+ return { ...current, ...update };
278
+ }
279
+ function channelsUpdate(channels) {
280
+ const update = {};
281
+ if (channels.includes("telegram")) update.telegram = true;
282
+ if (channels.includes("webpush")) update.webpush = true;
283
+ if (channels.includes("inapp")) update.inapp = true;
284
+ return update;
285
+ }
286
+ function mutedChainSettings(settings, chainId, muted) {
287
+ return {
288
+ mutedChains: muted ? [.../* @__PURE__ */ new Set([...settings.mutedChains, chainId])] : settings.mutedChains.filter((id) => id !== chainId)
289
+ };
290
+ }
291
+ function createNotification(item) {
292
+ return {
293
+ ...item,
294
+ id: `notif_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
295
+ timestamp: Date.now()
296
+ };
297
+ }
298
+ function sendItem(channel, item) {
299
+ return channel.send({
300
+ id: item.id,
301
+ userId: "",
302
+ txHash: item.txHash,
303
+ chainId: "",
304
+ chainName: item.chainName,
305
+ status: item.status,
306
+ title: item.title,
307
+ body: item.body,
308
+ valueFormatted: item.valueFormatted,
309
+ explorerUrl: item.explorerUrl,
310
+ timestamp: item.timestamp
311
+ });
312
+ }
313
+ function addInAppNotification(channel, item) {
314
+ void sendItem(channel, createNotification(item));
315
+ }
316
+ async function clearOneInAppNotification(channel, id) {
317
+ const remaining = channel.getHistory().filter((item) => item.id !== id);
318
+ channel.clear();
319
+ await Promise.all(remaining.map((item) => sendItem(channel, item)));
320
+ return channel.getHistory();
321
+ }
322
+
323
+ // src/destination.ts
324
+ import { isEvmAddress, isZeroAddress } from "@naculus/connect-core";
325
+ var DEAD_ADDRESSES = /* @__PURE__ */ new Set([
326
+ "0x0000000000000000000000000000000000000001",
327
+ "0x000000000000000000000000000000000000dead"
328
+ ]);
329
+ function isBurnDestination(address) {
330
+ const lower = address.toLowerCase();
331
+ return DEAD_ADDRESSES.has(lower) || lower.includes("dead");
332
+ }
333
+ function validateDestination(address) {
334
+ if (!address) return { isValid: false, level: "blocked", issue: "empty" };
335
+ if (!isEvmAddress(address)) {
336
+ return { isValid: false, level: "blocked", issue: "invalid_format" };
337
+ }
338
+ if (isZeroAddress(address)) {
339
+ return { isValid: false, level: "blocked", issue: "zero_address" };
340
+ }
341
+ if (isBurnDestination(address)) {
342
+ return { isValid: false, level: "blocked", issue: "burn_address" };
343
+ }
344
+ return { isValid: true, level: "ok", issue: null };
345
+ }
346
+ function bareEvmAddress(account) {
347
+ if (!account) return null;
348
+ const value = account.includes(":") ? account.split(":").pop() : account;
349
+ return value && isEvmAddress(value) ? value : null;
350
+ }
351
+
352
+ // src/routing.ts
353
+ import { isValidAddress } from "@naculus/connect-core";
354
+ function isPositiveIntegerAmount(amount) {
355
+ return /^(?:0|[1-9][0-9]*)$/.test(amount) && BigInt(amount) > 0n;
356
+ }
357
+ function isQuotableInput(input) {
358
+ return Boolean(
359
+ input.fromChain && input.toChain && input.fromToken && isPositiveIntegerAmount(input.amount)
360
+ );
361
+ }
362
+ function compareCostsKey(input) {
363
+ const options = input.options;
364
+ const optionsKey = options ? Object.keys(options).sort().map((key) => {
365
+ const value = options[key];
366
+ return `${key}=${typeof value === "object" && value !== null ? "[object]" : String(value)}`;
367
+ }).join("&") : "";
368
+ return `${input.operation}|${(input.chains ?? []).join(",")}|${optionsKey}`;
369
+ }
370
+ function validateRouteRecipient(quote, recipient) {
371
+ if (typeof recipient !== "string" || recipient.trim() === "") {
372
+ return {
373
+ code: "invalid_recipient",
374
+ message: "A recipient address is required"
375
+ };
376
+ }
377
+ const namespace = quote?.toChain?.split(":")[0];
378
+ if (namespace && !isValidAddress(recipient, namespace)) {
379
+ return {
380
+ code: "invalid_recipient",
381
+ message: `"${recipient}" is not a valid ${namespace} address for the destination chain ${quote?.toChain}`
382
+ };
383
+ }
384
+ return null;
385
+ }
386
+ function toExecuteRouteError(cause) {
387
+ const error = cause;
388
+ return {
389
+ code: error?.code ?? "execution_failed",
390
+ message: error?.message ?? String(cause),
391
+ details: error?.details
392
+ };
393
+ }
394
+
229
395
  // src/web3-store.ts
230
396
  import { logger } from "@naculus/connect-core";
231
397
  function withTimeout(promise, ms, label = "Operation") {
@@ -315,18 +481,32 @@ function web3Reducer(state, action) {
315
481
  }
316
482
  }
317
483
  export {
484
+ DEFAULT_NOTIFICATION_SETTINGS,
485
+ NOTIFICATION_SETTINGS_KEY,
318
486
  __resetResolverForTests,
487
+ addInAppNotification,
319
488
  atomicSupportFor,
489
+ bareEvmAddress,
320
490
  chainNamespace,
321
491
  chainNumber,
322
492
  chainsForNamespace,
493
+ channelsUpdate,
494
+ clearOneInAppNotification,
495
+ compareCostsKey,
323
496
  createDecimalsCache,
497
+ createNotification,
498
+ createNotificationSettingsStorage,
324
499
  decodeRevertReason,
325
500
  describeChain,
326
501
  eip155ChainIdToNumber,
327
502
  getResolver,
328
503
  initialWeb3State,
504
+ isBurnDestination,
329
505
  isEvmChainId,
506
+ isPositiveIntegerAmount,
507
+ isQuotableInput,
508
+ loadNotificationSettings,
509
+ mutedChainSettings,
330
510
  normalizeCapabilities,
331
511
  normalizeEip155ChainId,
332
512
  readDecimals,
@@ -334,12 +514,17 @@ export {
334
514
  resolveChain,
335
515
  resolveEvmChainId,
336
516
  resolveRevertReason,
517
+ saveNotificationSettings,
337
518
  selectChainCapabilities,
338
519
  toChainSwitchError,
339
520
  toEip155Accounts,
521
+ toExecuteRouteError,
340
522
  toViemChain,
341
523
  tokenChainMismatch,
342
524
  tokenKey,
525
+ updateNotificationSettings,
526
+ validateDestination,
527
+ validateRouteRecipient,
343
528
  web3Reducer,
344
529
  withRetry,
345
530
  withTimeout,