import { Address, ChainID, ChainNamespace, EIP1193Provider } from "../types"; /** A restore taken with `deferRestore`, written once its await is over. */ export type WalletRestore = (wallet: { chainId: ChainID; address: Address; }) => void; /** * A ticket identifying one wallet observation, taken before any async work. * See `WalletStateStore.observe`. */ export interface Observation { readonly id: number; readonly namespace: ChainNamespace; } /** What the store needs from the SDK that owns it. */ export interface WalletStateStoreDeps { /** Opt-out or an excluded timezone: purge persisted identity, don't keep it. */ isPersistedIdentityPurgeRequired(): boolean; /** Excluded host or path: skip writing and restoring, but keep the cookie. */ isPageExcluded(): boolean; /** Any visitor-level or page-level suppression: never LEARN a wallet. */ isTrackingSuppressed(): boolean; /** Whether the identity cookie is shared across subdomains. */ crossSubdomainCookies(): boolean; /** * The last chain heard from a provider, or undefined if none. * * Used to refuse a captured chain the wallet has since left. That state * belongs to provider tracking, so the store asks rather than owns it. */ providerChainId(provider: EIP1193Provider): number | undefined; /** * A provider stopped being the active one. * * From the SDK's point of view its connection has ended, so whatever the * owner records about it (a reported connect, for one) must stop counting. * The active provider is reassigned from several paths, so the store * reports it centrally rather than each caller remembering to. */ onProviderDisplaced(previous: EIP1193Provider): void; } /** * Wallet identity and chain state, and the cookie that outlives the page. * * Split out of `FormoAnalytics` so there is one owner for "which wallet is * active, on which chain". Before this, `_chainState`, the derived * `currentAddress`/`currentChainId`, the active-wallet cookie and the session * generation were manipulated from a dozen places across a 3400-line class, * which is what made the ordering races in #341 possible to write. */ export declare class WalletStateStore { private readonly deps; private state; /** * Which namespace last claimed the wallet slot. Last-connected wins, so an * EVM connect after a Solana one repoints the derived values, and vice * versa, rather than one namespace permanently shadowing the other. */ private _activeNamespace?; /** Which namespace currently owns the derived values, if any. */ get activeNamespace(): ChainNamespace | undefined; /** * Ticket counter for wallet observations. * * Every signal from a wallet is handled asynchronously: resolving an * address, emitting an event. Between the moment a handler decides what to * do and the moment it commits, a newer signal can arrive and be fully * processed. Whichever handler resumes last then writes its captured data * over the newer state. * * Bespoke guards were added for each case as it was found - a per-provider * disconnect count, a per-namespace session generation, a "currently * processing" flag - and a fourth review round kept producing new ones, * because each guard answers one question and none establishes an order. * * A ticket does. A handler takes one before its first await; anything it * commits afterwards is refused if a newer observation has claimed the * namespace since. */ private observationSeq; /** * Advanced by every write that can make a namespace active: a ticket, or * a plain `set` (a backfill from a signature, a chain write). Kept per * namespace and in total. */ private claimSeq; private namespaceClaims; private newestObservation; /** * How many disconnects a namespace has begun. * * Separate from the observation ticket on purpose, because they answer * different questions. A ticket asks "is the state I captured still the * newest?", which is what a switch or a probe needs. This asks "did the * wallet go away after I started?", which is what a connect handler needs. * * Conflating them loses connects: a connect observation superseded by a * NEWER connect must still be reported by somebody, and the ticket cannot * tell that apart from being superseded by a disconnect. */ private disconnectCount; /** How many times reset() has wiped identity; a deferred restore must not undo one. */ private resetCount; /** Derived from the active namespace. Read by integrations and by events. */ address?: Address; chainId?: ChainID; constructor(deps: WalletStateStoreDeps); /** Which namespace a chain id belongs to. */ namespaceOf(chainId?: ChainID): ChainNamespace; get evmAddress(): Address | undefined; get solanaAddress(): Address | undefined; get evmChainId(): ChainID | undefined; get provider(): EIP1193Provider | undefined; /** * Take a ticket for an observation about to be processed. * * MUST be called before the handler's first await, so the order recorded is * the order the signals arrived in, not the order their async work happens * to finish in. */ observe(namespace: ChainNamespace): Observation; /** * Record that a namespace's wallet is being torn down. * * Called at EVERY teardown site before anything is awaited, and whether or * not a disconnect event will be emitted: whether the app opted into * disconnect autocapture has no bearing on ordering. */ beginDisconnect(namespace: ChainNamespace): void; /** How many disconnects this namespace has begun so far. */ disconnectsSoFar(namespace: ChainNamespace): number; /** * The newest observation for a namespace, for a caller that must NOT * supersede its own caller. * * `disconnect()` is reached both directly by a consumer and from a handler * that already holds a ticket. Taking a fresh ticket there would invalidate * the handler that called it. Reading the current value and checking it * later asks the same question without changing the answer for anyone else. */ snapshot(namespace: ChainNamespace): number; /** Whether nothing newer has claimed the namespace since `snapshot`. */ isUnchangedSince(namespace: ChainNamespace, snapshot: number): boolean; /** * The current newest observation, as a ticket, WITHOUT taking a new one. * * For a handler that must respect the order but has nothing to claim: it * still abandons its work if something newer arrives, but it does not * supersede whatever is already in flight. */ currentObservation(namespace: ChainNamespace): Observation; /** * Whether this observation is still the newest for its namespace. * * False means a newer signal arrived while this handler was suspended, and * it must abandon whatever it captured rather than write it over the newer * state. Superseding, not dropping: the newer handler is already running. */ isCurrent(observation: Observation): boolean; /** * Write a namespace's wallet WITHOUT changing which namespace is active. * * For putting a still-connected wallet back after a disconnect on its * namespace cleared it. That wallet did not just connect, so it only * becomes active when nothing else is. */ restore(chainId: ChainID, address: Address): void; /** * A `restore` decided now but written after an await. Take it before the * first await, for the same reason as `observe`. * * The write is refused if anything wrote to the namespace meanwhile (a * connect, a chain write) or if reset() ran. */ deferRestore(chainId: ChainID): WalletRestore; set provider(next: EIP1193Provider | undefined); /** * Update a namespace and re-derive `address`/`chainId`. * * Accepts a namespace or a chain id. A chain id is also stored as the * namespace's chain unless the update names one explicitly. */ set(namespaceOrChainId: ChainNamespace | ChainID | undefined, update: { address?: Address; chainId?: ChainID; provider?: EIP1193Provider; }): void; /** Wipe a namespace. Per-namespace so a Solana disconnect spares EVM. */ clear(namespaceOrChainId: ChainNamespace | ChainID | undefined): void; /** Both namespaces, keeping the EVM provider so tracking can resume. */ reset(): void; /** Drop the active provider without touching identity. */ clearProvider(): void; /** * Repoint the active identity at a wallet, as `identify(setActive)` does, * without disturbing per-namespace chain state. * * Deliberately transient. The next wallet event for either namespace * re-derives from namespace state and replaces this, which is the intended * order: a wallet that actually connects outranks one merely named by * `identify()`. The alternative, making an identify sticky until another * identify, would silently mis-attribute every event after a connect. */ setActiveAddress(address: Address | undefined): void; /** Repoint (or forget) the derived chain while keeping the wallet. */ setActiveChainId(chainId: ChainID | undefined): void; /** Forget the derived chain while keeping the wallet. */ clearActiveChainId(): void; /** * Record validated wallet/chain state WITHOUT emitting an event. * * Integrations (the wagmi handler among them) must call this on every * connect / chain change / disconnect, even when the matching autocapture * event is disabled. Otherwise `chainId` stays stale and the exclusion gate, * which keys off it rather than the event payload, can be bypassed. */ syncWalletState(params: { chainId?: ChainID; address?: Address; }): void; /** * Learn an EVM wallet observed on an autocaptured signature or transaction, * when nothing is known yet. * * Deliberately conservative: it never overwrites a different wallet, and it * corrects only a stale chain for the wallet already known. */ backfill(address: Address, chainId?: ChainID, provider?: EIP1193Provider): void; /** * Apply an EVM connect/switch seen while tracking is suppressed: never * learn the wallet, but if it is a switch away from one already learned, * drop the stale one so it cannot attach to a later allowed-page event. */ clearStaleEvmWalletOnSwitchWhileSuppressed(address: string): void; /** * Persist (or clear) the wallet snapshot, so the next page load can * repopulate identity before the wallet reconnects. Without it, every * `track()` / `page()` between page-show and reconnection ships with no * address. */ persist(): void; /** Seed identity from the persisted snapshot, once, during construction. */ load(): void; private clearForChain; private displaceProvider; /** * Re-derive `address`/`chainId` from the active namespace. * * Last-connected wins, then fall through to the other namespace so a * still-connected wallet keeps attribution when its neighbour disconnects. */ private syncDerived; } //# sourceMappingURL=WalletStateStore.d.ts.map