@usebutr/react 0.1.1 → 0.1.3

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
@@ -1,4 +1,4 @@
1
- import { Account, Balance, ChainPlatform, ConnectedWallet, ConnectorMeta, WalletAdapter, WalletManagerConfig, WalletSource, WalletStore, WalletStoreState } from "@usebutr/core";
1
+ import { Account, Balance, ChainPlatform, ConnectedWallet, ConnectorMeta, WalletAdapter, WalletManagerConfig, WalletSnapshot, WalletSource, WalletStore, WalletStoreState } from "@usebutr/core";
2
2
  import React from "react";
3
3
 
4
4
  //#region src/context.d.ts
@@ -16,6 +16,21 @@ type WalletManagerProviderProps = {
16
16
  /** Auto-discovery source. Omit to skip auto-discovery; no
17
17
  * protocol code enters the bundle. */
18
18
  discovery?: WalletSource;
19
+ /**
20
+ * Seed the store synchronously with persisted wallet state — typically
21
+ * the return value of `readWalletSnapshot(cookies, { keyPrefix })`
22
+ * called from a Server Component. With `initialState`, the store
23
+ * starts with `isHydrated: true`, the pool populated with shadow
24
+ * adapters, and every connector id in `reconnectingIds`. The
25
+ * primary hooks (`useActiveWallet`, `useConnectedWallets`, …)
26
+ * return values from render zero on both server and client.
27
+ *
28
+ * Pass `undefined` to fall back to the legacy async hydration path
29
+ * (`isHydrated` starts `false`, flips `true` after `hydrateWallets`
30
+ * resolves on mount). Apps that don't render server-side typically
31
+ * leave this unset.
32
+ */
33
+ initialState?: WalletSnapshot;
19
34
  onConnect?: WalletManagerConfig["onConnect"];
20
35
  onConnectError?: WalletManagerConfig["onConnectError"];
21
36
  onDisconnect?: WalletManagerConfig["onDisconnect"];
@@ -46,7 +61,22 @@ declare const useWalletStoreContext: () => WalletStore;
46
61
  declare const useDiscoveredWallets: () => ReadonlyArray<WalletAdapter>;
47
62
  //#endregion
48
63
  //#region src/hooks/selectors.d.ts
49
- /** Connection status: "idle" | "connecting" | "success" | "error". */
64
+ /**
65
+ * Connection status of the **active** wallet — wagmi-aligned vocabulary:
66
+ *
67
+ * - `"idle"` / `"connecting"` / `"success"` / `"error"` — the
68
+ * user-initiated connect attempt's state, as written by the
69
+ * reducer.
70
+ * - `"reconnecting"` — derived. Returned when the active wallet is
71
+ * still backed by a shadow adapter (its connector id appears in
72
+ * `state.reconnectingIds`). Indicates "we have the data, the
73
+ * silent reconnect hasn't verified the live wallet yet."
74
+ *
75
+ * The derivation lives here rather than in the reducer so the state
76
+ * machine stays a narrow tracker of the in-flight connect attempt
77
+ * while the public hook gives consumers the broader vocabulary they
78
+ * need to render.
79
+ */
50
80
  declare const useConnectionStatus: () => import("@usebutr/core").ConnectionStatus;
51
81
  /** True iff `connectionStatus === "connecting"`. */
52
82
  declare const useIsConnecting: () => boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/context.tsx","../src/hooks/selectors.ts","../src/hooks/actions.ts","../src/hooks/async-resources.ts"],"mappings":";;;;cAUM,kBAAA,EAAoB,KAAA,CAAM,OAAO,CAAC,WAAA;;AAFuC;;;;;KAgB1E,0BAAA;EACH,QAAA,EAAU,KAAA,CAAM,SAAA,EAfiC;EAiBjD,UAAA,GAAa,KAAA,CAAM,aAAA,GAHhB;EAKH,eAAA,IAAmB,EAAA,aAAe,aAAA;;;EAGlC,SAAA,GAAY,YAAA;EACZ,SAAA,GAAY,mBAAA;EACZ,cAAA,GAAiB,mBAAA;EACjB,YAAA,GAAe,mBAAA;EACf,UAAA,GAAa,mBAAA;EACb,OAAA,GAAU,mBAAA;EACV,aAAA,GAAgB,mBAAA;EAChB,cAAA,GAAiB,mBAAA;EACjB,sBAAA,GAAyB,mBAAA;EACzB,OAAA,GAAU,mBAAA;EACV,gBAAA,GAAmB,mBAAA;AAAA;;;;;;;;;;;cAmCf,qBAAA,EAAuB,KAAA,CAAM,EAAE,CAAC,0BAAA;;;cAgEhC,qBAAA,QAA4B,WAMjC;;;cAIK,oBAAA,QAA2B,aAAa,CAAC,aAAA;;;;cCnGzC,mBAAA,gCAAmB,gBAGxB;;cAGK,eAAA;;cAMA,wBAAA;;cAMA,oBAAA;;cAMA,kBAAA,gCAAkB,eAAA;;cAMlB,kBAAA;ADxE6C;AAAA,cC8E7C,aAAA;;;;cAQA,qBAAA;;cAUA,OAAA,QAAO,GAAA,SAAA,eAAA;;cAMP,mBAAA,QAA0B,KAAK,CAAC,eAAA;;cAMhC,YAAA,QAAY,GAAA,CAAA,aAAA;;cAMZ,eAAA,QAAsB,eAAe;;cAUrC,iBAAA,GAAqB,aAAA,EAAe,aAAA,YAAuB,eAAe;;cAgB1E,sBAAA,GAA0B,aAA4B,EAAb,aAAa;;;;;;;;cAYtD,WAAA,GAAe,WAAA,qBAA8B,aAAa,CAAC,OAAA;;;;;;;;;;;cAuB3D,cAAA,GAAkB,WAAA,gCAAyC,eAAe;;cAiB1E,YAAA,SAEI,WAAA,aAAmB,eAAA;;cAIvB,oBAAA,SAEI,aAAA,EAAe,aAAA,KAAa,eAAA;;cAQhC,uBAAA,SAAuB,EAAA,aAAA,UAAA,yBAAA,mBAAA;;;;;;;;;;ADhLW;AAAA;;cCqMlC,cAAA,MAAqB,QAAA,GAAW,KAAA,EAAO,gBAAA,KAAqB,CAAA,KAAC,CAAA;;;;;;;ADvOY;;;;;;;;AAE5B;AAAA;cES7C,gBAAA,SAAgB,WAAA,UAAA,SAAA,IAAA,MAAA,0BAAA,eAAA,WAAA,OAAA,IAAA,KAAA,EAAA,KAAA,cAAA,OAAA;AAAA,cAKhB,mBAAA,SAAmB,WAAA;AAAA,cAKnB,qBAAA,SAAqB,WAAA;AAAA,cAKrB,eAAA,SAAe,aAAA,0BAAA,aAAA,EAAA,WAAA;AAAA,cAKf,cAAA;AAAA,cAKA,sBAAA,SAAsB,WAAA,UAAA,OAAA,0BAAA,OAAA;AAAA,cAKtB,gBAAA,SAAgB,WAAA;;;;;;;;;;;cAehB,kBAAA,SAAkB,WAAA,aAAA,OAAA;;;cAOlB,wBAAA;AAAA,cAKA,qBAAA,SAAqB,KAAA,0BAAA,eAAA;;;;;;AFpEoD;;;;;;;;KGS1E,UAAA;EACC,IAAA;EAAY,KAAA;EAAa,MAAA;AAAA;EACzB,IAAA;EAAY,KAAA;EAAa,MAAA;AAAA;EACzB,IAAA,EAAM,CAAC;EAAE,KAAA;EAAa,MAAA;AAAA;EACtB,IAAA;EAAY,KAAA;EAAgB,MAAA;AAAA;;;;;;;;;;;;;cA+F5B,SAAA,GAAa,WAAA,qBAA8B,UAAU;AAAA,KAQtD,gBAAA,GAAmB,UAAU,CAAC,OAAA;EAAa,OAAA;AAAA;;;;;;;;;cAU1C,UAAA,GAAc,WAAA,kBAA6B,IAAA,cAAgB,gBAehE"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/context.tsx","../src/hooks/selectors.ts","../src/hooks/actions.ts","../src/hooks/async-resources.ts"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import{createWalletStore as e,logError as t,walletEqual as n}from"@usebutr/core";import{createContext as r,use as i,useCallback as a,useEffect as o,useMemo as s,useReducer as c,useRef as l,useState as u}from"react";import{jsx as d}from"react/jsx-runtime";import{useStore as f}from"zustand";import{shallow as p}from"zustand/shallow";import{useStoreWithEqualityFn as m}from"zustand/traditional";const h=r(null),g=[],_=r(g),v=(e,t)=>{let n=t.createConnector;return{connectors:t.connectors??[],createConnector:t=>e.get(t)??n?.(t)??null,onConnect:t.onConnect,onConnectError:t.onConnectError,onDisconnect:t.onDisconnect,onHydrated:t.onHydrated,onReset:t.onReset,onSlowConnect:t.onSlowConnect,onStorageError:t.onStorageError,slowConnectThresholdMs:t.slowConnectThresholdMs,storage:t.storage,storageKeyPrefix:t.storageKeyPrefix}},y=n=>{let{children:r,discovery:i}=n,[a]=u(()=>new Map),[s,c]=u(g),[f]=u(()=>e(v(a,n))),[p]=u(()=>i),m=l(!1);return o(()=>{if(m.current)return;m.current=!0;let e=f.getState();(async()=>{try{await e.hydrateWallets()}catch(e){t(`[butr] failed to hydrate wallets:`,e)}})()},[f]),o(()=>{if(p)return p.subscribe(e=>{a.has(e.id)||(a.set(e.id,e),c(t=>[...t,e]),f.getState().tryRestoreFromPending(e.id))})},[a,p,f]),d(h.Provider,{value:f,children:d(_.Provider,{value:s,children:r})})},b=()=>{let e=i(h);if(!e)throw Error(`useWalletStoreContext must be used within WalletManagerProvider`);return e},ee=()=>i(_),x=[],S=(e,t)=>{if(e===t)return!0;if(e.length!==t.length)return!1;for(let n=0;n<e.length;n+=1){let r=e[n],i=t[n];if(!r||!i||r.walletAddress!==i.walletAddress||r.chain.id!==i.chain.id)return!1}return!0},C=()=>f(b(),e=>e.connectionStatus),w=()=>f(b(),e=>e.connectionStatus===`connecting`),T=()=>f(b(),e=>e.connectingConnectorId),E=()=>f(b(),e=>e.activeConnectorId),D=()=>f(b(),e=>e.connectionError),O=()=>f(b(),e=>e.pool.size>0),k=()=>f(b(),e=>e.isHydrated),A=()=>f(b(),e=>e.isUserDisconnected),j=()=>f(b(),e=>e.pool),M=()=>{let e=j();return s(()=>[...e.values()],[e])},N=()=>f(b(),e=>e.selection),P=()=>m(b(),e=>e.activeConnectorId?e.pool.get(e.activeConnectorId):void 0,n),F=e=>m(b(),t=>{if(!e)return;let n=t.selection.get(e);return n?t.pool.get(n):void 0},n),I=e=>f(b(),t=>t.selection.has(e)),te=e=>m(b(),t=>{let n=e??t.activeConnectorId,r=n?t.pool.get(n):void 0;return r?r.accounts:x},S),L=e=>m(b(),t=>{let n=e??t.activeConnectorId;return n?t.pool.get(n):void 0},n),R=()=>{let e=b();return t=>e.getState().pool.get(t)},z=()=>{let e=b();return t=>{let n=e.getState(),r=n.selection.get(t);return r?n.pool.get(r):void 0}},B=()=>f(b(),e=>e.getConnectorInstance),V=e=>m(b(),e,p),H=()=>f(b(),e=>e.connectWallet),U=()=>f(b(),e=>e.disconnectWallet),W=()=>f(b(),e=>e.setActiveConnector),G=()=>f(b(),e=>e.setSelection),K=()=>f(b(),e=>e.reset),q=()=>f(b(),e=>e.updateWalletAccount),J=()=>f(b(),e=>e.refreshWallet),Y=()=>f(b(),e=>e.requestAccounts),X=()=>f(b(),e=>e.resetConnectionStatus),Z=()=>f(b(),e=>e.setConnectionError),Q=(e,t)=>{switch(t.type){case`reset`:return{data:null,error:null,status:`idle`};case`load`:return{data:null,error:null,status:`loading`};case`success`:return{data:t.data,error:null,status:`success`};case`error`:return{data:null,error:t.error,status:`error`};default:return{data:null,error:null,status:`idle`}}},ne={data:null,error:null,status:`idle`},$=e=>{let[t,n]=c(Q,ne);return o(()=>{if(!e){n({type:`reset`});return}n({type:`load`});let t=!1;return(async()=>{try{let r=await e();t||n({data:r,type:`success`})}catch(e){t||n({error:e,type:`error`})}})(),()=>{t=!0}},[e]),t},re=e=>{let t=L(e);return $(s(()=>t?()=>t.connector.getSigner():null,[t]))},ie=(e,t)=>{let n=L(e),[r,i]=c(e=>e+1,0),o=a(()=>{i()},[]);return{...$(s(()=>n?()=>n.connector.getBalance(t):null,[n,t,r])),refetch:o}};export{y as WalletManagerProvider,h as WalletStoreContext,te as useAccounts,E as useActiveConnectorId,P as useActiveWallet,ie as useBalance,H as useConnectWallet,M as useConnectedWallets,T as useConnectingConnectorId,D as useConnectionError,C as useConnectionStatus,U as useDisconnectWallet,ee as useDiscoveredWallets,B as useGetConnectorInstance,z as useGetSelectedWallet,R as useGetWallet,w as useIsConnecting,k as useIsHydrated,I as useIsPlatformConnected,A as useIsUserDisconnected,j as usePool,J as useRefreshWallet,Y as useRequestAccounts,X as useResetConnectionStatus,K as useResetWallet,F as useSelectedWallet,N as useSelection,W as useSetActiveConnector,Z as useSetConnectionError,G as useSetSelection,re as useSigner,q as useUpdateWalletAccount,O as useWalletConnected,L as useWalletEntry,V as useWalletStore,b as useWalletStoreContext};
1
+ import{createWalletStore as e,logError as t,walletEqual as n}from"@usebutr/core";import{createContext as r,use as i,useCallback as a,useEffect as o,useMemo as s,useReducer as c,useRef as l,useState as u}from"react";import{jsx as d}from"react/jsx-runtime";import{useStore as f}from"zustand";import{shallow as p}from"zustand/shallow";import{useStoreWithEqualityFn as m}from"zustand/traditional";const h=r(null),g=[],_=r(g),v=(e,t)=>{let n=t.createConnector;return{connectors:t.connectors??[],createConnector:t=>e.get(t)??n?.(t)??null,initialState:t.initialState,onConnect:t.onConnect,onConnectError:t.onConnectError,onDisconnect:t.onDisconnect,onHydrated:t.onHydrated,onReset:t.onReset,onSlowConnect:t.onSlowConnect,onStorageError:t.onStorageError,slowConnectThresholdMs:t.slowConnectThresholdMs,storage:t.storage,storageKeyPrefix:t.storageKeyPrefix}},y=n=>{let{children:r,discovery:i}=n,[a]=u(()=>new Map),[s,c]=u(g),[f]=u(()=>e(v(a,n))),[p]=u(()=>i),m=l(!1);return o(()=>{if(m.current)return;m.current=!0;let e=f.getState();(async()=>{try{await e.hydrateWallets()}catch(e){t(`[butr] failed to hydrate wallets:`,e)}})()},[f]),o(()=>{if(p)return p.subscribe(e=>{a.has(e.id)||(a.set(e.id,e),c(t=>[...t,e]),f.getState().tryRestoreFromPending(e.id))})},[a,p,f]),d(h.Provider,{value:f,children:d(_.Provider,{value:s,children:r})})},b=()=>{let e=i(h);if(!e)throw Error(`useWalletStoreContext must be used within WalletManagerProvider`);return e},ee=()=>i(_),x=[],S=(e,t)=>{if(e===t)return!0;if(e.length!==t.length)return!1;for(let n=0;n<e.length;n+=1){let r=e[n],i=t[n];if(!r||!i||r.walletAddress!==i.walletAddress||r.chain.id!==i.chain.id)return!1}return!0},C=()=>f(b(),e=>e.activeConnectorId&&e.reconnectingIds.has(e.activeConnectorId)?`reconnecting`:e.connectionStatus),w=()=>f(b(),e=>e.connectionStatus===`connecting`),T=()=>f(b(),e=>e.connectingConnectorId),E=()=>f(b(),e=>e.activeConnectorId),D=()=>f(b(),e=>e.connectionError),O=()=>f(b(),e=>e.pool.size>0),k=()=>f(b(),e=>e.isHydrated),A=()=>f(b(),e=>e.isUserDisconnected),j=()=>f(b(),e=>e.pool),M=()=>{let e=j();return s(()=>[...e.values()],[e])},N=()=>f(b(),e=>e.selection),P=()=>m(b(),e=>e.activeConnectorId?e.pool.get(e.activeConnectorId):void 0,n),F=e=>m(b(),t=>{if(!e)return;let n=t.selection.get(e);return n?t.pool.get(n):void 0},n),I=e=>f(b(),t=>t.selection.has(e)),te=e=>m(b(),t=>{let n=e??t.activeConnectorId,r=n?t.pool.get(n):void 0;return r?r.accounts:x},S),L=e=>m(b(),t=>{let n=e??t.activeConnectorId;return n?t.pool.get(n):void 0},n),R=()=>{let e=b();return t=>e.getState().pool.get(t)},z=()=>{let e=b();return t=>{let n=e.getState(),r=n.selection.get(t);return r?n.pool.get(r):void 0}},B=()=>f(b(),e=>e.getConnectorInstance),V=e=>m(b(),e,p),H=()=>f(b(),e=>e.connectWallet),U=()=>f(b(),e=>e.disconnectWallet),W=()=>f(b(),e=>e.setActiveConnector),G=()=>f(b(),e=>e.setSelection),K=()=>f(b(),e=>e.reset),q=()=>f(b(),e=>e.updateWalletAccount),J=()=>f(b(),e=>e.refreshWallet),Y=()=>f(b(),e=>e.requestAccounts),X=()=>f(b(),e=>e.resetConnectionStatus),Z=()=>f(b(),e=>e.setConnectionError),Q=(e,t)=>{switch(t.type){case`reset`:return{data:null,error:null,status:`idle`};case`load`:return{data:null,error:null,status:`loading`};case`success`:return{data:t.data,error:null,status:`success`};case`error`:return{data:null,error:t.error,status:`error`};default:return{data:null,error:null,status:`idle`}}},ne={data:null,error:null,status:`idle`},$=e=>{let[t,n]=c(Q,ne);return o(()=>{if(!e){n({type:`reset`});return}n({type:`load`});let t=!1;return(async()=>{try{let r=await e();t||n({data:r,type:`success`})}catch(e){t||n({error:e,type:`error`})}})(),()=>{t=!0}},[e]),t},re=e=>{let t=L(e);return $(s(()=>t?()=>t.connector.getSigner():null,[t]))},ie=(e,t)=>{let n=L(e),[r,i]=c(e=>e+1,0),o=a(()=>{i()},[]);return{...$(s(()=>n?()=>n.connector.getBalance(t):null,[n,t,r])),refetch:o}};export{y as WalletManagerProvider,h as WalletStoreContext,te as useAccounts,E as useActiveConnectorId,P as useActiveWallet,ie as useBalance,H as useConnectWallet,M as useConnectedWallets,T as useConnectingConnectorId,D as useConnectionError,C as useConnectionStatus,U as useDisconnectWallet,ee as useDiscoveredWallets,B as useGetConnectorInstance,z as useGetSelectedWallet,R as useGetWallet,w as useIsConnecting,k as useIsHydrated,I as useIsPlatformConnected,A as useIsUserDisconnected,j as usePool,J as useRefreshWallet,Y as useRequestAccounts,X as useResetConnectionStatus,K as useResetWallet,F as useSelectedWallet,N as useSelection,W as useSetActiveConnector,Z as useSetConnectionError,G as useSetSelection,re as useSigner,q as useUpdateWalletAccount,O as useWalletConnected,L as useWalletEntry,V as useWalletStore,b as useWalletStoreContext};
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/context.tsx","../src/hooks/selectors.ts","../src/hooks/actions.ts","../src/hooks/async-resources.ts"],"sourcesContent":["import type {\n ConnectorMeta,\n WalletAdapter,\n WalletManagerConfig,\n WalletSource,\n WalletStore,\n} from \"@usebutr/core\";\nimport { createWalletStore, logError } from \"@usebutr/core\";\nimport React, { createContext, use, useEffect, useRef, useState } from \"react\";\n\nconst WalletStoreContext: React.Context<WalletStore | null> = createContext<WalletStore | null>(\n null,\n);\n\nconst EMPTY_DISCOVERED: ReadonlyArray<WalletAdapter> = [];\nconst DiscoveredWalletsContext: React.Context<ReadonlyArray<WalletAdapter>> =\n createContext<ReadonlyArray<WalletAdapter>>(EMPTY_DISCOVERED);\n\n/**\n * All props — especially `on*` lifecycle callbacks, `storage`, and `discovery` —\n * are captured once at mount via `useState` lazy initializers and are authoritative\n * for the provider's lifetime. Later prop changes are silently ignored, so consumers\n * must pass stable references: module-level values, `useRef`, or `useCallback`.\n */\ntype WalletManagerProviderProps = {\n children: React.ReactNode;\n /** Metadata for explicitly-registered connectors. */\n connectors?: Array<ConnectorMeta>;\n /** Explicit/manual connector factory. Resolved after `discovery`. */\n createConnector?: (id: string) => WalletAdapter | null;\n /** Auto-discovery source. Omit to skip auto-discovery; no\n * protocol code enters the bundle. */\n discovery?: WalletSource;\n onConnect?: WalletManagerConfig[\"onConnect\"];\n onConnectError?: WalletManagerConfig[\"onConnectError\"];\n onDisconnect?: WalletManagerConfig[\"onDisconnect\"];\n onHydrated?: WalletManagerConfig[\"onHydrated\"];\n onReset?: WalletManagerConfig[\"onReset\"];\n onSlowConnect?: WalletManagerConfig[\"onSlowConnect\"];\n onStorageError?: WalletManagerConfig[\"onStorageError\"];\n slowConnectThresholdMs?: WalletManagerConfig[\"slowConnectThresholdMs\"];\n storage?: WalletManagerConfig[\"storage\"];\n storageKeyPrefix?: WalletManagerConfig[\"storageKeyPrefix\"];\n};\n\n/** Build a WalletManagerConfig from flat provider props. */\nconst buildInitialConfig = (\n adapters: Map<string, WalletAdapter>,\n props: Omit<WalletManagerProviderProps, \"children\" | \"discovery\">,\n): WalletManagerConfig => {\n const userCreate = props.createConnector;\n return {\n connectors: props.connectors ?? [],\n createConnector: (id) => adapters.get(id) ?? userCreate?.(id) ?? null,\n onConnect: props.onConnect,\n onConnectError: props.onConnectError,\n onDisconnect: props.onDisconnect,\n onHydrated: props.onHydrated,\n onReset: props.onReset,\n onSlowConnect: props.onSlowConnect,\n onStorageError: props.onStorageError,\n slowConnectThresholdMs: props.slowConnectThresholdMs,\n storage: props.storage,\n storageKeyPrefix: props.storageKeyPrefix,\n };\n};\n\n/**\n * The butr provider. Pass `discovery` (a `WalletSource` such as\n * `autoDiscovery()` from `@usebutr/wallets`) for auto-discovered wallets,\n * and/or `createConnector` to register explicit adapters\n * (WalletConnect, Ledger, custom). When both are present an id is\n * resolved from discovered adapters first, then `createConnector`.\n *\n * The store and the discovery subscription are created once for the\n * provider's lifetime; props captured at mount are authoritative.\n */\nconst WalletManagerProvider: React.FC<WalletManagerProviderProps> = (props) => {\n const { children, discovery: discoveryProp } = props;\n\n // `adapters` is mutated in-place by the discovery subscription so the\n // store's `createConnector` closure always sees the latest discovered set.\n const [adapters] = useState<Map<string, WalletAdapter>>(() => new Map());\n const [discoveredList, setDiscoveredList] =\n useState<ReadonlyArray<WalletAdapter>>(EMPTY_DISCOVERED);\n\n // Store is created once; `buildInitialConfig` closes over `props` at first\n // render and `adapters` (the stable Map). Subsequent re-renders do not\n // re-run this initializer.\n // eslint-disable-next-line react-hooks/exhaustive-deps -- captured once on mount\n const [store] = useState<WalletStore>(() =>\n createWalletStore(buildInitialConfig(adapters, props)),\n );\n\n // Discovery subscription ref is also locked to the first render value.\n // eslint-disable-next-line react-hooks/exhaustive-deps -- captured once on mount\n const [discovery] = useState<WalletSource | undefined>(() => discoveryProp);\n\n const hasHydratedRef = useRef(false);\n\n useEffect(() => {\n if (hasHydratedRef.current) {\n return;\n }\n hasHydratedRef.current = true;\n const state = store.getState();\n void (async () => {\n try {\n await state.hydrateWallets();\n } catch (error: unknown) {\n logError(\"[butr] failed to hydrate wallets:\", error);\n }\n })();\n }, [store]);\n\n useEffect(() => {\n if (!discovery) {\n return;\n }\n const unsubscribe = discovery.subscribe((adapter) => {\n if (adapters.has(adapter.id)) {\n return;\n }\n adapters.set(adapter.id, adapter);\n setDiscoveredList((prev) => [...prev, adapter]);\n void store.getState().tryRestoreFromPending(adapter.id);\n });\n return unsubscribe;\n }, [adapters, discovery, store]);\n\n return (\n <WalletStoreContext.Provider value={store}>\n <DiscoveredWalletsContext.Provider value={discoveredList}>\n {children}\n </DiscoveredWalletsContext.Provider>\n </WalletStoreContext.Provider>\n );\n};\n\n/** Read the store from context. Exported for adapter packages building\n * custom discovery wiring. */\nconst useWalletStoreContext = (): WalletStore => {\n const store = use(WalletStoreContext);\n if (!store) {\n throw new Error(\"useWalletStoreContext must be used within WalletManagerProvider\");\n }\n return store;\n};\n\n/** Reactive list of wallets announced via the `discovery` source since\n * the provider mounted. Empty when no `discovery` was passed. */\nconst useDiscoveredWallets = (): ReadonlyArray<WalletAdapter> => use(DiscoveredWalletsContext);\n\nexport type { WalletManagerProviderProps };\nexport { WalletManagerProvider, WalletStoreContext, useDiscoveredWallets, useWalletStoreContext };\n","import type { Account, ChainPlatform, ConnectedWallet, WalletStoreState } from \"@usebutr/core\";\nimport { walletEqual } from \"@usebutr/core\";\nimport { useMemo } from \"react\";\nimport { useStore } from \"zustand\";\nimport { shallow } from \"zustand/shallow\";\nimport { useStoreWithEqualityFn } from \"zustand/traditional\";\n\nimport { useWalletStoreContext } from \"../context\";\n\n/**\n * Selector hooks — pure reactive reads of the wallet store. Each\n * subscribes via `useSyncExternalStore` (through Zustand's `useStore`)\n * and returns the value directly; no `AsyncState` wrapper, no\n * mutation side effects.\n *\n * Three sub-groups in this file:\n * - **Connection-state** scalars (status, error, flags)\n * - **Pool / selection / accounts** (reactive structures)\n * - **Stable accessors** (return functions safe for callbacks)\n *\n * The classification is \"what's returned\" rather than \"is it async.\"\n * For dispatchers, see `./actions.ts`. For async resources\n * (`useSigner`, `useBalance`), see `./async-resources.ts`.\n */\n\nconst EMPTY_ACCOUNTS: ReadonlyArray<Account> = [];\n\nconst accountsEqual = (a: ReadonlyArray<Account>, b: ReadonlyArray<Account>) => {\n if (a === b) {\n return true;\n }\n if (a.length !== b.length) {\n return false;\n }\n for (let i = 0; i < a.length; i += 1) {\n const x = a[i];\n const y = b[i];\n if (!x || !y) {\n return false;\n }\n if (x.walletAddress !== y.walletAddress || x.chain.id !== y.chain.id) {\n return false;\n }\n }\n return true;\n};\n\n// ============================================================================\n// Connection state\n// ============================================================================\n\n/** Connection status: \"idle\" | \"connecting\" | \"success\" | \"error\". */\nconst useConnectionStatus = () => {\n const store = useWalletStoreContext();\n return useStore(store, (state) => state.connectionStatus);\n};\n\n/** True iff `connectionStatus === \"connecting\"`. */\nconst useIsConnecting = () => {\n const store = useWalletStoreContext();\n return useStore(store, (state) => state.connectionStatus === \"connecting\");\n};\n\n/** ID of the wallet currently in the connecting flight (null when idle). */\nconst useConnectingConnectorId = () => {\n const store = useWalletStoreContext();\n return useStore(store, (state) => state.connectingConnectorId);\n};\n\n/** ID of the wallet currently focused as the global active selection. */\nconst useActiveConnectorId = () => {\n const store = useWalletStoreContext();\n return useStore(store, (state) => state.activeConnectorId);\n};\n\n/** Last connection error message, if any. */\nconst useConnectionError = () => {\n const store = useWalletStoreContext();\n return useStore(store, (state) => state.connectionError);\n};\n\n/** True if at least one wallet is connected. */\nconst useWalletConnected = () => {\n const store = useWalletStoreContext();\n return useStore(store, (state) => state.pool.size > 0);\n};\n\n/** Has the store finished its initial hydration pass? */\nconst useIsHydrated = () => {\n const store = useWalletStoreContext();\n return useStore(store, (state) => state.isHydrated);\n};\n\n/** Session-scoped disconnect-intent flag. True after an explicit disconnect or\n * reset, false after a fresh session or a new connection. Consumers use this\n * to suppress auto-reconnect immediately after a manual disconnect. */\nconst useIsUserDisconnected = () => {\n const store = useWalletStoreContext();\n return useStore(store, (state) => state.isUserDisconnected);\n};\n\n// ============================================================================\n// Pool / selection / accounts\n// ============================================================================\n\n/** Full pool of connected wallets, keyed by `connectorId`. Re-renders on any pool change. */\nconst usePool = () => {\n const store = useWalletStoreContext();\n return useStore(store, (state) => state.pool);\n};\n\n/** Pool projected as an array. Stable when the pool reference is stable. */\nconst useConnectedWallets = (): Array<ConnectedWallet> => {\n const pool = usePool();\n return useMemo(() => [...pool.values()], [pool]);\n};\n\n/** Per-platform selection: `Map<ChainPlatform, connectorId>`. */\nconst useSelection = () => {\n const store = useWalletStoreContext();\n return useStore(store, (state) => state.selection);\n};\n\n/** Globally active wallet (the one in front of the user right now). */\nconst useActiveWallet = (): ConnectedWallet | undefined => {\n const store = useWalletStoreContext();\n return useStoreWithEqualityFn(\n store,\n (state) => (state.activeConnectorId ? state.pool.get(state.activeConnectorId) : undefined),\n walletEqual,\n );\n};\n\n/** Reactive lookup of the wallet selected for a given platform. */\nconst useSelectedWallet = (chainPlatform: ChainPlatform | null): ConnectedWallet | undefined => {\n const store = useWalletStoreContext();\n return useStoreWithEqualityFn(\n store,\n (state) => {\n if (!chainPlatform) {\n return undefined;\n }\n const id = state.selection.get(chainPlatform);\n return id ? state.pool.get(id) : undefined;\n },\n walletEqual,\n );\n};\n\n/** Reactive boolean: is there a selected wallet for a given platform? */\nconst useIsPlatformConnected = (chainPlatform: ChainPlatform): boolean => {\n const store = useWalletStoreContext();\n return useStore(store, (state) => state.selection.has(chainPlatform));\n};\n\n/**\n * All known accounts on a wallet. Defaults to the active wallet when\n * `connectorId` is omitted. Re-renders only when the accounts list\n * actually changes (by address + chain id), so wallet-side\n * `accountChanged` events bridged via `Connector.subscribe` flow through\n * cleanly.\n */\nconst useAccounts = (connectorId?: string | null): ReadonlyArray<Account> => {\n const store = useWalletStoreContext();\n return useStoreWithEqualityFn(\n store,\n (state) => {\n const id = connectorId ?? state.activeConnectorId;\n const wallet = id ? state.pool.get(id) : undefined;\n return wallet ? wallet.accounts : EMPTY_ACCOUNTS;\n },\n accountsEqual,\n );\n};\n\n/**\n * Subscribe to the pool entry for a `connectorId`. Defaults to the\n * active wallet when omitted. Re-renders only when the resolved\n * wallet's identity (connectorId / address / chainId) changes.\n *\n * This used to live alongside the async hooks because `useSigner` /\n * `useBalance` consume it internally; it belongs with the other\n * selectors since its return shape is `ConnectedWallet | undefined`\n * with no async wrapper.\n */\nconst useWalletEntry = (connectorId: string | null | undefined): ConnectedWallet | undefined => {\n const store = useWalletStoreContext();\n return useStoreWithEqualityFn(\n store,\n (state) => {\n const id = connectorId ?? state.activeConnectorId;\n return id ? state.pool.get(id) : undefined;\n },\n walletEqual,\n );\n};\n\n// ============================================================================\n// Stable accessors (return functions; safe to use in callbacks)\n// ============================================================================\n\n/** Stable accessor: `(connectorId) => ConnectedWallet | undefined`. */\nconst useGetWallet = () => {\n const store = useWalletStoreContext();\n return (connectorId: string) => store.getState().pool.get(connectorId);\n};\n\n/** Stable accessor: `(platform) => ConnectedWallet | undefined`. */\nconst useGetSelectedWallet = () => {\n const store = useWalletStoreContext();\n return (chainPlatform: ChainPlatform) => {\n const state = store.getState();\n const id = state.selection.get(chainPlatform);\n return id ? state.pool.get(id) : undefined;\n };\n};\n\n/** Stable accessor for raw connector instances. */\nconst useGetConnectorInstance = () => {\n const store = useWalletStoreContext();\n return useStore(store, (state) => state.getConnectorInstance);\n};\n\n// ============================================================================\n// Direct store access (escape hatch)\n// ============================================================================\n\n/**\n * Direct access to the Zustand store for custom selectors. Uses shallow\n * equality on the selector result, so returning an inline object or array\n * is safe — no infinite re-render loop. Primitive selectors are\n * unaffected (shallow falls back to `Object.is`).\n *\n * @example\n * const { pool, activeConnectorId } = useWalletStore((state) => ({\n * pool: state.pool,\n * activeConnectorId: state.activeConnectorId,\n * }));\n */\nconst useWalletStore = <T>(selector: (state: WalletStoreState) => T) => {\n const store = useWalletStoreContext();\n return useStoreWithEqualityFn(store, selector, shallow);\n};\n\nexport {\n useAccounts,\n useActiveConnectorId,\n useActiveWallet,\n useConnectedWallets,\n useConnectingConnectorId,\n useConnectionError,\n useConnectionStatus,\n useGetConnectorInstance,\n useGetSelectedWallet,\n useGetWallet,\n useIsConnecting,\n useIsHydrated,\n useIsPlatformConnected,\n useIsUserDisconnected,\n usePool,\n useSelectedWallet,\n useSelection,\n useWalletConnected,\n useWalletEntry,\n useWalletStore,\n};\n","import { useStore } from \"zustand\";\n\nimport { useWalletStoreContext } from \"../context\";\n\n/**\n * Action hooks — dispatchers that mutate the wallet store. Each\n * returns a stable function reference (Zustand's `useStore` against\n * action selectors is identity-stable across renders), safe to pass\n * to event handlers or effects without `useCallback`.\n *\n * No reactive subscription beyond identity stability — these don't\n * re-render the component when store state changes. For reactive\n * reads, see `./selectors.ts`.\n */\n\n/** Begin the connect flow for a connector id. Routes through the\n * store's connect handler, which calls the connector's `connect()`,\n * reads accounts, and writes the resulting `ConnectedWallet` into\n * the pool. */\nconst useConnectWallet = () => {\n const store = useWalletStoreContext();\n return useStore(store, (state) => state.connectWallet);\n};\n\nconst useDisconnectWallet = () => {\n const store = useWalletStoreContext();\n return useStore(store, (state) => state.disconnectWallet);\n};\n\nconst useSetActiveConnector = () => {\n const store = useWalletStoreContext();\n return useStore(store, (state) => state.setActiveConnector);\n};\n\nconst useSetSelection = () => {\n const store = useWalletStoreContext();\n return useStore(store, (state) => state.setSelection);\n};\n\nconst useResetWallet = () => {\n const store = useWalletStoreContext();\n return useStore(store, (state) => state.reset);\n};\n\nconst useUpdateWalletAccount = () => {\n const store = useWalletStoreContext();\n return useStore(store, (state) => state.updateWalletAccount);\n};\n\nconst useRefreshWallet = () => {\n const store = useWalletStoreContext();\n return useStore(store, (state) => state.refreshWallet);\n};\n\n/**\n * Open the wallet's account-selection UI (if the connector supports it),\n * then re-read `getAccounts()` and update the pool entry's `accounts`\n * array. EVM wallets call `wallet_requestPermissions` under the hood;\n * Wallet Standard wallets that don't expose a picker just refresh.\n *\n * Consumers should hide the trigger when\n * `wallet.connector.requestAccounts` is undefined — the action still\n * resolves cleanly, but no picker will open.\n */\nconst useRequestAccounts = () => {\n const store = useWalletStoreContext();\n return useStore(store, (state) => state.requestAccounts);\n};\n\n/** Clear `connectionError` + reset `connectionStatus` to idle. Useful when\n * surfacing an error in UI and giving the user a \"dismiss\" affordance. */\nconst useResetConnectionStatus = () => {\n const store = useWalletStoreContext();\n return useStore(store, (state) => state.resetConnectionStatus);\n};\n\nconst useSetConnectionError = () => {\n const store = useWalletStoreContext();\n return useStore(store, (state) => state.setConnectionError);\n};\n\nexport {\n useConnectWallet,\n useDisconnectWallet,\n useRefreshWallet,\n useRequestAccounts,\n useResetConnectionStatus,\n useResetWallet,\n useSetActiveConnector,\n useSetConnectionError,\n useSetSelection,\n useUpdateWalletAccount,\n};\n","import type { Balance } from \"@usebutr/core\";\nimport { useCallback, useEffect, useMemo, useReducer } from \"react\";\n\nimport { useWalletEntry } from \"./selectors\";\n\n/**\n * Async-resource hooks — return `AsyncState<T>` and run lifecycle\n * effects under the hood. Each composes `useAsyncResource` (defined\n * below) with a selector from `./selectors.ts` (`useWalletEntry`) plus\n * a stable closure that calls the connector.\n *\n * Adding a new async hook is ~5 lines: import `useAsyncResource`,\n * memoise the request closure on the wallet identity, return the\n * state. The cancellation discipline lives once, in\n * `useAsyncResource`.\n */\n\ntype AsyncState<T> =\n | { data: null; error: null; status: \"idle\" }\n | { data: null; error: null; status: \"loading\" }\n | { data: T; error: null; status: \"success\" }\n | { data: null; error: unknown; status: \"error\" };\n\ntype AsyncAction<T> =\n | { type: \"reset\" }\n | { type: \"load\" }\n | { data: T; type: \"success\" }\n | { error: unknown; type: \"error\" };\n\n/** Pure async-lifecycle reducer. One dispatch per state transition\n * keeps `useEffect` clear of cascading setState calls — each effect\n * branch invokes the reducer exactly once. */\nconst asyncReducer = <T>(_state: AsyncState<T>, action: AsyncAction<T>): AsyncState<T> => {\n switch (action.type) {\n case \"reset\": {\n return { data: null, error: null, status: \"idle\" };\n }\n case \"load\": {\n return { data: null, error: null, status: \"loading\" };\n }\n case \"success\": {\n return { data: action.data, error: null, status: \"success\" };\n }\n case \"error\": {\n return { data: null, error: action.error, status: \"error\" };\n }\n default: {\n // Exhaustiveness check — TS errors here if `AsyncAction` grows\n // a variant without a case.\n const exhaustiveCheck: never = action;\n void exhaustiveCheck;\n return { data: null, error: null, status: \"idle\" };\n }\n }\n};\n\nconst IDLE: AsyncState<never> = { data: null, error: null, status: \"idle\" };\n\n/**\n * Generic async-resource hook. Encapsulates the load → fetch → cancel-\n * on-deps-change → dispatch-result lifecycle that every async wallet\n * read needs. `fn` is the request closure; pass `null` to stay idle.\n *\n * Invalidation is keyed on the identity of `fn` itself — callers\n * stabilise via `useMemo` and re-create the closure when they want a\n * refetch. This keeps the React-hooks exhaustive-deps lint rule happy\n * (the effect's deps list is the literal `[fn]`).\n *\n * Why factored out: every consumer (`useSigner`, `useBalance`, future\n * `useTokenBalance`, `useTransactionReceipt`, …) needs the exact same\n * cancellation discipline. Centralising it keeps the fragile parts\n * (`cancelled` flag, dispatch order) in one place — adding a new\n * async hook becomes a 3-line definition.\n */\nconst useAsyncResource = <T>(fn: (() => Promise<T>) | null): AsyncState<T> => {\n const [state, dispatch] = useReducer(asyncReducer<T>, IDLE);\n\n useEffect(() => {\n if (!fn) {\n dispatch({ type: \"reset\" });\n return;\n }\n dispatch({ type: \"load\" });\n let cancelled = false;\n void (async () => {\n try {\n const data = await fn();\n if (!cancelled) {\n dispatch({ data, type: \"success\" });\n }\n } catch (error: unknown) {\n if (!cancelled) {\n dispatch({ error, type: \"error\" });\n }\n }\n })();\n return () => {\n cancelled = true;\n };\n }, [fn]);\n\n return state;\n};\n\n/**\n * Cached signer for a connector. Invalidates when `connectorId`, account\n * address, or chain id changes — so a chain switch or account switch in the\n * wallet invalidates the cached signer automatically.\n *\n * If `connectorId` is omitted (or `null`/`undefined`), the active wallet's\n * signer is returned.\n *\n * Returns `{ data, error, status }`. `status` is `\"idle\"` when there's no\n * wallet, `\"loading\"` while the connector resolves, `\"success\"` once the\n * signer is available, `\"error\"` if `getSigner()` rejected.\n */\nconst useSigner = (connectorId?: string | null): AsyncState<unknown> => {\n const wallet = useWalletEntry(connectorId);\n // Stabilise the request closure so `useAsyncResource` only re-runs\n // when the resolved wallet identity changes (not on every render).\n const fn = useMemo(() => (wallet ? () => wallet.connector.getSigner() : null), [wallet]);\n return useAsyncResource(fn);\n};\n\ntype UseBalanceResult = AsyncState<Balance> & { refetch: () => void };\n\n/**\n * Cached balance for a connector. Invalidates on the same events as\n * `useSigner` (connectorId / address / chainId), plus an explicit `refetch`\n * handle for poll-on-demand or after-action refreshes.\n *\n * If `connectorId` is omitted, the active wallet's balance is returned.\n * `mint` is forwarded to the connector — semantics depend on the chain.\n */\nconst useBalance = (connectorId?: string | null, mint?: string): UseBalanceResult => {\n const wallet = useWalletEntry(connectorId);\n const [counter, bumpCounter] = useReducer((n: number) => n + 1, 0);\n const refetch = useCallback(() => {\n bumpCounter();\n }, []);\n // `counter` participates in the closure identity, so calling\n // `refetch()` produces a new `fn` and `useAsyncResource` re-runs.\n const fn = useMemo(\n () => (wallet ? () => wallet.connector.getBalance(mint) : null),\n // oxlint-disable-next-line eslint-plugin-react-hooks/exhaustive-deps -- counter is the refetch trigger; not used inside the closure\n [wallet, mint, counter],\n );\n const state = useAsyncResource(fn);\n return { ...state, refetch };\n};\n\nexport type { AsyncState, UseBalanceResult };\nexport { useBalance, useSigner };\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/context.tsx","../src/hooks/selectors.ts","../src/hooks/actions.ts","../src/hooks/async-resources.ts"],"sourcesContent":["import type {\n ConnectorMeta,\n WalletAdapter,\n WalletManagerConfig,\n WalletSnapshot,\n WalletSource,\n WalletStore,\n} from \"@usebutr/core\";\nimport { createWalletStore, logError } from \"@usebutr/core\";\nimport React, { createContext, use, useEffect, useRef, useState } from \"react\";\n\nconst WalletStoreContext: React.Context<WalletStore | null> = createContext<WalletStore | null>(\n null,\n);\n\nconst EMPTY_DISCOVERED: ReadonlyArray<WalletAdapter> = [];\nconst DiscoveredWalletsContext: React.Context<ReadonlyArray<WalletAdapter>> =\n createContext<ReadonlyArray<WalletAdapter>>(EMPTY_DISCOVERED);\n\n/**\n * All props — especially `on*` lifecycle callbacks, `storage`, and `discovery` —\n * are captured once at mount via `useState` lazy initializers and are authoritative\n * for the provider's lifetime. Later prop changes are silently ignored, so consumers\n * must pass stable references: module-level values, `useRef`, or `useCallback`.\n */\ntype WalletManagerProviderProps = {\n children: React.ReactNode;\n /** Metadata for explicitly-registered connectors. */\n connectors?: Array<ConnectorMeta>;\n /** Explicit/manual connector factory. Resolved after `discovery`. */\n createConnector?: (id: string) => WalletAdapter | null;\n /** Auto-discovery source. Omit to skip auto-discovery; no\n * protocol code enters the bundle. */\n discovery?: WalletSource;\n /**\n * Seed the store synchronously with persisted wallet state — typically\n * the return value of `readWalletSnapshot(cookies, { keyPrefix })`\n * called from a Server Component. With `initialState`, the store\n * starts with `isHydrated: true`, the pool populated with shadow\n * adapters, and every connector id in `reconnectingIds`. The\n * primary hooks (`useActiveWallet`, `useConnectedWallets`, …)\n * return values from render zero on both server and client.\n *\n * Pass `undefined` to fall back to the legacy async hydration path\n * (`isHydrated` starts `false`, flips `true` after `hydrateWallets`\n * resolves on mount). Apps that don't render server-side typically\n * leave this unset.\n */\n initialState?: WalletSnapshot;\n onConnect?: WalletManagerConfig[\"onConnect\"];\n onConnectError?: WalletManagerConfig[\"onConnectError\"];\n onDisconnect?: WalletManagerConfig[\"onDisconnect\"];\n onHydrated?: WalletManagerConfig[\"onHydrated\"];\n onReset?: WalletManagerConfig[\"onReset\"];\n onSlowConnect?: WalletManagerConfig[\"onSlowConnect\"];\n onStorageError?: WalletManagerConfig[\"onStorageError\"];\n slowConnectThresholdMs?: WalletManagerConfig[\"slowConnectThresholdMs\"];\n storage?: WalletManagerConfig[\"storage\"];\n storageKeyPrefix?: WalletManagerConfig[\"storageKeyPrefix\"];\n};\n\n/** Build a WalletManagerConfig from flat provider props. */\nconst buildInitialConfig = (\n adapters: Map<string, WalletAdapter>,\n props: Omit<WalletManagerProviderProps, \"children\" | \"discovery\">,\n): WalletManagerConfig => {\n const userCreate = props.createConnector;\n return {\n connectors: props.connectors ?? [],\n createConnector: (id) => adapters.get(id) ?? userCreate?.(id) ?? null,\n initialState: props.initialState,\n onConnect: props.onConnect,\n onConnectError: props.onConnectError,\n onDisconnect: props.onDisconnect,\n onHydrated: props.onHydrated,\n onReset: props.onReset,\n onSlowConnect: props.onSlowConnect,\n onStorageError: props.onStorageError,\n slowConnectThresholdMs: props.slowConnectThresholdMs,\n storage: props.storage,\n storageKeyPrefix: props.storageKeyPrefix,\n };\n};\n\n/**\n * The butr provider. Pass `discovery` (a `WalletSource` such as\n * `autoDiscovery()` from `@usebutr/wallets`) for auto-discovered wallets,\n * and/or `createConnector` to register explicit adapters\n * (WalletConnect, Ledger, custom). When both are present an id is\n * resolved from discovered adapters first, then `createConnector`.\n *\n * The store and the discovery subscription are created once for the\n * provider's lifetime; props captured at mount are authoritative.\n */\nconst WalletManagerProvider: React.FC<WalletManagerProviderProps> = (props) => {\n const { children, discovery: discoveryProp } = props;\n\n // `adapters` is mutated in-place by the discovery subscription so the\n // store's `createConnector` closure always sees the latest discovered set.\n const [adapters] = useState<Map<string, WalletAdapter>>(() => new Map());\n const [discoveredList, setDiscoveredList] =\n useState<ReadonlyArray<WalletAdapter>>(EMPTY_DISCOVERED);\n\n // Store is created once; `buildInitialConfig` closes over `props` at first\n // render and `adapters` (the stable Map). Subsequent re-renders do not\n // re-run this initializer.\n // eslint-disable-next-line react-hooks/exhaustive-deps -- captured once on mount\n const [store] = useState<WalletStore>(() =>\n createWalletStore(buildInitialConfig(adapters, props)),\n );\n\n // Discovery subscription ref is also locked to the first render value.\n // eslint-disable-next-line react-hooks/exhaustive-deps -- captured once on mount\n const [discovery] = useState<WalletSource | undefined>(() => discoveryProp);\n\n const hasHydratedRef = useRef(false);\n\n useEffect(() => {\n if (hasHydratedRef.current) {\n return;\n }\n hasHydratedRef.current = true;\n const state = store.getState();\n void (async () => {\n try {\n await state.hydrateWallets();\n } catch (error: unknown) {\n logError(\"[butr] failed to hydrate wallets:\", error);\n }\n })();\n }, [store]);\n\n useEffect(() => {\n if (!discovery) {\n return;\n }\n const unsubscribe = discovery.subscribe((adapter) => {\n if (adapters.has(adapter.id)) {\n return;\n }\n adapters.set(adapter.id, adapter);\n setDiscoveredList((prev) => [...prev, adapter]);\n void store.getState().tryRestoreFromPending(adapter.id);\n });\n return unsubscribe;\n }, [adapters, discovery, store]);\n\n return (\n <WalletStoreContext.Provider value={store}>\n <DiscoveredWalletsContext.Provider value={discoveredList}>\n {children}\n </DiscoveredWalletsContext.Provider>\n </WalletStoreContext.Provider>\n );\n};\n\n/** Read the store from context. Exported for adapter packages building\n * custom discovery wiring. */\nconst useWalletStoreContext = (): WalletStore => {\n const store = use(WalletStoreContext);\n if (!store) {\n throw new Error(\"useWalletStoreContext must be used within WalletManagerProvider\");\n }\n return store;\n};\n\n/** Reactive list of wallets announced via the `discovery` source since\n * the provider mounted. Empty when no `discovery` was passed. */\nconst useDiscoveredWallets = (): ReadonlyArray<WalletAdapter> => use(DiscoveredWalletsContext);\n\nexport type { WalletManagerProviderProps };\nexport { WalletManagerProvider, WalletStoreContext, useDiscoveredWallets, useWalletStoreContext };\n","import type { Account, ChainPlatform, ConnectedWallet, WalletStoreState } from \"@usebutr/core\";\nimport { walletEqual } from \"@usebutr/core\";\nimport { useMemo } from \"react\";\nimport { useStore } from \"zustand\";\nimport { shallow } from \"zustand/shallow\";\nimport { useStoreWithEqualityFn } from \"zustand/traditional\";\n\nimport { useWalletStoreContext } from \"../context\";\n\n/**\n * Selector hooks — pure reactive reads of the wallet store. Each\n * subscribes via `useSyncExternalStore` (through Zustand's `useStore`)\n * and returns the value directly; no `AsyncState` wrapper, no\n * mutation side effects.\n *\n * Three sub-groups in this file:\n * - **Connection-state** scalars (status, error, flags)\n * - **Pool / selection / accounts** (reactive structures)\n * - **Stable accessors** (return functions safe for callbacks)\n *\n * The classification is \"what's returned\" rather than \"is it async.\"\n * For dispatchers, see `./actions.ts`. For async resources\n * (`useSigner`, `useBalance`), see `./async-resources.ts`.\n */\n\nconst EMPTY_ACCOUNTS: ReadonlyArray<Account> = [];\n\nconst accountsEqual = (a: ReadonlyArray<Account>, b: ReadonlyArray<Account>) => {\n if (a === b) {\n return true;\n }\n if (a.length !== b.length) {\n return false;\n }\n for (let i = 0; i < a.length; i += 1) {\n const x = a[i];\n const y = b[i];\n if (!x || !y) {\n return false;\n }\n if (x.walletAddress !== y.walletAddress || x.chain.id !== y.chain.id) {\n return false;\n }\n }\n return true;\n};\n\n// ============================================================================\n// Connection state\n// ============================================================================\n\n/**\n * Connection status of the **active** wallet — wagmi-aligned vocabulary:\n *\n * - `\"idle\"` / `\"connecting\"` / `\"success\"` / `\"error\"` — the\n * user-initiated connect attempt's state, as written by the\n * reducer.\n * - `\"reconnecting\"` — derived. Returned when the active wallet is\n * still backed by a shadow adapter (its connector id appears in\n * `state.reconnectingIds`). Indicates \"we have the data, the\n * silent reconnect hasn't verified the live wallet yet.\"\n *\n * The derivation lives here rather than in the reducer so the state\n * machine stays a narrow tracker of the in-flight connect attempt\n * while the public hook gives consumers the broader vocabulary they\n * need to render.\n */\nconst useConnectionStatus = () => {\n const store = useWalletStoreContext();\n return useStore(store, (state) => {\n if (state.activeConnectorId && state.reconnectingIds.has(state.activeConnectorId)) {\n return \"reconnecting\" as const;\n }\n return state.connectionStatus;\n });\n};\n\n/** True iff `connectionStatus === \"connecting\"`. */\nconst useIsConnecting = () => {\n const store = useWalletStoreContext();\n return useStore(store, (state) => state.connectionStatus === \"connecting\");\n};\n\n/** ID of the wallet currently in the connecting flight (null when idle). */\nconst useConnectingConnectorId = () => {\n const store = useWalletStoreContext();\n return useStore(store, (state) => state.connectingConnectorId);\n};\n\n/** ID of the wallet currently focused as the global active selection. */\nconst useActiveConnectorId = () => {\n const store = useWalletStoreContext();\n return useStore(store, (state) => state.activeConnectorId);\n};\n\n/** Last connection error message, if any. */\nconst useConnectionError = () => {\n const store = useWalletStoreContext();\n return useStore(store, (state) => state.connectionError);\n};\n\n/** True if at least one wallet is connected. */\nconst useWalletConnected = () => {\n const store = useWalletStoreContext();\n return useStore(store, (state) => state.pool.size > 0);\n};\n\n/** Has the store finished its initial hydration pass? */\nconst useIsHydrated = () => {\n const store = useWalletStoreContext();\n return useStore(store, (state) => state.isHydrated);\n};\n\n/** Session-scoped disconnect-intent flag. True after an explicit disconnect or\n * reset, false after a fresh session or a new connection. Consumers use this\n * to suppress auto-reconnect immediately after a manual disconnect. */\nconst useIsUserDisconnected = () => {\n const store = useWalletStoreContext();\n return useStore(store, (state) => state.isUserDisconnected);\n};\n\n// ============================================================================\n// Pool / selection / accounts\n// ============================================================================\n\n/** Full pool of connected wallets, keyed by `connectorId`. Re-renders on any pool change. */\nconst usePool = () => {\n const store = useWalletStoreContext();\n return useStore(store, (state) => state.pool);\n};\n\n/** Pool projected as an array. Stable when the pool reference is stable. */\nconst useConnectedWallets = (): Array<ConnectedWallet> => {\n const pool = usePool();\n return useMemo(() => [...pool.values()], [pool]);\n};\n\n/** Per-platform selection: `Map<ChainPlatform, connectorId>`. */\nconst useSelection = () => {\n const store = useWalletStoreContext();\n return useStore(store, (state) => state.selection);\n};\n\n/** Globally active wallet (the one in front of the user right now). */\nconst useActiveWallet = (): ConnectedWallet | undefined => {\n const store = useWalletStoreContext();\n return useStoreWithEqualityFn(\n store,\n (state) => (state.activeConnectorId ? state.pool.get(state.activeConnectorId) : undefined),\n walletEqual,\n );\n};\n\n/** Reactive lookup of the wallet selected for a given platform. */\nconst useSelectedWallet = (chainPlatform: ChainPlatform | null): ConnectedWallet | undefined => {\n const store = useWalletStoreContext();\n return useStoreWithEqualityFn(\n store,\n (state) => {\n if (!chainPlatform) {\n return undefined;\n }\n const id = state.selection.get(chainPlatform);\n return id ? state.pool.get(id) : undefined;\n },\n walletEqual,\n );\n};\n\n/** Reactive boolean: is there a selected wallet for a given platform? */\nconst useIsPlatformConnected = (chainPlatform: ChainPlatform): boolean => {\n const store = useWalletStoreContext();\n return useStore(store, (state) => state.selection.has(chainPlatform));\n};\n\n/**\n * All known accounts on a wallet. Defaults to the active wallet when\n * `connectorId` is omitted. Re-renders only when the accounts list\n * actually changes (by address + chain id), so wallet-side\n * `accountChanged` events bridged via `Connector.subscribe` flow through\n * cleanly.\n */\nconst useAccounts = (connectorId?: string | null): ReadonlyArray<Account> => {\n const store = useWalletStoreContext();\n return useStoreWithEqualityFn(\n store,\n (state) => {\n const id = connectorId ?? state.activeConnectorId;\n const wallet = id ? state.pool.get(id) : undefined;\n return wallet ? wallet.accounts : EMPTY_ACCOUNTS;\n },\n accountsEqual,\n );\n};\n\n/**\n * Subscribe to the pool entry for a `connectorId`. Defaults to the\n * active wallet when omitted. Re-renders only when the resolved\n * wallet's identity (connectorId / address / chainId) changes.\n *\n * This used to live alongside the async hooks because `useSigner` /\n * `useBalance` consume it internally; it belongs with the other\n * selectors since its return shape is `ConnectedWallet | undefined`\n * with no async wrapper.\n */\nconst useWalletEntry = (connectorId: string | null | undefined): ConnectedWallet | undefined => {\n const store = useWalletStoreContext();\n return useStoreWithEqualityFn(\n store,\n (state) => {\n const id = connectorId ?? state.activeConnectorId;\n return id ? state.pool.get(id) : undefined;\n },\n walletEqual,\n );\n};\n\n// ============================================================================\n// Stable accessors (return functions; safe to use in callbacks)\n// ============================================================================\n\n/** Stable accessor: `(connectorId) => ConnectedWallet | undefined`. */\nconst useGetWallet = () => {\n const store = useWalletStoreContext();\n return (connectorId: string) => store.getState().pool.get(connectorId);\n};\n\n/** Stable accessor: `(platform) => ConnectedWallet | undefined`. */\nconst useGetSelectedWallet = () => {\n const store = useWalletStoreContext();\n return (chainPlatform: ChainPlatform) => {\n const state = store.getState();\n const id = state.selection.get(chainPlatform);\n return id ? state.pool.get(id) : undefined;\n };\n};\n\n/** Stable accessor for raw connector instances. */\nconst useGetConnectorInstance = () => {\n const store = useWalletStoreContext();\n return useStore(store, (state) => state.getConnectorInstance);\n};\n\n// ============================================================================\n// Direct store access (escape hatch)\n// ============================================================================\n\n/**\n * Direct access to the Zustand store for custom selectors. Uses shallow\n * equality on the selector result, so returning an inline object or array\n * is safe — no infinite re-render loop. Primitive selectors are\n * unaffected (shallow falls back to `Object.is`).\n *\n * @example\n * const { pool, activeConnectorId } = useWalletStore((state) => ({\n * pool: state.pool,\n * activeConnectorId: state.activeConnectorId,\n * }));\n */\nconst useWalletStore = <T>(selector: (state: WalletStoreState) => T) => {\n const store = useWalletStoreContext();\n return useStoreWithEqualityFn(store, selector, shallow);\n};\n\nexport {\n useAccounts,\n useActiveConnectorId,\n useActiveWallet,\n useConnectedWallets,\n useConnectingConnectorId,\n useConnectionError,\n useConnectionStatus,\n useGetConnectorInstance,\n useGetSelectedWallet,\n useGetWallet,\n useIsConnecting,\n useIsHydrated,\n useIsPlatformConnected,\n useIsUserDisconnected,\n usePool,\n useSelectedWallet,\n useSelection,\n useWalletConnected,\n useWalletEntry,\n useWalletStore,\n};\n","import { useStore } from \"zustand\";\n\nimport { useWalletStoreContext } from \"../context\";\n\n/**\n * Action hooks — dispatchers that mutate the wallet store. Each\n * returns a stable function reference (Zustand's `useStore` against\n * action selectors is identity-stable across renders), safe to pass\n * to event handlers or effects without `useCallback`.\n *\n * No reactive subscription beyond identity stability — these don't\n * re-render the component when store state changes. For reactive\n * reads, see `./selectors.ts`.\n */\n\n/** Begin the connect flow for a connector id. Routes through the\n * store's connect handler, which calls the connector's `connect()`,\n * reads accounts, and writes the resulting `ConnectedWallet` into\n * the pool. */\nconst useConnectWallet = () => {\n const store = useWalletStoreContext();\n return useStore(store, (state) => state.connectWallet);\n};\n\nconst useDisconnectWallet = () => {\n const store = useWalletStoreContext();\n return useStore(store, (state) => state.disconnectWallet);\n};\n\nconst useSetActiveConnector = () => {\n const store = useWalletStoreContext();\n return useStore(store, (state) => state.setActiveConnector);\n};\n\nconst useSetSelection = () => {\n const store = useWalletStoreContext();\n return useStore(store, (state) => state.setSelection);\n};\n\nconst useResetWallet = () => {\n const store = useWalletStoreContext();\n return useStore(store, (state) => state.reset);\n};\n\nconst useUpdateWalletAccount = () => {\n const store = useWalletStoreContext();\n return useStore(store, (state) => state.updateWalletAccount);\n};\n\nconst useRefreshWallet = () => {\n const store = useWalletStoreContext();\n return useStore(store, (state) => state.refreshWallet);\n};\n\n/**\n * Open the wallet's account-selection UI (if the connector supports it),\n * then re-read `getAccounts()` and update the pool entry's `accounts`\n * array. EVM wallets call `wallet_requestPermissions` under the hood;\n * Wallet Standard wallets that don't expose a picker just refresh.\n *\n * Consumers should hide the trigger when\n * `wallet.connector.requestAccounts` is undefined — the action still\n * resolves cleanly, but no picker will open.\n */\nconst useRequestAccounts = () => {\n const store = useWalletStoreContext();\n return useStore(store, (state) => state.requestAccounts);\n};\n\n/** Clear `connectionError` + reset `connectionStatus` to idle. Useful when\n * surfacing an error in UI and giving the user a \"dismiss\" affordance. */\nconst useResetConnectionStatus = () => {\n const store = useWalletStoreContext();\n return useStore(store, (state) => state.resetConnectionStatus);\n};\n\nconst useSetConnectionError = () => {\n const store = useWalletStoreContext();\n return useStore(store, (state) => state.setConnectionError);\n};\n\nexport {\n useConnectWallet,\n useDisconnectWallet,\n useRefreshWallet,\n useRequestAccounts,\n useResetConnectionStatus,\n useResetWallet,\n useSetActiveConnector,\n useSetConnectionError,\n useSetSelection,\n useUpdateWalletAccount,\n};\n","import type { Balance } from \"@usebutr/core\";\nimport { useCallback, useEffect, useMemo, useReducer } from \"react\";\n\nimport { useWalletEntry } from \"./selectors\";\n\n/**\n * Async-resource hooks — return `AsyncState<T>` and run lifecycle\n * effects under the hood. Each composes `useAsyncResource` (defined\n * below) with a selector from `./selectors.ts` (`useWalletEntry`) plus\n * a stable closure that calls the connector.\n *\n * Adding a new async hook is ~5 lines: import `useAsyncResource`,\n * memoise the request closure on the wallet identity, return the\n * state. The cancellation discipline lives once, in\n * `useAsyncResource`.\n */\n\ntype AsyncState<T> =\n | { data: null; error: null; status: \"idle\" }\n | { data: null; error: null; status: \"loading\" }\n | { data: T; error: null; status: \"success\" }\n | { data: null; error: unknown; status: \"error\" };\n\ntype AsyncAction<T> =\n | { type: \"reset\" }\n | { type: \"load\" }\n | { data: T; type: \"success\" }\n | { error: unknown; type: \"error\" };\n\n/** Pure async-lifecycle reducer. One dispatch per state transition\n * keeps `useEffect` clear of cascading setState calls — each effect\n * branch invokes the reducer exactly once. */\nconst asyncReducer = <T>(_state: AsyncState<T>, action: AsyncAction<T>): AsyncState<T> => {\n switch (action.type) {\n case \"reset\": {\n return { data: null, error: null, status: \"idle\" };\n }\n case \"load\": {\n return { data: null, error: null, status: \"loading\" };\n }\n case \"success\": {\n return { data: action.data, error: null, status: \"success\" };\n }\n case \"error\": {\n return { data: null, error: action.error, status: \"error\" };\n }\n default: {\n // Exhaustiveness check — TS errors here if `AsyncAction` grows\n // a variant without a case.\n const exhaustiveCheck: never = action;\n void exhaustiveCheck;\n return { data: null, error: null, status: \"idle\" };\n }\n }\n};\n\nconst IDLE: AsyncState<never> = { data: null, error: null, status: \"idle\" };\n\n/**\n * Generic async-resource hook. Encapsulates the load → fetch → cancel-\n * on-deps-change → dispatch-result lifecycle that every async wallet\n * read needs. `fn` is the request closure; pass `null` to stay idle.\n *\n * Invalidation is keyed on the identity of `fn` itself — callers\n * stabilise via `useMemo` and re-create the closure when they want a\n * refetch. This keeps the React-hooks exhaustive-deps lint rule happy\n * (the effect's deps list is the literal `[fn]`).\n *\n * Why factored out: every consumer (`useSigner`, `useBalance`, future\n * `useTokenBalance`, `useTransactionReceipt`, …) needs the exact same\n * cancellation discipline. Centralising it keeps the fragile parts\n * (`cancelled` flag, dispatch order) in one place — adding a new\n * async hook becomes a 3-line definition.\n */\nconst useAsyncResource = <T>(fn: (() => Promise<T>) | null): AsyncState<T> => {\n const [state, dispatch] = useReducer(asyncReducer<T>, IDLE);\n\n useEffect(() => {\n if (!fn) {\n dispatch({ type: \"reset\" });\n return;\n }\n dispatch({ type: \"load\" });\n let cancelled = false;\n void (async () => {\n try {\n const data = await fn();\n if (!cancelled) {\n dispatch({ data, type: \"success\" });\n }\n } catch (error: unknown) {\n if (!cancelled) {\n dispatch({ error, type: \"error\" });\n }\n }\n })();\n return () => {\n cancelled = true;\n };\n }, [fn]);\n\n return state;\n};\n\n/**\n * Cached signer for a connector. Invalidates when `connectorId`, account\n * address, or chain id changes — so a chain switch or account switch in the\n * wallet invalidates the cached signer automatically.\n *\n * If `connectorId` is omitted (or `null`/`undefined`), the active wallet's\n * signer is returned.\n *\n * Returns `{ data, error, status }`. `status` is `\"idle\"` when there's no\n * wallet, `\"loading\"` while the connector resolves, `\"success\"` once the\n * signer is available, `\"error\"` if `getSigner()` rejected.\n */\nconst useSigner = (connectorId?: string | null): AsyncState<unknown> => {\n const wallet = useWalletEntry(connectorId);\n // Stabilise the request closure so `useAsyncResource` only re-runs\n // when the resolved wallet identity changes (not on every render).\n const fn = useMemo(() => (wallet ? () => wallet.connector.getSigner() : null), [wallet]);\n return useAsyncResource(fn);\n};\n\ntype UseBalanceResult = AsyncState<Balance> & { refetch: () => void };\n\n/**\n * Cached balance for a connector. Invalidates on the same events as\n * `useSigner` (connectorId / address / chainId), plus an explicit `refetch`\n * handle for poll-on-demand or after-action refreshes.\n *\n * If `connectorId` is omitted, the active wallet's balance is returned.\n * `mint` is forwarded to the connector — semantics depend on the chain.\n */\nconst useBalance = (connectorId?: string | null, mint?: string): UseBalanceResult => {\n const wallet = useWalletEntry(connectorId);\n const [counter, bumpCounter] = useReducer((n: number) => n + 1, 0);\n const refetch = useCallback(() => {\n bumpCounter();\n }, []);\n // `counter` participates in the closure identity, so calling\n // `refetch()` produces a new `fn` and `useAsyncResource` re-runs.\n const fn = useMemo(\n () => (wallet ? () => wallet.connector.getBalance(mint) : null),\n // oxlint-disable-next-line eslint-plugin-react-hooks/exhaustive-deps -- counter is the refetch trigger; not used inside the closure\n [wallet, mint, counter],\n );\n const state = useAsyncResource(fn);\n return { ...state, refetch };\n};\n\nexport type { AsyncState, UseBalanceResult };\nexport { useBalance, useSigner };\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usebutr/react",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "React provider and hooks for butr. Depends on @usebutr/core, react, zustand.",
5
5
  "license": "MIT",
6
6
  "author": "Pedro Filho <pedro@filho.me>",
@@ -25,22 +25,22 @@
25
25
  "access": "public"
26
26
  },
27
27
  "dependencies": {
28
- "@usebutr/core": "0.2.0"
28
+ "@usebutr/core": "0.2.2"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@testing-library/jest-dom": "^6.9.1",
32
32
  "@testing-library/react": "^16.3.2",
33
- "@types/react": "^19.0.0",
34
- "@types/react-dom": "^19.0.0",
33
+ "@types/react": "^19.2.15",
34
+ "@types/react-dom": "^19.2.3",
35
35
  "jsdom": "^29.1.1",
36
36
  "react": "^19.2.6",
37
37
  "react-dom": "^19.2.6",
38
38
  "typescript": "^6.0.3",
39
- "vitest": "^4.1.6",
40
- "zustand": "^5.0.0",
39
+ "vitest": "^4.1.8",
40
+ "zustand": "^5.0.14",
41
41
  "@repo/config-vitest": "0.0.0",
42
42
  "@repo/typescript-config": "0.0.0",
43
- "@usebutr/testing": "0.1.1"
43
+ "@usebutr/testing": "0.1.3"
44
44
  },
45
45
  "peerDependencies": {
46
46
  "react": ">=18.0.0",