@usebutr/core 0.2.2 → 0.3.0

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.ts CHANGED
@@ -170,7 +170,7 @@ type ConnectionErrorKind = ConnectionError["kind"];
170
170
  declare const mapConnectionError: (raw: unknown) => ConnectionError;
171
171
  //#endregion
172
172
  //#region src/types/wallet.d.ts
173
- type ChainPlatform = "evm" | "svm" | "sui" | "bitcoin";
173
+ type ChainPlatform = "evm" | "svm" | "sui" | "bitcoin" | "polkadot";
174
174
  type Account = {
175
175
  chain: ChainBase;
176
176
  id: string;
@@ -425,6 +425,15 @@ type SuiWallet = WalletBase & {
425
425
  type BitcoinWallet = WalletBase & {
426
426
  signTransaction?(tx: unknown, account?: Account): Promise<Uint8Array>;
427
427
  };
428
+ /**
429
+ * Polkadot/Substrate wallet surface. No standalone `signTransaction`:
430
+ * building an extrinsic needs chain metadata (an RPC round-trip butr
431
+ * doesn't ship), so transaction signing happens through the
432
+ * `getSigner()` handoff — the consumer builds and submits with the
433
+ * wallet's signer (e.g. polkadot-api). Message signing works via the
434
+ * injected `signer.signRaw`. Same shape as `EvmWallet`.
435
+ */
436
+ type PolkadotWallet = WalletBase;
428
437
  /** Per-platform full adapter shapes — `Connector` + the platform's
429
438
  * `Wallet` surface. These are the discriminated-union variants of
430
439
  * `WalletAdapter`. */
@@ -432,6 +441,7 @@ type EvmAdapter = Connector<"evm"> & EvmWallet;
432
441
  type SvmAdapter = Connector<"svm"> & SvmWallet;
433
442
  type SuiAdapter = Connector<"sui"> & SuiWallet;
434
443
  type BitcoinAdapter = Connector<"bitcoin"> & BitcoinWallet;
444
+ type PolkadotAdapter = Connector<"polkadot"> & PolkadotWallet;
435
445
  /**
436
446
  * Full adapter interface — discriminated union by `chainPlatform`.
437
447
  *
@@ -450,7 +460,7 @@ type BitcoinAdapter = Connector<"bitcoin"> & BitcoinWallet;
450
460
  * supports this feature"; the discriminated union narrows "this
451
461
  * platform has this concept at all".
452
462
  */
453
- type WalletAdapter = EvmAdapter | SvmAdapter | SuiAdapter | BitcoinAdapter;
463
+ type WalletAdapter = EvmAdapter | SvmAdapter | SuiAdapter | BitcoinAdapter | PolkadotAdapter;
454
464
  /** Deprecated. Use one of the per-platform `*Wallet` types or
455
465
  * `WalletBase`. Retained as an alias so existing names resolve. */
456
466
  type Wallet = WalletBase;
@@ -956,5 +966,5 @@ declare const logError: (...args: ReadonlyArray<unknown>) => void;
956
966
  */
957
967
  declare const sanitizeIcon: (icon: string | undefined) => string | undefined;
958
968
  //#endregion
959
- export { type Account, type Balance, type BitcoinAdapter, type BitcoinWallet, type BrowserStorageDrivers, type ChainBase, type ChainPlatform, type ChainsByPlatform, type ConnectedWallet, type ConnectionError, type ConnectionErrorKind, type ConnectionStatus, type Connector, type ConnectorEvent, type ConnectorMeta, type CookieDriverOptions, type CookieSource, EMPTY_SNAPSHOT, type EvmAdapter, type EvmWallet, type HydrationOutcome, type InitialCookies, type MaybePromise, type PlatformDiscoverer, type SignerForPlatform, type SignerOf, type SnapshotOptions, type StorageDriver, type StoredPoolEntry, type StoredPoolRecord, type StoredSelectionRecord, type SuiAdapter, type SuiWallet, type SvmAdapter, type SvmWallet, type Wallet, type WalletAdapter, type WalletAvailability, type WalletBase, type WalletCapabilities, type WalletManagerConfig, type WalletPersistence, type WalletSnapshot, type WalletSource, WalletStorage, type WalletStore, type WalletStoreState, buildChainsByPlatform, createBrowserStorageDriver, createCookieStorageDriver, createMemoryStorageDriver, createWalletSource, createWalletStore, logError, logWarn, mapConnectionError, readWalletSnapshot, sanitizeIcon, walletEqual };
969
+ export { type Account, type Balance, type BitcoinAdapter, type BitcoinWallet, type BrowserStorageDrivers, type ChainBase, type ChainPlatform, type ChainsByPlatform, type ConnectedWallet, type ConnectionError, type ConnectionErrorKind, type ConnectionStatus, type Connector, type ConnectorEvent, type ConnectorMeta, type CookieDriverOptions, type CookieSource, EMPTY_SNAPSHOT, type EvmAdapter, type EvmWallet, type HydrationOutcome, type InitialCookies, type MaybePromise, type PlatformDiscoverer, type PolkadotAdapter, type PolkadotWallet, type SignerForPlatform, type SignerOf, type SnapshotOptions, type StorageDriver, type StoredPoolEntry, type StoredPoolRecord, type StoredSelectionRecord, type SuiAdapter, type SuiWallet, type SvmAdapter, type SvmWallet, type Wallet, type WalletAdapter, type WalletAvailability, type WalletBase, type WalletCapabilities, type WalletManagerConfig, type WalletPersistence, type WalletSnapshot, type WalletSource, WalletStorage, type WalletStore, type WalletStoreState, buildChainsByPlatform, createBrowserStorageDriver, createCookieStorageDriver, createMemoryStorageDriver, createWalletSource, createWalletStore, logError, logWarn, mapConnectionError, readWalletSnapshot, sanitizeIcon, walletEqual };
960
970
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/types/chain.ts","../src/storage/persistence.ts","../src/storage/snapshot.ts","../src/types/errors.ts","../src/types/wallet.ts","../src/types/chains-by-platform.ts","../src/types/discoverer.ts","../src/types/signer.ts","../src/wallet-source.ts","../src/store/reducer.ts","../src/storage/browser-storage-driver.ts","../src/storage/cookie-storage-driver.ts","../src/storage/wallet-storage.ts","../src/store/wallet-store.ts","../src/wallet-equal.ts","../src/logger.ts","../src/sanitize-icon.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/types/chain.ts","../src/storage/persistence.ts","../src/storage/snapshot.ts","../src/types/errors.ts","../src/types/wallet.ts","../src/types/chains-by-platform.ts","../src/types/discoverer.ts","../src/types/signer.ts","../src/wallet-source.ts","../src/store/reducer.ts","../src/storage/browser-storage-driver.ts","../src/storage/cookie-storage-driver.ts","../src/storage/wallet-storage.ts","../src/store/wallet-store.ts","../src/wallet-equal.ts","../src/logger.ts","../src/sanitize-icon.ts"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import{createStore as e}from"zustand/vanilla";const t=e=>({bitcoin:e.bitcoin??[],evm:e.evm??[],sui:e.sui??[],svm:e.svm??[]}),n=e=>{if(e instanceof Error){let t=e.message,n=t.toLowerCase();if(t===`Connection timeout`)return{kind:`Timeout`,message:t};if(t===`Failed to get account`||n.includes(`not connected`))return{kind:`NotConnected`,message:t};let r=e.code;return r===4001?{kind:`UserRejected`,message:t}:r===-32002?{kind:`RequestPending`,message:t}:r===4100||r===4900||r===4901?{kind:`NotConnected`,message:t}:n.includes(`user rejected`)||n.includes(`user denied`)?{kind:`UserRejected`,message:t}:n.includes(`locked`)?{kind:`WalletLocked`,message:t}:n.includes(`chain`)&&(n.includes(`mismatch`)||n.includes(`unsupported`))?{kind:`ChainMismatch`,message:t}:{cause:e,kind:`Unknown`,message:t}}return typeof e==`string`?{kind:`Unknown`,message:e}:{cause:e,kind:`Unknown`,message:`Connection failed`}},r=e=>({subscribe:e}),i=(...e)=>{console.warn(...e)},a=(...e)=>{console.error(...e)},o=e=>{try{return typeof globalThis<`u`&&globalThis[e]!==void 0}catch{return!1}},s=()=>{let e=new Map;return{getItem(t){return e.get(t)??null},removeItem(t){e.delete(t)},setItem(t,n){e.set(t,n)}}},c=e=>{if(!o(e))return s();let t=globalThis[e];return{getItem(e){try{return t.getItem(e)}catch{return null}},removeItem(e){try{t.removeItem(e)}catch{}},setItem(e,n){try{t.setItem(e,n)}catch{}}}},l=()=>({persistent:c(`localStorage`),session:c(`sessionStorage`)}),u=e=>encodeURIComponent(e),d=e=>decodeURIComponent(e),f=()=>{let e=new Map;if(typeof document>`u`)return e;let t=document.cookie;if(!t)return e;for(let n of t.split(`;`)){let t=n.trim();if(!t)continue;let r=t.indexOf(`=`);if(r===-1)continue;let i=t.slice(0,r),a=t.slice(r+1);try{e.set(i,d(a))}catch{}}return e},p=(e,t,n,r=!1)=>{let i=[`${e}=${u(t)}`,`Path=${n.path??`/`}`];if(n.domain&&i.push(`Domain=${n.domain}`),r)i.push(`Max-Age=0`);else{let e=n.maxAgeSeconds??2592e3;i.push(`Max-Age=${e}`)}return i.push(`SameSite=${n.sameSite??`lax`}`),(n.secure??!0)&&i.push(`Secure`),i.join(`; `)},m=e=>e?typeof e[Symbol.iterator]==`function`?new Map(e):new Map(Object.entries(e)):null,h=(e={})=>{let t=m(e.initialCookies);return typeof document>`u`?t?{getItem(e){return t.get(e)??null},removeItem(){},setItem(){}}:{getItem(){return null},removeItem(){},setItem(){}}:{getItem(e){return f().get(e)??null},removeItem(t){document.cookie=p(t,``,e,!0)},setItem(t,n){document.cookie=p(t,n,e)}}},g=new Set([`evm`,`svm`,`sui`,`bitcoin`]),_=Object.freeze({activeConnectorId:null,pool:Object.freeze({}),selection:Object.freeze({})}),v=e=>{if(typeof e[Symbol.iterator]!=`function`)return new Map(Object.entries(e));let t=new Map;for(let n of e){if(Array.isArray(n)){let[e,r]=n;typeof e==`string`&&typeof r==`string`&&t.set(e,r);continue}n&&typeof n==`object`&&`name`in n&&`value`in n&&typeof n.name==`string`&&typeof n.value==`string`&&t.set(n.name,n.value)}return t},y=e=>{if(!e||typeof e!=`object`)return!1;let t=e;return!(typeof t.walletAddress!=`string`||typeof t.id!=`string`||!t.chain||typeof t.chain!=`object`)},b=(e,t)=>{if(!t||typeof t!=`object`)return!1;let n=t;return!(typeof n.connectorId!=`string`||n.connectorId!==e||typeof n.chainPlatform!=`string`||!g.has(n.chainPlatform)||typeof n.name!=`string`||n.name.length===0||n.icon!==void 0&&typeof n.icon!=`string`||!y(n.account)||!Array.isArray(n.accounts)||!n.accounts.every(y))},x=e=>{if(!e)return{};try{let t=JSON.parse(e);if(!t||typeof t!=`object`)return{};let n={};for(let[e,r]of Object.entries(t))b(e,r)?n[e]=r:i(`[butr] readWalletSnapshot: dropping invalid pool entry for ${e}`);return n}catch(e){return i(`[butr] readWalletSnapshot: failed to parse pool cookie:`,e),{}}},S=e=>{if(!e)return{};try{let t=JSON.parse(e);if(!t||typeof t!=`object`)return{};let n={};for(let[e,r]of Object.entries(t))g.has(e)&&typeof r==`string`&&r.length>0&&(n[e]=r);return n}catch(e){return i(`[butr] readWalletSnapshot: failed to parse selection cookie:`,e),{}}},C=(e,t={})=>{let n=t.keyPrefix||`butr`,r=v(e),i=x(r.get(`${n}-pool`)),a=S(r.get(`${n}-selection`)),o=r.get(`${n}-active`),s=null;if(o&&o.length>0&&i[o])s=o;else{let e=Object.keys(i)[0];e&&(s=e)}return{activeConnectorId:s,pool:i,selection:a}},w=new Set([`evm`,`svm`,`sui`,`bitcoin`]),T=e=>{if(!e||typeof e!=`object`)return!1;let t=e;return!(typeof t.walletAddress!=`string`||typeof t.id!=`string`||!t.chain||typeof t.chain!=`object`)},E=(e,t)=>{if(!t||typeof t!=`object`)return!1;let n=t;return!(typeof n.connectorId!=`string`||n.connectorId!==e||typeof n.chainPlatform!=`string`||!w.has(n.chainPlatform)||typeof n.name!=`string`||n.name.length===0||n.icon!==void 0&&typeof n.icon!=`string`||!T(n.account)||!Array.isArray(n.accounts)||!n.accounts.every(T))};var D=class{poolKey;selectionKey;activeKey;userDisconnectedKey;persistent;session;poolMutationQueue=Promise.resolve();constructor(e){if(this.poolKey=`${e.keyPrefix}-pool`,this.selectionKey=`${e.keyPrefix}-selection`,this.activeKey=`${e.keyPrefix}-active`,this.userDisconnectedKey=`${e.keyPrefix}-user-disconnected`,e.persistent&&e.session)this.persistent=e.persistent,this.session=e.session;else{let t=l();this.persistent=e.persistent??t.persistent,this.session=e.session??t.session}}async serializePoolMutation(e){let t=this.poolMutationQueue,n=()=>{};this.poolMutationQueue=new Promise(e=>{n=e});try{await t}catch{}try{return await e()}finally{n()}}async getPool(){try{let e=await this.persistent.getItem(this.poolKey);if(!e)return{};let t=JSON.parse(e);if(!t||typeof t!=`object`)return await this.clearPool(),{};let n={};for(let[e,r]of Object.entries(t))E(e,r)?n[e]=r:i(`[butr] dropping invalid pool entry for ${e}`);return n}catch(e){return i(`[butr] failed to parse pool from storage:`,e),await this.clearPool(),{}}}setPool(e){return this.serializePoolMutation(async()=>{try{let t={...await this.getPool()};for(let[n,r]of e){let e={account:r.account,accounts:r.accounts,chainPlatform:r.connector.chainPlatform,connectorId:n,icon:r.connector.icon,name:r.connector.name};if(!E(n,e))throw Error(`[butr] refusing to persist invalid pool entry for ${n}`);t[n]=e}await this.persistent.setItem(this.poolKey,JSON.stringify(t))}catch(e){i(`[butr] failed to persist pool:`,e)}})}removePoolEntry(e){return this.serializePoolMutation(async()=>{try{let t=await this.getPool();if(t[e]){let{[e]:n,...r}=t;await this.persistent.setItem(this.poolKey,JSON.stringify(r))}}catch(t){i(`[butr] failed to remove pool entry ${e}:`,t)}})}clearPool(){return this.serializePoolMutation(async()=>{await this.persistent.removeItem(this.poolKey)})}async getSelection(){try{let e=await this.persistent.getItem(this.selectionKey);if(!e)return{};let t=JSON.parse(e);if(!t||typeof t!=`object`)return{};let n={};for(let[e,r]of Object.entries(t))w.has(e)&&typeof r==`string`&&r.length>0&&(n[e]=r);return n}catch(e){return i(`[butr] failed to parse selection from storage:`,e),{}}}async setSelection(e){try{let t={};for(let[n,r]of e)t[n]=r;await this.persistent.setItem(this.selectionKey,JSON.stringify(t))}catch(e){i(`[butr] failed to persist selection:`,e)}}async getActiveConnectorId(){try{let e=await this.persistent.getItem(this.activeKey);return e&&e.length>0?e:null}catch{return null}}async setActiveConnectorId(e){try{await(e===null?this.persistent.removeItem(this.activeKey):this.persistent.setItem(this.activeKey,e))}catch(e){i(`[butr] failed to persist active connector id:`,e)}}async clearAll(){await Promise.all([this.clearPool(),this.persistent.removeItem(this.selectionKey),this.persistent.removeItem(this.activeKey)])}async isUserDisconnected(){try{return await this.session.getItem(this.userDisconnectedKey)===`true`}catch{return!1}}async markUserDisconnected(e){try{await(e?this.session.setItem(this.userDisconnectedKey,`true`):this.session.removeItem(this.userDisconnectedKey))}catch{}}};const O=e=>{let t=new Map,n=e=>{let n=t.get(e);if(n){try{n()}catch(e){i(`[butr] unsubscribe threw:`,e)}t.delete(e)}};return{attach:(r,a)=>{if(a.subscribe){n(r);try{let i=a.subscribe(t=>{switch(t.type){case`accountChanged`:e.onAccountChanged(r,t.accounts,t.account);break;case`disconnected`:n(r),e.onDisconnected(r,a.chainPlatform);break;default:}});t.set(r,i)}catch(e){i(`[butr] subscribe failed for ${r}:`,e)}}},detach:n,detachAll:()=>{let e=[];for(let n of t.keys())e.push(n);for(let t of e)n(t)}}},k=async(e,t,n)=>{if(e.getAccounts){let t=await e.getAccounts().catch(()=>null);if(t&&t.length>0)return t}return t.length>0?[...t]:[n]},A=async(e,t,n)=>{try{await n.connect({silent:!0});let r=await n.getAccount()||t.account;return{connectorId:e,entry:{account:r,accounts:await k(n,t.accounts,r),connector:n},kind:`ok`}}catch(t){return{connectorId:e,error:t,kind:`fail`}}},j=(e,t)=>{let n=new Map;return{drainPending:async e=>{let r=n.get(e);if(!r)return null;let i=t(e);if(!i)return null;let a=await A(e,r,i);return n.delete(e),a},hydrate:async()=>{let[r,i,a,o]=await Promise.all([e.getPool(),e.getSelection(),e.getActiveConnectorId(),e.isUserDisconnected()]),s=new Map,c=[];n.clear();let l=[];for(let[e,i]of Object.entries(r)){if(!i)continue;let r=t(e);if(!r){n.set(e,i);continue}l.push(A(e,i,r))}let u=await Promise.all(l);for(let e of u){if(e.kind===`ok`){s.set(e.connectorId,e.entry);continue}c.push({connectorId:e.connectorId,reason:e.error})}let d=new Map;for(let[e,t]of Object.entries(i))t&&s.has(t)&&d.set(e,t);for(let[e,t]of s){let n=t.connector.chainPlatform;d.has(n)||d.set(n,e)}let f=null;return a&&s.has(a)?f=a:s.size>0&&(f=s.keys().next().value??null),{activeConnectorId:f,dropped:c,isUserDisconnected:o,pendingIds:[...n.keys()],pool:s,selection:d}},pendingIds:()=>[...n.keys()]}},M={activeConnectorId:null,connectingConnectorId:null,connectionError:null,connectionStatus:`idle`,isHydrated:!1,isUserDisconnected:!1,pool:new Map,reconnectingIds:new Set,selection:new Map},N=(e,t)=>{for(let[n,r]of e)if(r.connector.chainPlatform===t)return n},P=(e,t)=>{switch(t.type){case`HYDRATED`:{let n=new Map(e.pool);for(let[e,r]of t.pool)n.set(e,r);let r=new Map(e.selection);for(let[e,n]of t.selection)r.set(e,n);let i=null;t.activeConnectorId&&n.has(t.activeConnectorId)?i=t.activeConnectorId:e.activeConnectorId&&n.has(e.activeConnectorId)?i=e.activeConnectorId:n.size>0&&(i=n.keys().next().value??null);let a=e.reconnectingIds;if(e.reconnectingIds.size>0&&t.pool.size>0){let n=new Set(e.reconnectingIds);for(let e of t.pool.keys())n.delete(e);n.size!==e.reconnectingIds.size&&(a=n)}return{...e,activeConnectorId:i,isHydrated:!0,isUserDisconnected:t.isUserDisconnected,pool:n,reconnectingIds:a,selection:r}}case`USER_DISCONNECTED_SET`:return{...e,isUserDisconnected:t.value};case`CONNECT_STARTED`:return{...e,connectingConnectorId:t.connectorId,connectionError:null,connectionStatus:`connecting`};case`CONNECT_SUCCEEDED`:{let{connectorId:n,entry:r}=t,i=e.pool.get(n),a=e.reconnectingIds.has(n)?new Set([...e.reconnectingIds].filter(e=>e!==n)):e.reconnectingIds;if(i&&i.account.walletAddress.toLowerCase()===r.account.walletAddress.toLowerCase())return{...e,activeConnectorId:n,connectingConnectorId:null,connectionStatus:`success`,pool:i.connector===r.connector?e.pool:new Map([...e.pool,[n,r]]),reconnectingIds:a};let o=new Map([...e.pool,[n,r]]),s=new Map([...e.selection,[r.connector.chainPlatform,n]]);return{...e,activeConnectorId:n,connectingConnectorId:null,connectionStatus:`success`,pool:o,reconnectingIds:a,selection:s}}case`CONNECT_FAILED`:return{...e,connectingConnectorId:null,connectionError:t.error,connectionStatus:`error`};case`DISCONNECTED`:{let n=e.pool.get(t.connectorId);if(!n)return e;let r=new Map(e.pool);r.delete(t.connectorId);let i=new Map(e.selection),a=n.connector.chainPlatform;if(i.get(a)===t.connectorId){i.delete(a);let e=N(r,a);e&&i.set(a,e)}let o=r.keys().next(),s=e.activeConnectorId===t.connectorId?o.value??null:e.activeConnectorId,c=e.reconnectingIds.has(t.connectorId)?new Set([...e.reconnectingIds].filter(e=>e!==t.connectorId)):e.reconnectingIds;return{...e,activeConnectorId:s,pool:r,reconnectingIds:c,selection:i}}case`ACCOUNTS_REFRESHED`:{let n=e.pool.get(t.connectorId);if(!n)return e;let r;r=t.active?t.active:t.accounts.some(e=>e.walletAddress===n.account.walletAddress&&e.chain.id===n.account.chain.id)?n.account:t.accounts[0]??n.account;let i=r.walletAddress===n.account.walletAddress&&r.chain.id===n.account.chain.id,a=t.accounts.length===n.accounts.length&&t.accounts.every((e,t)=>e.walletAddress===n.accounts[t]?.walletAddress);if(i&&a)return e;let o={...n,account:r,accounts:[...t.accounts]},s=new Map([...e.pool,[t.connectorId,o]]);return{...e,pool:s}}case`WALLET_REFRESHED`:{let n=e.pool.get(t.connectorId);if(!n)return e;let r=new Map([...e.pool,[t.connectorId,{...n}]]);return{...e,pool:r}}case`ACTIVE_CHANGED`:return{...e,activeConnectorId:t.connectorId};case`SELECTION_CHANGED`:{let n=new Map(e.selection);return t.connectorId===null?n.delete(t.chainPlatform):n.set(t.chainPlatform,t.connectorId),{...e,selection:n}}case`STATUS_RESET`:return{...e,connectingConnectorId:null,connectionError:null,connectionStatus:`idle`};case`ERROR_SET`:return{...e,connectionError:t.error,connectionStatus:t.error?`error`:`idle`};case`RESET`:return{...M,isHydrated:e.isHydrated,isUserDisconnected:!0,reconnectingIds:new Set};default:return e}};var F=class extends Error{code=`BUTR_RECONNECTING`;connectorId;method;constructor(e,t){super(`[butr] ${e} called on shadow connector "${t}" — wait for silent reconnect to complete (check reconnectingIds.has(id) before calling).`),this.name=`ShadowConnectorError`,this.connectorId=t,this.method=e}};const I=Object.freeze({getBalance:!1,getTransactionReceipt:!1,requestAccounts:!1,sendTransaction:!1,signIn:!1,signMessage:!1,signTransaction:!1,subscribe:!1,switchAccount:!1,switchChain:!1}),L=e=>{let t=e.connectorId,n=e.name,r=e.icon,i=e=>Promise.reject(new F(e,t)),a={capabilities:I,connect:()=>i(`connect`),disconnect:()=>i(`disconnect`),getAccount:()=>i(`getAccount`),getBalance:()=>i(`getBalance`),getSigner:()=>i(`getSigner`),getTransactionReceipt:()=>i(`getTransactionReceipt`),icon:r,id:t,name:n,sendTx:()=>i(`sendTx`),sendTxToChain:()=>i(`sendTxToChain`),signMessage:()=>i(`signMessage`),switchChain:()=>i(`switchChain`)};switch(e.chainPlatform){case`bitcoin`:return{...a,chainPlatform:`bitcoin`};case`evm`:return{...a,chainPlatform:`evm`};case`sui`:return{...a,chainPlatform:`sui`};case`svm`:return{...a,chainPlatform:`svm`};default:throw e.chainPlatform,Error(`[butr] unknown chainPlatform: ${e.chainPlatform}`)}},R=async(e,t)=>{try{await e()}catch(e){t(e)}},z=e=>{let t=new Map,n=new Set;for(let[r,i]of Object.entries(e.pool)){if(!i)continue;let e=L(i);t.set(r,{account:i.account,accounts:[...i.accounts],connector:e}),n.add(r)}let r=new Map;for(let[n,i]of Object.entries(e.selection))i&&t.has(i)&&r.set(n,i);let i=e.activeConnectorId&&t.has(e.activeConnectorId)?e.activeConnectorId:t.keys().next().value??null;return{...M,activeConnectorId:i,isHydrated:!0,pool:t,reconnectingIds:n,selection:r}},B=e=>e.toLowerCase(),V=t=>{let r=t.storageKeyPrefix||`butr`,o=t.storage||new D({keyPrefix:r}),s=e=>n=>{if(t.onStorageError){try{t.onStorageError(n,e)}catch(e){i(`[butr] onStorageError threw:`,e)}return}i(`[butr] ${e}:`,n)},c=j(o,t.createConnector);return e()((e,r)=>{let l=t=>{e(e=>P(e,t),!1)},u=()=>R(()=>o.setPool(r().pool),s(`failed to persist pool`)),d=()=>R(()=>o.setSelection(r().selection),s(`failed to persist selection`)),f=()=>R(()=>o.setActiveConnectorId(r().activeConnectorId),s(`failed to persist active connector`)),p=(e,t,n)=>{l({accounts:t,active:n,connectorId:e,type:`ACCOUNTS_REFRESHED`}),u()},m=O({onAccountChanged:(e,t,n)=>{p(e,[...t],n)},onDisconnected:(e,n)=>{l({connectorId:e,type:`DISCONNECTED`}),t.onDisconnect?.(n)}});return{...t.initialState?z(t.initialState):M,_config:t,_storage:o,connectWallet:async(e,a,o)=>{r().setUserDisconnected(!1);let s=t.createConnector(e);if(!s)throw Error(`Failed to create connector for ${e}`);l({connectorId:e,type:`CONNECT_STARTED`});let c=t.slowConnectThresholdMs??5e3,p=t.onSlowConnect?setTimeout(()=>{try{t.onSlowConnect?.(e)}catch(e){i(`[butr] onSlowConnect threw:`,e)}},c):null;try{let n=s.connect();n.catch(()=>{}),await Promise.race([n,new Promise((e,t)=>{setTimeout(()=>{t(Error(`Connection timeout`))},9e4)})]);let r=await s.getAccount();if(!r)throw Error(`Failed to get account`);let i=s.getAccounts?await s.getAccounts().catch(()=>null):null,o={account:r,accounts:i&&i.length>0?i:[r],connector:s};l({connectorId:e,entry:o,type:`CONNECT_SUCCEEDED`}),m.attach(e,s),await Promise.all([u(),d(),f()]),t.onConnect?.(o),a?.(o)}catch(r){let a=n(r);l({error:a,type:`CONNECT_FAILED`});try{await s.disconnect?.()}catch(e){i(`[butr] disconnect during error recovery failed:`,e)}try{t.onConnectError?.(a,e)}catch(e){i(`[butr] onConnectError threw:`,e)}throw o?.(r),r}finally{p&&clearTimeout(p)}},disconnectWallet:e=>{r().setUserDisconnected(!0);let n=r();if(!n.isHydrated)return;let i=n.pool.get(e);if(!i)return;m.detach(e),R(()=>i.connector.disconnect?.()??Promise.resolve(),a);let c=i.connector.chainPlatform;l({connectorId:e,type:`DISCONNECTED`}),r().pool.size===0?R(()=>o.clearAll(),s(`failed to clear storage`)):(R(()=>o.removePoolEntry(e),s(`failed to remove pool entry`)),d(),f()),t.onDisconnect?.(c)},getConnectorInstance:e=>t.createConnector(e),hydrateWallets:async()=>{let e=await c.hydrate();l({activeConnectorId:e.activeConnectorId,isUserDisconnected:e.isUserDisconnected,pool:e.pool,selection:e.selection,type:`HYDRATED`});for(let[t,n]of e.pool)m.attach(t,n.connector);if(await Promise.all([d(),f()]),Promise.all(c.pendingIds().map(e=>r().tryRestoreFromPending(e))),t.onHydrated)try{t.onHydrated({dropped:e.dropped,pendingIds:[...e.pendingIds],restoredIds:[...e.pool.keys()]})}catch(e){i(`[butr] onHydrated threw:`,e)}else if(e.dropped.length>0)for(let{connectorId:t,reason:n}of e.dropped)i(`[butr] failed to restore connector ${t}:`,n)},refreshWallet:e=>{l({connectorId:e,type:`WALLET_REFRESHED`})},requestAccounts:async e=>{let t=r().pool.get(e);if(!t)return;await t.connector.requestAccounts?.();let n=t.connector.getAccounts?await t.connector.getAccounts().catch(()=>null):null;!n||n.length===0||p(e,n)},reset:()=>{r().setUserDisconnected(!0);let e=r();if(e.isHydrated){m.detachAll();for(let t of e.pool.values())R(()=>t.connector.disconnect?.()??Promise.resolve(),a);if(R(()=>o.clearAll(),s(`failed to clear storage`)),t.onReset){let e=t.onReset;R(()=>Promise.resolve(e()),a)}l({type:`RESET`})}},resetConnectionStatus:()=>{l({type:`STATUS_RESET`})},setActiveConnector:e=>{l({connectorId:e,type:`ACTIVE_CHANGED`}),f()},setConnectionError:e=>{l({error:e,type:`ERROR_SET`})},setSelection:(e,t)=>{l({chainPlatform:e,connectorId:t,type:`SELECTION_CHANGED`}),d()},setUserDisconnected:e=>{l({type:`USER_DISCONNECTED_SET`,value:e}),R(()=>o.markUserDisconnected(e),s(`failed to persist disconnect intent`))},tryRestoreFromPending:async e=>{let n=await c.drainPending(e);if(n){if(n.kind===`fail`){i(`[butr] late restore failed for ${e}:`,n.error);return}l({connectorId:e,entry:n.entry,type:`CONNECT_SUCCEEDED`}),m.attach(e,n.entry.connector),await Promise.all([u(),d(),f()]),t.onConnect?.(n.entry)}},updateWalletAccount:(e,t)=>{let n=r().pool.get(e);if(!n)return;let i=t.chain,a=n.accounts.map(e=>e.chain.id===i.id?e:{chain:i,id:`${i.id}:${B(e.walletAddress)}`,walletAddress:e.walletAddress});p(e,a.some(e=>B(e.walletAddress)===B(t.walletAddress))?a:[...a,t],t)}}})},H=(e,t)=>e===t?!0:!e||!t?!1:e.connector.id===t.connector.id&&e.account.walletAddress===t.account.walletAddress&&e.account.chain.id===t.account.chain.id,U=e=>{if(e===void 0)return;let t=e.trim();return t.length>0?t:void 0};export{_ as EMPTY_SNAPSHOT,D as WalletStorage,t as buildChainsByPlatform,l as createBrowserStorageDriver,h as createCookieStorageDriver,s as createMemoryStorageDriver,r as createWalletSource,V as createWalletStore,a as logError,i as logWarn,n as mapConnectionError,C as readWalletSnapshot,U as sanitizeIcon,H as walletEqual};
1
+ import{createStore as e}from"zustand/vanilla";const t=e=>({bitcoin:e.bitcoin??[],evm:e.evm??[],polkadot:e.polkadot??[],sui:e.sui??[],svm:e.svm??[]}),n=e=>{if(e instanceof Error){let t=e.message,n=t.toLowerCase();if(t===`Connection timeout`)return{kind:`Timeout`,message:t};if(t===`Failed to get account`||n.includes(`not connected`))return{kind:`NotConnected`,message:t};let r=e.code;return r===4001?{kind:`UserRejected`,message:t}:r===-32002?{kind:`RequestPending`,message:t}:r===4100||r===4900||r===4901?{kind:`NotConnected`,message:t}:n.includes(`user rejected`)||n.includes(`user denied`)?{kind:`UserRejected`,message:t}:n.includes(`locked`)?{kind:`WalletLocked`,message:t}:n.includes(`chain`)&&(n.includes(`mismatch`)||n.includes(`unsupported`))?{kind:`ChainMismatch`,message:t}:{cause:e,kind:`Unknown`,message:t}}return typeof e==`string`?{kind:`Unknown`,message:e}:{cause:e,kind:`Unknown`,message:`Connection failed`}},r=e=>({subscribe:e}),i=(...e)=>{console.warn(...e)},a=(...e)=>{console.error(...e)},o=e=>{try{return typeof globalThis<`u`&&globalThis[e]!==void 0}catch{return!1}},s=()=>{let e=new Map;return{getItem(t){return e.get(t)??null},removeItem(t){e.delete(t)},setItem(t,n){e.set(t,n)}}},c=e=>{if(!o(e))return s();let t=globalThis[e];return{getItem(e){try{return t.getItem(e)}catch{return null}},removeItem(e){try{t.removeItem(e)}catch{}},setItem(e,n){try{t.setItem(e,n)}catch{}}}},l=()=>({persistent:c(`localStorage`),session:c(`sessionStorage`)}),u=e=>encodeURIComponent(e),d=e=>decodeURIComponent(e),f=()=>{let e=new Map;if(typeof document>`u`)return e;let t=document.cookie;if(!t)return e;for(let n of t.split(`;`)){let t=n.trim();if(!t)continue;let r=t.indexOf(`=`);if(r===-1)continue;let i=t.slice(0,r),a=t.slice(r+1);try{e.set(i,d(a))}catch{}}return e},p=(e,t,n,r=!1)=>{let i=[`${e}=${u(t)}`,`Path=${n.path??`/`}`];if(n.domain&&i.push(`Domain=${n.domain}`),r)i.push(`Max-Age=0`);else{let e=n.maxAgeSeconds??2592e3;i.push(`Max-Age=${e}`)}return i.push(`SameSite=${n.sameSite??`lax`}`),(n.secure??!0)&&i.push(`Secure`),i.join(`; `)},m=e=>e?typeof e[Symbol.iterator]==`function`?new Map(e):new Map(Object.entries(e)):null,h=(e={})=>{let t=m(e.initialCookies);return typeof document>`u`?t?{getItem(e){return t.get(e)??null},removeItem(){},setItem(){}}:{getItem(){return null},removeItem(){},setItem(){}}:{getItem(e){return f().get(e)??null},removeItem(t){document.cookie=p(t,``,e,!0)},setItem(t,n){document.cookie=p(t,n,e)}}},g=new Set([`evm`,`svm`,`sui`,`bitcoin`]),_=Object.freeze({activeConnectorId:null,pool:Object.freeze({}),selection:Object.freeze({})}),v=e=>{if(typeof e[Symbol.iterator]!=`function`)return new Map(Object.entries(e));let t=new Map;for(let n of e){if(Array.isArray(n)){let[e,r]=n;typeof e==`string`&&typeof r==`string`&&t.set(e,r);continue}n&&typeof n==`object`&&`name`in n&&`value`in n&&typeof n.name==`string`&&typeof n.value==`string`&&t.set(n.name,n.value)}return t},y=e=>{if(!e||typeof e!=`object`)return!1;let t=e;return!(typeof t.walletAddress!=`string`||typeof t.id!=`string`||!t.chain||typeof t.chain!=`object`)},b=(e,t)=>{if(!t||typeof t!=`object`)return!1;let n=t;return!(typeof n.connectorId!=`string`||n.connectorId!==e||typeof n.chainPlatform!=`string`||!g.has(n.chainPlatform)||typeof n.name!=`string`||n.name.length===0||n.icon!==void 0&&typeof n.icon!=`string`||!y(n.account)||!Array.isArray(n.accounts)||!n.accounts.every(y))},x=e=>{if(!e)return{};try{let t=JSON.parse(e);if(!t||typeof t!=`object`)return{};let n={};for(let[e,r]of Object.entries(t))b(e,r)?n[e]=r:i(`[butr] readWalletSnapshot: dropping invalid pool entry for ${e}`);return n}catch(e){return i(`[butr] readWalletSnapshot: failed to parse pool cookie:`,e),{}}},S=e=>{if(!e)return{};try{let t=JSON.parse(e);if(!t||typeof t!=`object`)return{};let n={};for(let[e,r]of Object.entries(t))g.has(e)&&typeof r==`string`&&r.length>0&&(n[e]=r);return n}catch(e){return i(`[butr] readWalletSnapshot: failed to parse selection cookie:`,e),{}}},C=(e,t={})=>{let n=t.keyPrefix||`butr`,r=v(e),i=x(r.get(`${n}-pool`)),a=S(r.get(`${n}-selection`)),o=r.get(`${n}-active`),s=null;if(o&&o.length>0&&i[o])s=o;else{let e=Object.keys(i)[0];e&&(s=e)}return{activeConnectorId:s,pool:i,selection:a}},w=new Set([`evm`,`svm`,`sui`,`bitcoin`]),T=e=>{if(!e||typeof e!=`object`)return!1;let t=e;return!(typeof t.walletAddress!=`string`||typeof t.id!=`string`||!t.chain||typeof t.chain!=`object`)},E=(e,t)=>{if(!t||typeof t!=`object`)return!1;let n=t;return!(typeof n.connectorId!=`string`||n.connectorId!==e||typeof n.chainPlatform!=`string`||!w.has(n.chainPlatform)||typeof n.name!=`string`||n.name.length===0||n.icon!==void 0&&typeof n.icon!=`string`||!T(n.account)||!Array.isArray(n.accounts)||!n.accounts.every(T))};var D=class{poolKey;selectionKey;activeKey;userDisconnectedKey;persistent;session;poolMutationQueue=Promise.resolve();constructor(e){if(this.poolKey=`${e.keyPrefix}-pool`,this.selectionKey=`${e.keyPrefix}-selection`,this.activeKey=`${e.keyPrefix}-active`,this.userDisconnectedKey=`${e.keyPrefix}-user-disconnected`,e.persistent&&e.session)this.persistent=e.persistent,this.session=e.session;else{let t=l();this.persistent=e.persistent??t.persistent,this.session=e.session??t.session}}async serializePoolMutation(e){let t=this.poolMutationQueue,n=()=>{};this.poolMutationQueue=new Promise(e=>{n=e});try{await t}catch{}try{return await e()}finally{n()}}async getPool(){try{let e=await this.persistent.getItem(this.poolKey);if(!e)return{};let t=JSON.parse(e);if(!t||typeof t!=`object`)return await this.clearPool(),{};let n={};for(let[e,r]of Object.entries(t))E(e,r)?n[e]=r:i(`[butr] dropping invalid pool entry for ${e}`);return n}catch(e){return i(`[butr] failed to parse pool from storage:`,e),await this.clearPool(),{}}}setPool(e){return this.serializePoolMutation(async()=>{try{let t={...await this.getPool()};for(let[n,r]of e){let e={account:r.account,accounts:r.accounts,chainPlatform:r.connector.chainPlatform,connectorId:n,icon:r.connector.icon,name:r.connector.name};if(!E(n,e))throw Error(`[butr] refusing to persist invalid pool entry for ${n}`);t[n]=e}await this.persistent.setItem(this.poolKey,JSON.stringify(t))}catch(e){i(`[butr] failed to persist pool:`,e)}})}removePoolEntry(e){return this.serializePoolMutation(async()=>{try{let t=await this.getPool();if(t[e]){let{[e]:n,...r}=t;await this.persistent.setItem(this.poolKey,JSON.stringify(r))}}catch(t){i(`[butr] failed to remove pool entry ${e}:`,t)}})}clearPool(){return this.serializePoolMutation(async()=>{await this.persistent.removeItem(this.poolKey)})}async getSelection(){try{let e=await this.persistent.getItem(this.selectionKey);if(!e)return{};let t=JSON.parse(e);if(!t||typeof t!=`object`)return{};let n={};for(let[e,r]of Object.entries(t))w.has(e)&&typeof r==`string`&&r.length>0&&(n[e]=r);return n}catch(e){return i(`[butr] failed to parse selection from storage:`,e),{}}}async setSelection(e){try{let t={};for(let[n,r]of e)t[n]=r;await this.persistent.setItem(this.selectionKey,JSON.stringify(t))}catch(e){i(`[butr] failed to persist selection:`,e)}}async getActiveConnectorId(){try{let e=await this.persistent.getItem(this.activeKey);return e&&e.length>0?e:null}catch{return null}}async setActiveConnectorId(e){try{await(e===null?this.persistent.removeItem(this.activeKey):this.persistent.setItem(this.activeKey,e))}catch(e){i(`[butr] failed to persist active connector id:`,e)}}async clearAll(){await Promise.all([this.clearPool(),this.persistent.removeItem(this.selectionKey),this.persistent.removeItem(this.activeKey)])}async isUserDisconnected(){try{return await this.session.getItem(this.userDisconnectedKey)===`true`}catch{return!1}}async markUserDisconnected(e){try{await(e?this.session.setItem(this.userDisconnectedKey,`true`):this.session.removeItem(this.userDisconnectedKey))}catch{}}};const O=e=>{let t=new Map,n=e=>{let n=t.get(e);if(n){try{n()}catch(e){i(`[butr] unsubscribe threw:`,e)}t.delete(e)}};return{attach:(r,a)=>{if(a.subscribe){n(r);try{let i=a.subscribe(t=>{switch(t.type){case`accountChanged`:e.onAccountChanged(r,t.accounts,t.account);break;case`disconnected`:n(r),e.onDisconnected(r,a.chainPlatform);break;default:}});t.set(r,i)}catch(e){i(`[butr] subscribe failed for ${r}:`,e)}}},detach:n,detachAll:()=>{let e=[];for(let n of t.keys())e.push(n);for(let t of e)n(t)}}},k=async(e,t,n)=>{if(e.getAccounts){let t=await e.getAccounts().catch(()=>null);if(t&&t.length>0)return t}return t.length>0?[...t]:[n]},A=async(e,t,n)=>{try{await n.connect({silent:!0});let r=await n.getAccount()||t.account;return{connectorId:e,entry:{account:r,accounts:await k(n,t.accounts,r),connector:n},kind:`ok`}}catch(t){return{connectorId:e,error:t,kind:`fail`}}},j=(e,t)=>{let n=new Map;return{drainPending:async e=>{let r=n.get(e);if(!r)return null;let i=t(e);if(!i)return null;let a=await A(e,r,i);return n.delete(e),a},hydrate:async()=>{let[r,i,a,o]=await Promise.all([e.getPool(),e.getSelection(),e.getActiveConnectorId(),e.isUserDisconnected()]),s=new Map,c=[];n.clear();let l=[];for(let[e,i]of Object.entries(r)){if(!i)continue;let r=t(e);if(!r){n.set(e,i);continue}l.push(A(e,i,r))}let u=await Promise.all(l);for(let e of u){if(e.kind===`ok`){s.set(e.connectorId,e.entry);continue}c.push({connectorId:e.connectorId,reason:e.error})}let d=new Map;for(let[e,t]of Object.entries(i))t&&s.has(t)&&d.set(e,t);for(let[e,t]of s){let n=t.connector.chainPlatform;d.has(n)||d.set(n,e)}let f=null;return a&&s.has(a)?f=a:s.size>0&&(f=s.keys().next().value??null),{activeConnectorId:f,dropped:c,isUserDisconnected:o,pendingIds:[...n.keys()],pool:s,selection:d}},pendingIds:()=>[...n.keys()]}},M={activeConnectorId:null,connectingConnectorId:null,connectionError:null,connectionStatus:`idle`,isHydrated:!1,isUserDisconnected:!1,pool:new Map,reconnectingIds:new Set,selection:new Map},N=(e,t)=>{for(let[n,r]of e)if(r.connector.chainPlatform===t)return n},P=(e,t)=>{switch(t.type){case`HYDRATED`:{let n=new Map(e.pool);for(let[e,r]of t.pool)n.set(e,r);let r=new Map(e.selection);for(let[e,n]of t.selection)r.set(e,n);let i=null;t.activeConnectorId&&n.has(t.activeConnectorId)?i=t.activeConnectorId:e.activeConnectorId&&n.has(e.activeConnectorId)?i=e.activeConnectorId:n.size>0&&(i=n.keys().next().value??null);let a=e.reconnectingIds;if(e.reconnectingIds.size>0&&t.pool.size>0){let n=new Set(e.reconnectingIds);for(let e of t.pool.keys())n.delete(e);n.size!==e.reconnectingIds.size&&(a=n)}return{...e,activeConnectorId:i,isHydrated:!0,isUserDisconnected:t.isUserDisconnected,pool:n,reconnectingIds:a,selection:r}}case`USER_DISCONNECTED_SET`:return{...e,isUserDisconnected:t.value};case`CONNECT_STARTED`:return{...e,connectingConnectorId:t.connectorId,connectionError:null,connectionStatus:`connecting`};case`CONNECT_SUCCEEDED`:{let{connectorId:n,entry:r}=t,i=e.pool.get(n),a=e.reconnectingIds.has(n)?new Set([...e.reconnectingIds].filter(e=>e!==n)):e.reconnectingIds;if(i&&i.account.walletAddress.toLowerCase()===r.account.walletAddress.toLowerCase())return{...e,activeConnectorId:n,connectingConnectorId:null,connectionStatus:`success`,pool:i.connector===r.connector?e.pool:new Map([...e.pool,[n,r]]),reconnectingIds:a};let o=new Map([...e.pool,[n,r]]),s=new Map([...e.selection,[r.connector.chainPlatform,n]]);return{...e,activeConnectorId:n,connectingConnectorId:null,connectionStatus:`success`,pool:o,reconnectingIds:a,selection:s}}case`CONNECT_FAILED`:return{...e,connectingConnectorId:null,connectionError:t.error,connectionStatus:`error`};case`DISCONNECTED`:{let n=e.pool.get(t.connectorId);if(!n)return e;let r=new Map(e.pool);r.delete(t.connectorId);let i=new Map(e.selection),a=n.connector.chainPlatform;if(i.get(a)===t.connectorId){i.delete(a);let e=N(r,a);e&&i.set(a,e)}let o=r.keys().next(),s=e.activeConnectorId===t.connectorId?o.value??null:e.activeConnectorId,c=e.reconnectingIds.has(t.connectorId)?new Set([...e.reconnectingIds].filter(e=>e!==t.connectorId)):e.reconnectingIds;return{...e,activeConnectorId:s,pool:r,reconnectingIds:c,selection:i}}case`ACCOUNTS_REFRESHED`:{let n=e.pool.get(t.connectorId);if(!n)return e;let r;r=t.active?t.active:t.accounts.some(e=>e.walletAddress===n.account.walletAddress&&e.chain.id===n.account.chain.id)?n.account:t.accounts[0]??n.account;let i=r.walletAddress===n.account.walletAddress&&r.chain.id===n.account.chain.id,a=t.accounts.length===n.accounts.length&&t.accounts.every((e,t)=>e.walletAddress===n.accounts[t]?.walletAddress);if(i&&a)return e;let o={...n,account:r,accounts:[...t.accounts]},s=new Map([...e.pool,[t.connectorId,o]]);return{...e,pool:s}}case`WALLET_REFRESHED`:{let n=e.pool.get(t.connectorId);if(!n)return e;let r=new Map([...e.pool,[t.connectorId,{...n}]]);return{...e,pool:r}}case`ACTIVE_CHANGED`:return{...e,activeConnectorId:t.connectorId};case`SELECTION_CHANGED`:{let n=new Map(e.selection);return t.connectorId===null?n.delete(t.chainPlatform):n.set(t.chainPlatform,t.connectorId),{...e,selection:n}}case`STATUS_RESET`:return{...e,connectingConnectorId:null,connectionError:null,connectionStatus:`idle`};case`ERROR_SET`:return{...e,connectionError:t.error,connectionStatus:t.error?`error`:`idle`};case`RESET`:return{...M,isHydrated:e.isHydrated,isUserDisconnected:!0,reconnectingIds:new Set};default:return e}};var F=class extends Error{code=`BUTR_RECONNECTING`;connectorId;method;constructor(e,t){super(`[butr] ${e} called on shadow connector "${t}" — wait for silent reconnect to complete (check reconnectingIds.has(id) before calling).`),this.name=`ShadowConnectorError`,this.connectorId=t,this.method=e}};const I=Object.freeze({getBalance:!1,getTransactionReceipt:!1,requestAccounts:!1,sendTransaction:!1,signIn:!1,signMessage:!1,signTransaction:!1,subscribe:!1,switchAccount:!1,switchChain:!1}),L=e=>{let t=e.connectorId,n=e.name,r=e.icon,i=e=>Promise.reject(new F(e,t)),a={capabilities:I,connect:()=>i(`connect`),disconnect:()=>i(`disconnect`),getAccount:()=>i(`getAccount`),getBalance:()=>i(`getBalance`),getSigner:()=>i(`getSigner`),getTransactionReceipt:()=>i(`getTransactionReceipt`),icon:r,id:t,name:n,sendTx:()=>i(`sendTx`),sendTxToChain:()=>i(`sendTxToChain`),signMessage:()=>i(`signMessage`),switchChain:()=>i(`switchChain`)};switch(e.chainPlatform){case`bitcoin`:return{...a,chainPlatform:`bitcoin`};case`evm`:return{...a,chainPlatform:`evm`};case`sui`:return{...a,chainPlatform:`sui`};case`svm`:return{...a,chainPlatform:`svm`};case`polkadot`:return{...a,chainPlatform:`polkadot`};default:throw e.chainPlatform,Error(`[butr] unknown chainPlatform: ${e.chainPlatform}`)}},R=async(e,t)=>{try{await e()}catch(e){t(e)}},z=e=>{let t=new Map,n=new Set;for(let[r,i]of Object.entries(e.pool)){if(!i)continue;let e=L(i);t.set(r,{account:i.account,accounts:[...i.accounts],connector:e}),n.add(r)}let r=new Map;for(let[n,i]of Object.entries(e.selection))i&&t.has(i)&&r.set(n,i);let i=e.activeConnectorId&&t.has(e.activeConnectorId)?e.activeConnectorId:t.keys().next().value??null;return{...M,activeConnectorId:i,isHydrated:!0,pool:t,reconnectingIds:n,selection:r}},B=e=>e.toLowerCase(),V=t=>{let r=t.storageKeyPrefix||`butr`,o=t.storage||new D({keyPrefix:r}),s=e=>n=>{if(t.onStorageError){try{t.onStorageError(n,e)}catch(e){i(`[butr] onStorageError threw:`,e)}return}i(`[butr] ${e}:`,n)},c=j(o,t.createConnector);return e()((e,r)=>{let l=t=>{e(e=>P(e,t),!1)},u=()=>R(()=>o.setPool(r().pool),s(`failed to persist pool`)),d=()=>R(()=>o.setSelection(r().selection),s(`failed to persist selection`)),f=()=>R(()=>o.setActiveConnectorId(r().activeConnectorId),s(`failed to persist active connector`)),p=(e,t,n)=>{l({accounts:t,active:n,connectorId:e,type:`ACCOUNTS_REFRESHED`}),u()},m=O({onAccountChanged:(e,t,n)=>{p(e,[...t],n)},onDisconnected:(e,n)=>{l({connectorId:e,type:`DISCONNECTED`}),t.onDisconnect?.(n)}});return{...t.initialState?z(t.initialState):M,_config:t,_storage:o,connectWallet:async(e,a,o)=>{r().setUserDisconnected(!1);let s=t.createConnector(e);if(!s)throw Error(`Failed to create connector for ${e}`);l({connectorId:e,type:`CONNECT_STARTED`});let c=t.slowConnectThresholdMs??5e3,p=t.onSlowConnect?setTimeout(()=>{try{t.onSlowConnect?.(e)}catch(e){i(`[butr] onSlowConnect threw:`,e)}},c):null;try{let n=s.connect();n.catch(()=>{}),await Promise.race([n,new Promise((e,t)=>{setTimeout(()=>{t(Error(`Connection timeout`))},9e4)})]);let r=await s.getAccount();if(!r)throw Error(`Failed to get account`);let i=s.getAccounts?await s.getAccounts().catch(()=>null):null,o={account:r,accounts:i&&i.length>0?i:[r],connector:s};l({connectorId:e,entry:o,type:`CONNECT_SUCCEEDED`}),m.attach(e,s),await Promise.all([u(),d(),f()]),t.onConnect?.(o),a?.(o)}catch(r){let a=n(r);l({error:a,type:`CONNECT_FAILED`});try{await s.disconnect?.()}catch(e){i(`[butr] disconnect during error recovery failed:`,e)}try{t.onConnectError?.(a,e)}catch(e){i(`[butr] onConnectError threw:`,e)}throw o?.(r),r}finally{p&&clearTimeout(p)}},disconnectWallet:e=>{r().setUserDisconnected(!0);let n=r();if(!n.isHydrated)return;let i=n.pool.get(e);if(!i)return;m.detach(e),R(()=>i.connector.disconnect?.()??Promise.resolve(),a);let c=i.connector.chainPlatform;l({connectorId:e,type:`DISCONNECTED`}),r().pool.size===0?R(()=>o.clearAll(),s(`failed to clear storage`)):(R(()=>o.removePoolEntry(e),s(`failed to remove pool entry`)),d(),f()),t.onDisconnect?.(c)},getConnectorInstance:e=>t.createConnector(e),hydrateWallets:async()=>{let e=await c.hydrate();l({activeConnectorId:e.activeConnectorId,isUserDisconnected:e.isUserDisconnected,pool:e.pool,selection:e.selection,type:`HYDRATED`});for(let[t,n]of e.pool)m.attach(t,n.connector);if(await Promise.all([d(),f()]),Promise.all(c.pendingIds().map(e=>r().tryRestoreFromPending(e))),t.onHydrated)try{t.onHydrated({dropped:e.dropped,pendingIds:[...e.pendingIds],restoredIds:[...e.pool.keys()]})}catch(e){i(`[butr] onHydrated threw:`,e)}else if(e.dropped.length>0)for(let{connectorId:t,reason:n}of e.dropped)i(`[butr] failed to restore connector ${t}:`,n)},refreshWallet:e=>{l({connectorId:e,type:`WALLET_REFRESHED`})},requestAccounts:async e=>{let t=r().pool.get(e);if(!t)return;await t.connector.requestAccounts?.();let n=t.connector.getAccounts?await t.connector.getAccounts().catch(()=>null):null;!n||n.length===0||p(e,n)},reset:()=>{r().setUserDisconnected(!0);let e=r();if(e.isHydrated){m.detachAll();for(let t of e.pool.values())R(()=>t.connector.disconnect?.()??Promise.resolve(),a);if(R(()=>o.clearAll(),s(`failed to clear storage`)),t.onReset){let e=t.onReset;R(()=>Promise.resolve(e()),a)}l({type:`RESET`})}},resetConnectionStatus:()=>{l({type:`STATUS_RESET`})},setActiveConnector:e=>{l({connectorId:e,type:`ACTIVE_CHANGED`}),f()},setConnectionError:e=>{l({error:e,type:`ERROR_SET`})},setSelection:(e,t)=>{l({chainPlatform:e,connectorId:t,type:`SELECTION_CHANGED`}),d()},setUserDisconnected:e=>{l({type:`USER_DISCONNECTED_SET`,value:e}),R(()=>o.markUserDisconnected(e),s(`failed to persist disconnect intent`))},tryRestoreFromPending:async e=>{let n=await c.drainPending(e);if(n){if(n.kind===`fail`){i(`[butr] late restore failed for ${e}:`,n.error);return}l({connectorId:e,entry:n.entry,type:`CONNECT_SUCCEEDED`}),m.attach(e,n.entry.connector),await Promise.all([u(),d(),f()]),t.onConnect?.(n.entry)}},updateWalletAccount:(e,t)=>{let n=r().pool.get(e);if(!n)return;let i=t.chain,a=n.accounts.map(e=>e.chain.id===i.id?e:{chain:i,id:`${i.id}:${B(e.walletAddress)}`,walletAddress:e.walletAddress});p(e,a.some(e=>B(e.walletAddress)===B(t.walletAddress))?a:[...a,t],t)}}})},H=(e,t)=>e===t?!0:!e||!t?!1:e.connector.id===t.connector.id&&e.account.walletAddress===t.account.walletAddress&&e.account.chain.id===t.account.chain.id,U=e=>{if(e===void 0)return;let t=e.trim();return t.length>0?t:void 0};export{_ as EMPTY_SNAPSHOT,D as WalletStorage,t as buildChainsByPlatform,l as createBrowserStorageDriver,h as createCookieStorageDriver,s as createMemoryStorageDriver,r as createWalletSource,V as createWalletStore,a as logError,i as logWarn,n as mapConnectionError,C as readWalletSnapshot,U as sanitizeIcon,H as walletEqual};
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["toCookieMap","VALID_CHAIN_PLATFORMS","isValidAccount","isValidPoolEntry"],"sources":["../src/types/chains-by-platform.ts","../src/types/errors.ts","../src/wallet-source.ts","../src/logger.ts","../src/storage/browser-storage-driver.ts","../src/storage/cookie-storage-driver.ts","../src/storage/snapshot.ts","../src/storage/wallet-storage.ts","../src/store/connector-lifecycle.ts","../src/store/hydration.ts","../src/store/reducer.ts","../src/store/shadow-adapter.ts","../src/store/wallet-store-helpers.ts","../src/store/wallet-store.ts","../src/wallet-equal.ts","../src/sanitize-icon.ts"],"sourcesContent":["import type { ChainBase } from \"./chain\";\nimport type { ChainPlatform } from \"./wallet\";\n\n/**\n * Map of every chain platform to the list of chains the consumer wants\n * to expose to its UI (chain switcher, picker, etc).\n *\n * The platform key set is fixed by `ChainPlatform`. The value is the\n * chain list — empty when the consumer doesn't want to support that\n * platform's chains in this view. This is the only type that callers\n * write down; the values come from per-platform packages\n * (`EVM_CHAINS_LIST`, `SVM_CHAINS_LIST`, etc).\n */\ntype ChainsByPlatform = Readonly<Record<ChainPlatform, ReadonlyArray<ChainBase>>>;\n\n/**\n * Build a fully-populated `ChainsByPlatform` from a partial. Platforms\n * the consumer doesn't specify default to an empty list.\n *\n * Use this in apps that target one or two chain platforms — importing\n * only those packages keeps unused chain registries out of the bundle.\n * Apps that want every chain reach for `CHAINS_BY_PLATFORM` from\n * `@usebutr/wallets` instead.\n *\n * @example\n * // EVM-only app — Solana/Sui/Bitcoin tables never enter the bundle\n * import { EVM_CHAINS_LIST } from \"@usebutr/evm\";\n * import { buildChainsByPlatform } from \"@usebutr/core\";\n *\n * const chains = buildChainsByPlatform({ evm: EVM_CHAINS_LIST });\n *\n * @example\n * // Multi-chain app — pull from each package the app actually uses\n * import { EVM_CHAINS_LIST } from \"@usebutr/evm\";\n * import { SVM_CHAINS_LIST } from \"@usebutr/svm\";\n *\n * const chains = buildChainsByPlatform({\n * evm: EVM_CHAINS_LIST,\n * svm: SVM_CHAINS_LIST,\n * });\n */\nconst buildChainsByPlatform = (partial: Partial<ChainsByPlatform>): ChainsByPlatform => ({\n bitcoin: partial.bitcoin ?? [],\n evm: partial.evm ?? [],\n sui: partial.sui ?? [],\n svm: partial.svm ?? [],\n});\n\nexport type { ChainsByPlatform };\nexport { buildChainsByPlatform };\n","/**\n * Tagged union of normalised connection errors.\n *\n * butr maps thrown values from connectors (which vary across wallet SDKs:\n * MetaMask uses EIP-1193 codes, Phantom throws stringly-typed errors,\n * embedded SDKs throw their own classes) into a small set of UX-meaningful\n * variants. Consumers branch on `kind` instead of regexing message strings.\n *\n * `message` is always present and human-readable. `cause` preserves the\n * original thrown value so callers can inspect raw connector errors when\n * the variant is `Unknown`.\n */\ntype ConnectionError =\n | { kind: \"UserRejected\"; message: string }\n | { kind: \"RequestPending\"; message: string }\n | { kind: \"WalletLocked\"; message: string }\n | { actualChain?: string; expectedChain?: string; kind: \"ChainMismatch\"; message: string }\n | { kind: \"NotConnected\"; message: string }\n | { kind: \"Timeout\"; message: string }\n | { cause?: unknown; kind: \"Unknown\"; message: string };\n\ntype ConnectionErrorKind = ConnectionError[\"kind\"];\n\n/**\n * Normalise a thrown value into a `ConnectionError`.\n *\n * Recognises:\n * - butr's own `Error(\"Connection timeout\")` (from the 90s connect timeout)\n * - butr's own `Error(\"Failed to get account\")` (from the connect flow)\n * - EIP-1193 numeric `code` properties (`4001` → UserRejected,\n * `-32002` → RequestPending, `4100`/`4900`/`4901` → NotConnected —\n * unauthorized / disconnected from all-or-one chains)\n * - common message substrings: \"user rejected\" / \"user denied\",\n * \"locked\", \"chain\", etc.\n * - anything else → `Unknown` with `cause` set to the original value.\n */\nconst mapConnectionError = (raw: unknown): ConnectionError => {\n if (raw instanceof Error) {\n const message = raw.message;\n const lower = message.toLowerCase();\n\n if (message === \"Connection timeout\") {\n return { kind: \"Timeout\", message };\n }\n if (message === \"Failed to get account\" || lower.includes(\"not connected\")) {\n return { kind: \"NotConnected\", message };\n }\n\n const code = (raw as { code?: unknown }).code;\n if (code === 4001) {\n return { kind: \"UserRejected\", message };\n }\n if (code === -32_002) {\n return { kind: \"RequestPending\", message };\n }\n // EIP-1193: 4100 unauthorized, 4900 disconnected (all chains),\n // 4901 disconnected (requested chain). All mean \"no usable session\"\n // from butr's perspective → NotConnected.\n if (code === 4100 || code === 4900 || code === 4901) {\n return { kind: \"NotConnected\", message };\n }\n\n if (lower.includes(\"user rejected\") || lower.includes(\"user denied\")) {\n return { kind: \"UserRejected\", message };\n }\n if (lower.includes(\"locked\")) {\n return { kind: \"WalletLocked\", message };\n }\n if (lower.includes(\"chain\") && (lower.includes(\"mismatch\") || lower.includes(\"unsupported\"))) {\n return { kind: \"ChainMismatch\", message };\n }\n\n return { cause: raw, kind: \"Unknown\", message };\n }\n\n if (typeof raw === \"string\") {\n return { kind: \"Unknown\", message: raw };\n }\n\n return { cause: raw, kind: \"Unknown\", message: \"Connection failed\" };\n};\n\nexport type { ConnectionError, ConnectionErrorKind };\nexport { mapConnectionError };\n","import type { WalletAdapter } from \"./types\";\n\n/**\n * A discovery seam. Implementations call `onAdapter(adapter)` each time\n * they find a wallet and return an unsubscribe handle. `@usebutr/wallets`\n * composes EVM + SVM into a single `WalletSource`; third parties can\n * implement this type without depending on `@usebutr/wallets`.\n */\ntype WalletSource = {\n subscribe(onAdapter: (adapter: WalletAdapter) => void): () => void;\n};\n\n/**\n * Wrap a bare `subscribe` function (the exact shape of\n * `discoverEvmAdapters` / `discoverSvmAdapters`) into a `WalletSource`,\n * so an EVM-only app can do\n * `createWalletSource(discoverEvmAdapters)` without importing anything\n * protocol-bearing beyond `@usebutr/evm`.\n */\nconst createWalletSource = (\n subscribe: (onAdapter: (adapter: WalletAdapter) => void) => () => void,\n): WalletSource => ({ subscribe });\n\nexport type { WalletSource };\nexport { createWalletSource };\n","/* eslint-disable no-console -- central logging sink; the one sanctioned console use */\nconst logWarn = (...args: ReadonlyArray<unknown>): void => {\n console.warn(...args);\n};\n\nconst logError = (...args: ReadonlyArray<unknown>): void => {\n console.error(...args);\n};\n/* eslint-enable no-console */\n\nexport { logError, logWarn };\n","import type { StorageDriver } from \"./persistence\";\n\ntype BrowserStorageDrivers = {\n /** Survives app restart (localStorage on web, AsyncStorage/MMKV on RN). */\n persistent: StorageDriver;\n /** Cleared when the app session ends (sessionStorage on web, in-memory on RN). */\n session: StorageDriver;\n};\n\ntype WebStorageKind = \"localStorage\" | \"sessionStorage\";\n\nconst hasWebStorage = (kind: WebStorageKind): boolean => {\n try {\n return (\n typeof globalThis !== \"undefined\" &&\n (globalThis as Record<string, unknown>)[kind] !== undefined\n );\n } catch {\n return false;\n }\n};\n\nconst createMemoryStorageDriver = (): StorageDriver => {\n const memory = new Map<string, string>();\n return {\n getItem(key) {\n return memory.get(key) ?? null;\n },\n removeItem(key) {\n memory.delete(key);\n },\n setItem(key, value) {\n memory.set(key, value);\n },\n };\n};\n\nconst createWebStorageDriver = (kind: WebStorageKind): StorageDriver => {\n if (!hasWebStorage(kind)) {\n return createMemoryStorageDriver();\n }\n\n const storage = (globalThis as unknown as Record<WebStorageKind, Storage>)[kind];\n\n return {\n getItem(key) {\n try {\n return storage.getItem(key);\n } catch {\n return null;\n }\n },\n removeItem(key) {\n try {\n storage.removeItem(key);\n } catch {\n // ignore\n }\n },\n setItem(key, value) {\n try {\n storage.setItem(key, value);\n } catch {\n // ignore quota/security errors\n }\n },\n };\n};\n\n/**\n * Default storage drivers for web. `persistent` wraps `localStorage`,\n * `session` wraps `sessionStorage`. Both fall back to in-memory maps\n * when the global isn't available (SSR, non-browser environments).\n */\nconst createBrowserStorageDriver = (): BrowserStorageDrivers => ({\n persistent: createWebStorageDriver(\"localStorage\"),\n session: createWebStorageDriver(\"sessionStorage\"),\n});\n\nexport type { BrowserStorageDrivers };\nexport { createBrowserStorageDriver, createMemoryStorageDriver };\n","import type { StorageDriver } from \"./persistence\";\n\ntype InitialCookies = Iterable<[string, string]> | Readonly<Record<string, string>>;\n\ntype CookieDriverOptions = {\n /**\n * Cookie `domain` attribute. Omit to scope to the current host.\n * Set to a parent domain (e.g. `.example.com`) to share state\n * across subdomains.\n */\n domain?: string;\n /**\n * Snapshot of cookies for the SSR pass — typically the result of\n * Next.js' `cookies()` (from `next/headers`) or a parsed\n * `req.headers.cookie`. Used only when `document` is unavailable.\n *\n * When provided, `getItem` reads from this snapshot during the\n * server render so the store sees the same persisted state the\n * client will see after hydration. Writes remain no-ops on the\n * server — emitting `Set-Cookie` has to be done by the framework\n * layer that owns the response.\n */\n initialCookies?: InitialCookies;\n /**\n * Lifetime in seconds. Defaults to 30 days. Pass `undefined`\n * (the default) for \"until the session ends\" — but note that\n * butr persists wallet state across sessions, so a finite\n * max-age is usually what consumers want.\n */\n maxAgeSeconds?: number;\n /**\n * URL path the cookie applies to. Defaults to `/` (whole origin).\n */\n path?: string;\n /**\n * `SameSite` attribute. Defaults to `\"lax\"` — restrictive enough\n * to avoid CSRF on cross-origin POSTs, permissive enough for\n * top-level navigations.\n */\n sameSite?: \"lax\" | \"strict\" | \"none\";\n /**\n * `Secure` attribute. Defaults to `true` (cookies only sent over\n * HTTPS). Force `false` for local development on plain `http://`.\n */\n secure?: boolean;\n};\n\nconst DEFAULT_MAX_AGE_SECONDS = 60 * 60 * 24 * 30;\n\nconst encode = (value: string): string => encodeURIComponent(value);\nconst decode = (value: string): string => decodeURIComponent(value);\n\nconst readCookies = (): Map<string, string> => {\n const out = new Map<string, string>();\n if (typeof document === \"undefined\") {\n return out;\n }\n const cookieString = document.cookie;\n if (!cookieString) {\n return out;\n }\n for (const segment of cookieString.split(\";\")) {\n const trimmed = segment.trim();\n if (!trimmed) {\n continue;\n }\n const eq = trimmed.indexOf(\"=\");\n if (eq === -1) {\n continue;\n }\n const name = trimmed.slice(0, eq);\n const raw = trimmed.slice(eq + 1);\n try {\n out.set(name, decode(raw));\n } catch {\n // Malformed entry; skip rather than crash hydration.\n }\n }\n return out;\n};\n\nconst buildCookieString = (\n name: string,\n value: string,\n options: CookieDriverOptions,\n expired = false,\n): string => {\n const parts: Array<string> = [`${name}=${encode(value)}`, `Path=${options.path ?? \"/\"}`];\n if (options.domain) {\n parts.push(`Domain=${options.domain}`);\n }\n if (expired) {\n parts.push(\"Max-Age=0\");\n } else {\n const maxAge = options.maxAgeSeconds ?? DEFAULT_MAX_AGE_SECONDS;\n parts.push(`Max-Age=${maxAge}`);\n }\n parts.push(`SameSite=${options.sameSite ?? \"lax\"}`);\n if (options.secure ?? true) {\n parts.push(\"Secure\");\n }\n return parts.join(\"; \");\n};\n\nconst toCookieMap = (input: InitialCookies | undefined): Map<string, string> | null => {\n if (!input) {\n return null;\n }\n if (typeof (input as Iterable<[string, string]>)[Symbol.iterator] === \"function\") {\n return new Map(input as Iterable<[string, string]>);\n }\n return new Map(Object.entries(input as Readonly<Record<string, string>>));\n};\n\n/**\n * Cookie-backed storage driver. Reads/writes `document.cookie` —\n * server-readable, survives reloads, scoped per `domain`/`path`.\n *\n * **When to use this:** SSR apps that need to know who's connected\n * during the server render (so they can stream the connected-wallet\n * UI without a client-side hydration flicker). Pass `initialCookies`\n * from a server-side cookie source (e.g. `cookies()` in Next.js'\n * `next/headers`, or a parsed `req.headers.cookie`) and the same\n * driver will serve those values during the server render and switch\n * to `document.cookie` once it mounts in the browser.\n *\n * **Trade-offs vs `localStorage`:** cookies travel with every\n * request, so they cost bytes on the wire. Keep the storage key\n * prefix short, and prefer this driver for the `persistent` slot\n * only — the `session` slot can stay in `sessionStorage` (which\n * cookies can't natively model anyway).\n *\n * **Server-side writes are no-ops.** Emitting `Set-Cookie` requires\n * access to the framework's response object, which a storage driver\n * shouldn't reach into. The store doesn't mutate persisted state\n * during the SSR pass anyway — writes only fire after client mount,\n * once `document.cookie` is reachable.\n */\nconst createCookieStorageDriver = (options: CookieDriverOptions = {}): StorageDriver => {\n const seeded = toCookieMap(options.initialCookies);\n\n if (typeof document === \"undefined\") {\n if (!seeded) {\n return {\n getItem() {\n return null;\n },\n removeItem() {\n // server-side no-op\n },\n setItem() {\n // server-side no-op\n },\n };\n }\n return {\n getItem(key) {\n return seeded.get(key) ?? null;\n },\n removeItem() {\n // server-side no-op — framework owns Set-Cookie\n },\n setItem() {\n // server-side no-op — framework owns Set-Cookie\n },\n };\n }\n return {\n getItem(key) {\n return readCookies().get(key) ?? null;\n },\n removeItem(key) {\n // eslint-disable-next-line unicorn/no-document-cookie -- cookie storage driver is the document.cookie abstraction by design\n document.cookie = buildCookieString(key, \"\", options, true);\n },\n setItem(key, value) {\n // eslint-disable-next-line unicorn/no-document-cookie -- cookie storage driver is the document.cookie abstraction by design\n document.cookie = buildCookieString(key, value, options);\n },\n };\n};\n\nexport type { CookieDriverOptions, InitialCookies };\nexport { createCookieStorageDriver };\n","import { logWarn } from \"../logger\";\nimport type { ChainPlatform } from \"../types\";\n\nimport type { StoredPoolEntry, StoredPoolRecord, StoredSelectionRecord } from \"./persistence\";\n\nconst VALID_CHAIN_PLATFORMS = new Set<ChainPlatform>([\"evm\", \"svm\", \"sui\", \"bitcoin\"]);\n\n/**\n * Server-safe view of a butr-persisted session — everything you can\n * know about a user's connected wallets from the cookie payload alone,\n * without instantiating a `Connector`.\n *\n * Notably absent: the `Connector` instance. A wallet extension exists\n * only in the browser, so a server render can know *which* wallet was\n * connected and *what address* it held, but cannot dispatch\n * `signMessage`/`sendTransaction` on it. Splitting display from action\n * along this seam keeps the impossibility expressed in the types\n * rather than hidden inside a runtime check.\n */\ntype WalletSnapshot = {\n activeConnectorId: string | null;\n pool: StoredPoolRecord;\n selection: StoredSelectionRecord;\n};\n\ntype CookieSource =\n | Iterable<{ name: string; value: string }>\n | Iterable<[string, string]>\n | Readonly<Record<string, string>>;\n\ntype SnapshotOptions = {\n /**\n * Same prefix passed to `WalletManagerProvider` / `WalletStorage`.\n * Defaults to `\"butr\"` to match the library default.\n */\n keyPrefix?: string;\n};\n\nconst EMPTY_SNAPSHOT: WalletSnapshot = Object.freeze({\n activeConnectorId: null,\n pool: Object.freeze({}) as StoredPoolRecord,\n selection: Object.freeze({}) as StoredSelectionRecord,\n});\n\nconst toCookieMap = (input: CookieSource): Map<string, string> => {\n // Plain record: `{ name: \"value\", ... }`. Distinguished from\n // iterables by the absence of a `Symbol.iterator` callable.\n const maybeIterator = (input as { [Symbol.iterator]?: unknown })[Symbol.iterator];\n if (typeof maybeIterator !== \"function\") {\n return new Map(Object.entries(input as Readonly<Record<string, string>>));\n }\n const out = new Map<string, string>();\n for (const entry of input as Iterable<{ name: string; value: string } | [string, string]>) {\n if (Array.isArray(entry)) {\n const [name, value] = entry;\n if (typeof name === \"string\" && typeof value === \"string\") {\n out.set(name, value);\n }\n continue;\n }\n if (\n entry &&\n typeof entry === \"object\" &&\n \"name\" in entry &&\n \"value\" in entry &&\n typeof entry.name === \"string\" &&\n typeof entry.value === \"string\"\n ) {\n out.set(entry.name, entry.value);\n }\n }\n return out;\n};\n\nconst isValidAccount = (value: unknown): boolean => {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const account = value as Record<string, unknown>;\n if (typeof account.walletAddress !== \"string\" || typeof account.id !== \"string\") {\n return false;\n }\n if (!account.chain || typeof account.chain !== \"object\") {\n return false;\n }\n return true;\n};\n\nconst isValidPoolEntry = (key: string, value: unknown): value is StoredPoolEntry => {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const entry = value as Record<string, unknown>;\n if (typeof entry.connectorId !== \"string\" || entry.connectorId !== key) {\n return false;\n }\n if (typeof entry.chainPlatform !== \"string\") {\n return false;\n }\n if (!VALID_CHAIN_PLATFORMS.has(entry.chainPlatform as ChainPlatform)) {\n return false;\n }\n if (typeof entry.name !== \"string\" || entry.name.length === 0) {\n return false;\n }\n if (entry.icon !== undefined && typeof entry.icon !== \"string\") {\n return false;\n }\n if (!isValidAccount(entry.account)) {\n return false;\n }\n if (!Array.isArray(entry.accounts) || !entry.accounts.every(isValidAccount)) {\n return false;\n }\n return true;\n};\n\nconst parsePool = (raw: string | undefined): StoredPoolRecord => {\n if (!raw) {\n return {};\n }\n try {\n const parsed: unknown = JSON.parse(raw);\n if (!parsed || typeof parsed !== \"object\") {\n return {};\n }\n const result: StoredPoolRecord = {};\n for (const [key, value] of Object.entries(parsed as Record<string, unknown>)) {\n if (isValidPoolEntry(key, value)) {\n result[key] = value;\n } else {\n logWarn(`[butr] readWalletSnapshot: dropping invalid pool entry for ${key}`);\n }\n }\n return result;\n } catch (error) {\n logWarn(\"[butr] readWalletSnapshot: failed to parse pool cookie:\", error);\n return {};\n }\n};\n\nconst parseSelection = (raw: string | undefined): StoredSelectionRecord => {\n if (!raw) {\n return {};\n }\n try {\n const parsed: unknown = JSON.parse(raw);\n if (!parsed || typeof parsed !== \"object\") {\n return {};\n }\n const result: StoredSelectionRecord = {};\n for (const [key, value] of Object.entries(parsed as Record<string, unknown>)) {\n if (\n VALID_CHAIN_PLATFORMS.has(key as ChainPlatform) &&\n typeof value === \"string\" &&\n value.length > 0\n ) {\n result[key as ChainPlatform] = value;\n }\n }\n return result;\n } catch (error) {\n logWarn(\"[butr] readWalletSnapshot: failed to parse selection cookie:\", error);\n return {};\n }\n};\n\n/**\n * Parse a cookie source into a server-safe `WalletSnapshot`.\n *\n * Pure, sync, no `document`, no React — runnable in any environment\n * (Server Component, route handler, edge middleware, even client\n * code). Pair with `createCookieStorageDriver({ initialCookies })`\n * and `<WalletManagerProvider initialSnapshot={…} />` to render a\n * connected shell server-side without a hydration flash.\n *\n * **Stale-snapshot semantics.** The snapshot reflects whatever the\n * browser most recently persisted. If the user has since uninstalled\n * the wallet, switched accounts, or disconnected in another tab, the\n * client-side hydration will reconcile reality and the live store\n * will diverge from the snapshot. Treat the snapshot as an\n * *optimistic* shell — accurate enough to avoid a paint flicker,\n * authoritative only after `useIsHydrated()` is true.\n *\n * **Inputs.** Accepts the three shapes Next.js / Express / Hono /\n * generic-Node cookie code naturally produces:\n * - A plain object: `{ \"butr-pool\": \"{...}\", … }`\n * - An array of `{ name, value }` (Next.js' `cookies().getAll()`)\n * - An iterable of `[name, value]` tuples\n *\n * Malformed entries are dropped with a `logWarn` (same policy as\n * `WalletStorage.getPool`) — a cross-tab corruption shouldn't crash\n * the server render.\n */\nconst readWalletSnapshot = (\n source: CookieSource,\n options: SnapshotOptions = {},\n): WalletSnapshot => {\n const keyPrefix = options.keyPrefix || \"butr\";\n const cookies = toCookieMap(source);\n\n const pool = parsePool(cookies.get(`${keyPrefix}-pool`));\n const selection = parseSelection(cookies.get(`${keyPrefix}-selection`));\n\n const rawActive = cookies.get(`${keyPrefix}-active`);\n let activeConnectorId: string | null = null;\n if (rawActive && rawActive.length > 0 && pool[rawActive]) {\n activeConnectorId = rawActive;\n } else {\n // Mirror `HydrationCoordinator.hydrate` selection-fallback: when no\n // explicit active is stored, surface any pool member so the render\n // shell can still show \"connected\" instead of \"disconnected\".\n const firstKey = Object.keys(pool)[0];\n if (firstKey) {\n activeConnectorId = firstKey;\n }\n }\n\n return { activeConnectorId, pool, selection };\n};\n\nexport type { CookieSource, SnapshotOptions, WalletSnapshot };\nexport { EMPTY_SNAPSHOT, readWalletSnapshot };\n","import { logWarn } from \"../logger\";\nimport type { ChainPlatform, ConnectedWallet } from \"../types\";\n\nimport { createBrowserStorageDriver } from \"./browser-storage-driver\";\nimport type {\n StorageDriver,\n StoredPoolEntry,\n StoredPoolRecord,\n StoredSelectionRecord,\n WalletPersistence,\n} from \"./persistence\";\n\nconst VALID_CHAIN_PLATFORMS = new Set<ChainPlatform>([\"evm\", \"svm\", \"sui\", \"bitcoin\"]);\n\ntype StorageConfig = {\n keyPrefix: string;\n /** Survives app restart. Defaults to localStorage on web. */\n persistent?: StorageDriver;\n /** Cleared on session end. Defaults to sessionStorage on web. */\n session?: StorageDriver;\n};\n\nconst isValidAccount = (value: unknown): boolean => {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const account = value as Record<string, unknown>;\n if (typeof account.walletAddress !== \"string\" || typeof account.id !== \"string\") {\n return false;\n }\n if (!account.chain || typeof account.chain !== \"object\") {\n return false;\n }\n return true;\n};\n\n/**\n * Structural validator for a serialized pool entry.\n *\n * Used in two directions:\n * - On read (`getPool`): malformed entries from storage are warned\n * and dropped — legacy / cross-tab corruption shouldn't crash the\n * consumer.\n * - On write (`setPool`): the runtime's reducer state is the source\n * of truth, and a malformed write would indicate a programming\n * error inside butr. Throw rather than silently corrupt storage.\n *\n * Same validator either way — keeping the two paths symmetric means\n * \"what's storable\" is a single fact.\n */\nconst isValidPoolEntry = (key: string, value: unknown): value is StoredPoolEntry => {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const entry = value as Record<string, unknown>;\n if (typeof entry.connectorId !== \"string\" || entry.connectorId !== key) {\n return false;\n }\n if (typeof entry.chainPlatform !== \"string\") {\n return false;\n }\n if (!VALID_CHAIN_PLATFORMS.has(entry.chainPlatform as ChainPlatform)) {\n return false;\n }\n if (typeof entry.name !== \"string\" || entry.name.length === 0) {\n return false;\n }\n // `icon` is optional (some adapters announce no icon); reject only\n // when present-but-wrong-typed.\n if (entry.icon !== undefined && typeof entry.icon !== \"string\") {\n return false;\n }\n if (!isValidAccount(entry.account)) {\n return false;\n }\n if (!Array.isArray(entry.accounts) || !entry.accounts.every(isValidAccount)) {\n return false;\n }\n return true;\n};\n\nclass WalletStorage implements WalletPersistence {\n private poolKey: string;\n private selectionKey: string;\n private activeKey: string;\n private userDisconnectedKey: string;\n private persistent: StorageDriver;\n private session: StorageDriver;\n /**\n * Serializes pool-key mutations so concurrent fire-and-forget\n * writes can't interleave their read-modify-write phases. Without\n * this, two simultaneous `setPool` calls both read the pre-write\n * state, each merge their own entries, and whichever finishes last\n * overwrites the other's additions. Reads (`getPool`) don't enter\n * the queue — they observe whatever's currently in the driver.\n */\n private poolMutationQueue: Promise<unknown> = Promise.resolve();\n\n constructor(config: StorageConfig) {\n this.poolKey = `${config.keyPrefix}-pool`;\n this.selectionKey = `${config.keyPrefix}-selection`;\n this.activeKey = `${config.keyPrefix}-active`;\n this.userDisconnectedKey = `${config.keyPrefix}-user-disconnected`;\n\n if (config.persistent && config.session) {\n this.persistent = config.persistent;\n this.session = config.session;\n } else {\n const defaults = createBrowserStorageDriver();\n this.persistent = config.persistent ?? defaults.persistent;\n this.session = config.session ?? defaults.session;\n }\n }\n\n /** Chain `fn` after the in-flight pool mutation. */\n private async serializePoolMutation<T>(fn: () => Promise<T>): Promise<T> {\n // Capture and advance the queue synchronously so concurrent\n // callers serialize against the same head — awaiting the queue\n // first would let two callers both observe the pre-advance head\n // and run in parallel.\n const previous = this.poolMutationQueue;\n // oxlint-disable-next-line unicorn/consistent-function-scoping -- assigned by Promise constructor below\n let resolve: () => void = () => {};\n this.poolMutationQueue = new Promise<void>((r) => {\n resolve = r;\n });\n try {\n await previous;\n } catch {\n // Previous mutation's failure shouldn't jam the queue — each\n // call site already observes its own rejection.\n }\n try {\n return await fn();\n } finally {\n resolve();\n }\n }\n\n // ---- Pool ----\n\n async getPool(): Promise<StoredPoolRecord> {\n try {\n const stored = await this.persistent.getItem(this.poolKey);\n if (!stored) {\n return {};\n }\n const parsed: unknown = JSON.parse(stored);\n if (!parsed || typeof parsed !== \"object\") {\n await this.clearPool();\n return {};\n }\n const result: StoredPoolRecord = {};\n for (const [key, value] of Object.entries(parsed as Record<string, unknown>)) {\n if (isValidPoolEntry(key, value)) {\n result[key] = value;\n } else {\n logWarn(`[butr] dropping invalid pool entry for ${key}`);\n }\n }\n return result;\n } catch (error) {\n logWarn(\"[butr] failed to parse pool from storage:\", error);\n await this.clearPool();\n return {};\n }\n }\n\n /**\n * Upsert the in-memory pool into storage. Additive: entries in\n * `pool` are written; entries already in storage that aren't in\n * `pool` are kept. The in-memory pool reflects \"what's live right\n * now\", not \"the complete list of remembered connections\" — a\n * silent reconnect that fails on reload leaves the entry out of\n * the pool but the saved entry stays so the next load can retry.\n * Use `removePoolEntry` for explicit eviction (the user clicked\n * Disconnect) and `clearAll` for a full wipe (reset).\n */\n setPool(pool: Map<string, ConnectedWallet>): Promise<void> {\n return this.serializePoolMutation(async () => {\n try {\n const existing = await this.getPool();\n const serializable: StoredPoolRecord = { ...existing };\n for (const [connectorId, wallet] of pool) {\n const entry: StoredPoolEntry = {\n account: wallet.account,\n accounts: wallet.accounts,\n chainPlatform: wallet.connector.chainPlatform,\n connectorId,\n icon: wallet.connector.icon,\n name: wallet.connector.name,\n };\n // The runtime's reducer state is the source of truth — a\n // malformed entry here means a programming error inside butr,\n // not data drift. Throw loudly so the bug surfaces at the\n // write site rather than silently corrupting storage and\n // re-emerging as a \"wallet didn't restore\" puzzle on the next\n // page load.\n if (!isValidPoolEntry(connectorId, entry)) {\n throw new Error(`[butr] refusing to persist invalid pool entry for ${connectorId}`);\n }\n serializable[connectorId] = entry;\n }\n await this.persistent.setItem(this.poolKey, JSON.stringify(serializable));\n } catch (error) {\n logWarn(\"[butr] failed to persist pool:\", error);\n }\n });\n }\n\n removePoolEntry(connectorId: string): Promise<void> {\n return this.serializePoolMutation(async () => {\n try {\n const stored = await this.getPool();\n if (stored[connectorId]) {\n const { [connectorId]: _, ...remaining } = stored;\n await this.persistent.setItem(this.poolKey, JSON.stringify(remaining));\n }\n } catch (error) {\n logWarn(`[butr] failed to remove pool entry ${connectorId}:`, error);\n }\n });\n }\n\n clearPool(): Promise<void> {\n return this.serializePoolMutation(async () => {\n await this.persistent.removeItem(this.poolKey);\n });\n }\n\n // ---- Selection ----\n\n async getSelection(): Promise<StoredSelectionRecord> {\n try {\n const stored = await this.persistent.getItem(this.selectionKey);\n if (!stored) {\n return {};\n }\n const parsed: unknown = JSON.parse(stored);\n if (!parsed || typeof parsed !== \"object\") {\n return {};\n }\n const result: StoredSelectionRecord = {};\n for (const [key, value] of Object.entries(parsed as Record<string, unknown>)) {\n if (\n VALID_CHAIN_PLATFORMS.has(key as ChainPlatform) &&\n typeof value === \"string\" &&\n value.length > 0\n ) {\n result[key as ChainPlatform] = value;\n }\n }\n return result;\n } catch (error) {\n logWarn(\"[butr] failed to parse selection from storage:\", error);\n return {};\n }\n }\n\n async setSelection(selection: Map<ChainPlatform, string>): Promise<void> {\n try {\n const serializable: StoredSelectionRecord = {};\n for (const [platform, connectorId] of selection) {\n serializable[platform] = connectorId;\n }\n await this.persistent.setItem(this.selectionKey, JSON.stringify(serializable));\n } catch (error) {\n logWarn(\"[butr] failed to persist selection:\", error);\n }\n }\n\n // ---- Active connector ----\n\n async getActiveConnectorId(): Promise<string | null> {\n try {\n const value = await this.persistent.getItem(this.activeKey);\n return value && value.length > 0 ? value : null;\n } catch {\n return null;\n }\n }\n\n async setActiveConnectorId(connectorId: string | null): Promise<void> {\n try {\n await (connectorId === null\n ? this.persistent.removeItem(this.activeKey)\n : this.persistent.setItem(this.activeKey, connectorId));\n } catch (error) {\n logWarn(\"[butr] failed to persist active connector id:\", error);\n }\n }\n\n // ---- Bulk clear ----\n\n async clearAll(): Promise<void> {\n // Pool removal goes through the mutation queue so it can't race\n // with an in-flight `setPool` / `removePoolEntry`. The selection\n // and active keys have no read-modify-write writers, so they can\n // remove concurrently.\n await Promise.all([\n this.clearPool(),\n this.persistent.removeItem(this.selectionKey),\n this.persistent.removeItem(this.activeKey),\n ]);\n }\n\n // ---- Disconnect intent ----\n\n /**\n * Disconnect-intent tracking.\n *\n * Lives in the session driver: survives component remounts (unlike refs)\n * but clears when the session ends (unlike the persistent driver).\n * Prevents auto-connect from firing immediately after a manual disconnect,\n * while still allowing auto-connect on fresh sessions.\n */\n async isUserDisconnected(): Promise<boolean> {\n try {\n const value = await this.session.getItem(this.userDisconnectedKey);\n return value === \"true\";\n } catch {\n return false;\n }\n }\n\n async markUserDisconnected(value: boolean): Promise<void> {\n try {\n await (value\n ? this.session.setItem(this.userDisconnectedKey, \"true\")\n : this.session.removeItem(this.userDisconnectedKey));\n } catch {\n // ignore\n }\n }\n}\n\nexport { WalletStorage };\n","import { logWarn } from \"../logger\";\nimport type { Account, ChainPlatform, Connector } from \"../types\";\n\n/**\n * Side-effect callbacks the lifecycle bridge invokes when the\n * underlying wallet emits an event. The bridge owns event-to-action\n * mapping and the unsubscribe handle; the runtime owns what those\n * actions actually do (dispatch reducer events, persist, notify\n * consumer callbacks).\n */\ntype LifecycleHandlers = {\n /** Wallet exposed a new accounts list (or active account swap). The\n * bridge forwards the full array — single-account wallets included.\n * `active` is the address the wallet picked as current. */\n onAccountChanged: (\n connectorId: string,\n accounts: ReadonlyArray<Account>,\n active: Account,\n ) => void;\n /** Wallet disconnected externally (user locked, extension removed,\n * WC session ended on the relay). The bridge has already cleared\n * the subscription for `connectorId` by the time this fires. */\n onDisconnected: (connectorId: string, chainPlatform: ChainPlatform) => void;\n};\n\n/**\n * Owns the \"exactly one subscription per connector\" invariant plus the\n * event-to-handler choreography. Replaces three previously-scattered\n * call sites in the runtime (`hydrate`, `tryRestoreFromPending`,\n * `connectWallet`) and the matching teardown sites\n * (`disconnectWallet`, `reset`, the disconnect-event handler).\n */\ntype ConnectorLifecycle = {\n /**\n * Subscribe to the connector's events. Idempotent — calling `attach`\n * twice for the same `connectorId` detaches the previous subscription\n * before installing the new one, preserving the \"exactly one\"\n * invariant. No-op for connectors without a `subscribe` method.\n */\n attach: (connectorId: string, connector: Connector) => void;\n /** Detach a single connector. Safe to call when no subscription is\n * registered. */\n detach: (connectorId: string) => void;\n /** Detach every active subscription. Used during `reset`. */\n detachAll: () => void;\n};\n\nconst createConnectorLifecycle = (handlers: LifecycleHandlers): ConnectorLifecycle => {\n // Private to the bridge — the runtime never reads or writes this Map.\n const unsubscribers = new Map<string, () => void>();\n\n const detach = (connectorId: string) => {\n const unsub = unsubscribers.get(connectorId);\n if (!unsub) {\n return;\n }\n try {\n unsub();\n } catch (error: unknown) {\n logWarn(\"[butr] unsubscribe threw:\", error);\n }\n unsubscribers.delete(connectorId);\n };\n\n return {\n attach: (connectorId, connector) => {\n if (!connector.subscribe) {\n return;\n }\n // Idempotent install — drop any prior subscription first so a\n // re-attach doesn't leak listeners on the wallet side.\n detach(connectorId);\n try {\n const unsub = connector.subscribe((event) => {\n switch (event.type) {\n case \"accountChanged\": {\n handlers.onAccountChanged(connectorId, event.accounts, event.account);\n break;\n }\n case \"disconnected\": {\n // Clear the subscription before notifying so the handler\n // sees a clean state and can't re-enter via dispatch\n // chains.\n detach(connectorId);\n handlers.onDisconnected(connectorId, connector.chainPlatform);\n break;\n }\n default: {\n const exhaustiveCheck: never = event;\n void exhaustiveCheck;\n }\n }\n });\n unsubscribers.set(connectorId, unsub);\n } catch (error: unknown) {\n logWarn(`[butr] subscribe failed for ${connectorId}:`, error);\n }\n },\n\n detach,\n\n detachAll: () => {\n // Snapshot keys before iterating because `detach` mutates the Map.\n const ids: Array<string> = [];\n for (const id of unsubscribers.keys()) {\n ids.push(id);\n }\n for (const id of ids) {\n detach(id);\n }\n },\n };\n};\n\nexport type { ConnectorLifecycle, LifecycleHandlers };\nexport { createConnectorLifecycle };\n","import type { StoredPoolEntry, WalletPersistence } from \"../storage\";\nimport type {\n Account,\n ChainPlatform,\n ConnectedWallet,\n Connector,\n WalletManagerConfig,\n} from \"../types\";\n\n/**\n * Restore outcome for a single pool entry. `kind: \"ok\"` carries the\n * fully-formed `ConnectedWallet`. `kind: \"fail\"` carries the raw error\n * so the caller can route it to telemetry.\n */\ntype RestoreOutcome =\n | { connectorId: string; entry: ConnectedWallet; kind: \"ok\" }\n | { connectorId: string; error: unknown; kind: \"fail\" };\n\n/**\n * Snapshot returned by `HydrationCoordinator.hydrate()` — everything\n * the reducer needs to compose a `HYDRATED` event plus the metadata the\n * runtime uses to wire subscriptions and surface the outcome.\n */\ntype HydrateResult = {\n activeConnectorId: string | null;\n /** Entries whose restore failed (connect() rejected, the connector\n * threw mid-flight). Reported for telemetry only — storage is\n * preserved so the next load can retry. The most common cause is\n * EIP-1193 `eth_accounts: []` from a locked wallet, which clears as\n * soon as the user unlocks and reloads. */\n dropped: Array<{ connectorId: string; reason: unknown }>;\n isUserDisconnected: boolean;\n /** Entries parked because `createConnector(id)` returned `null` at\n * hydration time (typical for Wallet Standard adapters announced\n * asynchronously). The coordinator keeps an internal record of these\n * and resolves them when the runtime later calls `drainPending`. */\n pendingIds: Array<string>;\n pool: Map<string, ConnectedWallet>;\n selection: Map<ChainPlatform, string>;\n};\n\ntype HydrationCoordinator = {\n /**\n * Restore a parked entry whose adapter has now become available.\n * Returns `null` if no entry is parked under `connectorId`. Returns an\n * `ok` outcome with the new `ConnectedWallet`, or a `fail` outcome\n * with the error (storage cleanup is handled internally).\n */\n drainPending(connectorId: string): Promise<RestoreOutcome | null>;\n /**\n * Read storage, instantiate every adapter that's currently\n * registered, restore in parallel, park the rest. After the returned\n * promise resolves, the coordinator holds an internal pending queue\n * indexed by `connectorId`. Resolve queued entries via `drainPending`.\n */\n hydrate(): Promise<HydrateResult>;\n /**\n * Snapshot of currently-parked ids. The runtime uses this once\n * immediately after `hydrate()` resolves to drain entries whose\n * adapter announced *during* the hydration window (the discovery race\n * we used to resolve manually inside the store).\n */\n pendingIds(): Array<string>;\n};\n\n/** Pick the right `accounts` list: fresh from the connector if it\n * exposes `getAccounts`, otherwise the persisted list, otherwise just\n * the active account. */\nconst resolveHydratedAccounts = async (\n connector: Connector,\n storedAccounts: ReadonlyArray<Account>,\n fallbackAccount: Account,\n): Promise<Array<Account>> => {\n if (connector.getAccounts) {\n const fresh = await connector.getAccounts().catch(() => null);\n if (fresh && fresh.length > 0) {\n return fresh;\n }\n }\n if (storedAccounts.length > 0) {\n return [...storedAccounts];\n }\n return [fallbackAccount];\n};\n\nconst restoreOneEntry = async (\n connectorId: string,\n entry: { account: Account; accounts: ReadonlyArray<Account> },\n connector: Connector,\n): Promise<RestoreOutcome> => {\n try {\n // Eager restore: ask the wallet to reconnect to already-authorized\n // accounts without prompting (Wallet Standard `silent`, EIP-1193\n // `eth_accounts`). A wallet that can't do this silently rejects;\n // the entry stays out of the pool for this session but storage is\n // preserved so the next load (e.g. once the user unlocks their\n // wallet) can retry. EIP-1193 `eth_accounts: []` is ambiguous\n // between \"locked\" and \"deauthorized\" — dropping the storage entry\n // on a transient `[]` was the cause of the reload-disconnect race.\n await connector.connect({ silent: true });\n const freshAccount = await connector.getAccount();\n const accountToUse = freshAccount || entry.account;\n const accounts = await resolveHydratedAccounts(connector, entry.accounts, accountToUse);\n return {\n connectorId,\n entry: {\n account: accountToUse,\n accounts,\n connector: connector as ConnectedWallet[\"connector\"],\n },\n kind: \"ok\",\n };\n } catch (error) {\n return { connectorId, error, kind: \"fail\" };\n }\n};\n\nconst createHydrationCoordinator = (\n storage: WalletPersistence,\n createConnector: WalletManagerConfig[\"createConnector\"],\n): HydrationCoordinator => {\n // Storage entries only leave on explicit `disconnectWallet`. Failed\n // restores stay parked / reported as `dropped` — they re-attempt on\n // the next hydrate, so a transient `eth_accounts: []` from a locked\n // wallet doesn't permanently erase the connection.\n //\n // Private to the coordinator — the runtime never touches the queue\n // directly. Storing the raw `StoredPoolEntry` is enough; the entry\n // gets re-restored through the same `restoreOneEntry` path used by\n // the eager pass, so the two code paths can't drift.\n const pending = new Map<string, StoredPoolEntry>();\n\n return {\n drainPending: async (connectorId) => {\n const entry = pending.get(connectorId);\n if (!entry) {\n return null;\n }\n const connector = createConnector(connectorId);\n if (!connector) {\n // Adapter still not available — leave the entry parked.\n return null;\n }\n const outcome = await restoreOneEntry(connectorId, entry, connector);\n pending.delete(connectorId);\n return outcome;\n },\n\n hydrate: async () => {\n const [storedPool, storedSelection, storedActive, userDisconnected] = await Promise.all([\n storage.getPool(),\n storage.getSelection(),\n storage.getActiveConnectorId(),\n storage.isUserDisconnected(),\n ]);\n\n const pool = new Map<string, ConnectedWallet>();\n const dropped: Array<{ connectorId: string; reason: unknown }> = [];\n\n // Clear pending from any prior hydrate() in the same coordinator\n // instance (e.g. tests that rehydrate). The runtime only calls\n // hydrate() once per provider mount, but defending against the\n // multi-call case is cheap.\n pending.clear();\n\n // Build the restore-task list eagerly. A null factory result\n // parks the entry; an instantiated connector enters the parallel\n // restore pool.\n const tasks: Array<Promise<RestoreOutcome>> = [];\n for (const [connectorId, entry] of Object.entries(storedPool)) {\n if (!entry) {\n continue;\n }\n const connector = createConnector(connectorId);\n if (!connector) {\n pending.set(connectorId, entry);\n continue;\n }\n tasks.push(restoreOneEntry(connectorId, entry, connector));\n }\n\n const outcomes = await Promise.all(tasks);\n for (const outcome of outcomes) {\n if (outcome.kind === \"ok\") {\n pool.set(outcome.connectorId, outcome.entry);\n continue;\n }\n dropped.push({ connectorId: outcome.connectorId, reason: outcome.error });\n }\n\n // Reconcile selection: drop stale entries, fill platforms with\n // a pool member but no stored selection.\n const selection = new Map<ChainPlatform, string>();\n for (const [platform, connectorId] of Object.entries(storedSelection)) {\n if (connectorId && pool.has(connectorId)) {\n selection.set(platform as ChainPlatform, connectorId);\n }\n }\n for (const [connectorId, wallet] of pool) {\n const platform = wallet.connector.chainPlatform;\n if (!selection.has(platform)) {\n selection.set(platform, connectorId);\n }\n }\n\n let activeConnectorId: string | null = null;\n if (storedActive && pool.has(storedActive)) {\n activeConnectorId = storedActive;\n } else if (pool.size > 0) {\n activeConnectorId = pool.keys().next().value ?? null;\n }\n\n return {\n activeConnectorId,\n dropped,\n isUserDisconnected: userDisconnected,\n pendingIds: [...pending.keys()],\n pool,\n selection,\n };\n },\n\n pendingIds: () => [...pending.keys()],\n };\n};\n\nexport type { HydrateResult, HydrationCoordinator, RestoreOutcome };\nexport { createHydrationCoordinator };\n","import type { Account, ChainPlatform, ConnectedWallet } from \"../types\";\nimport type { ConnectionError } from \"../types/errors\";\n\n/**\n * Public connection-status enum. `state.connectionStatus` in the\n * reducer only takes the first four values (\"idle\" | \"connecting\" |\n * \"success\" | \"error\") — those track the user-initiated connect\n * attempt. The fifth value, `\"reconnecting\"`, is *derived* by\n * `useConnectionStatus` when the active wallet is still backed by a\n * shadow adapter (its id is in `state.reconnectingIds`). The\n * derivation lives in the React selector so the reducer can stay a\n * narrow state machine while the public API matches wagmi's\n * vocabulary.\n */\ntype ConnectionStatus = \"idle\" | \"connecting\" | \"success\" | \"error\" | \"reconnecting\";\n\ntype State = {\n activeConnectorId: string | null;\n connectingConnectorId: string | null;\n connectionError: ConnectionError | null;\n connectionStatus: ConnectionStatus;\n isHydrated: boolean;\n isUserDisconnected: boolean;\n pool: Map<string, ConnectedWallet>;\n /**\n * Connector IDs whose pool entry is currently backed by a shadow\n * adapter — created from `WalletManagerConfig.initialState` and\n * waiting for the live adapter to be announced (via discovery) and\n * the silent reconnect to succeed. The set shrinks as entries\n * upgrade (`HYDRATED` / `CONNECT_SUCCEEDED` clear matching ids) or\n * drop (`DISCONNECTED` / `RESET`). Empty for stores constructed\n * without `initialState`.\n */\n reconnectingIds: ReadonlySet<string>;\n selection: Map<ChainPlatform, string>;\n};\n\n/**\n * Lifecycle events: hydration, full reset, and the explicit\n * user-disconnected flag the runtime sets to suppress eager reconnect.\n */\ntype LifecycleEvent =\n | {\n activeConnectorId: string | null;\n isUserDisconnected: boolean;\n pool: Map<string, ConnectedWallet>;\n selection: Map<ChainPlatform, string>;\n type: \"HYDRATED\";\n }\n | { type: \"RESET\" }\n | { type: \"USER_DISCONNECTED_SET\"; value: boolean };\n\n/**\n * Connection status events: the transitions of the in-flight connect\n * attempt — start, success/failure, status/error reset. None of these\n * mutate the pool directly; `CONNECT_SUCCEEDED` is the bridge to\n * `PoolMutationEvent`.\n */\ntype ConnectionStatusEvent =\n | { connectorId: string; type: \"CONNECT_STARTED\" }\n | { connectorId: string; entry: ConnectedWallet; type: \"CONNECT_SUCCEEDED\" }\n | { error: ConnectionError; type: \"CONNECT_FAILED\" }\n | { error: ConnectionError | null; type: \"ERROR_SET\" }\n | { type: \"STATUS_RESET\" };\n\n/**\n * Pool-mutation events: changes to the set of connected wallets and\n * their per-wallet account lists. `DISCONNECTED` removes an entry;\n * the refresh events update an existing one in-place.\n */\ntype PoolMutationEvent =\n | {\n /** When set, becomes the new active account. Used by paths that\n * know which account should be active (wallet event, manual\n * update). When unset, the reducer preserves the current active\n * if it's still in `accounts`, else picks `accounts[0]`. */\n accounts: Array<Account>;\n active?: Account;\n connectorId: string;\n type: \"ACCOUNTS_REFRESHED\";\n }\n | { connectorId: string; type: \"DISCONNECTED\" }\n | { connectorId: string; type: \"WALLET_REFRESHED\" };\n\n/**\n * Selection events: which connector is active, and which connector\n * serves each chain platform. Pure pointer updates — no pool mutation.\n */\ntype SelectionEvent =\n | { chainPlatform: ChainPlatform; connectorId: string | null; type: \"SELECTION_CHANGED\" }\n | { connectorId: string | null; type: \"ACTIVE_CHANGED\" };\n\n/**\n * The single event type the reducer dispatches on. Grouped above by\n * concern so a reader sees the map before the cases — adding an event\n * means picking the group it belongs to.\n */\ntype Event = ConnectionStatusEvent | LifecycleEvent | PoolMutationEvent | SelectionEvent;\n\nconst initialState: State = {\n activeConnectorId: null,\n connectingConnectorId: null,\n connectionError: null,\n connectionStatus: \"idle\",\n isHydrated: false,\n isUserDisconnected: false,\n pool: new Map(),\n reconnectingIds: new Set(),\n selection: new Map(),\n};\n\n/** Find any connector in the pool serving a given platform. */\nconst findConnectorForPlatform = (\n pool: Map<string, ConnectedWallet>,\n chainPlatform: ChainPlatform,\n): string | undefined => {\n for (const [id, wallet] of pool) {\n if (wallet.connector.chainPlatform === chainPlatform) {\n return id;\n }\n }\n return undefined;\n};\n\n/** Pure reducer: every state transition is expressed as `(state, event) => state`.\n * No I/O, no async, no side effects. Side effects live in the runtime. */\nconst reducer = (state: State, event: Event): State => {\n switch (event.type) {\n case \"HYDRATED\": {\n // Merge instead of replace. CONNECT_SUCCEEDED can fire during the\n // late-restore window — discovery announces an adapter, the runtime\n // calls tryRestoreFromPending, drainPending succeeds, the reducer\n // adds the entry to state.pool — all BEFORE hydrate's outer await\n // resolves and HYDRATED dispatches. A wholesale replace at that\n // point wipes the freshly-restored late entry. Merging keeps both\n // the eager-restored entries (event.pool) and the late-restored\n // entries (state.pool).\n const pool = new Map<string, ConnectedWallet>(state.pool);\n for (const [id, entry] of event.pool) {\n pool.set(id, entry);\n }\n const selection = new Map<ChainPlatform, string>(state.selection);\n for (const [platform, id] of event.selection) {\n selection.set(platform, id);\n }\n // Prefer hydrate's activeConnectorId when valid in the merged\n // pool (matches the pre-reload selection). Fall back to any late-\n // restore-driven active if hydrate's choice didn't restore eagerly.\n let activeConnectorId: string | null = null;\n if (event.activeConnectorId && pool.has(event.activeConnectorId)) {\n activeConnectorId = event.activeConnectorId;\n } else if (state.activeConnectorId && pool.has(state.activeConnectorId)) {\n activeConnectorId = state.activeConnectorId;\n } else if (pool.size > 0) {\n activeConnectorId = pool.keys().next().value ?? null;\n }\n // Clear reconnectingIds for every entry that came back via the\n // event's live-pool. Shadows that weren't upgraded (adapter not\n // yet announced) stay in `state.pool` and stay in\n // `reconnectingIds`; they'll be cleared individually by\n // CONNECT_SUCCEEDED when discovery's late-restore path resolves.\n let nextReconnecting: ReadonlySet<string> = state.reconnectingIds;\n if (state.reconnectingIds.size > 0 && event.pool.size > 0) {\n const next = new Set(state.reconnectingIds);\n for (const id of event.pool.keys()) {\n next.delete(id);\n }\n if (next.size !== state.reconnectingIds.size) {\n nextReconnecting = next;\n }\n }\n return {\n ...state,\n activeConnectorId,\n isHydrated: true,\n isUserDisconnected: event.isUserDisconnected,\n pool,\n reconnectingIds: nextReconnecting,\n selection,\n };\n }\n\n case \"USER_DISCONNECTED_SET\": {\n return { ...state, isUserDisconnected: event.value };\n }\n\n case \"CONNECT_STARTED\": {\n return {\n ...state,\n connectingConnectorId: event.connectorId,\n connectionError: null,\n connectionStatus: \"connecting\",\n };\n }\n\n case \"CONNECT_SUCCEEDED\": {\n const { connectorId, entry } = event;\n const existing = state.pool.get(connectorId);\n\n // Drop the id from reconnectingIds when present. Covers both the\n // late-restore path (discovery announced after the initial seed)\n // and ordinary connects — the latter typically have an empty set\n // so this is a no-op for them.\n const nextReconnecting: ReadonlySet<string> = state.reconnectingIds.has(connectorId)\n ? new Set([...state.reconnectingIds].filter((id) => id !== connectorId))\n : state.reconnectingIds;\n\n // Same connector + same address: only flip the status, keep object refs stable\n // so reactive selectors don't churn.\n if (\n existing &&\n existing.account.walletAddress.toLowerCase() === entry.account.walletAddress.toLowerCase()\n ) {\n return {\n ...state,\n activeConnectorId: connectorId,\n connectingConnectorId: null,\n connectionStatus: \"success\",\n pool:\n // When the existing entry was a shadow, the address may\n // match but the connector reference is the placeholder.\n // Swap in the live entry so subsequent calls don't throw\n // ShadowConnectorError.\n existing.connector === entry.connector\n ? state.pool\n : new Map([...state.pool, [connectorId, entry] as const]),\n reconnectingIds: nextReconnecting,\n };\n }\n\n const newPool = new Map([...state.pool, [connectorId, entry] as const]);\n const newSelection = new Map([\n ...state.selection,\n [entry.connector.chainPlatform, connectorId] as const,\n ]);\n\n return {\n ...state,\n activeConnectorId: connectorId,\n connectingConnectorId: null,\n connectionStatus: \"success\",\n pool: newPool,\n reconnectingIds: nextReconnecting,\n selection: newSelection,\n };\n }\n\n case \"CONNECT_FAILED\": {\n return {\n ...state,\n connectingConnectorId: null,\n connectionError: event.error,\n connectionStatus: \"error\",\n };\n }\n\n case \"DISCONNECTED\": {\n const wallet = state.pool.get(event.connectorId);\n if (!wallet) {\n return state;\n }\n\n const newPool = new Map(state.pool);\n newPool.delete(event.connectorId);\n\n const newSelection = new Map(state.selection);\n const platform = wallet.connector.chainPlatform;\n if (newSelection.get(platform) === event.connectorId) {\n newSelection.delete(platform);\n const fallback = findConnectorForPlatform(newPool, platform);\n if (fallback) {\n newSelection.set(platform, fallback);\n }\n }\n\n const firstId = newPool.keys().next();\n const activeConnectorId =\n state.activeConnectorId === event.connectorId\n ? (firstId.value ?? null)\n : state.activeConnectorId;\n\n const nextReconnecting: ReadonlySet<string> = state.reconnectingIds.has(event.connectorId)\n ? new Set([...state.reconnectingIds].filter((id) => id !== event.connectorId))\n : state.reconnectingIds;\n\n return {\n ...state,\n activeConnectorId,\n pool: newPool,\n reconnectingIds: nextReconnecting,\n selection: newSelection,\n };\n }\n\n case \"ACCOUNTS_REFRESHED\": {\n const wallet = state.pool.get(event.connectorId);\n if (!wallet) {\n return state;\n }\n // Pick the new active address. Three policies in priority order:\n // 1. Explicit `event.active` (wallet event or manual update knows\n // what should be active).\n // 2. Preserve current active if it's still in the new list.\n // 3. Fall back to `accounts[0]` so the pool entry doesn't end up\n // pointing at an address the wallet no longer exposes.\n let nextAccount: Account;\n if (event.active) {\n nextAccount = event.active;\n } else {\n const stillHasCurrent = event.accounts.some(\n (a) =>\n a.walletAddress === wallet.account.walletAddress &&\n a.chain.id === wallet.account.chain.id,\n );\n nextAccount = stillHasCurrent ? wallet.account : (event.accounts[0] ?? wallet.account);\n }\n // No-op short-circuit: when the wallet event echoes the current\n // active and the accounts array hasn't changed shape, skip the\n // Map clone so `useSyncExternalStore` subscribers don't re-render.\n // (Cheap heuristic — equality by reference for `accounts` would\n // miss content changes; we compare by length + address set.)\n const sameAccount =\n nextAccount.walletAddress === wallet.account.walletAddress &&\n nextAccount.chain.id === wallet.account.chain.id;\n const sameAccountsShape =\n event.accounts.length === wallet.accounts.length &&\n event.accounts.every((a, i) => a.walletAddress === wallet.accounts[i]?.walletAddress);\n if (sameAccount && sameAccountsShape) {\n return state;\n }\n const updated: ConnectedWallet = {\n ...wallet,\n account: nextAccount,\n accounts: [...event.accounts],\n };\n const newPool = new Map([...state.pool, [event.connectorId, updated] as const]);\n return { ...state, pool: newPool };\n }\n\n case \"WALLET_REFRESHED\": {\n const wallet = state.pool.get(event.connectorId);\n if (!wallet) {\n return state;\n }\n const newPool = new Map([...state.pool, [event.connectorId, { ...wallet }] as const]);\n return { ...state, pool: newPool };\n }\n\n case \"ACTIVE_CHANGED\": {\n return { ...state, activeConnectorId: event.connectorId };\n }\n\n case \"SELECTION_CHANGED\": {\n const newSelection = new Map(state.selection);\n if (event.connectorId === null) {\n newSelection.delete(event.chainPlatform);\n } else {\n newSelection.set(event.chainPlatform, event.connectorId);\n }\n return { ...state, selection: newSelection };\n }\n\n case \"STATUS_RESET\": {\n return {\n ...state,\n connectingConnectorId: null,\n connectionError: null,\n connectionStatus: \"idle\",\n };\n }\n\n case \"ERROR_SET\": {\n return {\n ...state,\n connectionError: event.error,\n connectionStatus: event.error ? \"error\" : \"idle\",\n };\n }\n\n case \"RESET\": {\n return {\n ...initialState,\n isHydrated: state.isHydrated,\n isUserDisconnected: true,\n reconnectingIds: new Set(),\n };\n }\n\n default: {\n // Exhaustiveness check — TS errors if a new event type is added without a case.\n const exhaustiveCheck: never = event;\n void exhaustiveCheck;\n return state;\n }\n }\n};\n\nexport type {\n ConnectionStatus,\n ConnectionStatusEvent,\n Event,\n LifecycleEvent,\n PoolMutationEvent,\n SelectionEvent,\n State,\n};\nexport { initialState, reducer };\n","import type { StoredPoolEntry } from \"../storage/persistence\";\nimport type {\n BitcoinAdapter,\n EvmAdapter,\n SuiAdapter,\n SvmAdapter,\n WalletAdapter,\n WalletCapabilities,\n} from \"../types\";\n\n/**\n * Error thrown when a method is called on a shadow adapter — the\n * placeholder `WalletAdapter` that the store seeds into the pool when\n * an `initialState` is provided (e.g. from a server-rendered cookie\n * snapshot). Shadow adapters carry the identity and account data of a\n * previously-connected wallet, but the live wallet extension hasn't\n * been verified yet — the silent reconnect happens asynchronously\n * after mount.\n *\n * UI code that gates affordances on `wallet.connector.capabilities.*`\n * never reaches a shadow method (capabilities are all `false`). Code\n * that calls through anyway hits this typed error, which is the\n * correct loud failure: the consumer ignored the capability gate.\n *\n * Consumers wanting to wait out the reconnecting window should branch\n * on whether `connectorId` is in `state.reconnectingIds`.\n */\nclass ShadowConnectorError extends Error {\n // Stable programmatic identifier — consumers branch on `.code` to\n // distinguish this from generic connector errors. Read via\n // `toMatchObject({ code: \"BUTR_RECONNECTING\" })` in tests, which\n // static analysis can't trace.\n // fallow-ignore-next-line unused-class-member\n readonly code = \"BUTR_RECONNECTING\";\n readonly connectorId: string;\n readonly method: string;\n constructor(method: string, connectorId: string) {\n super(\n `[butr] ${method} called on shadow connector \"${connectorId}\" — wait for silent reconnect to complete (check reconnectingIds.has(id) before calling).`,\n );\n this.name = \"ShadowConnectorError\";\n this.connectorId = connectorId;\n this.method = method;\n }\n}\n\nconst ALL_FALSE_CAPABILITIES: WalletCapabilities = Object.freeze({\n getBalance: false,\n getTransactionReceipt: false,\n requestAccounts: false,\n sendTransaction: false,\n signIn: false,\n signMessage: false,\n signTransaction: false,\n subscribe: false,\n switchAccount: false,\n switchChain: false,\n});\n\n/**\n * Builds a placeholder `WalletAdapter` from a persisted pool entry.\n *\n * Used by `createWalletStore` when `WalletManagerConfig.initialState`\n * is provided: each stored entry becomes a `ConnectedWallet` whose\n * `connector` is one of these. The store flips `isHydrated` true\n * synchronously and exposes the data through the usual hooks\n * (`useActiveWallet`, `useConnectedWallets`, …) — but the connector\n * can't actually talk to a wallet yet, so its capabilities are all\n * `false` and its methods throw `ShadowConnectorError` if called.\n *\n * The hydration coordinator's silent-reconnect pass upgrades each\n * shadow to a live `WalletAdapter` by calling `createConnector(id)`\n * (which only succeeds once the live adapter has been announced via\n * discovery or registered explicitly) and replacing the pool entry.\n * On success the connector id is removed from `reconnectingIds`; on\n * failure the entry is dropped from the pool and storage.\n *\n * The entry's `name` is required (the storage validator rejects\n * entries without it); `icon` is optional only because some live\n * adapters genuinely have no icon to begin with.\n */\nconst createShadowAdapter = (entry: StoredPoolEntry): WalletAdapter => {\n const id = entry.connectorId;\n const name = entry.name;\n const icon = entry.icon;\n\n const reject = (method: string): Promise<never> =>\n Promise.reject(new ShadowConnectorError(method, id));\n\n // Shape that satisfies the intersection of `Connector<P>` and\n // `WalletBase`. Platform-specific optional methods (`signIn`,\n // `signTransaction`) are intentionally omitted — they don't exist on\n // a shadow, and their absence is a valid runtime state for the\n // per-platform adapter types (which mark them `?:`).\n const base = {\n capabilities: ALL_FALSE_CAPABILITIES,\n connect: () => reject(\"connect\"),\n disconnect: () => reject(\"disconnect\"),\n getAccount: () => reject(\"getAccount\"),\n getBalance: () => reject(\"getBalance\"),\n getSigner: () => reject(\"getSigner\"),\n getTransactionReceipt: () => reject(\"getTransactionReceipt\"),\n icon,\n id,\n name,\n sendTx: () => reject(\"sendTx\"),\n sendTxToChain: () => reject(\"sendTxToChain\"),\n signMessage: () => reject(\"signMessage\"),\n switchChain: () => reject(\"switchChain\"),\n };\n\n // Discriminated-union return: the runtime `chainPlatform` value\n // determines which variant we hand back. The cast is required\n // because TypeScript can't narrow the literal `chainPlatform` field\n // through a spread.\n switch (entry.chainPlatform) {\n case \"bitcoin\": {\n return { ...base, chainPlatform: \"bitcoin\" } as BitcoinAdapter;\n }\n case \"evm\": {\n return { ...base, chainPlatform: \"evm\" } as EvmAdapter;\n }\n case \"sui\": {\n return { ...base, chainPlatform: \"sui\" } as SuiAdapter;\n }\n case \"svm\": {\n return { ...base, chainPlatform: \"svm\" } as SvmAdapter;\n }\n default: {\n // Exhaustive — TS errors if a ChainPlatform variant is added.\n const _exhaustive: never = entry.chainPlatform;\n void _exhaustive;\n throw new Error(`[butr] unknown chainPlatform: ${entry.chainPlatform as string}`);\n }\n }\n};\n\n/**\n * Type guard. Returns true when an adapter is a placeholder created\n * by `createShadowAdapter`. Useful for the hydration coordinator\n * (which needs to know which pool entries still need upgrading) and\n * for consumers writing wagmi-style \"is this connection verified yet\"\n * checks without subscribing to `reconnectingIds` directly.\n *\n * Detection is structural: a shadow has all capabilities set to false.\n * Live adapters always advertise at least one capability (every wallet\n * surface includes `getBalance`, `signMessage`, `switchChain` as\n * required methods, and adapter constructors set their flags\n * accordingly).\n */\nconst isShadowAdapter = (adapter: WalletAdapter): boolean => {\n const caps = adapter.capabilities;\n return (\n !caps.getBalance &&\n !caps.getTransactionReceipt &&\n !caps.requestAccounts &&\n !caps.sendTransaction &&\n !caps.signMessage &&\n !caps.subscribe &&\n !caps.switchChain\n );\n};\n\nexport { createShadowAdapter, isShadowAdapter, ShadowConnectorError };\n","/** Run an async operation fire-and-forget, calling onError if it throws. */\nconst run = async (fn: () => Promise<void>, onError: (e: unknown) => void): Promise<void> => {\n try {\n await fn();\n } catch (error) {\n onError(error);\n }\n};\n\nexport { run };\n","import { createStore } from \"zustand/vanilla\";\n\nimport { logError, logWarn } from \"../logger\";\nimport { WalletStorage } from \"../storage\";\nimport type { WalletPersistence, WalletSnapshot } from \"../storage\";\nimport type {\n Account,\n ChainPlatform,\n ConnectedWallet,\n WalletAdapter,\n WalletManagerConfig,\n} from \"../types\";\nimport type { ConnectionError } from \"../types/errors\";\nimport { mapConnectionError } from \"../types/errors\";\n\nimport { createConnectorLifecycle } from \"./connector-lifecycle\";\nimport { createHydrationCoordinator } from \"./hydration\";\nimport { type Event, type State, initialState, reducer } from \"./reducer\";\nimport { createShadowAdapter } from \"./shadow-adapter\";\nimport { run } from \"./wallet-store-helpers\";\n\n/**\n * Build a synchronously-populated `State` from `config.initialState`.\n * Each pool entry becomes a `ConnectedWallet` whose `connector` is a\n * shadow adapter; every connector id enters `reconnectingIds` so\n * consumers can branch on \"is this connection verified\" without\n * waiting for the async silent reconnect. `isHydrated` flips true\n * synchronously — the consumer's first render sees the persisted\n * state in the live store, not undefined.\n */\nconst seedStateFromSnapshot = (snapshot: WalletSnapshot): State => {\n const pool = new Map<string, ConnectedWallet>();\n const reconnectingIds = new Set<string>();\n for (const [connectorId, entry] of Object.entries(snapshot.pool)) {\n if (!entry) {\n continue;\n }\n const connector: WalletAdapter = createShadowAdapter(entry);\n pool.set(connectorId, {\n account: entry.account,\n accounts: [...entry.accounts],\n connector,\n });\n reconnectingIds.add(connectorId);\n }\n const selection = new Map<ChainPlatform, string>();\n for (const [platform, connectorId] of Object.entries(snapshot.selection)) {\n if (connectorId && pool.has(connectorId)) {\n selection.set(platform as ChainPlatform, connectorId);\n }\n }\n const activeConnectorId =\n snapshot.activeConnectorId && pool.has(snapshot.activeConnectorId)\n ? snapshot.activeConnectorId\n : (pool.keys().next().value ?? null);\n return {\n ...initialState,\n activeConnectorId,\n isHydrated: true,\n pool,\n reconnectingIds,\n selection,\n };\n};\n\nconst CONNECT_TIMEOUT_MS = 90_000;\nconst DEFAULT_SLOW_CONNECT_THRESHOLD_MS = 5000;\n\nconst normaliseAddress = (addr: string): string => addr.toLowerCase();\n\ntype ExtractState<S> = S extends { getState: () => infer T } ? T : never;\n\ntype RuntimeMembers = {\n _config: WalletManagerConfig;\n _storage: WalletPersistence;\n connectWallet: (\n connectorId: string,\n onSuccess?: (wallet: ConnectedWallet) => void,\n onError?: (error: Error) => void,\n ) => Promise<void>;\n disconnectWallet: (connectorId: string) => void;\n getConnectorInstance: (id: string) => ReturnType<WalletManagerConfig[\"createConnector\"]>;\n hydrateWallets: () => Promise<void>;\n refreshWallet: (connectorId: string) => void;\n requestAccounts: (connectorId: string) => Promise<void>;\n reset: () => void;\n resetConnectionStatus: () => void;\n setActiveConnector: (connectorId: string | null) => void;\n setConnectionError: (error: ConnectionError | null) => void;\n setSelection: (chainPlatform: ChainPlatform, connectorId: string | null) => void;\n setUserDisconnected: (value: boolean) => void;\n tryRestoreFromPending: (connectorId: string) => Promise<void>;\n updateWalletAccount: (connectorId: string, account: Account) => void;\n};\n\ntype WalletStore = ReturnType<typeof createWalletStore>;\ntype WalletStoreState = ExtractState<WalletStore>;\n\nconst createWalletStore = (config: WalletManagerConfig) => {\n const storageKeyPrefix = config.storageKeyPrefix || \"butr\";\n const storage = config.storage || new WalletStorage({ keyPrefix: storageKeyPrefix });\n\n // Storage-error handler. Default: `console.warn` (preserves the\n // pre-`onStorageError` behaviour). Consumer-provided callback routes\n // failures through their telemetry pipeline.\n const reportStorageError = (context: string) => (error: unknown) => {\n if (config.onStorageError) {\n try {\n config.onStorageError(error, context);\n } catch (cbError: unknown) {\n logWarn(\"[butr] onStorageError threw:\", cbError);\n }\n return;\n }\n logWarn(`[butr] ${context}:`, error);\n };\n\n // Owns the full restore lifecycle: read storage, instantiate adapters,\n // restore in parallel, park entries whose adapter wasn't registered\n // yet, drain parked entries when discovery announces a matching id.\n // `pendingRestores` lives inside the coordinator — the runtime no\n // longer manages the queue directly.\n const hydration = createHydrationCoordinator(storage, config.createConnector);\n\n return createStore<State & RuntimeMembers>()((set, get) => {\n const dispatch = (event: Event) => {\n set((prev) => reducer(prev, event), false);\n };\n\n // Each persister returns the storage write's Promise so call\n // sites that need a \"storage is now durable\" guarantee (e.g.\n // `connectWallet`, `disconnectWallet`) can await before\n // resolving. Wallet-event-driven helpers (`refreshPoolEntry`)\n // still fire-and-forget — the order of resolution for those\n // doesn't matter because `WalletStorage` serializes pool\n // mutations through an internal queue.\n const persistPool = (): Promise<void> =>\n run(() => storage.setPool(get().pool), reportStorageError(\"failed to persist pool\"));\n\n const persistSelection = (): Promise<void> =>\n run(\n () => storage.setSelection(get().selection),\n reportStorageError(\"failed to persist selection\"),\n );\n\n const persistActive = (): Promise<void> =>\n run(\n () => storage.setActiveConnectorId(get().activeConnectorId),\n reportStorageError(\"failed to persist active connector\"),\n );\n\n // Single entry point for \"the pool entry's exposed accounts\n // changed.\" Three call sites use it: the wallet-event bridge\n // (`accountChanged`), the consumer-facing `updateWalletAccount`\n // action, and the `requestAccounts` action. `active` is set when\n // the caller knows which address is now active (wallet event,\n // manual update); omitted when the caller just refreshed the\n // list and wants the reducer's preserve-or-fallback logic.\n const refreshPoolEntry = (connectorId: string, accounts: Array<Account>, active?: Account) => {\n dispatch({ accounts, active, connectorId, type: \"ACCOUNTS_REFRESHED\" });\n void persistPool();\n };\n\n // Owns the \"exactly one subscription per connector\" invariant,\n // the event-to-handler choreography, and the unsubscribers Map.\n // Attach is idempotent: the runtime calls it after every\n // restore / connect without first checking whether a prior\n // subscription exists.\n const lifecycle = createConnectorLifecycle({\n onAccountChanged: (connectorId, accounts, active) => {\n // Full-replace the accounts array so the pool entry mirrors\n // the wallet's current exposure. Drops the previously-active\n // address on single-account wallets (Phantom EVM/SVM,\n // MetaMask Snap); preserves the multi-account list on\n // MetaMask, Rabby, etc. The wallet tells us which is active.\n refreshPoolEntry(connectorId, [...accounts], active);\n },\n onDisconnected: (connectorId, chainPlatform) => {\n // Mirror an external disconnect into the reducer so the UI\n // hides the wallet, but DON'T touch storage — EIP-1193 emits\n // `accountsChanged: []` (which we forward as a `disconnected`\n // event) both when the user revokes permissions AND when\n // MetaMask simply auto-locks. The two are indistinguishable\n // from our side, so persisting the empty pool would erase the\n // saved connection on every wallet lock. Keeping storage\n // intact lets the next hydrate retry — the user-locked case\n // self-heals once they unlock and reload, and a genuine\n // revocation gets cleaned up by the next call to\n // `disconnectWallet` (or just lingers as dead bytes).\n dispatch({ connectorId, type: \"DISCONNECTED\" });\n config.onDisconnect?.(chainPlatform);\n },\n });\n\n const seeded: State = config.initialState\n ? seedStateFromSnapshot(config.initialState)\n : initialState;\n\n return {\n ...seeded,\n _config: config,\n _storage: storage,\n connectWallet: async (connectorId, onSuccess, onError) => {\n get().setUserDisconnected(false);\n\n const connector = config.createConnector(connectorId);\n if (!connector) {\n throw new Error(`Failed to create connector for ${connectorId}`);\n }\n\n dispatch({ connectorId, type: \"CONNECT_STARTED\" });\n\n // Slow-connect telemetry. Fires once if the wallet doesn't\n // resolve within the configured threshold. Cleared in finally.\n const slowThreshold = config.slowConnectThresholdMs ?? DEFAULT_SLOW_CONNECT_THRESHOLD_MS;\n const slowTimer = config.onSlowConnect\n ? setTimeout(() => {\n try {\n config.onSlowConnect?.(connectorId);\n } catch (cbError: unknown) {\n logWarn(\"[butr] onSlowConnect threw:\", cbError);\n }\n }, slowThreshold)\n : null;\n\n try {\n const connectPromise = connector.connect();\n // oxlint-disable-next-line promise/prefer-await-to-then -- suppress unhandled rejection; we await via Promise.race below\n void connectPromise.catch(() => {});\n await Promise.race([\n connectPromise,\n new Promise((_, reject) => {\n setTimeout(() => {\n reject(new Error(\"Connection timeout\"));\n }, CONNECT_TIMEOUT_MS);\n }),\n ]);\n\n const account = await connector.getAccount();\n if (!account) {\n throw new Error(\"Failed to get account\");\n }\n\n // Pull all known accounts when the connector supports it.\n const fetchedAccounts = connector.getAccounts\n ? await connector.getAccounts().catch(() => null)\n : null;\n const accounts =\n fetchedAccounts && fetchedAccounts.length > 0 ? fetchedAccounts : [account];\n\n const entry: ConnectedWallet = { account, accounts, connector };\n dispatch({ connectorId, entry, type: \"CONNECT_SUCCEEDED\" });\n\n lifecycle.attach(connectorId, connector);\n\n // Await so callers can `await connectWallet(id)` and trust\n // that storage reflects the new connection on the very next\n // line — load `useConnectedWallets` snapshots, expect\n // localStorage to contain the entry, etc.\n await Promise.all([persistPool(), persistSelection(), persistActive()]);\n\n config.onConnect?.(entry);\n onSuccess?.(entry);\n } catch (error) {\n const normalised = mapConnectionError(error);\n dispatch({ error: normalised, type: \"CONNECT_FAILED\" });\n try {\n await connector.disconnect?.();\n } catch (disconnectError: unknown) {\n logWarn(\"[butr] disconnect during error recovery failed:\", disconnectError);\n }\n try {\n config.onConnectError?.(normalised, connectorId);\n } catch (cbError: unknown) {\n logWarn(\"[butr] onConnectError threw:\", cbError);\n }\n onError?.(error as Error);\n throw error;\n } finally {\n if (slowTimer) {\n clearTimeout(slowTimer);\n }\n }\n },\n\n disconnectWallet: (connectorId) => {\n get().setUserDisconnected(true);\n\n const state = get();\n if (!state.isHydrated) {\n return;\n }\n const wallet = state.pool.get(connectorId);\n if (!wallet) {\n return;\n }\n\n lifecycle.detach(connectorId);\n\n void run(() => wallet.connector.disconnect?.() ?? Promise.resolve(), logError);\n\n const platform = wallet.connector.chainPlatform;\n dispatch({ connectorId, type: \"DISCONNECTED\" });\n\n if (get().pool.size === 0) {\n void run(() => storage.clearAll(), reportStorageError(\"failed to clear storage\"));\n } else {\n // Explicit eviction so storage doesn't keep retrying the\n // disconnected wallet on the next load. `persistPool` is\n // additive (see `WalletStorage.setPool`), so we can't rely\n // on it to remove entries — that's what `removePoolEntry`\n // is for.\n void run(\n () => storage.removePoolEntry(connectorId),\n reportStorageError(\"failed to remove pool entry\"),\n );\n void persistSelection();\n void persistActive();\n }\n\n config.onDisconnect?.(platform);\n },\n\n getConnectorInstance: (id) => config.createConnector(id),\n\n hydrateWallets: async () => {\n const result = await hydration.hydrate();\n dispatch({\n activeConnectorId: result.activeConnectorId,\n isUserDisconnected: result.isUserDisconnected,\n pool: result.pool,\n selection: result.selection,\n type: \"HYDRATED\",\n });\n // Wire up wallet-side event subscriptions for every restored\n // connector so account/chain swaps after a refresh keep the\n // reducer in sync without consumer effort.\n for (const [connectorId, wallet] of result.pool) {\n lifecycle.attach(connectorId, wallet.connector);\n }\n // Persist any reconciled values back so future loads stay consistent.\n await Promise.all([persistSelection(), persistActive()]);\n // Drain: catch the race where a Wallet Standard adapter\n // announced BEFORE hydration finished populating its queue.\n // Without this, the discovery callback's\n // `tryRestoreFromPending(adapter.id)` would have hit the\n // empty queue and silently returned, leaving SVM wallets\n // un-restored on reload. The coordinator's `pendingIds()`\n // is the post-hydrate snapshot; each id resolves\n // independently. Late-restore failures surface per-entry\n // inside `tryRestoreFromPending`.\n void Promise.all(hydration.pendingIds().map((id) => get().tryRestoreFromPending(id)));\n // Surface the hydration outcome. Three buckets: restored,\n // pending (waiting for adapter announcement), dropped\n // (genuine restore failures). The default `console.warn`\n // for dropped entries is replaced by this callback when\n // set — consumers route to telemetry / UX hints.\n if (config.onHydrated) {\n try {\n config.onHydrated({\n dropped: result.dropped,\n pendingIds: [...result.pendingIds],\n restoredIds: [...result.pool.keys()],\n });\n } catch (cbError: unknown) {\n logWarn(\"[butr] onHydrated threw:\", cbError);\n }\n } else if (result.dropped.length > 0) {\n // Preserve the pre-`onHydrated` console.warn behaviour\n // when no callback is set, so silent drops still log.\n for (const { connectorId, reason } of result.dropped) {\n logWarn(`[butr] failed to restore connector ${connectorId}:`, reason);\n }\n }\n },\n refreshWallet: (connectorId) => {\n dispatch({ connectorId, type: \"WALLET_REFRESHED\" });\n },\n\n requestAccounts: async (connectorId) => {\n const entry = get().pool.get(connectorId);\n if (!entry) {\n return;\n }\n // Ask the wallet to open its picker (if supported). EVM\n // wallets honour this via `wallet_requestPermissions`;\n // Wallet Standard wallets typically have no equivalent\n // RPC, so the call resolves immediately and the refresh\n // below simply re-reads the current list.\n await entry.connector.requestAccounts?.();\n const fresh = entry.connector.getAccounts\n ? await entry.connector.getAccounts().catch(() => null)\n : null;\n if (!fresh || fresh.length === 0) {\n return;\n }\n // No `active` — the reducer preserves the current active\n // if it's still in the refreshed list, else picks [0].\n refreshPoolEntry(connectorId, fresh);\n },\n\n reset: () => {\n get().setUserDisconnected(true);\n\n const state = get();\n if (!state.isHydrated) {\n return;\n }\n\n lifecycle.detachAll();\n\n for (const wallet of state.pool.values()) {\n void run(() => wallet.connector.disconnect?.() ?? Promise.resolve(), logError);\n }\n\n void run(() => storage.clearAll(), reportStorageError(\"failed to clear storage\"));\n\n if (config.onReset) {\n const onReset = config.onReset;\n void run(() => Promise.resolve(onReset()), logError);\n }\n\n dispatch({ type: \"RESET\" });\n },\n\n resetConnectionStatus: () => {\n dispatch({ type: \"STATUS_RESET\" });\n },\n\n setActiveConnector: (connectorId) => {\n dispatch({ connectorId, type: \"ACTIVE_CHANGED\" });\n void persistActive();\n },\n\n setConnectionError: (error) => {\n dispatch({ error, type: \"ERROR_SET\" });\n },\n\n setSelection: (chainPlatform, connectorId) => {\n dispatch({ chainPlatform, connectorId, type: \"SELECTION_CHANGED\" });\n void persistSelection();\n },\n\n setUserDisconnected: (value: boolean) => {\n dispatch({ type: \"USER_DISCONNECTED_SET\", value });\n void run(\n () => storage.markUserDisconnected(value),\n reportStorageError(\"failed to persist disconnect intent\"),\n );\n },\n\n tryRestoreFromPending: async (connectorId) => {\n // Note: deliberately NOT gating on `isUserDisconnected`.\n // The eager hydration path doesn't check that flag either —\n // stored entries restore on reload regardless of whether\n // the user's most recent action was a disconnect. The\n // late-restore path mirrors that policy for symmetry.\n const outcome = await hydration.drainPending(connectorId);\n if (!outcome) {\n return;\n }\n if (outcome.kind === \"fail\") {\n logWarn(`[butr] late restore failed for ${connectorId}:`, outcome.error);\n return;\n }\n // Reuse the reducer's connect-success path so selection /\n // active reconciliation matches a normal connect flow.\n dispatch({ connectorId, entry: outcome.entry, type: \"CONNECT_SUCCEEDED\" });\n lifecycle.attach(connectorId, outcome.entry.connector);\n await Promise.all([persistPool(), persistSelection(), persistActive()]);\n config.onConnect?.(outcome.entry);\n },\n\n updateWalletAccount: (connectorId, account) => {\n // Manual variant of the wallet-event path. Builds the\n // next accounts list (dedupe-by-address + chain refresh\n // since chain is wallet-level) and routes through\n // ACCOUNTS_REFRESHED so there's one reducer case for\n // \"the wallet's exposure changed.\"\n const entry = get().pool.get(connectorId);\n if (!entry) {\n return;\n }\n const newChain = account.chain;\n const remapped = entry.accounts.map((a) =>\n a.chain.id === newChain.id\n ? a\n : {\n chain: newChain,\n id: `${newChain.id}:${normaliseAddress(a.walletAddress)}`,\n walletAddress: a.walletAddress,\n },\n );\n const seen = remapped.some(\n (a) => normaliseAddress(a.walletAddress) === normaliseAddress(account.walletAddress),\n );\n const accounts = seen ? remapped : [...remapped, account];\n refreshPoolEntry(connectorId, accounts, account);\n },\n };\n });\n};\n\nexport type { WalletStore, WalletStoreState };\nexport { createWalletStore };\n","import type { ConnectedWallet } from \"./types\";\n\n/**\n * Two wallet snapshots are equivalent for selector purposes iff they\n * share connectorId, active account address, and active account chain\n * id. Used by `useStoreWithEqualityFn` consumers (active-wallet,\n * selected-wallet, useWalletEntry) to suppress spurious re-renders\n * when the underlying Map churns but the resolved entry hasn't changed.\n *\n * Hoisted to its own module so the equivalence rule lives in one place\n * — if we ever extend the snapshot (e.g. to consider `accounts.length`),\n * every selector hook picks up the new rule for free.\n */\nconst walletEqual = (a: ConnectedWallet | undefined, b: ConnectedWallet | undefined): boolean => {\n if (a === b) {\n return true;\n }\n if (!a || !b) {\n return false;\n }\n return (\n a.connector.id === b.connector.id &&\n a.account.walletAddress === b.account.walletAddress &&\n a.account.chain.id === b.account.chain.id\n );\n};\n\nexport { walletEqual };\n","/**\n * Normalize a wallet-announced icon string.\n *\n * Wallets announce their icon through external metadata — EIP-6963\n * `providerInfo.icon`, Wallet Standard `wallet.icon`. That value is\n * not under butr's control, and some wallets ship data-URI icons with\n * surrounding whitespace (a newline left over from a pretty-printed\n * manifest). Strict consumers reject it: Next.js's `<Image>` throws\n * because `src` must not start with a control character.\n *\n * Trims surrounding whitespace and treats an all-whitespace (or empty)\n * icon as absent, so consumers get either a usable string or\n * `undefined` — never a blank or malformed one. `undefined` passes\n * through untouched.\n */\nconst sanitizeIcon = (icon: string | undefined): string | undefined => {\n if (icon === undefined) {\n return undefined;\n }\n const trimmed = icon.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n};\n\nexport { sanitizeIcon };\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["toCookieMap","VALID_CHAIN_PLATFORMS","isValidAccount","isValidPoolEntry"],"sources":["../src/types/chains-by-platform.ts","../src/types/errors.ts","../src/wallet-source.ts","../src/logger.ts","../src/storage/browser-storage-driver.ts","../src/storage/cookie-storage-driver.ts","../src/storage/snapshot.ts","../src/storage/wallet-storage.ts","../src/store/connector-lifecycle.ts","../src/store/hydration.ts","../src/store/reducer.ts","../src/store/shadow-adapter.ts","../src/store/wallet-store-helpers.ts","../src/store/wallet-store.ts","../src/wallet-equal.ts","../src/sanitize-icon.ts"],"sourcesContent":["import type { ChainBase } from \"./chain\";\nimport type { ChainPlatform } from \"./wallet\";\n\n/**\n * Map of every chain platform to the list of chains the consumer wants\n * to expose to its UI (chain switcher, picker, etc).\n *\n * The platform key set is fixed by `ChainPlatform`. The value is the\n * chain list — empty when the consumer doesn't want to support that\n * platform's chains in this view. This is the only type that callers\n * write down; the values come from per-platform packages\n * (`EVM_CHAINS_LIST`, `SVM_CHAINS_LIST`, etc).\n */\ntype ChainsByPlatform = Readonly<Record<ChainPlatform, ReadonlyArray<ChainBase>>>;\n\n/**\n * Build a fully-populated `ChainsByPlatform` from a partial. Platforms\n * the consumer doesn't specify default to an empty list.\n *\n * Use this in apps that target one or two chain platforms — importing\n * only those packages keeps unused chain registries out of the bundle.\n * Apps that want every chain reach for `CHAINS_BY_PLATFORM` from\n * `@usebutr/wallets` instead.\n *\n * @example\n * // EVM-only app — Solana/Sui/Bitcoin tables never enter the bundle\n * import { EVM_CHAINS_LIST } from \"@usebutr/evm\";\n * import { buildChainsByPlatform } from \"@usebutr/core\";\n *\n * const chains = buildChainsByPlatform({ evm: EVM_CHAINS_LIST });\n *\n * @example\n * // Multi-chain app — pull from each package the app actually uses\n * import { EVM_CHAINS_LIST } from \"@usebutr/evm\";\n * import { SVM_CHAINS_LIST } from \"@usebutr/svm\";\n *\n * const chains = buildChainsByPlatform({\n * evm: EVM_CHAINS_LIST,\n * svm: SVM_CHAINS_LIST,\n * });\n */\nconst buildChainsByPlatform = (partial: Partial<ChainsByPlatform>): ChainsByPlatform => ({\n bitcoin: partial.bitcoin ?? [],\n evm: partial.evm ?? [],\n polkadot: partial.polkadot ?? [],\n sui: partial.sui ?? [],\n svm: partial.svm ?? [],\n});\n\nexport type { ChainsByPlatform };\nexport { buildChainsByPlatform };\n","/**\n * Tagged union of normalised connection errors.\n *\n * butr maps thrown values from connectors (which vary across wallet SDKs:\n * MetaMask uses EIP-1193 codes, Phantom throws stringly-typed errors,\n * embedded SDKs throw their own classes) into a small set of UX-meaningful\n * variants. Consumers branch on `kind` instead of regexing message strings.\n *\n * `message` is always present and human-readable. `cause` preserves the\n * original thrown value so callers can inspect raw connector errors when\n * the variant is `Unknown`.\n */\ntype ConnectionError =\n | { kind: \"UserRejected\"; message: string }\n | { kind: \"RequestPending\"; message: string }\n | { kind: \"WalletLocked\"; message: string }\n | { actualChain?: string; expectedChain?: string; kind: \"ChainMismatch\"; message: string }\n | { kind: \"NotConnected\"; message: string }\n | { kind: \"Timeout\"; message: string }\n | { cause?: unknown; kind: \"Unknown\"; message: string };\n\ntype ConnectionErrorKind = ConnectionError[\"kind\"];\n\n/**\n * Normalise a thrown value into a `ConnectionError`.\n *\n * Recognises:\n * - butr's own `Error(\"Connection timeout\")` (from the 90s connect timeout)\n * - butr's own `Error(\"Failed to get account\")` (from the connect flow)\n * - EIP-1193 numeric `code` properties (`4001` → UserRejected,\n * `-32002` → RequestPending, `4100`/`4900`/`4901` → NotConnected —\n * unauthorized / disconnected from all-or-one chains)\n * - common message substrings: \"user rejected\" / \"user denied\",\n * \"locked\", \"chain\", etc.\n * - anything else → `Unknown` with `cause` set to the original value.\n */\nconst mapConnectionError = (raw: unknown): ConnectionError => {\n if (raw instanceof Error) {\n const message = raw.message;\n const lower = message.toLowerCase();\n\n if (message === \"Connection timeout\") {\n return { kind: \"Timeout\", message };\n }\n if (message === \"Failed to get account\" || lower.includes(\"not connected\")) {\n return { kind: \"NotConnected\", message };\n }\n\n const code = (raw as { code?: unknown }).code;\n if (code === 4001) {\n return { kind: \"UserRejected\", message };\n }\n if (code === -32_002) {\n return { kind: \"RequestPending\", message };\n }\n // EIP-1193: 4100 unauthorized, 4900 disconnected (all chains),\n // 4901 disconnected (requested chain). All mean \"no usable session\"\n // from butr's perspective → NotConnected.\n if (code === 4100 || code === 4900 || code === 4901) {\n return { kind: \"NotConnected\", message };\n }\n\n if (lower.includes(\"user rejected\") || lower.includes(\"user denied\")) {\n return { kind: \"UserRejected\", message };\n }\n if (lower.includes(\"locked\")) {\n return { kind: \"WalletLocked\", message };\n }\n if (lower.includes(\"chain\") && (lower.includes(\"mismatch\") || lower.includes(\"unsupported\"))) {\n return { kind: \"ChainMismatch\", message };\n }\n\n return { cause: raw, kind: \"Unknown\", message };\n }\n\n if (typeof raw === \"string\") {\n return { kind: \"Unknown\", message: raw };\n }\n\n return { cause: raw, kind: \"Unknown\", message: \"Connection failed\" };\n};\n\nexport type { ConnectionError, ConnectionErrorKind };\nexport { mapConnectionError };\n","import type { WalletAdapter } from \"./types\";\n\n/**\n * A discovery seam. Implementations call `onAdapter(adapter)` each time\n * they find a wallet and return an unsubscribe handle. `@usebutr/wallets`\n * composes EVM + SVM into a single `WalletSource`; third parties can\n * implement this type without depending on `@usebutr/wallets`.\n */\ntype WalletSource = {\n subscribe(onAdapter: (adapter: WalletAdapter) => void): () => void;\n};\n\n/**\n * Wrap a bare `subscribe` function (the exact shape of\n * `discoverEvmAdapters` / `discoverSvmAdapters`) into a `WalletSource`,\n * so an EVM-only app can do\n * `createWalletSource(discoverEvmAdapters)` without importing anything\n * protocol-bearing beyond `@usebutr/evm`.\n */\nconst createWalletSource = (\n subscribe: (onAdapter: (adapter: WalletAdapter) => void) => () => void,\n): WalletSource => ({ subscribe });\n\nexport type { WalletSource };\nexport { createWalletSource };\n","/* eslint-disable no-console -- central logging sink; the one sanctioned console use */\nconst logWarn = (...args: ReadonlyArray<unknown>): void => {\n console.warn(...args);\n};\n\nconst logError = (...args: ReadonlyArray<unknown>): void => {\n console.error(...args);\n};\n/* eslint-enable no-console */\n\nexport { logError, logWarn };\n","import type { StorageDriver } from \"./persistence\";\n\ntype BrowserStorageDrivers = {\n /** Survives app restart (localStorage on web, AsyncStorage/MMKV on RN). */\n persistent: StorageDriver;\n /** Cleared when the app session ends (sessionStorage on web, in-memory on RN). */\n session: StorageDriver;\n};\n\ntype WebStorageKind = \"localStorage\" | \"sessionStorage\";\n\nconst hasWebStorage = (kind: WebStorageKind): boolean => {\n try {\n return (\n typeof globalThis !== \"undefined\" &&\n (globalThis as Record<string, unknown>)[kind] !== undefined\n );\n } catch {\n return false;\n }\n};\n\nconst createMemoryStorageDriver = (): StorageDriver => {\n const memory = new Map<string, string>();\n return {\n getItem(key) {\n return memory.get(key) ?? null;\n },\n removeItem(key) {\n memory.delete(key);\n },\n setItem(key, value) {\n memory.set(key, value);\n },\n };\n};\n\nconst createWebStorageDriver = (kind: WebStorageKind): StorageDriver => {\n if (!hasWebStorage(kind)) {\n return createMemoryStorageDriver();\n }\n\n const storage = (globalThis as unknown as Record<WebStorageKind, Storage>)[kind];\n\n return {\n getItem(key) {\n try {\n return storage.getItem(key);\n } catch {\n return null;\n }\n },\n removeItem(key) {\n try {\n storage.removeItem(key);\n } catch {\n // ignore\n }\n },\n setItem(key, value) {\n try {\n storage.setItem(key, value);\n } catch {\n // ignore quota/security errors\n }\n },\n };\n};\n\n/**\n * Default storage drivers for web. `persistent` wraps `localStorage`,\n * `session` wraps `sessionStorage`. Both fall back to in-memory maps\n * when the global isn't available (SSR, non-browser environments).\n */\nconst createBrowserStorageDriver = (): BrowserStorageDrivers => ({\n persistent: createWebStorageDriver(\"localStorage\"),\n session: createWebStorageDriver(\"sessionStorage\"),\n});\n\nexport type { BrowserStorageDrivers };\nexport { createBrowserStorageDriver, createMemoryStorageDriver };\n","import type { StorageDriver } from \"./persistence\";\n\ntype InitialCookies = Iterable<[string, string]> | Readonly<Record<string, string>>;\n\ntype CookieDriverOptions = {\n /**\n * Cookie `domain` attribute. Omit to scope to the current host.\n * Set to a parent domain (e.g. `.example.com`) to share state\n * across subdomains.\n */\n domain?: string;\n /**\n * Snapshot of cookies for the SSR pass — typically the result of\n * Next.js' `cookies()` (from `next/headers`) or a parsed\n * `req.headers.cookie`. Used only when `document` is unavailable.\n *\n * When provided, `getItem` reads from this snapshot during the\n * server render so the store sees the same persisted state the\n * client will see after hydration. Writes remain no-ops on the\n * server — emitting `Set-Cookie` has to be done by the framework\n * layer that owns the response.\n */\n initialCookies?: InitialCookies;\n /**\n * Lifetime in seconds. Defaults to 30 days. Pass `undefined`\n * (the default) for \"until the session ends\" — but note that\n * butr persists wallet state across sessions, so a finite\n * max-age is usually what consumers want.\n */\n maxAgeSeconds?: number;\n /**\n * URL path the cookie applies to. Defaults to `/` (whole origin).\n */\n path?: string;\n /**\n * `SameSite` attribute. Defaults to `\"lax\"` — restrictive enough\n * to avoid CSRF on cross-origin POSTs, permissive enough for\n * top-level navigations.\n */\n sameSite?: \"lax\" | \"strict\" | \"none\";\n /**\n * `Secure` attribute. Defaults to `true` (cookies only sent over\n * HTTPS). Force `false` for local development on plain `http://`.\n */\n secure?: boolean;\n};\n\nconst DEFAULT_MAX_AGE_SECONDS = 60 * 60 * 24 * 30;\n\nconst encode = (value: string): string => encodeURIComponent(value);\nconst decode = (value: string): string => decodeURIComponent(value);\n\nconst readCookies = (): Map<string, string> => {\n const out = new Map<string, string>();\n if (typeof document === \"undefined\") {\n return out;\n }\n const cookieString = document.cookie;\n if (!cookieString) {\n return out;\n }\n for (const segment of cookieString.split(\";\")) {\n const trimmed = segment.trim();\n if (!trimmed) {\n continue;\n }\n const eq = trimmed.indexOf(\"=\");\n if (eq === -1) {\n continue;\n }\n const name = trimmed.slice(0, eq);\n const raw = trimmed.slice(eq + 1);\n try {\n out.set(name, decode(raw));\n } catch {\n // Malformed entry; skip rather than crash hydration.\n }\n }\n return out;\n};\n\nconst buildCookieString = (\n name: string,\n value: string,\n options: CookieDriverOptions,\n expired = false,\n): string => {\n const parts: Array<string> = [`${name}=${encode(value)}`, `Path=${options.path ?? \"/\"}`];\n if (options.domain) {\n parts.push(`Domain=${options.domain}`);\n }\n if (expired) {\n parts.push(\"Max-Age=0\");\n } else {\n const maxAge = options.maxAgeSeconds ?? DEFAULT_MAX_AGE_SECONDS;\n parts.push(`Max-Age=${maxAge}`);\n }\n parts.push(`SameSite=${options.sameSite ?? \"lax\"}`);\n if (options.secure ?? true) {\n parts.push(\"Secure\");\n }\n return parts.join(\"; \");\n};\n\nconst toCookieMap = (input: InitialCookies | undefined): Map<string, string> | null => {\n if (!input) {\n return null;\n }\n if (typeof (input as Iterable<[string, string]>)[Symbol.iterator] === \"function\") {\n return new Map(input as Iterable<[string, string]>);\n }\n return new Map(Object.entries(input as Readonly<Record<string, string>>));\n};\n\n/**\n * Cookie-backed storage driver. Reads/writes `document.cookie` —\n * server-readable, survives reloads, scoped per `domain`/`path`.\n *\n * **When to use this:** SSR apps that need to know who's connected\n * during the server render (so they can stream the connected-wallet\n * UI without a client-side hydration flicker). Pass `initialCookies`\n * from a server-side cookie source (e.g. `cookies()` in Next.js'\n * `next/headers`, or a parsed `req.headers.cookie`) and the same\n * driver will serve those values during the server render and switch\n * to `document.cookie` once it mounts in the browser.\n *\n * **Trade-offs vs `localStorage`:** cookies travel with every\n * request, so they cost bytes on the wire. Keep the storage key\n * prefix short, and prefer this driver for the `persistent` slot\n * only — the `session` slot can stay in `sessionStorage` (which\n * cookies can't natively model anyway).\n *\n * **Server-side writes are no-ops.** Emitting `Set-Cookie` requires\n * access to the framework's response object, which a storage driver\n * shouldn't reach into. The store doesn't mutate persisted state\n * during the SSR pass anyway — writes only fire after client mount,\n * once `document.cookie` is reachable.\n */\nconst createCookieStorageDriver = (options: CookieDriverOptions = {}): StorageDriver => {\n const seeded = toCookieMap(options.initialCookies);\n\n if (typeof document === \"undefined\") {\n if (!seeded) {\n return {\n getItem() {\n return null;\n },\n removeItem() {\n // server-side no-op\n },\n setItem() {\n // server-side no-op\n },\n };\n }\n return {\n getItem(key) {\n return seeded.get(key) ?? null;\n },\n removeItem() {\n // server-side no-op — framework owns Set-Cookie\n },\n setItem() {\n // server-side no-op — framework owns Set-Cookie\n },\n };\n }\n return {\n getItem(key) {\n return readCookies().get(key) ?? null;\n },\n removeItem(key) {\n // eslint-disable-next-line unicorn/no-document-cookie -- cookie storage driver is the document.cookie abstraction by design\n document.cookie = buildCookieString(key, \"\", options, true);\n },\n setItem(key, value) {\n // eslint-disable-next-line unicorn/no-document-cookie -- cookie storage driver is the document.cookie abstraction by design\n document.cookie = buildCookieString(key, value, options);\n },\n };\n};\n\nexport type { CookieDriverOptions, InitialCookies };\nexport { createCookieStorageDriver };\n","import { logWarn } from \"../logger\";\nimport type { ChainPlatform } from \"../types\";\n\nimport type { StoredPoolEntry, StoredPoolRecord, StoredSelectionRecord } from \"./persistence\";\n\nconst VALID_CHAIN_PLATFORMS = new Set<ChainPlatform>([\"evm\", \"svm\", \"sui\", \"bitcoin\"]);\n\n/**\n * Server-safe view of a butr-persisted session — everything you can\n * know about a user's connected wallets from the cookie payload alone,\n * without instantiating a `Connector`.\n *\n * Notably absent: the `Connector` instance. A wallet extension exists\n * only in the browser, so a server render can know *which* wallet was\n * connected and *what address* it held, but cannot dispatch\n * `signMessage`/`sendTransaction` on it. Splitting display from action\n * along this seam keeps the impossibility expressed in the types\n * rather than hidden inside a runtime check.\n */\ntype WalletSnapshot = {\n activeConnectorId: string | null;\n pool: StoredPoolRecord;\n selection: StoredSelectionRecord;\n};\n\ntype CookieSource =\n | Iterable<{ name: string; value: string }>\n | Iterable<[string, string]>\n | Readonly<Record<string, string>>;\n\ntype SnapshotOptions = {\n /**\n * Same prefix passed to `WalletManagerProvider` / `WalletStorage`.\n * Defaults to `\"butr\"` to match the library default.\n */\n keyPrefix?: string;\n};\n\nconst EMPTY_SNAPSHOT: WalletSnapshot = Object.freeze({\n activeConnectorId: null,\n pool: Object.freeze({}) as StoredPoolRecord,\n selection: Object.freeze({}) as StoredSelectionRecord,\n});\n\nconst toCookieMap = (input: CookieSource): Map<string, string> => {\n // Plain record: `{ name: \"value\", ... }`. Distinguished from\n // iterables by the absence of a `Symbol.iterator` callable.\n const maybeIterator = (input as { [Symbol.iterator]?: unknown })[Symbol.iterator];\n if (typeof maybeIterator !== \"function\") {\n return new Map(Object.entries(input as Readonly<Record<string, string>>));\n }\n const out = new Map<string, string>();\n for (const entry of input as Iterable<{ name: string; value: string } | [string, string]>) {\n if (Array.isArray(entry)) {\n const [name, value] = entry;\n if (typeof name === \"string\" && typeof value === \"string\") {\n out.set(name, value);\n }\n continue;\n }\n if (\n entry &&\n typeof entry === \"object\" &&\n \"name\" in entry &&\n \"value\" in entry &&\n typeof entry.name === \"string\" &&\n typeof entry.value === \"string\"\n ) {\n out.set(entry.name, entry.value);\n }\n }\n return out;\n};\n\nconst isValidAccount = (value: unknown): boolean => {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const account = value as Record<string, unknown>;\n if (typeof account.walletAddress !== \"string\" || typeof account.id !== \"string\") {\n return false;\n }\n if (!account.chain || typeof account.chain !== \"object\") {\n return false;\n }\n return true;\n};\n\nconst isValidPoolEntry = (key: string, value: unknown): value is StoredPoolEntry => {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const entry = value as Record<string, unknown>;\n if (typeof entry.connectorId !== \"string\" || entry.connectorId !== key) {\n return false;\n }\n if (typeof entry.chainPlatform !== \"string\") {\n return false;\n }\n if (!VALID_CHAIN_PLATFORMS.has(entry.chainPlatform as ChainPlatform)) {\n return false;\n }\n if (typeof entry.name !== \"string\" || entry.name.length === 0) {\n return false;\n }\n if (entry.icon !== undefined && typeof entry.icon !== \"string\") {\n return false;\n }\n if (!isValidAccount(entry.account)) {\n return false;\n }\n if (!Array.isArray(entry.accounts) || !entry.accounts.every(isValidAccount)) {\n return false;\n }\n return true;\n};\n\nconst parsePool = (raw: string | undefined): StoredPoolRecord => {\n if (!raw) {\n return {};\n }\n try {\n const parsed: unknown = JSON.parse(raw);\n if (!parsed || typeof parsed !== \"object\") {\n return {};\n }\n const result: StoredPoolRecord = {};\n for (const [key, value] of Object.entries(parsed as Record<string, unknown>)) {\n if (isValidPoolEntry(key, value)) {\n result[key] = value;\n } else {\n logWarn(`[butr] readWalletSnapshot: dropping invalid pool entry for ${key}`);\n }\n }\n return result;\n } catch (error) {\n logWarn(\"[butr] readWalletSnapshot: failed to parse pool cookie:\", error);\n return {};\n }\n};\n\nconst parseSelection = (raw: string | undefined): StoredSelectionRecord => {\n if (!raw) {\n return {};\n }\n try {\n const parsed: unknown = JSON.parse(raw);\n if (!parsed || typeof parsed !== \"object\") {\n return {};\n }\n const result: StoredSelectionRecord = {};\n for (const [key, value] of Object.entries(parsed as Record<string, unknown>)) {\n if (\n VALID_CHAIN_PLATFORMS.has(key as ChainPlatform) &&\n typeof value === \"string\" &&\n value.length > 0\n ) {\n result[key as ChainPlatform] = value;\n }\n }\n return result;\n } catch (error) {\n logWarn(\"[butr] readWalletSnapshot: failed to parse selection cookie:\", error);\n return {};\n }\n};\n\n/**\n * Parse a cookie source into a server-safe `WalletSnapshot`.\n *\n * Pure, sync, no `document`, no React — runnable in any environment\n * (Server Component, route handler, edge middleware, even client\n * code). Pair with `createCookieStorageDriver({ initialCookies })`\n * and `<WalletManagerProvider initialSnapshot={…} />` to render a\n * connected shell server-side without a hydration flash.\n *\n * **Stale-snapshot semantics.** The snapshot reflects whatever the\n * browser most recently persisted. If the user has since uninstalled\n * the wallet, switched accounts, or disconnected in another tab, the\n * client-side hydration will reconcile reality and the live store\n * will diverge from the snapshot. Treat the snapshot as an\n * *optimistic* shell — accurate enough to avoid a paint flicker,\n * authoritative only after `useIsHydrated()` is true.\n *\n * **Inputs.** Accepts the three shapes Next.js / Express / Hono /\n * generic-Node cookie code naturally produces:\n * - A plain object: `{ \"butr-pool\": \"{...}\", … }`\n * - An array of `{ name, value }` (Next.js' `cookies().getAll()`)\n * - An iterable of `[name, value]` tuples\n *\n * Malformed entries are dropped with a `logWarn` (same policy as\n * `WalletStorage.getPool`) — a cross-tab corruption shouldn't crash\n * the server render.\n */\nconst readWalletSnapshot = (\n source: CookieSource,\n options: SnapshotOptions = {},\n): WalletSnapshot => {\n const keyPrefix = options.keyPrefix || \"butr\";\n const cookies = toCookieMap(source);\n\n const pool = parsePool(cookies.get(`${keyPrefix}-pool`));\n const selection = parseSelection(cookies.get(`${keyPrefix}-selection`));\n\n const rawActive = cookies.get(`${keyPrefix}-active`);\n let activeConnectorId: string | null = null;\n if (rawActive && rawActive.length > 0 && pool[rawActive]) {\n activeConnectorId = rawActive;\n } else {\n // Mirror `HydrationCoordinator.hydrate` selection-fallback: when no\n // explicit active is stored, surface any pool member so the render\n // shell can still show \"connected\" instead of \"disconnected\".\n const firstKey = Object.keys(pool)[0];\n if (firstKey) {\n activeConnectorId = firstKey;\n }\n }\n\n return { activeConnectorId, pool, selection };\n};\n\nexport type { CookieSource, SnapshotOptions, WalletSnapshot };\nexport { EMPTY_SNAPSHOT, readWalletSnapshot };\n","import { logWarn } from \"../logger\";\nimport type { ChainPlatform, ConnectedWallet } from \"../types\";\n\nimport { createBrowserStorageDriver } from \"./browser-storage-driver\";\nimport type {\n StorageDriver,\n StoredPoolEntry,\n StoredPoolRecord,\n StoredSelectionRecord,\n WalletPersistence,\n} from \"./persistence\";\n\nconst VALID_CHAIN_PLATFORMS = new Set<ChainPlatform>([\"evm\", \"svm\", \"sui\", \"bitcoin\"]);\n\ntype StorageConfig = {\n keyPrefix: string;\n /** Survives app restart. Defaults to localStorage on web. */\n persistent?: StorageDriver;\n /** Cleared on session end. Defaults to sessionStorage on web. */\n session?: StorageDriver;\n};\n\nconst isValidAccount = (value: unknown): boolean => {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const account = value as Record<string, unknown>;\n if (typeof account.walletAddress !== \"string\" || typeof account.id !== \"string\") {\n return false;\n }\n if (!account.chain || typeof account.chain !== \"object\") {\n return false;\n }\n return true;\n};\n\n/**\n * Structural validator for a serialized pool entry.\n *\n * Used in two directions:\n * - On read (`getPool`): malformed entries from storage are warned\n * and dropped — legacy / cross-tab corruption shouldn't crash the\n * consumer.\n * - On write (`setPool`): the runtime's reducer state is the source\n * of truth, and a malformed write would indicate a programming\n * error inside butr. Throw rather than silently corrupt storage.\n *\n * Same validator either way — keeping the two paths symmetric means\n * \"what's storable\" is a single fact.\n */\nconst isValidPoolEntry = (key: string, value: unknown): value is StoredPoolEntry => {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const entry = value as Record<string, unknown>;\n if (typeof entry.connectorId !== \"string\" || entry.connectorId !== key) {\n return false;\n }\n if (typeof entry.chainPlatform !== \"string\") {\n return false;\n }\n if (!VALID_CHAIN_PLATFORMS.has(entry.chainPlatform as ChainPlatform)) {\n return false;\n }\n if (typeof entry.name !== \"string\" || entry.name.length === 0) {\n return false;\n }\n // `icon` is optional (some adapters announce no icon); reject only\n // when present-but-wrong-typed.\n if (entry.icon !== undefined && typeof entry.icon !== \"string\") {\n return false;\n }\n if (!isValidAccount(entry.account)) {\n return false;\n }\n if (!Array.isArray(entry.accounts) || !entry.accounts.every(isValidAccount)) {\n return false;\n }\n return true;\n};\n\nclass WalletStorage implements WalletPersistence {\n private poolKey: string;\n private selectionKey: string;\n private activeKey: string;\n private userDisconnectedKey: string;\n private persistent: StorageDriver;\n private session: StorageDriver;\n /**\n * Serializes pool-key mutations so concurrent fire-and-forget\n * writes can't interleave their read-modify-write phases. Without\n * this, two simultaneous `setPool` calls both read the pre-write\n * state, each merge their own entries, and whichever finishes last\n * overwrites the other's additions. Reads (`getPool`) don't enter\n * the queue — they observe whatever's currently in the driver.\n */\n private poolMutationQueue: Promise<unknown> = Promise.resolve();\n\n constructor(config: StorageConfig) {\n this.poolKey = `${config.keyPrefix}-pool`;\n this.selectionKey = `${config.keyPrefix}-selection`;\n this.activeKey = `${config.keyPrefix}-active`;\n this.userDisconnectedKey = `${config.keyPrefix}-user-disconnected`;\n\n if (config.persistent && config.session) {\n this.persistent = config.persistent;\n this.session = config.session;\n } else {\n const defaults = createBrowserStorageDriver();\n this.persistent = config.persistent ?? defaults.persistent;\n this.session = config.session ?? defaults.session;\n }\n }\n\n /** Chain `fn` after the in-flight pool mutation. */\n private async serializePoolMutation<T>(fn: () => Promise<T>): Promise<T> {\n // Capture and advance the queue synchronously so concurrent\n // callers serialize against the same head — awaiting the queue\n // first would let two callers both observe the pre-advance head\n // and run in parallel.\n const previous = this.poolMutationQueue;\n // oxlint-disable-next-line unicorn/consistent-function-scoping -- assigned by Promise constructor below\n let resolve: () => void = () => {};\n this.poolMutationQueue = new Promise<void>((r) => {\n resolve = r;\n });\n try {\n await previous;\n } catch {\n // Previous mutation's failure shouldn't jam the queue — each\n // call site already observes its own rejection.\n }\n try {\n return await fn();\n } finally {\n resolve();\n }\n }\n\n // ---- Pool ----\n\n async getPool(): Promise<StoredPoolRecord> {\n try {\n const stored = await this.persistent.getItem(this.poolKey);\n if (!stored) {\n return {};\n }\n const parsed: unknown = JSON.parse(stored);\n if (!parsed || typeof parsed !== \"object\") {\n await this.clearPool();\n return {};\n }\n const result: StoredPoolRecord = {};\n for (const [key, value] of Object.entries(parsed as Record<string, unknown>)) {\n if (isValidPoolEntry(key, value)) {\n result[key] = value;\n } else {\n logWarn(`[butr] dropping invalid pool entry for ${key}`);\n }\n }\n return result;\n } catch (error) {\n logWarn(\"[butr] failed to parse pool from storage:\", error);\n await this.clearPool();\n return {};\n }\n }\n\n /**\n * Upsert the in-memory pool into storage. Additive: entries in\n * `pool` are written; entries already in storage that aren't in\n * `pool` are kept. The in-memory pool reflects \"what's live right\n * now\", not \"the complete list of remembered connections\" — a\n * silent reconnect that fails on reload leaves the entry out of\n * the pool but the saved entry stays so the next load can retry.\n * Use `removePoolEntry` for explicit eviction (the user clicked\n * Disconnect) and `clearAll` for a full wipe (reset).\n */\n setPool(pool: Map<string, ConnectedWallet>): Promise<void> {\n return this.serializePoolMutation(async () => {\n try {\n const existing = await this.getPool();\n const serializable: StoredPoolRecord = { ...existing };\n for (const [connectorId, wallet] of pool) {\n const entry: StoredPoolEntry = {\n account: wallet.account,\n accounts: wallet.accounts,\n chainPlatform: wallet.connector.chainPlatform,\n connectorId,\n icon: wallet.connector.icon,\n name: wallet.connector.name,\n };\n // The runtime's reducer state is the source of truth — a\n // malformed entry here means a programming error inside butr,\n // not data drift. Throw loudly so the bug surfaces at the\n // write site rather than silently corrupting storage and\n // re-emerging as a \"wallet didn't restore\" puzzle on the next\n // page load.\n if (!isValidPoolEntry(connectorId, entry)) {\n throw new Error(`[butr] refusing to persist invalid pool entry for ${connectorId}`);\n }\n serializable[connectorId] = entry;\n }\n await this.persistent.setItem(this.poolKey, JSON.stringify(serializable));\n } catch (error) {\n logWarn(\"[butr] failed to persist pool:\", error);\n }\n });\n }\n\n removePoolEntry(connectorId: string): Promise<void> {\n return this.serializePoolMutation(async () => {\n try {\n const stored = await this.getPool();\n if (stored[connectorId]) {\n const { [connectorId]: _, ...remaining } = stored;\n await this.persistent.setItem(this.poolKey, JSON.stringify(remaining));\n }\n } catch (error) {\n logWarn(`[butr] failed to remove pool entry ${connectorId}:`, error);\n }\n });\n }\n\n clearPool(): Promise<void> {\n return this.serializePoolMutation(async () => {\n await this.persistent.removeItem(this.poolKey);\n });\n }\n\n // ---- Selection ----\n\n async getSelection(): Promise<StoredSelectionRecord> {\n try {\n const stored = await this.persistent.getItem(this.selectionKey);\n if (!stored) {\n return {};\n }\n const parsed: unknown = JSON.parse(stored);\n if (!parsed || typeof parsed !== \"object\") {\n return {};\n }\n const result: StoredSelectionRecord = {};\n for (const [key, value] of Object.entries(parsed as Record<string, unknown>)) {\n if (\n VALID_CHAIN_PLATFORMS.has(key as ChainPlatform) &&\n typeof value === \"string\" &&\n value.length > 0\n ) {\n result[key as ChainPlatform] = value;\n }\n }\n return result;\n } catch (error) {\n logWarn(\"[butr] failed to parse selection from storage:\", error);\n return {};\n }\n }\n\n async setSelection(selection: Map<ChainPlatform, string>): Promise<void> {\n try {\n const serializable: StoredSelectionRecord = {};\n for (const [platform, connectorId] of selection) {\n serializable[platform] = connectorId;\n }\n await this.persistent.setItem(this.selectionKey, JSON.stringify(serializable));\n } catch (error) {\n logWarn(\"[butr] failed to persist selection:\", error);\n }\n }\n\n // ---- Active connector ----\n\n async getActiveConnectorId(): Promise<string | null> {\n try {\n const value = await this.persistent.getItem(this.activeKey);\n return value && value.length > 0 ? value : null;\n } catch {\n return null;\n }\n }\n\n async setActiveConnectorId(connectorId: string | null): Promise<void> {\n try {\n await (connectorId === null\n ? this.persistent.removeItem(this.activeKey)\n : this.persistent.setItem(this.activeKey, connectorId));\n } catch (error) {\n logWarn(\"[butr] failed to persist active connector id:\", error);\n }\n }\n\n // ---- Bulk clear ----\n\n async clearAll(): Promise<void> {\n // Pool removal goes through the mutation queue so it can't race\n // with an in-flight `setPool` / `removePoolEntry`. The selection\n // and active keys have no read-modify-write writers, so they can\n // remove concurrently.\n await Promise.all([\n this.clearPool(),\n this.persistent.removeItem(this.selectionKey),\n this.persistent.removeItem(this.activeKey),\n ]);\n }\n\n // ---- Disconnect intent ----\n\n /**\n * Disconnect-intent tracking.\n *\n * Lives in the session driver: survives component remounts (unlike refs)\n * but clears when the session ends (unlike the persistent driver).\n * Prevents auto-connect from firing immediately after a manual disconnect,\n * while still allowing auto-connect on fresh sessions.\n */\n async isUserDisconnected(): Promise<boolean> {\n try {\n const value = await this.session.getItem(this.userDisconnectedKey);\n return value === \"true\";\n } catch {\n return false;\n }\n }\n\n async markUserDisconnected(value: boolean): Promise<void> {\n try {\n await (value\n ? this.session.setItem(this.userDisconnectedKey, \"true\")\n : this.session.removeItem(this.userDisconnectedKey));\n } catch {\n // ignore\n }\n }\n}\n\nexport { WalletStorage };\n","import { logWarn } from \"../logger\";\nimport type { Account, ChainPlatform, Connector } from \"../types\";\n\n/**\n * Side-effect callbacks the lifecycle bridge invokes when the\n * underlying wallet emits an event. The bridge owns event-to-action\n * mapping and the unsubscribe handle; the runtime owns what those\n * actions actually do (dispatch reducer events, persist, notify\n * consumer callbacks).\n */\ntype LifecycleHandlers = {\n /** Wallet exposed a new accounts list (or active account swap). The\n * bridge forwards the full array — single-account wallets included.\n * `active` is the address the wallet picked as current. */\n onAccountChanged: (\n connectorId: string,\n accounts: ReadonlyArray<Account>,\n active: Account,\n ) => void;\n /** Wallet disconnected externally (user locked, extension removed,\n * WC session ended on the relay). The bridge has already cleared\n * the subscription for `connectorId` by the time this fires. */\n onDisconnected: (connectorId: string, chainPlatform: ChainPlatform) => void;\n};\n\n/**\n * Owns the \"exactly one subscription per connector\" invariant plus the\n * event-to-handler choreography. Replaces three previously-scattered\n * call sites in the runtime (`hydrate`, `tryRestoreFromPending`,\n * `connectWallet`) and the matching teardown sites\n * (`disconnectWallet`, `reset`, the disconnect-event handler).\n */\ntype ConnectorLifecycle = {\n /**\n * Subscribe to the connector's events. Idempotent — calling `attach`\n * twice for the same `connectorId` detaches the previous subscription\n * before installing the new one, preserving the \"exactly one\"\n * invariant. No-op for connectors without a `subscribe` method.\n */\n attach: (connectorId: string, connector: Connector) => void;\n /** Detach a single connector. Safe to call when no subscription is\n * registered. */\n detach: (connectorId: string) => void;\n /** Detach every active subscription. Used during `reset`. */\n detachAll: () => void;\n};\n\nconst createConnectorLifecycle = (handlers: LifecycleHandlers): ConnectorLifecycle => {\n // Private to the bridge — the runtime never reads or writes this Map.\n const unsubscribers = new Map<string, () => void>();\n\n const detach = (connectorId: string) => {\n const unsub = unsubscribers.get(connectorId);\n if (!unsub) {\n return;\n }\n try {\n unsub();\n } catch (error: unknown) {\n logWarn(\"[butr] unsubscribe threw:\", error);\n }\n unsubscribers.delete(connectorId);\n };\n\n return {\n attach: (connectorId, connector) => {\n if (!connector.subscribe) {\n return;\n }\n // Idempotent install — drop any prior subscription first so a\n // re-attach doesn't leak listeners on the wallet side.\n detach(connectorId);\n try {\n const unsub = connector.subscribe((event) => {\n switch (event.type) {\n case \"accountChanged\": {\n handlers.onAccountChanged(connectorId, event.accounts, event.account);\n break;\n }\n case \"disconnected\": {\n // Clear the subscription before notifying so the handler\n // sees a clean state and can't re-enter via dispatch\n // chains.\n detach(connectorId);\n handlers.onDisconnected(connectorId, connector.chainPlatform);\n break;\n }\n default: {\n const exhaustiveCheck: never = event;\n void exhaustiveCheck;\n }\n }\n });\n unsubscribers.set(connectorId, unsub);\n } catch (error: unknown) {\n logWarn(`[butr] subscribe failed for ${connectorId}:`, error);\n }\n },\n\n detach,\n\n detachAll: () => {\n // Snapshot keys before iterating because `detach` mutates the Map.\n const ids: Array<string> = [];\n for (const id of unsubscribers.keys()) {\n ids.push(id);\n }\n for (const id of ids) {\n detach(id);\n }\n },\n };\n};\n\nexport type { ConnectorLifecycle, LifecycleHandlers };\nexport { createConnectorLifecycle };\n","import type { StoredPoolEntry, WalletPersistence } from \"../storage\";\nimport type {\n Account,\n ChainPlatform,\n ConnectedWallet,\n Connector,\n WalletManagerConfig,\n} from \"../types\";\n\n/**\n * Restore outcome for a single pool entry. `kind: \"ok\"` carries the\n * fully-formed `ConnectedWallet`. `kind: \"fail\"` carries the raw error\n * so the caller can route it to telemetry.\n */\ntype RestoreOutcome =\n | { connectorId: string; entry: ConnectedWallet; kind: \"ok\" }\n | { connectorId: string; error: unknown; kind: \"fail\" };\n\n/**\n * Snapshot returned by `HydrationCoordinator.hydrate()` — everything\n * the reducer needs to compose a `HYDRATED` event plus the metadata the\n * runtime uses to wire subscriptions and surface the outcome.\n */\ntype HydrateResult = {\n activeConnectorId: string | null;\n /** Entries whose restore failed (connect() rejected, the connector\n * threw mid-flight). Reported for telemetry only — storage is\n * preserved so the next load can retry. The most common cause is\n * EIP-1193 `eth_accounts: []` from a locked wallet, which clears as\n * soon as the user unlocks and reloads. */\n dropped: Array<{ connectorId: string; reason: unknown }>;\n isUserDisconnected: boolean;\n /** Entries parked because `createConnector(id)` returned `null` at\n * hydration time (typical for Wallet Standard adapters announced\n * asynchronously). The coordinator keeps an internal record of these\n * and resolves them when the runtime later calls `drainPending`. */\n pendingIds: Array<string>;\n pool: Map<string, ConnectedWallet>;\n selection: Map<ChainPlatform, string>;\n};\n\ntype HydrationCoordinator = {\n /**\n * Restore a parked entry whose adapter has now become available.\n * Returns `null` if no entry is parked under `connectorId`. Returns an\n * `ok` outcome with the new `ConnectedWallet`, or a `fail` outcome\n * with the error (storage cleanup is handled internally).\n */\n drainPending(connectorId: string): Promise<RestoreOutcome | null>;\n /**\n * Read storage, instantiate every adapter that's currently\n * registered, restore in parallel, park the rest. After the returned\n * promise resolves, the coordinator holds an internal pending queue\n * indexed by `connectorId`. Resolve queued entries via `drainPending`.\n */\n hydrate(): Promise<HydrateResult>;\n /**\n * Snapshot of currently-parked ids. The runtime uses this once\n * immediately after `hydrate()` resolves to drain entries whose\n * adapter announced *during* the hydration window (the discovery race\n * we used to resolve manually inside the store).\n */\n pendingIds(): Array<string>;\n};\n\n/** Pick the right `accounts` list: fresh from the connector if it\n * exposes `getAccounts`, otherwise the persisted list, otherwise just\n * the active account. */\nconst resolveHydratedAccounts = async (\n connector: Connector,\n storedAccounts: ReadonlyArray<Account>,\n fallbackAccount: Account,\n): Promise<Array<Account>> => {\n if (connector.getAccounts) {\n const fresh = await connector.getAccounts().catch(() => null);\n if (fresh && fresh.length > 0) {\n return fresh;\n }\n }\n if (storedAccounts.length > 0) {\n return [...storedAccounts];\n }\n return [fallbackAccount];\n};\n\nconst restoreOneEntry = async (\n connectorId: string,\n entry: { account: Account; accounts: ReadonlyArray<Account> },\n connector: Connector,\n): Promise<RestoreOutcome> => {\n try {\n // Eager restore: ask the wallet to reconnect to already-authorized\n // accounts without prompting (Wallet Standard `silent`, EIP-1193\n // `eth_accounts`). A wallet that can't do this silently rejects;\n // the entry stays out of the pool for this session but storage is\n // preserved so the next load (e.g. once the user unlocks their\n // wallet) can retry. EIP-1193 `eth_accounts: []` is ambiguous\n // between \"locked\" and \"deauthorized\" — dropping the storage entry\n // on a transient `[]` was the cause of the reload-disconnect race.\n await connector.connect({ silent: true });\n const freshAccount = await connector.getAccount();\n const accountToUse = freshAccount || entry.account;\n const accounts = await resolveHydratedAccounts(connector, entry.accounts, accountToUse);\n return {\n connectorId,\n entry: {\n account: accountToUse,\n accounts,\n connector: connector as ConnectedWallet[\"connector\"],\n },\n kind: \"ok\",\n };\n } catch (error) {\n return { connectorId, error, kind: \"fail\" };\n }\n};\n\nconst createHydrationCoordinator = (\n storage: WalletPersistence,\n createConnector: WalletManagerConfig[\"createConnector\"],\n): HydrationCoordinator => {\n // Storage entries only leave on explicit `disconnectWallet`. Failed\n // restores stay parked / reported as `dropped` — they re-attempt on\n // the next hydrate, so a transient `eth_accounts: []` from a locked\n // wallet doesn't permanently erase the connection.\n //\n // Private to the coordinator — the runtime never touches the queue\n // directly. Storing the raw `StoredPoolEntry` is enough; the entry\n // gets re-restored through the same `restoreOneEntry` path used by\n // the eager pass, so the two code paths can't drift.\n const pending = new Map<string, StoredPoolEntry>();\n\n return {\n drainPending: async (connectorId) => {\n const entry = pending.get(connectorId);\n if (!entry) {\n return null;\n }\n const connector = createConnector(connectorId);\n if (!connector) {\n // Adapter still not available — leave the entry parked.\n return null;\n }\n const outcome = await restoreOneEntry(connectorId, entry, connector);\n pending.delete(connectorId);\n return outcome;\n },\n\n hydrate: async () => {\n const [storedPool, storedSelection, storedActive, userDisconnected] = await Promise.all([\n storage.getPool(),\n storage.getSelection(),\n storage.getActiveConnectorId(),\n storage.isUserDisconnected(),\n ]);\n\n const pool = new Map<string, ConnectedWallet>();\n const dropped: Array<{ connectorId: string; reason: unknown }> = [];\n\n // Clear pending from any prior hydrate() in the same coordinator\n // instance (e.g. tests that rehydrate). The runtime only calls\n // hydrate() once per provider mount, but defending against the\n // multi-call case is cheap.\n pending.clear();\n\n // Build the restore-task list eagerly. A null factory result\n // parks the entry; an instantiated connector enters the parallel\n // restore pool.\n const tasks: Array<Promise<RestoreOutcome>> = [];\n for (const [connectorId, entry] of Object.entries(storedPool)) {\n if (!entry) {\n continue;\n }\n const connector = createConnector(connectorId);\n if (!connector) {\n pending.set(connectorId, entry);\n continue;\n }\n tasks.push(restoreOneEntry(connectorId, entry, connector));\n }\n\n const outcomes = await Promise.all(tasks);\n for (const outcome of outcomes) {\n if (outcome.kind === \"ok\") {\n pool.set(outcome.connectorId, outcome.entry);\n continue;\n }\n dropped.push({ connectorId: outcome.connectorId, reason: outcome.error });\n }\n\n // Reconcile selection: drop stale entries, fill platforms with\n // a pool member but no stored selection.\n const selection = new Map<ChainPlatform, string>();\n for (const [platform, connectorId] of Object.entries(storedSelection)) {\n if (connectorId && pool.has(connectorId)) {\n selection.set(platform as ChainPlatform, connectorId);\n }\n }\n for (const [connectorId, wallet] of pool) {\n const platform = wallet.connector.chainPlatform;\n if (!selection.has(platform)) {\n selection.set(platform, connectorId);\n }\n }\n\n let activeConnectorId: string | null = null;\n if (storedActive && pool.has(storedActive)) {\n activeConnectorId = storedActive;\n } else if (pool.size > 0) {\n activeConnectorId = pool.keys().next().value ?? null;\n }\n\n return {\n activeConnectorId,\n dropped,\n isUserDisconnected: userDisconnected,\n pendingIds: [...pending.keys()],\n pool,\n selection,\n };\n },\n\n pendingIds: () => [...pending.keys()],\n };\n};\n\nexport type { HydrateResult, HydrationCoordinator, RestoreOutcome };\nexport { createHydrationCoordinator };\n","import type { Account, ChainPlatform, ConnectedWallet } from \"../types\";\nimport type { ConnectionError } from \"../types/errors\";\n\n/**\n * Public connection-status enum. `state.connectionStatus` in the\n * reducer only takes the first four values (\"idle\" | \"connecting\" |\n * \"success\" | \"error\") — those track the user-initiated connect\n * attempt. The fifth value, `\"reconnecting\"`, is *derived* by\n * `useConnectionStatus` when the active wallet is still backed by a\n * shadow adapter (its id is in `state.reconnectingIds`). The\n * derivation lives in the React selector so the reducer can stay a\n * narrow state machine while the public API matches wagmi's\n * vocabulary.\n */\ntype ConnectionStatus = \"idle\" | \"connecting\" | \"success\" | \"error\" | \"reconnecting\";\n\ntype State = {\n activeConnectorId: string | null;\n connectingConnectorId: string | null;\n connectionError: ConnectionError | null;\n connectionStatus: ConnectionStatus;\n isHydrated: boolean;\n isUserDisconnected: boolean;\n pool: Map<string, ConnectedWallet>;\n /**\n * Connector IDs whose pool entry is currently backed by a shadow\n * adapter — created from `WalletManagerConfig.initialState` and\n * waiting for the live adapter to be announced (via discovery) and\n * the silent reconnect to succeed. The set shrinks as entries\n * upgrade (`HYDRATED` / `CONNECT_SUCCEEDED` clear matching ids) or\n * drop (`DISCONNECTED` / `RESET`). Empty for stores constructed\n * without `initialState`.\n */\n reconnectingIds: ReadonlySet<string>;\n selection: Map<ChainPlatform, string>;\n};\n\n/**\n * Lifecycle events: hydration, full reset, and the explicit\n * user-disconnected flag the runtime sets to suppress eager reconnect.\n */\ntype LifecycleEvent =\n | {\n activeConnectorId: string | null;\n isUserDisconnected: boolean;\n pool: Map<string, ConnectedWallet>;\n selection: Map<ChainPlatform, string>;\n type: \"HYDRATED\";\n }\n | { type: \"RESET\" }\n | { type: \"USER_DISCONNECTED_SET\"; value: boolean };\n\n/**\n * Connection status events: the transitions of the in-flight connect\n * attempt — start, success/failure, status/error reset. None of these\n * mutate the pool directly; `CONNECT_SUCCEEDED` is the bridge to\n * `PoolMutationEvent`.\n */\ntype ConnectionStatusEvent =\n | { connectorId: string; type: \"CONNECT_STARTED\" }\n | { connectorId: string; entry: ConnectedWallet; type: \"CONNECT_SUCCEEDED\" }\n | { error: ConnectionError; type: \"CONNECT_FAILED\" }\n | { error: ConnectionError | null; type: \"ERROR_SET\" }\n | { type: \"STATUS_RESET\" };\n\n/**\n * Pool-mutation events: changes to the set of connected wallets and\n * their per-wallet account lists. `DISCONNECTED` removes an entry;\n * the refresh events update an existing one in-place.\n */\ntype PoolMutationEvent =\n | {\n /** When set, becomes the new active account. Used by paths that\n * know which account should be active (wallet event, manual\n * update). When unset, the reducer preserves the current active\n * if it's still in `accounts`, else picks `accounts[0]`. */\n accounts: Array<Account>;\n active?: Account;\n connectorId: string;\n type: \"ACCOUNTS_REFRESHED\";\n }\n | { connectorId: string; type: \"DISCONNECTED\" }\n | { connectorId: string; type: \"WALLET_REFRESHED\" };\n\n/**\n * Selection events: which connector is active, and which connector\n * serves each chain platform. Pure pointer updates — no pool mutation.\n */\ntype SelectionEvent =\n | { chainPlatform: ChainPlatform; connectorId: string | null; type: \"SELECTION_CHANGED\" }\n | { connectorId: string | null; type: \"ACTIVE_CHANGED\" };\n\n/**\n * The single event type the reducer dispatches on. Grouped above by\n * concern so a reader sees the map before the cases — adding an event\n * means picking the group it belongs to.\n */\ntype Event = ConnectionStatusEvent | LifecycleEvent | PoolMutationEvent | SelectionEvent;\n\nconst initialState: State = {\n activeConnectorId: null,\n connectingConnectorId: null,\n connectionError: null,\n connectionStatus: \"idle\",\n isHydrated: false,\n isUserDisconnected: false,\n pool: new Map(),\n reconnectingIds: new Set(),\n selection: new Map(),\n};\n\n/** Find any connector in the pool serving a given platform. */\nconst findConnectorForPlatform = (\n pool: Map<string, ConnectedWallet>,\n chainPlatform: ChainPlatform,\n): string | undefined => {\n for (const [id, wallet] of pool) {\n if (wallet.connector.chainPlatform === chainPlatform) {\n return id;\n }\n }\n return undefined;\n};\n\n/** Pure reducer: every state transition is expressed as `(state, event) => state`.\n * No I/O, no async, no side effects. Side effects live in the runtime. */\nconst reducer = (state: State, event: Event): State => {\n switch (event.type) {\n case \"HYDRATED\": {\n // Merge instead of replace. CONNECT_SUCCEEDED can fire during the\n // late-restore window — discovery announces an adapter, the runtime\n // calls tryRestoreFromPending, drainPending succeeds, the reducer\n // adds the entry to state.pool — all BEFORE hydrate's outer await\n // resolves and HYDRATED dispatches. A wholesale replace at that\n // point wipes the freshly-restored late entry. Merging keeps both\n // the eager-restored entries (event.pool) and the late-restored\n // entries (state.pool).\n const pool = new Map<string, ConnectedWallet>(state.pool);\n for (const [id, entry] of event.pool) {\n pool.set(id, entry);\n }\n const selection = new Map<ChainPlatform, string>(state.selection);\n for (const [platform, id] of event.selection) {\n selection.set(platform, id);\n }\n // Prefer hydrate's activeConnectorId when valid in the merged\n // pool (matches the pre-reload selection). Fall back to any late-\n // restore-driven active if hydrate's choice didn't restore eagerly.\n let activeConnectorId: string | null = null;\n if (event.activeConnectorId && pool.has(event.activeConnectorId)) {\n activeConnectorId = event.activeConnectorId;\n } else if (state.activeConnectorId && pool.has(state.activeConnectorId)) {\n activeConnectorId = state.activeConnectorId;\n } else if (pool.size > 0) {\n activeConnectorId = pool.keys().next().value ?? null;\n }\n // Clear reconnectingIds for every entry that came back via the\n // event's live-pool. Shadows that weren't upgraded (adapter not\n // yet announced) stay in `state.pool` and stay in\n // `reconnectingIds`; they'll be cleared individually by\n // CONNECT_SUCCEEDED when discovery's late-restore path resolves.\n let nextReconnecting: ReadonlySet<string> = state.reconnectingIds;\n if (state.reconnectingIds.size > 0 && event.pool.size > 0) {\n const next = new Set(state.reconnectingIds);\n for (const id of event.pool.keys()) {\n next.delete(id);\n }\n if (next.size !== state.reconnectingIds.size) {\n nextReconnecting = next;\n }\n }\n return {\n ...state,\n activeConnectorId,\n isHydrated: true,\n isUserDisconnected: event.isUserDisconnected,\n pool,\n reconnectingIds: nextReconnecting,\n selection,\n };\n }\n\n case \"USER_DISCONNECTED_SET\": {\n return { ...state, isUserDisconnected: event.value };\n }\n\n case \"CONNECT_STARTED\": {\n return {\n ...state,\n connectingConnectorId: event.connectorId,\n connectionError: null,\n connectionStatus: \"connecting\",\n };\n }\n\n case \"CONNECT_SUCCEEDED\": {\n const { connectorId, entry } = event;\n const existing = state.pool.get(connectorId);\n\n // Drop the id from reconnectingIds when present. Covers both the\n // late-restore path (discovery announced after the initial seed)\n // and ordinary connects — the latter typically have an empty set\n // so this is a no-op for them.\n const nextReconnecting: ReadonlySet<string> = state.reconnectingIds.has(connectorId)\n ? new Set([...state.reconnectingIds].filter((id) => id !== connectorId))\n : state.reconnectingIds;\n\n // Same connector + same address: only flip the status, keep object refs stable\n // so reactive selectors don't churn.\n if (\n existing &&\n existing.account.walletAddress.toLowerCase() === entry.account.walletAddress.toLowerCase()\n ) {\n return {\n ...state,\n activeConnectorId: connectorId,\n connectingConnectorId: null,\n connectionStatus: \"success\",\n pool:\n // When the existing entry was a shadow, the address may\n // match but the connector reference is the placeholder.\n // Swap in the live entry so subsequent calls don't throw\n // ShadowConnectorError.\n existing.connector === entry.connector\n ? state.pool\n : new Map([...state.pool, [connectorId, entry] as const]),\n reconnectingIds: nextReconnecting,\n };\n }\n\n const newPool = new Map([...state.pool, [connectorId, entry] as const]);\n const newSelection = new Map([\n ...state.selection,\n [entry.connector.chainPlatform, connectorId] as const,\n ]);\n\n return {\n ...state,\n activeConnectorId: connectorId,\n connectingConnectorId: null,\n connectionStatus: \"success\",\n pool: newPool,\n reconnectingIds: nextReconnecting,\n selection: newSelection,\n };\n }\n\n case \"CONNECT_FAILED\": {\n return {\n ...state,\n connectingConnectorId: null,\n connectionError: event.error,\n connectionStatus: \"error\",\n };\n }\n\n case \"DISCONNECTED\": {\n const wallet = state.pool.get(event.connectorId);\n if (!wallet) {\n return state;\n }\n\n const newPool = new Map(state.pool);\n newPool.delete(event.connectorId);\n\n const newSelection = new Map(state.selection);\n const platform = wallet.connector.chainPlatform;\n if (newSelection.get(platform) === event.connectorId) {\n newSelection.delete(platform);\n const fallback = findConnectorForPlatform(newPool, platform);\n if (fallback) {\n newSelection.set(platform, fallback);\n }\n }\n\n const firstId = newPool.keys().next();\n const activeConnectorId =\n state.activeConnectorId === event.connectorId\n ? (firstId.value ?? null)\n : state.activeConnectorId;\n\n const nextReconnecting: ReadonlySet<string> = state.reconnectingIds.has(event.connectorId)\n ? new Set([...state.reconnectingIds].filter((id) => id !== event.connectorId))\n : state.reconnectingIds;\n\n return {\n ...state,\n activeConnectorId,\n pool: newPool,\n reconnectingIds: nextReconnecting,\n selection: newSelection,\n };\n }\n\n case \"ACCOUNTS_REFRESHED\": {\n const wallet = state.pool.get(event.connectorId);\n if (!wallet) {\n return state;\n }\n // Pick the new active address. Three policies in priority order:\n // 1. Explicit `event.active` (wallet event or manual update knows\n // what should be active).\n // 2. Preserve current active if it's still in the new list.\n // 3. Fall back to `accounts[0]` so the pool entry doesn't end up\n // pointing at an address the wallet no longer exposes.\n let nextAccount: Account;\n if (event.active) {\n nextAccount = event.active;\n } else {\n const stillHasCurrent = event.accounts.some(\n (a) =>\n a.walletAddress === wallet.account.walletAddress &&\n a.chain.id === wallet.account.chain.id,\n );\n nextAccount = stillHasCurrent ? wallet.account : (event.accounts[0] ?? wallet.account);\n }\n // No-op short-circuit: when the wallet event echoes the current\n // active and the accounts array hasn't changed shape, skip the\n // Map clone so `useSyncExternalStore` subscribers don't re-render.\n // (Cheap heuristic — equality by reference for `accounts` would\n // miss content changes; we compare by length + address set.)\n const sameAccount =\n nextAccount.walletAddress === wallet.account.walletAddress &&\n nextAccount.chain.id === wallet.account.chain.id;\n const sameAccountsShape =\n event.accounts.length === wallet.accounts.length &&\n event.accounts.every((a, i) => a.walletAddress === wallet.accounts[i]?.walletAddress);\n if (sameAccount && sameAccountsShape) {\n return state;\n }\n const updated: ConnectedWallet = {\n ...wallet,\n account: nextAccount,\n accounts: [...event.accounts],\n };\n const newPool = new Map([...state.pool, [event.connectorId, updated] as const]);\n return { ...state, pool: newPool };\n }\n\n case \"WALLET_REFRESHED\": {\n const wallet = state.pool.get(event.connectorId);\n if (!wallet) {\n return state;\n }\n const newPool = new Map([...state.pool, [event.connectorId, { ...wallet }] as const]);\n return { ...state, pool: newPool };\n }\n\n case \"ACTIVE_CHANGED\": {\n return { ...state, activeConnectorId: event.connectorId };\n }\n\n case \"SELECTION_CHANGED\": {\n const newSelection = new Map(state.selection);\n if (event.connectorId === null) {\n newSelection.delete(event.chainPlatform);\n } else {\n newSelection.set(event.chainPlatform, event.connectorId);\n }\n return { ...state, selection: newSelection };\n }\n\n case \"STATUS_RESET\": {\n return {\n ...state,\n connectingConnectorId: null,\n connectionError: null,\n connectionStatus: \"idle\",\n };\n }\n\n case \"ERROR_SET\": {\n return {\n ...state,\n connectionError: event.error,\n connectionStatus: event.error ? \"error\" : \"idle\",\n };\n }\n\n case \"RESET\": {\n return {\n ...initialState,\n isHydrated: state.isHydrated,\n isUserDisconnected: true,\n reconnectingIds: new Set(),\n };\n }\n\n default: {\n // Exhaustiveness check — TS errors if a new event type is added without a case.\n const exhaustiveCheck: never = event;\n void exhaustiveCheck;\n return state;\n }\n }\n};\n\nexport type {\n ConnectionStatus,\n ConnectionStatusEvent,\n Event,\n LifecycleEvent,\n PoolMutationEvent,\n SelectionEvent,\n State,\n};\nexport { initialState, reducer };\n","import type { StoredPoolEntry } from \"../storage/persistence\";\nimport type {\n BitcoinAdapter,\n EvmAdapter,\n PolkadotAdapter,\n SuiAdapter,\n SvmAdapter,\n WalletAdapter,\n WalletCapabilities,\n} from \"../types\";\n\n/**\n * Error thrown when a method is called on a shadow adapter — the\n * placeholder `WalletAdapter` that the store seeds into the pool when\n * an `initialState` is provided (e.g. from a server-rendered cookie\n * snapshot). Shadow adapters carry the identity and account data of a\n * previously-connected wallet, but the live wallet extension hasn't\n * been verified yet — the silent reconnect happens asynchronously\n * after mount.\n *\n * UI code that gates affordances on `wallet.connector.capabilities.*`\n * never reaches a shadow method (capabilities are all `false`). Code\n * that calls through anyway hits this typed error, which is the\n * correct loud failure: the consumer ignored the capability gate.\n *\n * Consumers wanting to wait out the reconnecting window should branch\n * on whether `connectorId` is in `state.reconnectingIds`.\n */\nclass ShadowConnectorError extends Error {\n // Stable programmatic identifier — consumers branch on `.code` to\n // distinguish this from generic connector errors. Read via\n // `toMatchObject({ code: \"BUTR_RECONNECTING\" })` in tests, which\n // static analysis can't trace.\n // fallow-ignore-next-line unused-class-member\n readonly code = \"BUTR_RECONNECTING\";\n readonly connectorId: string;\n readonly method: string;\n constructor(method: string, connectorId: string) {\n super(\n `[butr] ${method} called on shadow connector \"${connectorId}\" — wait for silent reconnect to complete (check reconnectingIds.has(id) before calling).`,\n );\n this.name = \"ShadowConnectorError\";\n this.connectorId = connectorId;\n this.method = method;\n }\n}\n\nconst ALL_FALSE_CAPABILITIES: WalletCapabilities = Object.freeze({\n getBalance: false,\n getTransactionReceipt: false,\n requestAccounts: false,\n sendTransaction: false,\n signIn: false,\n signMessage: false,\n signTransaction: false,\n subscribe: false,\n switchAccount: false,\n switchChain: false,\n});\n\n/**\n * Builds a placeholder `WalletAdapter` from a persisted pool entry.\n *\n * Used by `createWalletStore` when `WalletManagerConfig.initialState`\n * is provided: each stored entry becomes a `ConnectedWallet` whose\n * `connector` is one of these. The store flips `isHydrated` true\n * synchronously and exposes the data through the usual hooks\n * (`useActiveWallet`, `useConnectedWallets`, …) — but the connector\n * can't actually talk to a wallet yet, so its capabilities are all\n * `false` and its methods throw `ShadowConnectorError` if called.\n *\n * The hydration coordinator's silent-reconnect pass upgrades each\n * shadow to a live `WalletAdapter` by calling `createConnector(id)`\n * (which only succeeds once the live adapter has been announced via\n * discovery or registered explicitly) and replacing the pool entry.\n * On success the connector id is removed from `reconnectingIds`; on\n * failure the entry is dropped from the pool and storage.\n *\n * The entry's `name` is required (the storage validator rejects\n * entries without it); `icon` is optional only because some live\n * adapters genuinely have no icon to begin with.\n */\nconst createShadowAdapter = (entry: StoredPoolEntry): WalletAdapter => {\n const id = entry.connectorId;\n const name = entry.name;\n const icon = entry.icon;\n\n const reject = (method: string): Promise<never> =>\n Promise.reject(new ShadowConnectorError(method, id));\n\n // Shape that satisfies the intersection of `Connector<P>` and\n // `WalletBase`. Platform-specific optional methods (`signIn`,\n // `signTransaction`) are intentionally omitted — they don't exist on\n // a shadow, and their absence is a valid runtime state for the\n // per-platform adapter types (which mark them `?:`).\n const base = {\n capabilities: ALL_FALSE_CAPABILITIES,\n connect: () => reject(\"connect\"),\n disconnect: () => reject(\"disconnect\"),\n getAccount: () => reject(\"getAccount\"),\n getBalance: () => reject(\"getBalance\"),\n getSigner: () => reject(\"getSigner\"),\n getTransactionReceipt: () => reject(\"getTransactionReceipt\"),\n icon,\n id,\n name,\n sendTx: () => reject(\"sendTx\"),\n sendTxToChain: () => reject(\"sendTxToChain\"),\n signMessage: () => reject(\"signMessage\"),\n switchChain: () => reject(\"switchChain\"),\n };\n\n // Cast required: TS can't narrow the literal `chainPlatform` field through a spread.\n switch (entry.chainPlatform) {\n case \"bitcoin\": {\n return { ...base, chainPlatform: \"bitcoin\" } as BitcoinAdapter;\n }\n case \"evm\": {\n return { ...base, chainPlatform: \"evm\" } as EvmAdapter;\n }\n case \"sui\": {\n return { ...base, chainPlatform: \"sui\" } as SuiAdapter;\n }\n case \"svm\": {\n return { ...base, chainPlatform: \"svm\" } as SvmAdapter;\n }\n case \"polkadot\": {\n return { ...base, chainPlatform: \"polkadot\" } as PolkadotAdapter;\n }\n default: {\n // Exhaustive — TS errors if a ChainPlatform variant is added.\n const _exhaustive: never = entry.chainPlatform;\n void _exhaustive;\n throw new Error(`[butr] unknown chainPlatform: ${entry.chainPlatform as string}`);\n }\n }\n};\n\n/**\n * Type guard. Returns true when an adapter is a placeholder created\n * by `createShadowAdapter`. Useful for the hydration coordinator\n * (which needs to know which pool entries still need upgrading) and\n * for consumers writing wagmi-style \"is this connection verified yet\"\n * checks without subscribing to `reconnectingIds` directly.\n *\n * Detection is structural: a shadow has all capabilities set to false.\n * Live adapters always advertise at least one capability (every wallet\n * surface includes `getBalance`, `signMessage`, `switchChain` as\n * required methods, and adapter constructors set their flags\n * accordingly).\n */\nconst isShadowAdapter = (adapter: WalletAdapter): boolean => {\n const caps = adapter.capabilities;\n return (\n !caps.getBalance &&\n !caps.getTransactionReceipt &&\n !caps.requestAccounts &&\n !caps.sendTransaction &&\n !caps.signMessage &&\n !caps.subscribe &&\n !caps.switchChain\n );\n};\n\nexport { createShadowAdapter, isShadowAdapter, ShadowConnectorError };\n","/** Run an async operation fire-and-forget, calling onError if it throws. */\nconst run = async (fn: () => Promise<void>, onError: (e: unknown) => void): Promise<void> => {\n try {\n await fn();\n } catch (error) {\n onError(error);\n }\n};\n\nexport { run };\n","import { createStore } from \"zustand/vanilla\";\n\nimport { logError, logWarn } from \"../logger\";\nimport { WalletStorage } from \"../storage\";\nimport type { WalletPersistence, WalletSnapshot } from \"../storage\";\nimport type {\n Account,\n ChainPlatform,\n ConnectedWallet,\n WalletAdapter,\n WalletManagerConfig,\n} from \"../types\";\nimport type { ConnectionError } from \"../types/errors\";\nimport { mapConnectionError } from \"../types/errors\";\n\nimport { createConnectorLifecycle } from \"./connector-lifecycle\";\nimport { createHydrationCoordinator } from \"./hydration\";\nimport { type Event, type State, initialState, reducer } from \"./reducer\";\nimport { createShadowAdapter } from \"./shadow-adapter\";\nimport { run } from \"./wallet-store-helpers\";\n\n/**\n * Build a synchronously-populated `State` from `config.initialState`.\n * Each pool entry becomes a `ConnectedWallet` whose `connector` is a\n * shadow adapter; every connector id enters `reconnectingIds` so\n * consumers can branch on \"is this connection verified\" without\n * waiting for the async silent reconnect. `isHydrated` flips true\n * synchronously — the consumer's first render sees the persisted\n * state in the live store, not undefined.\n */\nconst seedStateFromSnapshot = (snapshot: WalletSnapshot): State => {\n const pool = new Map<string, ConnectedWallet>();\n const reconnectingIds = new Set<string>();\n for (const [connectorId, entry] of Object.entries(snapshot.pool)) {\n if (!entry) {\n continue;\n }\n const connector: WalletAdapter = createShadowAdapter(entry);\n pool.set(connectorId, {\n account: entry.account,\n accounts: [...entry.accounts],\n connector,\n });\n reconnectingIds.add(connectorId);\n }\n const selection = new Map<ChainPlatform, string>();\n for (const [platform, connectorId] of Object.entries(snapshot.selection)) {\n if (connectorId && pool.has(connectorId)) {\n selection.set(platform as ChainPlatform, connectorId);\n }\n }\n const activeConnectorId =\n snapshot.activeConnectorId && pool.has(snapshot.activeConnectorId)\n ? snapshot.activeConnectorId\n : (pool.keys().next().value ?? null);\n return {\n ...initialState,\n activeConnectorId,\n isHydrated: true,\n pool,\n reconnectingIds,\n selection,\n };\n};\n\nconst CONNECT_TIMEOUT_MS = 90_000;\nconst DEFAULT_SLOW_CONNECT_THRESHOLD_MS = 5000;\n\nconst normaliseAddress = (addr: string): string => addr.toLowerCase();\n\ntype ExtractState<S> = S extends { getState: () => infer T } ? T : never;\n\ntype RuntimeMembers = {\n _config: WalletManagerConfig;\n _storage: WalletPersistence;\n connectWallet: (\n connectorId: string,\n onSuccess?: (wallet: ConnectedWallet) => void,\n onError?: (error: Error) => void,\n ) => Promise<void>;\n disconnectWallet: (connectorId: string) => void;\n getConnectorInstance: (id: string) => ReturnType<WalletManagerConfig[\"createConnector\"]>;\n hydrateWallets: () => Promise<void>;\n refreshWallet: (connectorId: string) => void;\n requestAccounts: (connectorId: string) => Promise<void>;\n reset: () => void;\n resetConnectionStatus: () => void;\n setActiveConnector: (connectorId: string | null) => void;\n setConnectionError: (error: ConnectionError | null) => void;\n setSelection: (chainPlatform: ChainPlatform, connectorId: string | null) => void;\n setUserDisconnected: (value: boolean) => void;\n tryRestoreFromPending: (connectorId: string) => Promise<void>;\n updateWalletAccount: (connectorId: string, account: Account) => void;\n};\n\ntype WalletStore = ReturnType<typeof createWalletStore>;\ntype WalletStoreState = ExtractState<WalletStore>;\n\nconst createWalletStore = (config: WalletManagerConfig) => {\n const storageKeyPrefix = config.storageKeyPrefix || \"butr\";\n const storage = config.storage || new WalletStorage({ keyPrefix: storageKeyPrefix });\n\n // Storage-error handler. Default: `console.warn` (preserves the\n // pre-`onStorageError` behaviour). Consumer-provided callback routes\n // failures through their telemetry pipeline.\n const reportStorageError = (context: string) => (error: unknown) => {\n if (config.onStorageError) {\n try {\n config.onStorageError(error, context);\n } catch (cbError: unknown) {\n logWarn(\"[butr] onStorageError threw:\", cbError);\n }\n return;\n }\n logWarn(`[butr] ${context}:`, error);\n };\n\n // Owns the full restore lifecycle: read storage, instantiate adapters,\n // restore in parallel, park entries whose adapter wasn't registered\n // yet, drain parked entries when discovery announces a matching id.\n // `pendingRestores` lives inside the coordinator — the runtime no\n // longer manages the queue directly.\n const hydration = createHydrationCoordinator(storage, config.createConnector);\n\n return createStore<State & RuntimeMembers>()((set, get) => {\n const dispatch = (event: Event) => {\n set((prev) => reducer(prev, event), false);\n };\n\n // Returns the write Promise so connect/disconnect can await durability;\n // `WalletStorage` serializes pool mutations internally so fire-and-forget callers are safe.\n const persistPool = (): Promise<void> =>\n run(() => storage.setPool(get().pool), reportStorageError(\"failed to persist pool\"));\n\n const persistSelection = (): Promise<void> =>\n run(\n () => storage.setSelection(get().selection),\n reportStorageError(\"failed to persist selection\"),\n );\n\n const persistActive = (): Promise<void> =>\n run(\n () => storage.setActiveConnectorId(get().activeConnectorId),\n reportStorageError(\"failed to persist active connector\"),\n );\n\n // Single entry point for \"the pool entry's exposed accounts\n // changed.\" Three call sites use it: the wallet-event bridge\n // (`accountChanged`), the consumer-facing `updateWalletAccount`\n // action, and the `requestAccounts` action. `active` is set when\n // the caller knows which address is now active (wallet event,\n // manual update); omitted when the caller just refreshed the\n // list and wants the reducer's preserve-or-fallback logic.\n const refreshPoolEntry = (connectorId: string, accounts: Array<Account>, active?: Account) => {\n dispatch({ accounts, active, connectorId, type: \"ACCOUNTS_REFRESHED\" });\n void persistPool();\n };\n\n // Owns the \"exactly one subscription per connector\" invariant,\n // the event-to-handler choreography, and the unsubscribers Map.\n // Attach is idempotent: the runtime calls it after every\n // restore / connect without first checking whether a prior\n // subscription exists.\n const lifecycle = createConnectorLifecycle({\n onAccountChanged: (connectorId, accounts, active) => {\n // Full-replace so the pool mirrors the wallet's current exposure; the wallet picks `active`.\n refreshPoolEntry(connectorId, [...accounts], active);\n },\n onDisconnected: (connectorId, chainPlatform) => {\n // Mirror an external disconnect into the reducer so the UI\n // hides the wallet, but DON'T touch storage — EIP-1193 emits\n // `accountsChanged: []` (which we forward as a `disconnected`\n // event) both when the user revokes permissions AND when\n // MetaMask simply auto-locks. The two are indistinguishable\n // from our side, so persisting the empty pool would erase the\n // saved connection on every wallet lock. Keeping storage\n // intact lets the next hydrate retry — the user-locked case\n // self-heals once they unlock and reload, and a genuine\n // revocation gets cleaned up by the next call to\n // `disconnectWallet` (or just lingers as dead bytes).\n dispatch({ connectorId, type: \"DISCONNECTED\" });\n config.onDisconnect?.(chainPlatform);\n },\n });\n\n const seeded: State = config.initialState\n ? seedStateFromSnapshot(config.initialState)\n : initialState;\n\n return {\n ...seeded,\n _config: config,\n _storage: storage,\n connectWallet: async (connectorId, onSuccess, onError) => {\n get().setUserDisconnected(false);\n\n const connector = config.createConnector(connectorId);\n if (!connector) {\n throw new Error(`Failed to create connector for ${connectorId}`);\n }\n\n dispatch({ connectorId, type: \"CONNECT_STARTED\" });\n\n // Slow-connect telemetry. Fires once if the wallet doesn't\n // resolve within the configured threshold. Cleared in finally.\n const slowThreshold = config.slowConnectThresholdMs ?? DEFAULT_SLOW_CONNECT_THRESHOLD_MS;\n const slowTimer = config.onSlowConnect\n ? setTimeout(() => {\n try {\n config.onSlowConnect?.(connectorId);\n } catch (cbError: unknown) {\n logWarn(\"[butr] onSlowConnect threw:\", cbError);\n }\n }, slowThreshold)\n : null;\n\n try {\n const connectPromise = connector.connect();\n // oxlint-disable-next-line promise/prefer-await-to-then -- suppress unhandled rejection; we await via Promise.race below\n void connectPromise.catch(() => {});\n await Promise.race([\n connectPromise,\n new Promise((_, reject) => {\n setTimeout(() => {\n reject(new Error(\"Connection timeout\"));\n }, CONNECT_TIMEOUT_MS);\n }),\n ]);\n\n const account = await connector.getAccount();\n if (!account) {\n throw new Error(\"Failed to get account\");\n }\n\n // Pull all known accounts when the connector supports it.\n const fetchedAccounts = connector.getAccounts\n ? await connector.getAccounts().catch(() => null)\n : null;\n const accounts =\n fetchedAccounts && fetchedAccounts.length > 0 ? fetchedAccounts : [account];\n\n const entry: ConnectedWallet = { account, accounts, connector };\n dispatch({ connectorId, entry, type: \"CONNECT_SUCCEEDED\" });\n\n lifecycle.attach(connectorId, connector);\n\n // Await so callers can `await connectWallet(id)` and trust\n // that storage reflects the new connection on the very next\n // line — load `useConnectedWallets` snapshots, expect\n // localStorage to contain the entry, etc.\n await Promise.all([persistPool(), persistSelection(), persistActive()]);\n\n config.onConnect?.(entry);\n onSuccess?.(entry);\n } catch (error) {\n const normalised = mapConnectionError(error);\n dispatch({ error: normalised, type: \"CONNECT_FAILED\" });\n try {\n await connector.disconnect?.();\n } catch (disconnectError: unknown) {\n logWarn(\"[butr] disconnect during error recovery failed:\", disconnectError);\n }\n try {\n config.onConnectError?.(normalised, connectorId);\n } catch (cbError: unknown) {\n logWarn(\"[butr] onConnectError threw:\", cbError);\n }\n onError?.(error as Error);\n throw error;\n } finally {\n if (slowTimer) {\n clearTimeout(slowTimer);\n }\n }\n },\n\n disconnectWallet: (connectorId) => {\n get().setUserDisconnected(true);\n\n const state = get();\n if (!state.isHydrated) {\n return;\n }\n const wallet = state.pool.get(connectorId);\n if (!wallet) {\n return;\n }\n\n lifecycle.detach(connectorId);\n\n void run(() => wallet.connector.disconnect?.() ?? Promise.resolve(), logError);\n\n const platform = wallet.connector.chainPlatform;\n dispatch({ connectorId, type: \"DISCONNECTED\" });\n\n if (get().pool.size === 0) {\n void run(() => storage.clearAll(), reportStorageError(\"failed to clear storage\"));\n } else {\n // Explicit eviction so storage doesn't keep retrying the\n // disconnected wallet on the next load. `persistPool` is\n // additive (see `WalletStorage.setPool`), so we can't rely\n // on it to remove entries — that's what `removePoolEntry`\n // is for.\n void run(\n () => storage.removePoolEntry(connectorId),\n reportStorageError(\"failed to remove pool entry\"),\n );\n void persistSelection();\n void persistActive();\n }\n\n config.onDisconnect?.(platform);\n },\n\n getConnectorInstance: (id) => config.createConnector(id),\n\n hydrateWallets: async () => {\n const result = await hydration.hydrate();\n dispatch({\n activeConnectorId: result.activeConnectorId,\n isUserDisconnected: result.isUserDisconnected,\n pool: result.pool,\n selection: result.selection,\n type: \"HYDRATED\",\n });\n // Wire up wallet-side event subscriptions for every restored\n // connector so account/chain swaps after a refresh keep the\n // reducer in sync without consumer effort.\n for (const [connectorId, wallet] of result.pool) {\n lifecycle.attach(connectorId, wallet.connector);\n }\n // Persist any reconciled values back so future loads stay consistent.\n await Promise.all([persistSelection(), persistActive()]);\n // Drain: catch the race where a Wallet Standard adapter\n // announced BEFORE hydration finished populating its queue.\n // Without this, the discovery callback's\n // `tryRestoreFromPending(adapter.id)` would have hit the\n // empty queue and silently returned, leaving SVM wallets\n // un-restored on reload. The coordinator's `pendingIds()`\n // is the post-hydrate snapshot; each id resolves\n // independently. Late-restore failures surface per-entry\n // inside `tryRestoreFromPending`.\n void Promise.all(hydration.pendingIds().map((id) => get().tryRestoreFromPending(id)));\n // Surface the hydration outcome. Three buckets: restored,\n // pending (waiting for adapter announcement), dropped\n // (genuine restore failures). The default `console.warn`\n // for dropped entries is replaced by this callback when\n // set — consumers route to telemetry / UX hints.\n if (config.onHydrated) {\n try {\n config.onHydrated({\n dropped: result.dropped,\n pendingIds: [...result.pendingIds],\n restoredIds: [...result.pool.keys()],\n });\n } catch (cbError: unknown) {\n logWarn(\"[butr] onHydrated threw:\", cbError);\n }\n } else if (result.dropped.length > 0) {\n // Preserve the pre-`onHydrated` console.warn behaviour\n // when no callback is set, so silent drops still log.\n for (const { connectorId, reason } of result.dropped) {\n logWarn(`[butr] failed to restore connector ${connectorId}:`, reason);\n }\n }\n },\n refreshWallet: (connectorId) => {\n dispatch({ connectorId, type: \"WALLET_REFRESHED\" });\n },\n\n requestAccounts: async (connectorId) => {\n const entry = get().pool.get(connectorId);\n if (!entry) {\n return;\n }\n // Ask the wallet to open its picker (if supported). EVM\n // wallets honour this via `wallet_requestPermissions`;\n // Wallet Standard wallets typically have no equivalent\n // RPC, so the call resolves immediately and the refresh\n // below simply re-reads the current list.\n await entry.connector.requestAccounts?.();\n const fresh = entry.connector.getAccounts\n ? await entry.connector.getAccounts().catch(() => null)\n : null;\n if (!fresh || fresh.length === 0) {\n return;\n }\n // No `active` — the reducer preserves the current active\n // if it's still in the refreshed list, else picks [0].\n refreshPoolEntry(connectorId, fresh);\n },\n\n reset: () => {\n get().setUserDisconnected(true);\n\n const state = get();\n if (!state.isHydrated) {\n return;\n }\n\n lifecycle.detachAll();\n\n for (const wallet of state.pool.values()) {\n void run(() => wallet.connector.disconnect?.() ?? Promise.resolve(), logError);\n }\n\n void run(() => storage.clearAll(), reportStorageError(\"failed to clear storage\"));\n\n if (config.onReset) {\n const onReset = config.onReset;\n void run(() => Promise.resolve(onReset()), logError);\n }\n\n dispatch({ type: \"RESET\" });\n },\n\n resetConnectionStatus: () => {\n dispatch({ type: \"STATUS_RESET\" });\n },\n\n setActiveConnector: (connectorId) => {\n dispatch({ connectorId, type: \"ACTIVE_CHANGED\" });\n void persistActive();\n },\n\n setConnectionError: (error) => {\n dispatch({ error, type: \"ERROR_SET\" });\n },\n\n setSelection: (chainPlatform, connectorId) => {\n dispatch({ chainPlatform, connectorId, type: \"SELECTION_CHANGED\" });\n void persistSelection();\n },\n\n setUserDisconnected: (value: boolean) => {\n dispatch({ type: \"USER_DISCONNECTED_SET\", value });\n void run(\n () => storage.markUserDisconnected(value),\n reportStorageError(\"failed to persist disconnect intent\"),\n );\n },\n\n tryRestoreFromPending: async (connectorId) => {\n // Note: deliberately NOT gating on `isUserDisconnected`.\n // The eager hydration path doesn't check that flag either —\n // stored entries restore on reload regardless of whether\n // the user's most recent action was a disconnect. The\n // late-restore path mirrors that policy for symmetry.\n const outcome = await hydration.drainPending(connectorId);\n if (!outcome) {\n return;\n }\n if (outcome.kind === \"fail\") {\n logWarn(`[butr] late restore failed for ${connectorId}:`, outcome.error);\n return;\n }\n // Reuse the reducer's connect-success path so selection /\n // active reconciliation matches a normal connect flow.\n dispatch({ connectorId, entry: outcome.entry, type: \"CONNECT_SUCCEEDED\" });\n lifecycle.attach(connectorId, outcome.entry.connector);\n await Promise.all([persistPool(), persistSelection(), persistActive()]);\n config.onConnect?.(outcome.entry);\n },\n\n updateWalletAccount: (connectorId, account) => {\n // Routes through ACCOUNTS_REFRESHED so the manual and wallet-event paths share a reducer case.\n const entry = get().pool.get(connectorId);\n if (!entry) {\n return;\n }\n const newChain = account.chain;\n const remapped = entry.accounts.map((a) =>\n a.chain.id === newChain.id\n ? a\n : {\n chain: newChain,\n id: `${newChain.id}:${normaliseAddress(a.walletAddress)}`,\n walletAddress: a.walletAddress,\n },\n );\n const seen = remapped.some(\n (a) => normaliseAddress(a.walletAddress) === normaliseAddress(account.walletAddress),\n );\n const accounts = seen ? remapped : [...remapped, account];\n refreshPoolEntry(connectorId, accounts, account);\n },\n };\n });\n};\n\nexport type { WalletStore, WalletStoreState };\nexport { createWalletStore };\n","import type { ConnectedWallet } from \"./types\";\n\n/**\n * Two wallet snapshots are equivalent for selector purposes iff they\n * share connectorId, active account address, and active account chain\n * id. Used by `useStoreWithEqualityFn` consumers (active-wallet,\n * selected-wallet, useWalletEntry) to suppress spurious re-renders\n * when the underlying Map churns but the resolved entry hasn't changed.\n *\n * Hoisted to its own module so the equivalence rule lives in one place\n * — if we ever extend the snapshot (e.g. to consider `accounts.length`),\n * every selector hook picks up the new rule for free.\n */\nconst walletEqual = (a: ConnectedWallet | undefined, b: ConnectedWallet | undefined): boolean => {\n if (a === b) {\n return true;\n }\n if (!a || !b) {\n return false;\n }\n return (\n a.connector.id === b.connector.id &&\n a.account.walletAddress === b.account.walletAddress &&\n a.account.chain.id === b.account.chain.id\n );\n};\n\nexport { walletEqual };\n","/**\n * Normalize a wallet-announced icon string.\n *\n * Wallets announce their icon through external metadata — EIP-6963\n * `providerInfo.icon`, Wallet Standard `wallet.icon`. That value is\n * not under butr's control, and some wallets ship data-URI icons with\n * surrounding whitespace (a newline left over from a pretty-printed\n * manifest). Strict consumers reject it: Next.js's `<Image>` throws\n * because `src` must not start with a control character.\n *\n * Trims surrounding whitespace and treats an all-whitespace (or empty)\n * icon as absent, so consumers get either a usable string or\n * `undefined` — never a blank or malformed one. `undefined` passes\n * through untouched.\n */\nconst sanitizeIcon = (icon: string | undefined): string | undefined => {\n if (icon === undefined) {\n return undefined;\n }\n const trimmed = icon.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n};\n\nexport { sanitizeIcon };\n"],"mappings":"8CAyCA,MAAM,EAAyB,IAA0D,CACvF,QAAS,EAAQ,SAAW,CAAC,EAC7B,IAAK,EAAQ,KAAO,CAAC,EACrB,SAAU,EAAQ,UAAY,CAAC,EAC/B,IAAK,EAAQ,KAAO,CAAC,EACrB,IAAK,EAAQ,KAAO,CAAC,CACvB,GCXM,EAAsB,GAAkC,CAC5D,GAAI,aAAe,MAAO,CACxB,IAAM,EAAU,EAAI,QACd,EAAQ,EAAQ,YAAY,EAElC,GAAI,IAAY,qBACd,MAAO,CAAE,KAAM,UAAW,SAAQ,EAEpC,GAAI,IAAY,yBAA2B,EAAM,SAAS,eAAe,EACvE,MAAO,CAAE,KAAM,eAAgB,SAAQ,EAGzC,IAAM,EAAQ,EAA2B,KAwBzC,OAvBI,IAAS,KACJ,CAAE,KAAM,eAAgB,SAAQ,EAErC,IAAS,OACJ,CAAE,KAAM,iBAAkB,SAAQ,EAKvC,IAAS,MAAQ,IAAS,MAAQ,IAAS,KACtC,CAAE,KAAM,eAAgB,SAAQ,EAGrC,EAAM,SAAS,eAAe,GAAK,EAAM,SAAS,aAAa,EAC1D,CAAE,KAAM,eAAgB,SAAQ,EAErC,EAAM,SAAS,QAAQ,EAClB,CAAE,KAAM,eAAgB,SAAQ,EAErC,EAAM,SAAS,OAAO,IAAM,EAAM,SAAS,UAAU,GAAK,EAAM,SAAS,aAAa,GACjF,CAAE,KAAM,gBAAiB,SAAQ,EAGnC,CAAE,MAAO,EAAK,KAAM,UAAW,SAAQ,CAChD,CAMA,OAJI,OAAO,GAAQ,SACV,CAAE,KAAM,UAAW,QAAS,CAAI,EAGlC,CAAE,MAAO,EAAK,KAAM,UAAW,QAAS,mBAAoB,CACrE,EC7DM,EACJ,IACkB,CAAE,WAAU,GCpB1B,GAAW,GAAG,IAAuC,CACzD,QAAQ,KAAK,GAAG,CAAI,CACtB,EAEM,GAAY,GAAG,IAAuC,CAC1D,QAAQ,MAAM,GAAG,CAAI,CACvB,ECIM,EAAiB,GAAkC,CACvD,GAAI,CACF,OACE,OAAO,WAAe,KACrB,WAAuC,KAAU,IAAA,EAEtD,MAAQ,CACN,MAAO,EACT,CACF,EAEM,MAAiD,CACrD,IAAM,EAAS,IAAI,IACnB,MAAO,CACL,QAAQ,EAAK,CACX,OAAO,EAAO,IAAI,CAAG,GAAK,IAC5B,EACA,WAAW,EAAK,CACd,EAAO,OAAO,CAAG,CACnB,EACA,QAAQ,EAAK,EAAO,CAClB,EAAO,IAAI,EAAK,CAAK,CACvB,CACF,CACF,EAEM,EAA0B,GAAwC,CACtE,GAAI,CAAC,EAAc,CAAI,EACrB,OAAO,EAA0B,EAGnC,IAAM,EAAW,WAA0D,GAE3E,MAAO,CACL,QAAQ,EAAK,CACX,GAAI,CACF,OAAO,EAAQ,QAAQ,CAAG,CAC5B,MAAQ,CACN,OAAO,IACT,CACF,EACA,WAAW,EAAK,CACd,GAAI,CACF,EAAQ,WAAW,CAAG,CACxB,MAAQ,CAER,CACF,EACA,QAAQ,EAAK,EAAO,CAClB,GAAI,CACF,EAAQ,QAAQ,EAAK,CAAK,CAC5B,MAAQ,CAER,CACF,CACF,CACF,EAOM,OAA2D,CAC/D,WAAY,EAAuB,cAAc,EACjD,QAAS,EAAuB,gBAAgB,CAClD,GC5BM,EAAU,GAA0B,mBAAmB,CAAK,EAC5D,EAAU,GAA0B,mBAAmB,CAAK,EAE5D,MAAyC,CAC7C,IAAM,EAAM,IAAI,IAChB,GAAI,OAAO,SAAa,IACtB,OAAO,EAET,IAAM,EAAe,SAAS,OAC9B,GAAI,CAAC,EACH,OAAO,EAET,IAAK,IAAM,KAAW,EAAa,MAAM,GAAG,EAAG,CAC7C,IAAM,EAAU,EAAQ,KAAK,EAC7B,GAAI,CAAC,EACH,SAEF,IAAM,EAAK,EAAQ,QAAQ,GAAG,EAC9B,GAAI,IAAO,GACT,SAEF,IAAM,EAAO,EAAQ,MAAM,EAAG,CAAE,EAC1B,EAAM,EAAQ,MAAM,EAAK,CAAC,EAChC,GAAI,CACF,EAAI,IAAI,EAAM,EAAO,CAAG,CAAC,CAC3B,MAAQ,CAER,CACF,CACA,OAAO,CACT,EAEM,GACJ,EACA,EACA,EACA,EAAU,KACC,CACX,IAAM,EAAuB,CAAC,GAAG,EAAK,GAAG,EAAO,CAAK,IAAK,QAAQ,EAAQ,MAAQ,KAAK,EAIvF,GAHI,EAAQ,QACV,EAAM,KAAK,UAAU,EAAQ,QAAQ,EAEnC,EACF,EAAM,KAAK,WAAW,MACjB,CACL,IAAM,EAAS,EAAQ,eAAiB,OACxC,EAAM,KAAK,WAAW,GAAQ,CAChC,CAKA,OAJA,EAAM,KAAK,YAAY,EAAQ,UAAY,OAAO,GAC9C,EAAQ,QAAU,KACpB,EAAM,KAAK,QAAQ,EAEd,EAAM,KAAK,IAAI,CACxB,EAEMA,EAAe,GACd,EAGD,OAAQ,EAAqC,OAAO,WAAc,WAC7D,IAAI,IAAI,CAAmC,EAE7C,IAAI,IAAI,OAAO,QAAQ,CAAyC,CAAC,EAL/D,KAgCL,GAA6B,EAA+B,CAAC,IAAqB,CACtF,IAAM,EAASA,EAAY,EAAQ,cAAc,EA4BjD,OA1BI,OAAO,SAAa,IACjB,EAaE,CACL,QAAQ,EAAK,CACX,OAAO,EAAO,IAAI,CAAG,GAAK,IAC5B,EACA,YAAa,CAEb,EACA,SAAU,CAEV,CACF,EAtBS,CACL,SAAU,CACR,OAAO,IACT,EACA,YAAa,CAEb,EACA,SAAU,CAEV,CACF,EAcG,CACL,QAAQ,EAAK,CACX,OAAO,EAAY,EAAE,IAAI,CAAG,GAAK,IACnC,EACA,WAAW,EAAK,CAEd,SAAS,OAAS,EAAkB,EAAK,GAAI,EAAS,EAAI,CAC5D,EACA,QAAQ,EAAK,EAAO,CAElB,SAAS,OAAS,EAAkB,EAAK,EAAO,CAAO,CACzD,CACF,CACF,EC/KMC,EAAwB,IAAI,IAAmB,CAAC,MAAO,MAAO,MAAO,SAAS,CAAC,EAiC/E,EAAiC,OAAO,OAAO,CACnD,kBAAmB,KACnB,KAAM,OAAO,OAAO,CAAC,CAAC,EACtB,UAAW,OAAO,OAAO,CAAC,CAAC,CAC7B,CAAC,EAEK,EAAe,GAA6C,CAIhE,GAAI,OADmB,EAA0C,OAAO,WAC3C,WAC3B,OAAO,IAAI,IAAI,OAAO,QAAQ,CAAyC,CAAC,EAE1E,IAAM,EAAM,IAAI,IAChB,IAAK,IAAM,KAAS,EAAuE,CACzF,GAAI,MAAM,QAAQ,CAAK,EAAG,CACxB,GAAM,CAAC,EAAM,GAAS,EAClB,OAAO,GAAS,UAAY,OAAO,GAAU,UAC/C,EAAI,IAAI,EAAM,CAAK,EAErB,QACF,CAEE,GACA,OAAO,GAAU,UACjB,SAAU,GACV,UAAW,GACX,OAAO,EAAM,MAAS,UACtB,OAAO,EAAM,OAAU,UAEvB,EAAI,IAAI,EAAM,KAAM,EAAM,KAAK,CAEnC,CACA,OAAO,CACT,EAEMC,EAAkB,GAA4B,CAClD,GAAI,CAAC,GAAS,OAAO,GAAU,SAC7B,MAAO,GAET,IAAM,EAAU,EAOhB,MAHA,EAHI,OAAO,EAAQ,eAAkB,UAAY,OAAO,EAAQ,IAAO,UAGnE,CAAC,EAAQ,OAAS,OAAO,EAAQ,OAAU,SAIjD,EAEMC,GAAoB,EAAa,IAA6C,CAClF,GAAI,CAAC,GAAS,OAAO,GAAU,SAC7B,MAAO,GAET,IAAM,EAAQ,EAsBd,MAHA,EAlBI,OAAO,EAAM,aAAgB,UAAY,EAAM,cAAgB,GAG/D,OAAO,EAAM,eAAkB,UAG/B,CAACF,EAAsB,IAAI,EAAM,aAA8B,GAG/D,OAAO,EAAM,MAAS,UAAY,EAAM,KAAK,SAAW,GAGxD,EAAM,OAAS,IAAA,IAAa,OAAO,EAAM,MAAS,UAGlD,CAACC,EAAe,EAAM,OAAO,GAG7B,CAAC,MAAM,QAAQ,EAAM,QAAQ,GAAK,CAAC,EAAM,SAAS,MAAMA,CAAc,EAI5E,EAEM,EAAa,GAA8C,CAC/D,GAAI,CAAC,EACH,MAAO,CAAC,EAEV,GAAI,CACF,IAAM,EAAkB,KAAK,MAAM,CAAG,EACtC,GAAI,CAAC,GAAU,OAAO,GAAW,SAC/B,MAAO,CAAC,EAEV,IAAM,EAA2B,CAAC,EAClC,IAAK,GAAM,CAAC,EAAK,KAAU,OAAO,QAAQ,CAAiC,EACrEC,EAAiB,EAAK,CAAK,EAC7B,EAAO,GAAO,EAEd,EAAQ,8DAA8D,GAAK,EAG/E,OAAO,CACT,OAAS,EAAO,CAEd,OADA,EAAQ,0DAA2D,CAAK,EACjE,CAAC,CACV,CACF,EAEM,EAAkB,GAAmD,CACzE,GAAI,CAAC,EACH,MAAO,CAAC,EAEV,GAAI,CACF,IAAM,EAAkB,KAAK,MAAM,CAAG,EACtC,GAAI,CAAC,GAAU,OAAO,GAAW,SAC/B,MAAO,CAAC,EAEV,IAAM,EAAgC,CAAC,EACvC,IAAK,GAAM,CAAC,EAAK,KAAU,OAAO,QAAQ,CAAiC,EAEvEF,EAAsB,IAAI,CAAoB,GAC9C,OAAO,GAAU,UACjB,EAAM,OAAS,IAEf,EAAO,GAAwB,GAGnC,OAAO,CACT,OAAS,EAAO,CAEd,OADA,EAAQ,+DAAgE,CAAK,EACtE,CAAC,CACV,CACF,EA6BM,GACJ,EACA,EAA2B,CAAC,IACT,CACnB,IAAM,EAAY,EAAQ,WAAa,OACjC,EAAU,EAAY,CAAM,EAE5B,EAAO,EAAU,EAAQ,IAAI,GAAG,EAAU,MAAM,CAAC,EACjD,EAAY,EAAe,EAAQ,IAAI,GAAG,EAAU,WAAW,CAAC,EAEhE,EAAY,EAAQ,IAAI,GAAG,EAAU,QAAQ,EAC/C,EAAmC,KACvC,GAAI,GAAa,EAAU,OAAS,GAAK,EAAK,GAC5C,EAAoB,MACf,CAIL,IAAM,EAAW,OAAO,KAAK,CAAI,EAAE,GAC/B,IACF,EAAoB,EAExB,CAEA,MAAO,CAAE,oBAAmB,OAAM,WAAU,CAC9C,EC/MM,EAAwB,IAAI,IAAmB,CAAC,MAAO,MAAO,MAAO,SAAS,CAAC,EAU/E,EAAkB,GAA4B,CAClD,GAAI,CAAC,GAAS,OAAO,GAAU,SAC7B,MAAO,GAET,IAAM,EAAU,EAOhB,MAHA,EAHI,OAAO,EAAQ,eAAkB,UAAY,OAAO,EAAQ,IAAO,UAGnE,CAAC,EAAQ,OAAS,OAAO,EAAQ,OAAU,SAIjD,EAgBM,GAAoB,EAAa,IAA6C,CAClF,GAAI,CAAC,GAAS,OAAO,GAAU,SAC7B,MAAO,GAET,IAAM,EAAQ,EAwBd,MAHA,EApBI,OAAO,EAAM,aAAgB,UAAY,EAAM,cAAgB,GAG/D,OAAO,EAAM,eAAkB,UAG/B,CAAC,EAAsB,IAAI,EAAM,aAA8B,GAG/D,OAAO,EAAM,MAAS,UAAY,EAAM,KAAK,SAAW,GAKxD,EAAM,OAAS,IAAA,IAAa,OAAO,EAAM,MAAS,UAGlD,CAAC,EAAe,EAAM,OAAO,GAG7B,CAAC,MAAM,QAAQ,EAAM,QAAQ,GAAK,CAAC,EAAM,SAAS,MAAM,CAAc,EAI5E,EAEA,IAAM,EAAN,KAAiD,CAC/C,QACA,aACA,UACA,oBACA,WACA,QASA,kBAA8C,QAAQ,QAAQ,EAE9D,YAAY,EAAuB,CAMjC,GALA,KAAK,QAAU,GAAG,EAAO,UAAU,OACnC,KAAK,aAAe,GAAG,EAAO,UAAU,YACxC,KAAK,UAAY,GAAG,EAAO,UAAU,SACrC,KAAK,oBAAsB,GAAG,EAAO,UAAU,oBAE3C,EAAO,YAAc,EAAO,QAC9B,KAAK,WAAa,EAAO,WACzB,KAAK,QAAU,EAAO,YACjB,CACL,IAAM,EAAW,EAA2B,EAC5C,KAAK,WAAa,EAAO,YAAc,EAAS,WAChD,KAAK,QAAU,EAAO,SAAW,EAAS,OAC5C,CACF,CAGA,MAAc,sBAAyB,EAAkC,CAKvE,IAAM,EAAW,KAAK,kBAElB,MAA4B,CAAC,EACjC,KAAK,kBAAoB,IAAI,QAAe,GAAM,CAChD,EAAU,CACZ,CAAC,EACD,GAAI,CACF,MAAM,CACR,MAAQ,CAGR,CACA,GAAI,CACF,OAAO,MAAM,EAAG,CAClB,QAAU,CACR,EAAQ,CACV,CACF,CAIA,MAAM,SAAqC,CACzC,GAAI,CACF,IAAM,EAAS,MAAM,KAAK,WAAW,QAAQ,KAAK,OAAO,EACzD,GAAI,CAAC,EACH,MAAO,CAAC,EAEV,IAAM,EAAkB,KAAK,MAAM,CAAM,EACzC,GAAI,CAAC,GAAU,OAAO,GAAW,SAE/B,OADA,MAAM,KAAK,UAAU,EACd,CAAC,EAEV,IAAM,EAA2B,CAAC,EAClC,IAAK,GAAM,CAAC,EAAK,KAAU,OAAO,QAAQ,CAAiC,EACrE,EAAiB,EAAK,CAAK,EAC7B,EAAO,GAAO,EAEd,EAAQ,0CAA0C,GAAK,EAG3D,OAAO,CACT,OAAS,EAAO,CAGd,OAFA,EAAQ,4CAA6C,CAAK,EAC1D,MAAM,KAAK,UAAU,EACd,CAAC,CACV,CACF,CAYA,QAAQ,EAAmD,CACzD,OAAO,KAAK,sBAAsB,SAAY,CAC5C,GAAI,CAEF,IAAM,EAAiC,CAAE,GAAG,MADrB,KAAK,QAAQ,CACiB,EACrD,IAAK,GAAM,CAAC,EAAa,KAAW,EAAM,CACxC,IAAM,EAAyB,CAC7B,QAAS,EAAO,QAChB,SAAU,EAAO,SACjB,cAAe,EAAO,UAAU,cAChC,cACA,KAAM,EAAO,UAAU,KACvB,KAAM,EAAO,UAAU,IACzB,EAOA,GAAI,CAAC,EAAiB,EAAa,CAAK,EACtC,MAAU,MAAM,qDAAqD,GAAa,EAEpF,EAAa,GAAe,CAC9B,CACA,MAAM,KAAK,WAAW,QAAQ,KAAK,QAAS,KAAK,UAAU,CAAY,CAAC,CAC1E,OAAS,EAAO,CACd,EAAQ,iCAAkC,CAAK,CACjD,CACF,CAAC,CACH,CAEA,gBAAgB,EAAoC,CAClD,OAAO,KAAK,sBAAsB,SAAY,CAC5C,GAAI,CACF,IAAM,EAAS,MAAM,KAAK,QAAQ,EAClC,GAAI,EAAO,GAAc,CACvB,GAAM,EAAG,GAAc,EAAG,GAAG,GAAc,EAC3C,MAAM,KAAK,WAAW,QAAQ,KAAK,QAAS,KAAK,UAAU,CAAS,CAAC,CACvE,CACF,OAAS,EAAO,CACd,EAAQ,sCAAsC,EAAY,GAAI,CAAK,CACrE,CACF,CAAC,CACH,CAEA,WAA2B,CACzB,OAAO,KAAK,sBAAsB,SAAY,CAC5C,MAAM,KAAK,WAAW,WAAW,KAAK,OAAO,CAC/C,CAAC,CACH,CAIA,MAAM,cAA+C,CACnD,GAAI,CACF,IAAM,EAAS,MAAM,KAAK,WAAW,QAAQ,KAAK,YAAY,EAC9D,GAAI,CAAC,EACH,MAAO,CAAC,EAEV,IAAM,EAAkB,KAAK,MAAM,CAAM,EACzC,GAAI,CAAC,GAAU,OAAO,GAAW,SAC/B,MAAO,CAAC,EAEV,IAAM,EAAgC,CAAC,EACvC,IAAK,GAAM,CAAC,EAAK,KAAU,OAAO,QAAQ,CAAiC,EAEvE,EAAsB,IAAI,CAAoB,GAC9C,OAAO,GAAU,UACjB,EAAM,OAAS,IAEf,EAAO,GAAwB,GAGnC,OAAO,CACT,OAAS,EAAO,CAEd,OADA,EAAQ,iDAAkD,CAAK,EACxD,CAAC,CACV,CACF,CAEA,MAAM,aAAa,EAAsD,CACvE,GAAI,CACF,IAAM,EAAsC,CAAC,EAC7C,IAAK,GAAM,CAAC,EAAU,KAAgB,EACpC,EAAa,GAAY,EAE3B,MAAM,KAAK,WAAW,QAAQ,KAAK,aAAc,KAAK,UAAU,CAAY,CAAC,CAC/E,OAAS,EAAO,CACd,EAAQ,sCAAuC,CAAK,CACtD,CACF,CAIA,MAAM,sBAA+C,CACnD,GAAI,CACF,IAAM,EAAQ,MAAM,KAAK,WAAW,QAAQ,KAAK,SAAS,EAC1D,OAAO,GAAS,EAAM,OAAS,EAAI,EAAQ,IAC7C,MAAQ,CACN,OAAO,IACT,CACF,CAEA,MAAM,qBAAqB,EAA2C,CACpE,GAAI,CACF,MAAO,IAAgB,KACnB,KAAK,WAAW,WAAW,KAAK,SAAS,EACzC,KAAK,WAAW,QAAQ,KAAK,UAAW,CAAW,EACzD,OAAS,EAAO,CACd,EAAQ,gDAAiD,CAAK,CAChE,CACF,CAIA,MAAM,UAA0B,CAK9B,MAAM,QAAQ,IAAI,CAChB,KAAK,UAAU,EACf,KAAK,WAAW,WAAW,KAAK,YAAY,EAC5C,KAAK,WAAW,WAAW,KAAK,SAAS,CAC3C,CAAC,CACH,CAYA,MAAM,oBAAuC,CAC3C,GAAI,CAEF,OAAO,MADa,KAAK,QAAQ,QAAQ,KAAK,mBAAmB,IAChD,MACnB,MAAQ,CACN,MAAO,EACT,CACF,CAEA,MAAM,qBAAqB,EAA+B,CACxD,GAAI,CACF,MAAO,EACH,KAAK,QAAQ,QAAQ,KAAK,oBAAqB,MAAM,EACrD,KAAK,QAAQ,WAAW,KAAK,mBAAmB,EACtD,MAAQ,CAER,CACF,CACF,EC/RA,MAAM,EAA4B,GAAoD,CAEpF,IAAM,EAAgB,IAAI,IAEpB,EAAU,GAAwB,CACtC,IAAM,EAAQ,EAAc,IAAI,CAAW,EACtC,KAGL,IAAI,CACF,EAAM,CACR,OAAS,EAAgB,CACvB,EAAQ,4BAA6B,CAAK,CAC5C,CACA,EAAc,OAAO,CAAW,CADhC,CAEF,EAEA,MAAO,CACL,QAAS,EAAa,IAAc,CAC7B,KAAU,UAKf,GAAO,CAAW,EAClB,GAAI,CACF,IAAM,EAAQ,EAAU,UAAW,GAAU,CAC3C,OAAQ,EAAM,KAAd,CACE,IAAK,iBACH,EAAS,iBAAiB,EAAa,EAAM,SAAU,EAAM,OAAO,EACpE,MAEF,IAAK,eAIH,EAAO,CAAW,EAClB,EAAS,eAAe,EAAa,EAAU,aAAa,EAC5D,MAEF,QAIF,CACF,CAAC,EACD,EAAc,IAAI,EAAa,CAAK,CACtC,OAAS,EAAgB,CACvB,EAAQ,+BAA+B,EAAY,GAAI,CAAK,CAC9D,CAzBkB,CA0BpB,EAEA,SAEA,cAAiB,CAEf,IAAM,EAAqB,CAAC,EAC5B,IAAK,IAAM,KAAM,EAAc,KAAK,EAClC,EAAI,KAAK,CAAE,EAEb,IAAK,IAAM,KAAM,EACf,EAAO,CAAE,CAEb,CACF,CACF,EC5CM,EAA0B,MAC9B,EACA,EACA,IAC4B,CAC5B,GAAI,EAAU,YAAa,CACzB,IAAM,EAAQ,MAAM,EAAU,YAAY,EAAE,UAAY,IAAI,EAC5D,GAAI,GAAS,EAAM,OAAS,EAC1B,OAAO,CAEX,CAIA,OAHI,EAAe,OAAS,EACnB,CAAC,GAAG,CAAc,EAEpB,CAAC,CAAe,CACzB,EAEM,EAAkB,MACtB,EACA,EACA,IAC4B,CAC5B,GAAI,CASF,MAAM,EAAU,QAAQ,CAAE,OAAQ,EAAK,CAAC,EAExC,IAAM,EAAe,MADM,EAAU,WAAW,GACX,EAAM,QAE3C,MAAO,CACL,cACA,MAAO,CACL,QAAS,EACT,SAAA,MALmB,EAAwB,EAAW,EAAM,SAAU,CAAY,EAMvE,WACb,EACA,KAAM,IACR,CACF,OAAS,EAAO,CACd,MAAO,CAAE,cAAa,QAAO,KAAM,MAAO,CAC5C,CACF,EAEM,GACJ,EACA,IACyB,CAUzB,IAAM,EAAU,IAAI,IAEpB,MAAO,CACL,aAAc,KAAO,IAAgB,CACnC,IAAM,EAAQ,EAAQ,IAAI,CAAW,EACrC,GAAI,CAAC,EACH,OAAO,KAET,IAAM,EAAY,EAAgB,CAAW,EAC7C,GAAI,CAAC,EAEH,OAAO,KAET,IAAM,EAAU,MAAM,EAAgB,EAAa,EAAO,CAAS,EAEnE,OADA,EAAQ,OAAO,CAAW,EACnB,CACT,EAEA,QAAS,SAAY,CACnB,GAAM,CAAC,EAAY,EAAiB,EAAc,GAAoB,MAAM,QAAQ,IAAI,CACtF,EAAQ,QAAQ,EAChB,EAAQ,aAAa,EACrB,EAAQ,qBAAqB,EAC7B,EAAQ,mBAAmB,CAC7B,CAAC,EAEK,EAAO,IAAI,IACX,EAA2D,CAAC,EAMlE,EAAQ,MAAM,EAKd,IAAM,EAAwC,CAAC,EAC/C,IAAK,GAAM,CAAC,EAAa,KAAU,OAAO,QAAQ,CAAU,EAAG,CAC7D,GAAI,CAAC,EACH,SAEF,IAAM,EAAY,EAAgB,CAAW,EAC7C,GAAI,CAAC,EAAW,CACd,EAAQ,IAAI,EAAa,CAAK,EAC9B,QACF,CACA,EAAM,KAAK,EAAgB,EAAa,EAAO,CAAS,CAAC,CAC3D,CAEA,IAAM,EAAW,MAAM,QAAQ,IAAI,CAAK,EACxC,IAAK,IAAM,KAAW,EAAU,CAC9B,GAAI,EAAQ,OAAS,KAAM,CACzB,EAAK,IAAI,EAAQ,YAAa,EAAQ,KAAK,EAC3C,QACF,CACA,EAAQ,KAAK,CAAE,YAAa,EAAQ,YAAa,OAAQ,EAAQ,KAAM,CAAC,CAC1E,CAIA,IAAM,EAAY,IAAI,IACtB,IAAK,GAAM,CAAC,EAAU,KAAgB,OAAO,QAAQ,CAAe,EAC9D,GAAe,EAAK,IAAI,CAAW,GACrC,EAAU,IAAI,EAA2B,CAAW,EAGxD,IAAK,GAAM,CAAC,EAAa,KAAW,EAAM,CACxC,IAAM,EAAW,EAAO,UAAU,cAC7B,EAAU,IAAI,CAAQ,GACzB,EAAU,IAAI,EAAU,CAAW,CAEvC,CAEA,IAAI,EAAmC,KAOvC,OANI,GAAgB,EAAK,IAAI,CAAY,EACvC,EAAoB,EACX,EAAK,KAAO,IACrB,EAAoB,EAAK,KAAK,EAAE,KAAK,EAAE,OAAS,MAG3C,CACL,oBACA,UACA,mBAAoB,EACpB,WAAY,CAAC,GAAG,EAAQ,KAAK,CAAC,EAC9B,OACA,WACF,CACF,EAEA,eAAkB,CAAC,GAAG,EAAQ,KAAK,CAAC,CACtC,CACF,EC7HM,EAAsB,CAC1B,kBAAmB,KACnB,sBAAuB,KACvB,gBAAiB,KACjB,iBAAkB,OAClB,WAAY,GACZ,mBAAoB,GACpB,KAAM,IAAI,IACV,gBAAiB,IAAI,IACrB,UAAW,IAAI,GACjB,EAGM,GACJ,EACA,IACuB,CACvB,IAAK,GAAM,CAAC,EAAI,KAAW,EACzB,GAAI,EAAO,UAAU,gBAAkB,EACrC,OAAO,CAIb,EAIM,GAAW,EAAc,IAAwB,CACrD,OAAQ,EAAM,KAAd,CACE,IAAK,WAAY,CASf,IAAM,EAAO,IAAI,IAA6B,EAAM,IAAI,EACxD,IAAK,GAAM,CAAC,EAAI,KAAU,EAAM,KAC9B,EAAK,IAAI,EAAI,CAAK,EAEpB,IAAM,EAAY,IAAI,IAA2B,EAAM,SAAS,EAChE,IAAK,GAAM,CAAC,EAAU,KAAO,EAAM,UACjC,EAAU,IAAI,EAAU,CAAE,EAK5B,IAAI,EAAmC,KACnC,EAAM,mBAAqB,EAAK,IAAI,EAAM,iBAAiB,EAC7D,EAAoB,EAAM,kBACjB,EAAM,mBAAqB,EAAK,IAAI,EAAM,iBAAiB,EACpE,EAAoB,EAAM,kBACjB,EAAK,KAAO,IACrB,EAAoB,EAAK,KAAK,EAAE,KAAK,EAAE,OAAS,MAOlD,IAAI,EAAwC,EAAM,gBAClD,GAAI,EAAM,gBAAgB,KAAO,GAAK,EAAM,KAAK,KAAO,EAAG,CACzD,IAAM,EAAO,IAAI,IAAI,EAAM,eAAe,EAC1C,IAAK,IAAM,KAAM,EAAM,KAAK,KAAK,EAC/B,EAAK,OAAO,CAAE,EAEZ,EAAK,OAAS,EAAM,gBAAgB,OACtC,EAAmB,EAEvB,CACA,MAAO,CACL,GAAG,EACH,oBACA,WAAY,GACZ,mBAAoB,EAAM,mBAC1B,OACA,gBAAiB,EACjB,WACF,CACF,CAEA,IAAK,wBACH,MAAO,CAAE,GAAG,EAAO,mBAAoB,EAAM,KAAM,EAGrD,IAAK,kBACH,MAAO,CACL,GAAG,EACH,sBAAuB,EAAM,YAC7B,gBAAiB,KACjB,iBAAkB,YACpB,EAGF,IAAK,oBAAqB,CACxB,GAAM,CAAE,cAAa,SAAU,EACzB,EAAW,EAAM,KAAK,IAAI,CAAW,EAMrC,EAAwC,EAAM,gBAAgB,IAAI,CAAW,EAC/E,IAAI,IAAI,CAAC,GAAG,EAAM,eAAe,EAAE,OAAQ,GAAO,IAAO,CAAW,CAAC,EACrE,EAAM,gBAIV,GACE,GACA,EAAS,QAAQ,cAAc,YAAY,IAAM,EAAM,QAAQ,cAAc,YAAY,EAEzF,MAAO,CACL,GAAG,EACH,kBAAmB,EACnB,sBAAuB,KACvB,iBAAkB,UAClB,KAKE,EAAS,YAAc,EAAM,UACzB,EAAM,KACN,IAAI,IAAI,CAAC,GAAG,EAAM,KAAM,CAAC,EAAa,CAAK,CAAU,CAAC,EAC5D,gBAAiB,CACnB,EAGF,IAAM,EAAU,IAAI,IAAI,CAAC,GAAG,EAAM,KAAM,CAAC,EAAa,CAAK,CAAU,CAAC,EAChE,EAAe,IAAI,IAAI,CAC3B,GAAG,EAAM,UACT,CAAC,EAAM,UAAU,cAAe,CAAW,CAC7C,CAAC,EAED,MAAO,CACL,GAAG,EACH,kBAAmB,EACnB,sBAAuB,KACvB,iBAAkB,UAClB,KAAM,EACN,gBAAiB,EACjB,UAAW,CACb,CACF,CAEA,IAAK,iBACH,MAAO,CACL,GAAG,EACH,sBAAuB,KACvB,gBAAiB,EAAM,MACvB,iBAAkB,OACpB,EAGF,IAAK,eAAgB,CACnB,IAAM,EAAS,EAAM,KAAK,IAAI,EAAM,WAAW,EAC/C,GAAI,CAAC,EACH,OAAO,EAGT,IAAM,EAAU,IAAI,IAAI,EAAM,IAAI,EAClC,EAAQ,OAAO,EAAM,WAAW,EAEhC,IAAM,EAAe,IAAI,IAAI,EAAM,SAAS,EACtC,EAAW,EAAO,UAAU,cAClC,GAAI,EAAa,IAAI,CAAQ,IAAM,EAAM,YAAa,CACpD,EAAa,OAAO,CAAQ,EAC5B,IAAM,EAAW,EAAyB,EAAS,CAAQ,EACvD,GACF,EAAa,IAAI,EAAU,CAAQ,CAEvC,CAEA,IAAM,EAAU,EAAQ,KAAK,EAAE,KAAK,EAC9B,EACJ,EAAM,oBAAsB,EAAM,YAC7B,EAAQ,OAAS,KAClB,EAAM,kBAEN,EAAwC,EAAM,gBAAgB,IAAI,EAAM,WAAW,EACrF,IAAI,IAAI,CAAC,GAAG,EAAM,eAAe,EAAE,OAAQ,GAAO,IAAO,EAAM,WAAW,CAAC,EAC3E,EAAM,gBAEV,MAAO,CACL,GAAG,EACH,oBACA,KAAM,EACN,gBAAiB,EACjB,UAAW,CACb,CACF,CAEA,IAAK,qBAAsB,CACzB,IAAM,EAAS,EAAM,KAAK,IAAI,EAAM,WAAW,EAC/C,GAAI,CAAC,EACH,OAAO,EAQT,IAAI,EACJ,AAQE,EARE,EAAM,OACM,EAAM,OAEI,EAAM,SAAS,KACpC,GACC,EAAE,gBAAkB,EAAO,QAAQ,eACnC,EAAE,MAAM,KAAO,EAAO,QAAQ,MAAM,EAEZ,EAAI,EAAO,QAAW,EAAM,SAAS,IAAM,EAAO,QAOhF,IAAM,EACJ,EAAY,gBAAkB,EAAO,QAAQ,eAC7C,EAAY,MAAM,KAAO,EAAO,QAAQ,MAAM,GAC1C,EACJ,EAAM,SAAS,SAAW,EAAO,SAAS,QAC1C,EAAM,SAAS,OAAO,EAAG,IAAM,EAAE,gBAAkB,EAAO,SAAS,IAAI,aAAa,EACtF,GAAI,GAAe,EACjB,OAAO,EAET,IAAM,EAA2B,CAC/B,GAAG,EACH,QAAS,EACT,SAAU,CAAC,GAAG,EAAM,QAAQ,CAC9B,EACM,EAAU,IAAI,IAAI,CAAC,GAAG,EAAM,KAAM,CAAC,EAAM,YAAa,CAAO,CAAU,CAAC,EAC9E,MAAO,CAAE,GAAG,EAAO,KAAM,CAAQ,CACnC,CAEA,IAAK,mBAAoB,CACvB,IAAM,EAAS,EAAM,KAAK,IAAI,EAAM,WAAW,EAC/C,GAAI,CAAC,EACH,OAAO,EAET,IAAM,EAAU,IAAI,IAAI,CAAC,GAAG,EAAM,KAAM,CAAC,EAAM,YAAa,CAAE,GAAG,CAAO,CAAC,CAAU,CAAC,EACpF,MAAO,CAAE,GAAG,EAAO,KAAM,CAAQ,CACnC,CAEA,IAAK,iBACH,MAAO,CAAE,GAAG,EAAO,kBAAmB,EAAM,WAAY,EAG1D,IAAK,oBAAqB,CACxB,IAAM,EAAe,IAAI,IAAI,EAAM,SAAS,EAM5C,OALI,EAAM,cAAgB,KACxB,EAAa,OAAO,EAAM,aAAa,EAEvC,EAAa,IAAI,EAAM,cAAe,EAAM,WAAW,EAElD,CAAE,GAAG,EAAO,UAAW,CAAa,CAC7C,CAEA,IAAK,eACH,MAAO,CACL,GAAG,EACH,sBAAuB,KACvB,gBAAiB,KACjB,iBAAkB,MACpB,EAGF,IAAK,YACH,MAAO,CACL,GAAG,EACH,gBAAiB,EAAM,MACvB,iBAAkB,EAAM,MAAQ,QAAU,MAC5C,EAGF,IAAK,QACH,MAAO,CACL,GAAG,EACH,WAAY,EAAM,WAClB,mBAAoB,GACpB,gBAAiB,IAAI,GACvB,EAGF,QAIE,OAAO,CAEX,CACF,EC/WA,IAAM,EAAN,cAAmC,KAAM,CAMvC,KAAgB,oBAChB,YACA,OACA,YAAY,EAAgB,EAAqB,CAC/C,MACE,UAAU,EAAO,+BAA+B,EAAY,0FAC9D,EACA,KAAK,KAAO,uBACZ,KAAK,YAAc,EACnB,KAAK,OAAS,CAChB,CACF,EAEA,MAAM,EAA6C,OAAO,OAAO,CAC/D,WAAY,GACZ,sBAAuB,GACvB,gBAAiB,GACjB,gBAAiB,GACjB,OAAQ,GACR,YAAa,GACb,gBAAiB,GACjB,UAAW,GACX,cAAe,GACf,YAAa,EACf,CAAC,EAwBK,EAAuB,GAA0C,CACrE,IAAM,EAAK,EAAM,YACX,EAAO,EAAM,KACb,EAAO,EAAM,KAEb,EAAU,GACd,QAAQ,OAAO,IAAI,EAAqB,EAAQ,CAAE,CAAC,EAO/C,EAAO,CACX,aAAc,EACd,YAAe,EAAO,SAAS,EAC/B,eAAkB,EAAO,YAAY,EACrC,eAAkB,EAAO,YAAY,EACrC,eAAkB,EAAO,YAAY,EACrC,cAAiB,EAAO,WAAW,EACnC,0BAA6B,EAAO,uBAAuB,EAC3D,OACA,KACA,OACA,WAAc,EAAO,QAAQ,EAC7B,kBAAqB,EAAO,eAAe,EAC3C,gBAAmB,EAAO,aAAa,EACvC,gBAAmB,EAAO,aAAa,CACzC,EAGA,OAAQ,EAAM,cAAd,CACE,IAAK,UACH,MAAO,CAAE,GAAG,EAAM,cAAe,SAAU,EAE7C,IAAK,MACH,MAAO,CAAE,GAAG,EAAM,cAAe,KAAM,EAEzC,IAAK,MACH,MAAO,CAAE,GAAG,EAAM,cAAe,KAAM,EAEzC,IAAK,MACH,MAAO,CAAE,GAAG,EAAM,cAAe,KAAM,EAEzC,IAAK,WACH,MAAO,CAAE,GAAG,EAAM,cAAe,UAAW,EAE9C,QAIE,MAF2B,EAAM,cAEvB,MAAM,iCAAiC,EAAM,eAAyB,CAEpF,CACF,ECvIM,EAAM,MAAO,EAAyB,IAAiD,CAC3F,GAAI,CACF,MAAM,EAAG,CACX,OAAS,EAAO,CACd,EAAQ,CAAK,CACf,CACF,ECuBM,EAAyB,GAAoC,CACjE,IAAM,EAAO,IAAI,IACX,EAAkB,IAAI,IAC5B,IAAK,GAAM,CAAC,EAAa,KAAU,OAAO,QAAQ,EAAS,IAAI,EAAG,CAChE,GAAI,CAAC,EACH,SAEF,IAAM,EAA2B,EAAoB,CAAK,EAC1D,EAAK,IAAI,EAAa,CACpB,QAAS,EAAM,QACf,SAAU,CAAC,GAAG,EAAM,QAAQ,EAC5B,WACF,CAAC,EACD,EAAgB,IAAI,CAAW,CACjC,CACA,IAAM,EAAY,IAAI,IACtB,IAAK,GAAM,CAAC,EAAU,KAAgB,OAAO,QAAQ,EAAS,SAAS,EACjE,GAAe,EAAK,IAAI,CAAW,GACrC,EAAU,IAAI,EAA2B,CAAW,EAGxD,IAAM,EACJ,EAAS,mBAAqB,EAAK,IAAI,EAAS,iBAAiB,EAC7D,EAAS,kBACR,EAAK,KAAK,EAAE,KAAK,EAAE,OAAS,KACnC,MAAO,CACL,GAAG,EACH,oBACA,WAAY,GACZ,OACA,kBACA,WACF,CACF,EAKM,EAAoB,GAAyB,EAAK,YAAY,EA8B9D,EAAqB,GAAgC,CACzD,IAAM,EAAmB,EAAO,kBAAoB,OAC9C,EAAU,EAAO,SAAW,IAAI,EAAc,CAAE,UAAW,CAAiB,CAAC,EAK7E,EAAsB,GAAqB,GAAmB,CAClE,GAAI,EAAO,eAAgB,CACzB,GAAI,CACF,EAAO,eAAe,EAAO,CAAO,CACtC,OAAS,EAAkB,CACzB,EAAQ,+BAAgC,CAAO,CACjD,CACA,MACF,CACA,EAAQ,UAAU,EAAQ,GAAI,CAAK,CACrC,EAOM,EAAY,EAA2B,EAAS,EAAO,eAAe,EAE5E,OAAO,EAAoC,GAAG,EAAK,IAAQ,CACzD,IAAM,EAAY,GAAiB,CACjC,EAAK,GAAS,EAAQ,EAAM,CAAK,EAAG,EAAK,CAC3C,EAIM,MACJ,MAAU,EAAQ,QAAQ,EAAI,EAAE,IAAI,EAAG,EAAmB,wBAAwB,CAAC,EAE/E,MACJ,MACQ,EAAQ,aAAa,EAAI,EAAE,SAAS,EAC1C,EAAmB,6BAA6B,CAClD,EAEI,MACJ,MACQ,EAAQ,qBAAqB,EAAI,EAAE,iBAAiB,EAC1D,EAAmB,oCAAoC,CACzD,EASI,GAAoB,EAAqB,EAA0B,IAAqB,CAC5F,EAAS,CAAE,WAAU,SAAQ,cAAa,KAAM,oBAAqB,CAAC,EACtE,EAAiB,CACnB,EAOM,EAAY,EAAyB,CACzC,kBAAmB,EAAa,EAAU,IAAW,CAEnD,EAAiB,EAAa,CAAC,GAAG,CAAQ,EAAG,CAAM,CACrD,EACA,gBAAiB,EAAa,IAAkB,CAY9C,EAAS,CAAE,cAAa,KAAM,cAAe,CAAC,EAC9C,EAAO,eAAe,CAAa,CACrC,CACF,CAAC,EAMD,MAAO,CACL,GALoB,EAAO,aACzB,EAAsB,EAAO,YAAY,EACzC,EAIF,QAAS,EACT,SAAU,EACV,cAAe,MAAO,EAAa,EAAW,IAAY,CACxD,EAAI,EAAE,oBAAoB,EAAK,EAE/B,IAAM,EAAY,EAAO,gBAAgB,CAAW,EACpD,GAAI,CAAC,EACH,MAAU,MAAM,kCAAkC,GAAa,EAGjE,EAAS,CAAE,cAAa,KAAM,iBAAkB,CAAC,EAIjD,IAAM,EAAgB,EAAO,wBAA0B,IACjD,EAAY,EAAO,cACrB,eAAiB,CACf,GAAI,CACF,EAAO,gBAAgB,CAAW,CACpC,OAAS,EAAkB,CACzB,EAAQ,8BAA+B,CAAO,CAChD,CACF,EAAG,CAAa,EAChB,KAEJ,GAAI,CACF,IAAM,EAAiB,EAAU,QAAQ,EAEzC,EAAoB,UAAY,CAAC,CAAC,EAClC,MAAM,QAAQ,KAAK,CACjB,EACA,IAAI,SAAS,EAAG,IAAW,CACzB,eAAiB,CACf,EAAW,MAAM,oBAAoB,CAAC,CACxC,EAAG,GAAkB,CACvB,CAAC,CACH,CAAC,EAED,IAAM,EAAU,MAAM,EAAU,WAAW,EAC3C,GAAI,CAAC,EACH,MAAU,MAAM,uBAAuB,EAIzC,IAAM,EAAkB,EAAU,YAC9B,MAAM,EAAU,YAAY,EAAE,UAAY,IAAI,EAC9C,KAIE,EAAyB,CAAE,UAAS,SAFxC,GAAmB,EAAgB,OAAS,EAAI,EAAkB,CAAC,CAAO,EAExB,WAAU,EAC9D,EAAS,CAAE,cAAa,QAAO,KAAM,mBAAoB,CAAC,EAE1D,EAAU,OAAO,EAAa,CAAS,EAMvC,MAAM,QAAQ,IAAI,CAAC,EAAY,EAAG,EAAiB,EAAG,EAAc,CAAC,CAAC,EAEtE,EAAO,YAAY,CAAK,EACxB,IAAY,CAAK,CACnB,OAAS,EAAO,CACd,IAAM,EAAa,EAAmB,CAAK,EAC3C,EAAS,CAAE,MAAO,EAAY,KAAM,gBAAiB,CAAC,EACtD,GAAI,CACF,MAAM,EAAU,aAAa,CAC/B,OAAS,EAA0B,CACjC,EAAQ,kDAAmD,CAAe,CAC5E,CACA,GAAI,CACF,EAAO,iBAAiB,EAAY,CAAW,CACjD,OAAS,EAAkB,CACzB,EAAQ,+BAAgC,CAAO,CACjD,CAEA,MADA,IAAU,CAAc,EAClB,CACR,QAAU,CACJ,GACF,aAAa,CAAS,CAE1B,CACF,EAEA,iBAAmB,GAAgB,CACjC,EAAI,EAAE,oBAAoB,EAAI,EAE9B,IAAM,EAAQ,EAAI,EAClB,GAAI,CAAC,EAAM,WACT,OAEF,IAAM,EAAS,EAAM,KAAK,IAAI,CAAW,EACzC,GAAI,CAAC,EACH,OAGF,EAAU,OAAO,CAAW,EAE5B,MAAe,EAAO,UAAU,aAAa,GAAK,QAAQ,QAAQ,EAAG,CAAQ,EAE7E,IAAM,EAAW,EAAO,UAAU,cAClC,EAAS,CAAE,cAAa,KAAM,cAAe,CAAC,EAE1C,EAAI,EAAE,KAAK,OAAS,EACtB,MAAe,EAAQ,SAAS,EAAG,EAAmB,yBAAyB,CAAC,GAOhF,MACQ,EAAQ,gBAAgB,CAAW,EACzC,EAAmB,6BAA6B,CAClD,EACA,EAAsB,EACtB,EAAmB,GAGrB,EAAO,eAAe,CAAQ,CAChC,EAEA,qBAAuB,GAAO,EAAO,gBAAgB,CAAE,EAEvD,eAAgB,SAAY,CAC1B,IAAM,EAAS,MAAM,EAAU,QAAQ,EACvC,EAAS,CACP,kBAAmB,EAAO,kBAC1B,mBAAoB,EAAO,mBAC3B,KAAM,EAAO,KACb,UAAW,EAAO,UAClB,KAAM,UACR,CAAC,EAID,IAAK,GAAM,CAAC,EAAa,KAAW,EAAO,KACzC,EAAU,OAAO,EAAa,EAAO,SAAS,EAmBhD,GAhBA,MAAM,QAAQ,IAAI,CAAC,EAAiB,EAAG,EAAc,CAAC,CAAC,EAUvD,QAAa,IAAI,EAAU,WAAW,EAAE,IAAK,GAAO,EAAI,EAAE,sBAAsB,CAAE,CAAC,CAAC,EAMhF,EAAO,WACT,GAAI,CACF,EAAO,WAAW,CAChB,QAAS,EAAO,QAChB,WAAY,CAAC,GAAG,EAAO,UAAU,EACjC,YAAa,CAAC,GAAG,EAAO,KAAK,KAAK,CAAC,CACrC,CAAC,CACH,OAAS,EAAkB,CACzB,EAAQ,2BAA4B,CAAO,CAC7C,MACK,GAAI,EAAO,QAAQ,OAAS,EAGjC,IAAK,GAAM,CAAE,cAAa,YAAY,EAAO,QAC3C,EAAQ,sCAAsC,EAAY,GAAI,CAAM,CAG1E,EACA,cAAgB,GAAgB,CAC9B,EAAS,CAAE,cAAa,KAAM,kBAAmB,CAAC,CACpD,EAEA,gBAAiB,KAAO,IAAgB,CACtC,IAAM,EAAQ,EAAI,EAAE,KAAK,IAAI,CAAW,EACxC,GAAI,CAAC,EACH,OAOF,MAAM,EAAM,UAAU,kBAAkB,EACxC,IAAM,EAAQ,EAAM,UAAU,YAC1B,MAAM,EAAM,UAAU,YAAY,EAAE,UAAY,IAAI,EACpD,KACA,CAAC,GAAS,EAAM,SAAW,GAK/B,EAAiB,EAAa,CAAK,CACrC,EAEA,UAAa,CACX,EAAI,EAAE,oBAAoB,EAAI,EAE9B,IAAM,EAAQ,EAAI,EACb,KAAM,WAIX,GAAU,UAAU,EAEpB,IAAK,IAAM,KAAU,EAAM,KAAK,OAAO,EACrC,MAAe,EAAO,UAAU,aAAa,GAAK,QAAQ,QAAQ,EAAG,CAAQ,EAK/E,GAFA,MAAe,EAAQ,SAAS,EAAG,EAAmB,yBAAyB,CAAC,EAE5E,EAAO,QAAS,CAClB,IAAM,EAAU,EAAO,QACvB,MAAe,QAAQ,QAAQ,EAAQ,CAAC,EAAG,CAAQ,CACrD,CAEA,EAAS,CAAE,KAAM,OAAQ,CAAC,CAbN,CActB,EAEA,0BAA6B,CAC3B,EAAS,CAAE,KAAM,cAAe,CAAC,CACnC,EAEA,mBAAqB,GAAgB,CACnC,EAAS,CAAE,cAAa,KAAM,gBAAiB,CAAC,EAChD,EAAmB,CACrB,EAEA,mBAAqB,GAAU,CAC7B,EAAS,CAAE,QAAO,KAAM,WAAY,CAAC,CACvC,EAEA,cAAe,EAAe,IAAgB,CAC5C,EAAS,CAAE,gBAAe,cAAa,KAAM,mBAAoB,CAAC,EAClE,EAAsB,CACxB,EAEA,oBAAsB,GAAmB,CACvC,EAAS,CAAE,KAAM,wBAAyB,OAAM,CAAC,EACjD,MACQ,EAAQ,qBAAqB,CAAK,EACxC,EAAmB,qCAAqC,CAC1D,CACF,EAEA,sBAAuB,KAAO,IAAgB,CAM5C,IAAM,EAAU,MAAM,EAAU,aAAa,CAAW,EACnD,KAGL,IAAI,EAAQ,OAAS,OAAQ,CAC3B,EAAQ,kCAAkC,EAAY,GAAI,EAAQ,KAAK,EACvE,MACF,CAGA,EAAS,CAAE,cAAa,MAAO,EAAQ,MAAO,KAAM,mBAAoB,CAAC,EACzE,EAAU,OAAO,EAAa,EAAQ,MAAM,SAAS,EACrD,MAAM,QAAQ,IAAI,CAAC,EAAY,EAAG,EAAiB,EAAG,EAAc,CAAC,CAAC,EACtE,EAAO,YAAY,EAAQ,KAAK,CANhC,CAOF,EAEA,qBAAsB,EAAa,IAAY,CAE7C,IAAM,EAAQ,EAAI,EAAE,KAAK,IAAI,CAAW,EACxC,GAAI,CAAC,EACH,OAEF,IAAM,EAAW,EAAQ,MACnB,EAAW,EAAM,SAAS,IAAK,GACnC,EAAE,MAAM,KAAO,EAAS,GACpB,EACA,CACE,MAAO,EACP,GAAI,GAAG,EAAS,GAAG,GAAG,EAAiB,EAAE,aAAa,IACtD,cAAe,EAAE,aACnB,CACN,EAKA,EAAiB,EAJJ,EAAS,KACnB,GAAM,EAAiB,EAAE,aAAa,IAAM,EAAiB,EAAQ,aAAa,CAEjE,EAAI,EAAW,CAAC,GAAG,EAAU,CAAO,EAChB,CAAO,CACjD,CACF,CACF,CAAC,CACH,EC3dM,GAAe,EAAgC,IAC/C,IAAM,EACD,GAEL,CAAC,GAAK,CAAC,EACF,GAGP,EAAE,UAAU,KAAO,EAAE,UAAU,IAC/B,EAAE,QAAQ,gBAAkB,EAAE,QAAQ,eACtC,EAAE,QAAQ,MAAM,KAAO,EAAE,QAAQ,MAAM,GCRrC,EAAgB,GAAiD,CACrE,GAAI,IAAS,IAAA,GACX,OAEF,IAAM,EAAU,EAAK,KAAK,EAC1B,OAAO,EAAQ,OAAS,EAAI,EAAU,IAAA,EACxC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usebutr/core",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Core types, store, storage, and discovery seam for butr. No React, no protocols.",
5
5
  "license": "MIT",
6
6
  "author": "Pedro Filho <pedro@filho.me>",
@@ -25,6 +25,7 @@
25
25
  "access": "public"
26
26
  },
27
27
  "devDependencies": {
28
+ "@types/node": "^25.9.1",
28
29
  "typescript": "^6.0.3",
29
30
  "vitest": "^4.1.8",
30
31
  "zustand": "^5.0.14",