export { OnRampDisallowReason, OnRampEligibility, OnRampError, OnRampErrorCode, OnRampOpenParams, OnRampPort, OnRampProviderId, OnRampSession, OnRampStatus, OnRampStatusEvent, normalizeDisallowReason } from '@nexus-cross/onramp'; /** * How a connector reaches the blockchain: * - embedded: In-app wallet via crossy-sdk-js (social login, no extension) * - app: CROSSx mobile app deep-link / QR approval * - extension: Browser extension (CROSSx Extension, injected EIP-1193) * - external: Third-party wallets via Reown/WalletConnect (MetaMask, Binance …) */ type ConnectorType = 'embedded' | 'app' | 'extension' | 'external'; /** * Well-known wallet identifiers used throughout the kit. * Maps 1:1 to posa's WalletType union so existing apps migrate trivially. */ type WalletId = 'cross_embedded' | 'cross_wallet' | 'cross_extension' | 'metamask' | 'binance' | (string & {}); interface WalletDescriptor { readonly id: WalletId; readonly name: string; readonly type: ConnectorType; readonly iconUrl?: string; /** EIP-6963 rdns for browser-extension detection */ readonly rdns?: string; /** Install URL when extension is not detected */ readonly installUrl?: string; /** Whether the wallet is available in the current environment */ installed?: boolean; } interface ConnectorResult { account: Account; chainId: number; } interface Account { /** 0x-prefixed hex string */ address: string; chainId: number; } interface ChainBalance { /** EIP-155 numeric chain ID */ chainId: number; /** Human-readable decimal string (e.g. "1234.5678") */ balance: string; /** Raw integer string in wei */ balanceWei: string; } declare const ConnectionStatus: { readonly CONNECTED: "connected"; readonly DISCONNECTED: "disconnected"; readonly CONNECTING: "connecting"; readonly RECONNECTING: "reconnecting"; }; type ConnectionStatus = (typeof ConnectionStatus)[keyof typeof ConnectionStatus]; interface WalletState { status: ConnectionStatus; /** Connected wallet address, undefined when disconnected */ address: string | undefined; /** Currently active wallet identifier */ activeWalletId: WalletId | null; /** True while auto-reconnecting from a previous session */ isReconnecting: boolean; /** Per-chain balances (populated when balance tracking is enabled) */ chainBalances: ChainBalance[]; } type ModalView = 'connect' | 'account' | 'chain' | 'wallet-info'; /** * Theme mode — mirrors `crossy-sdk-js`'s `SDKConfig.theme` literal union. * `autoDetectTheme: true` overrides this at runtime with * `prefers-color-scheme`. */ type ThemeMode = 'light' | 'dark'; /** * Semantic color overrides. Mirrors `crossy-sdk-js`'s `SDKColorOverrides` * 1:1 so `@nexus-cross/connect-kit-core` can forward them into the * embedded SDK and — via the CSS variables generated by * `buildThemeCssText` — apply them uniformly to WalletInfo / Portfolio / * AppLauncher / WalletConnectModal. * * All values are plain CSS color strings (e.g. `"#019D92"`, * `"rgba(18,18,18,0.7)"`). */ interface ColorOverrides { /** Buttons, accents, active state. Default `#019D92`. */ primary?: string; /** Error/alert highlight (PIN error etc.). Default `#E70077`. */ secondary?: string; /** Text/icon color placed on top of `primary`. Default `#FFFFFF`. */ onPrimary?: string; /** Card outline border. */ borderDefault?: string; /** Dividers, input outlines. */ borderSubtle?: string; /** Titles, primary text. */ textIconPrimary?: string; /** Secondary copy, labels. */ textIconSecondary?: string; /** Hints, disabled text, tertiary icons. */ textIconTertiary?: string; /** Inline highlighted surfaces (pills, hover, input field bg). */ surfaceDefault?: string; /** Stronger neutral surfaces (divider fill, secondary bg). */ surfaceSubtle?: string; /** Modal / card root background. */ bg?: string; /** Error state, independent from `secondary`. Default `#E70077`. */ error?: string; } /** * Per-mode theme overrides. `light` is applied when `theme === 'light'`, * `dark` is applied when `theme === 'dark'`. Omitted fields fall back to * crossy-sdk's built-in palette. * * Mirrors `crossy-sdk-js`'s `SDKThemeTokens` so the same literal value * can be forwarded straight into the embedded SDK. */ interface ThemeTokens { light?: ColorOverrides; dark?: ColorOverrides; } /** * @deprecated Use `ThemeMode` / `ThemeTokens` instead. * Legacy shape kept for older consumers that typed on the `mode`/ * `accentColor` fields. New code should pass `theme: 'dark'` and * `themeTokens: { dark: { primary: ... } }` at the `CrossConnectKitConfig` * level. */ interface Theme { mode: ThemeMode; accentColor?: string; borderRadius?: 'none' | 'small' | 'medium' | 'large'; fontFamily?: string; } /** * PIN 입력 모달에서 모바일 키보드 노출 방식. * * Mirrors `crossy-sdk-js`'s `PinKeyboardMode` 1:1 so the kit-level value * can be forwarded straight into the embedded SDK. * * - `'virtual'` (기본): SDK가 그리는 가상 numpad가 모바일에서 뜸. * - `'native'`: 모바일에서도 input box를 노출 → OS 기본 숫자 키보드가 뜸. * * 데스크탑에서는 두 값 모두 동일하게 input box가 보입니다. */ type PinKeyboardMode = 'virtual' | 'native'; /** * PIN 키보드 분기 옵션. * * - 값(`'native'` / `'virtual'`)을 주면 정적으로 고정. * - 함수를 주면 PIN 모달이 띄워질 때마다 호출되어 동적으로 결정. * * Mirrors `crossy-sdk-js`'s `PinKeyboardOption`. */ type PinKeyboardOption = PinKeyboardMode | (() => PinKeyboardMode); interface CrossConnectKitConfig { /** CROSS relay project ID (CROSSx 1.0 / @to-nexus/sdk walletConnect). Also used as `embeddedProjectId` fallback. */ crossProjectId: string; /** Reown project ID (for WalletConnect / external wallets) */ reownProjectId?: string; /** CROSSx 2.0 embedded wallet project ID (@nexus-cross/crossx-sdk). Falls back to `crossProjectId` when omitted. */ embeddedProjectId?: string; /** App metadata for WalletConnect pairing */ appMetadata: AppMetadata; /** * Legal document links surfaced in the connect modal footer * ("By continuing, you agree to … Terms of Service … Privacy Policy"). * When a URL is omitted the corresponding text still renders (styled in * the primary color) but is not clickable. Set both to make them links. */ legal?: LegalLinks; /** Supported networks */ networks: readonly NetworkConfig[]; /** Default network (first connection lands here) */ defaultNetwork?: NetworkConfig; /** * Theme mode applied across every surface the kit renders: * - crossy-sdk confirmation / login modals * - `WalletInfo`, `WalletPortfolio`, `WalletConnectModal`, `AppLauncher` * Defaults to `'dark'`. * * Accepts both the new literal (`'light' | 'dark'`) and the legacy * `Partial` object (`{ mode: 'dark' }`) so older DApps keep * building while migrating. */ theme?: ThemeMode | Partial; /** * When `true`, ignore `theme` and follow `prefers-color-scheme`. * Mirrors crossy-sdk's `SDKConfig.autoDetectTheme`. */ autoDetectTheme?: boolean; /** * Color token overrides forwarded to crossy-sdk's `themeTokens` _and_ * published as CSS variables so WalletInfo / Portfolio / AppLauncher / * WalletConnectModal render with the same palette. */ themeTokens?: ThemeTokens; /** * Mobile PIN keyboard mode forwarded to crossy-sdk's embedded modal. * * - 미지정/`'virtual'`: 기본 동작 (모바일=가상 numpad). * - `'native'`: 모바일에서도 OS 기본 숫자 키보드를 사용. * - 함수: PIN 모달이 띄워질 때마다 호출되어 동적으로 결정. * * @example pinKeyboard: 'native' * @example pinKeyboard: () => prefersNativeKeyboard() ? 'native' : 'virtual' */ pinKeyboard?: PinKeyboardOption; /** Which wallets to show in connect modal (defaults to all) */ wallets?: WalletId[]; /** Enable SSR hydration support (Next.js App Router) */ ssr?: boolean; /** * fiat→crypto 결제 표면(WalletInfo Buy 버튼, `useOnRamp` 훅) 활성화 토글. * default false. true이면 CROSSx 2.0 embedded-wallet-gateway의 `/onramp/*` * 엔드포인트로 라우팅된다 — DApp 식별자는 `embeddedProjectId`(미설정 시 * `crossProjectId` fallback)를 `X-Project-Id` 헤더로 사용한다. 엔드포인트 * URL/provider 이름/secret은 노출하지 않는다. */ onRampEnabled?: boolean; /** * cross-auth KYC 표면(`useKyc` 훅) 활성화 토글. default false. true이면 * connect-kit이 ``를 자동 마운트하고, 연결된 crossx 2.0 SDK * 세션에서 access token을 실시간으로 읽어 `Authorization: Bearer`로 주입한다. * DApp은 별도 토큰 배선 없이 `useKyc()`만 호출하면 된다. `X-Project-Id`는 * `embeddedProjectId`(미설정 시 `crossProjectId` fallback)를 사용한다. */ kycEnabled?: boolean; /** * ONEpop(소셜 핸들 드롭) 마스터 토글. default false. * * true이면 이 토글 **하나로** ONEpop이 특별한 추가 설정 없이 동작한다: * - `ConnectButton`이 WalletInfo에 `showOnePop`을 켜서 액션 행 ONEpop * 카드와 진입 화면을 노출한다 (prop으로 `showOnePop`을 명시하면 우선). * - ONEpop 화면에 표시할 `onePopSummary`(수령 대기 수/합계, X 연결 여부, * 최근 활동)를 `@nexus-cross/pop`으로 조회해 자동 주입한다(fallback). * one-pop-api `/drops`·`/histories`는 cross-auth SIWE JWT를 요구하므로 * **사용자가 ONEpop 화면을 실제로 열 때** 서명을 한 번 요청하고, 발급된 * 토큰은 세션 동안 주소별로 재사용한다. DApp이 `onePopSummary`를 직접 * 주입하면 조회를 건너뛴다. * - 화면의 액션(Send POP / Claim POPs / Activity / 배너)은 ONEpop 서비스 * 웹 딥링크가 기본 동작이고, `onOnePopSend` 등 콜백 주입 시 그것이 * 우선한다. * * false(기본)면 ONEpop 카드/화면이 렌더되지 않고 조회도 하지 않는다. */ onePopEnabled?: boolean; /** * When `true`, the kit switches the wallet to {@link defaultNetwork} * exactly once right after a connection is established. External * wallets (`cross_wallet`, `metamask`, …) connect on whatever chain * they last held (e.g. Ethereum Mainnet); `defaultNetwork` only * *defines* the kit's canonical chain, it does not move the wallet. * This enforces it on connect. * * Semantics (deliberately a one-shot, not a reactive lock): * - Fires once per connected session (connector + address). The * user is free to switch chains manually afterwards — the kit * will not yank them back. * - No-op when already on `defaultNetwork` (no wallet prompt). * - A rejected switch (or a wallet that can't add the chain) is * respected — the kit does not nag. * - Re-enforced once on the next connect after a disconnect. * * Requires `defaultNetwork` to be set. Default: `false`. */ enforceDefaultNetworkOnConnect?: boolean; /** * When `true` (default), any wallet chain switch the kit performs *for * an operation* is undone once that operation completes — the wallet is * restored to the chain it was on before. This covers the bridge/swap * flow (which switches to the source chain to submit the tx) and the * Send screen (which switches to the token's chain to send). * * Rationale: the temporary switch is an implementation detail of the * operation. Leaving the DApp's wallet stranded on, say, BSC after a * BSC→CROSS bridge makes the rest of the app (and the developer's own * flows) behave unexpectedly. app-launcher is a DApp-integration tool, * so the friendly default is to hand the chain back. * * Semantics: * - Best-effort: restore is fire-and-forget. A rejected switch-back * (or an unconfigured chain) never turns a successful bridge/send * into a failure. * - Only after success. A failed/cancelled operation leaves the * wallet on the operation chain so the user can retry without an * extra switch round-trip. * - No-op when no switch happened (e.g. already on the target chain, * or the gasless permit bridge path that never switches). * * Unlike {@link enforceDefaultNetworkOnConnect} — which pins to a fixed * canonical chain once at connect — this restores to whatever chain the * user actually had before the operation. Set `false` to leave the * wallet on the operation chain. Default: `true`. */ restoreChainAfterSwitch?: boolean; } interface AppMetadata { name: string; description?: string; url: string; icons?: string[]; } /** * URLs the connect modal's Terms of Service / Privacy Policy links point * to. Each is optional — omit one to render its label as non-clickable * text instead of an anchor. */ interface LegalLinks { /** URL the "Terms of Service" link points to. */ termsUrl?: string; /** URL the "Privacy Policy" link points to. */ privacyUrl?: string; } interface NetworkConfig { /** EIP-155 numeric chain ID */ id: number; name: string; nativeCurrency: { name: string; symbol: string; decimals: number; }; rpcUrl: string; blockExplorerUrl?: string; testnet?: boolean; } /** * The user-flow lifecycle events the kit EMITS. connect-kit is an SDK: it * never stores, batches, or ships telemetry — it only surfaces these * events through {@link AnalyticsPort} (react: `useConnectKitAnalytics`), * and the DApp is the collection owner. * * See `docs/connect-kit/09-analytics-events.md`. */ type ConnectKitEventName = 'connect_modal_opened' | 'connect_modal_closed' | 'wallet_selected' | 'connect_started' | 'connect_succeeded' | 'connect_failed' | 'connect_cancelled' | 'wallet_switched' | 'disconnected'; /** Why a connect attempt terminally failed. */ type ConnectFailReason = 'connector_error' | 'connector_not_found' | 'fallback_failed' | 'unknown'; /** Where in the flow the user abandoned a connect attempt. */ type ConnectCancelStage = 'modal' | 'popup' | 'stalled'; /** * A single user-flow event. Discriminated on {@link ConnectKitEventName}. * * PRIVACY: payloads carry only categorical/structural values — walletId, * walletType, chainId, reason, stage. Never an address, PIN, token, email, * balance, or signature. `timestamp` (ms epoch) is stamped by the react * adapter, not core (core has no clock). */ interface ConnectKitEventBase { name: ConnectKitEventName; /** ms epoch, stamped by the react adapter (core has no side effects). */ timestamp: number; } type ConnectKitEvent = (ConnectKitEventBase & { name: 'connect_modal_opened'; }) | (ConnectKitEventBase & { name: 'connect_modal_closed'; completed: boolean; }) | (ConnectKitEventBase & { name: 'wallet_selected'; walletId: WalletId; walletType: ConnectorType; }) | (ConnectKitEventBase & { name: 'connect_started'; walletId: WalletId; walletType: ConnectorType; }) | (ConnectKitEventBase & { name: 'connect_succeeded'; walletId: WalletId; walletType: ConnectorType; chainId: number; }) | (ConnectKitEventBase & { name: 'connect_failed'; walletId: WalletId; reason: ConnectFailReason; }) | (ConnectKitEventBase & { name: 'connect_cancelled'; walletId: WalletId; stage: ConnectCancelStage; }) | (ConnectKitEventBase & { name: 'wallet_switched'; index: number; }) | (ConnectKitEventBase & { name: 'disconnected'; walletId: WalletId | null; }); type Unsubscribe = () => void; /** * Abstracts a wallet connector's lifecycle. * * In crossx-kit the wagmi package provides concrete implementations: * - EmbeddedWalletConnector (crossy-sdk-js → wagmi connector) * - CrossAppConnector (cross-sdk-js → wagmi connector, QR/deep-link) * - CrossExtensionConnector (injected CROSSx Extension) * - ExternalWalletConnector (Reown/WalletConnect for MetaMask, Binance, etc.) */ interface ConnectorPort { readonly id: WalletId; readonly name: string; readonly type: ConnectorType; readonly iconUrl?: string; connect(params?: Record): Promise; disconnect(): Promise; getAccount(): Promise; isConnected(): boolean; /** * For embedded wallets: switch to a different sub-wallet (account index). * Returns null if the connector does not support wallet selection. */ selectWallet?(): Promise<{ address: string; index: number; } | null>; onAccountChanged(cb: (account: Account) => void): Unsubscribe; onChainChanged(cb: (chainId: number) => void): Unsubscribe; onDisconnect(cb: () => void): Unsubscribe; } interface StoragePort { get(key: string): Promise; set(key: string, value: string): Promise; remove(key: string): Promise; } interface ModalControlPort { open(view: ModalView): void; close(): void; isOpen(): boolean; onStateChange(cb: (isOpen: boolean, view: ModalView | null) => void): Unsubscribe; } interface ThemePort { getTheme(): Theme; setTheme(theme: Partial): void; onThemeChange(cb: (theme: Theme) => void): Unsubscribe; } /** * Detects available wallets in the current environment. * Implementations use EIP-6963, window.ethereum probing, etc. */ interface WalletDetectionPort { /** Returns currently detected wallets (snapshot) */ getDetectedWallets(): WalletDescriptor[]; /** Fires when the set of detected wallets changes (e.g. extension loads late) */ onWalletsChanged(cb: (wallets: WalletDescriptor[]) => void): Unsubscribe; /** Check if a specific wallet is installed by its rdns or id */ isInstalled(walletId: string): boolean; } /** * Tracks native/token balances across configured chains. * Implementations fetch via viem public clients or RPC. */ interface BalancePort { /** Fetch balances for the given address across all configured chains */ fetchBalances(address: string): Promise; /** Subscribe to balance updates (polling or event-driven) */ onBalanceChanged(address: string, cb: (balances: ChainBalance[]) => void): Unsubscribe; /** Force-refresh cached balances */ invalidate(): void; } type OAuthProvider = 'google' | 'apple'; /** * Entry-point port for social sign-in. The UI layer only calls `signIn` and * forgets — the underlying SDK is responsible for opening the OAuth popup, * exchanging tokens, and waking the wagmi connector. Errors are owned by the * SDK side (the UI does not display them; see CLAUDE.md §6). */ interface OAuthPort { signIn(provider: OAuthProvider): Promise; } /** * Outbound analytics sink. The kit EMITS user-flow events; it never * stores, batches, or ships them. The DApp implements this port (in the * react layer, via the `useConnectKitAnalytics` hook) to route events into * its own analytics (GA, Amplitude, backend, …). * * Sync + fire-and-forget (like {@link ModalControlPort}): a slow or * throwing handler must never block or break the connect flow — the react * adapter swallows handler errors. * * See `docs/connect-kit/09-analytics-events.md`. */ interface AnalyticsPort { track(event: ConnectKitEvent): void; } /** * Theme-mode resolution. * * Color/typography/layout tokens now live in * `@nexus-cross/crossx-design-system` and are published as `--ds-*` CSS * variables by `@nexus-cross/connect-kit-react`'s `CrossConnectKitProvider`. * Core keeps only the pure mode resolver, which has no env/SDK coupling. */ /** * Resolve the effective {@link ThemeMode} that should be applied. * When `autoDetect` is true (and a `window` is available), reads * `prefers-color-scheme`; otherwise falls back to `explicit ?? 'dark'`. */ declare function resolveThemeMode(explicit: ThemeMode | Partial<{ mode: ThemeMode; }> | undefined, autoDetect: boolean | undefined): ThemeMode; /** * Blockchain numeric formatting utilities — BigInt-only, floor-truncated. * * These utilities enforce CLAUDE.md §4 Blockchain Numeric Handling: * - Inputs are BigInt (never Number) for wei-scale values. * - Output is a display string; MUST NOT be parsed back into computation. * - Rounding is floor (truncate toward zero) — users never see more than * they actually have. BigInt division naturally floors. * * Signed inputs are accepted (e.g. PnL deltas): the sign is preserved and * the magnitude is floor-truncated to `displayDecimals`. */ /** * Format a BigInt balance in raw base units (e.g. wei) to a human-readable * decimal string with floor truncation at `displayDecimals` digits. * * @param value Raw balance in the smallest unit (wei for ETH, etc.) * @param decimals Token decimals (e.g. 18 for ETH, 6 for USDC) * @param displayDecimals How many fractional digits to show. Defaults to 4. * * @example * formatBalance(1234567890000000000n, 18) // "1.2345" * formatBalance(1234567890000000000n, 18, 2) // "1.23" * formatBalance(500000000n, 6) // "500" * formatBalance(-123n, 0) // "-123" */ declare function formatBalance(value: bigint, decimals: number, displayDecimals?: number): string; /** * Shortcut for 18-decimal tokens (native ETH/CROSS on EVM chains). * Equivalent to `formatBalance(wei, 18, displayDecimals)`. */ declare function formatWei(wei: bigint, displayDecimals?: number): string; export { type Account, type AnalyticsPort, type AppMetadata, type BalancePort, type ChainBalance, type ColorOverrides, type ConnectCancelStage, type ConnectFailReason, type ConnectKitEvent, type ConnectKitEventName, ConnectionStatus, type ConnectorPort, type ConnectorResult, type ConnectorType, type CrossConnectKitConfig, type LegalLinks, type ModalControlPort, type ModalView, type NetworkConfig, type OAuthPort, type OAuthProvider, type PinKeyboardMode, type PinKeyboardOption, type StoragePort, type Theme, type ThemeMode, type ThemePort, type ThemeTokens, type Unsubscribe, type WalletDescriptor, type WalletDetectionPort, type WalletId, type WalletState, formatBalance, formatWei, resolveThemeMode };