repositories
loading repo index
repositories
loading repo index
repository
loading code, commits, and activity
when blackout ?
stars
latest
clone command
git clone gitlawb://did:key:z6MkjiiY...3Cmt/when-blackoutgit clone gitlawb://did:key:z6MkjiiY.../when-blackout3914b18async from playground4h ago| #1 | import { useState, useEffect, useCallback, useRef } from "react"; |
| #2 | |
| #3 | /** |
| #4 | * Lightweight Solana wallet hook — connects directly to Phantom/Solflare |
| #5 | * browser extensions. No heavy @solana/* dependencies needed. |
| #6 | */ |
| #7 | |
| #8 | type WalletState = { |
| #9 | connected: boolean; |
| #10 | connecting: boolean; |
| #11 | publicKey: string | null; |
| #12 | balance: number | null; // SOL |
| #13 | walletName: string | null; |
| #14 | }; |
| #15 | |
| #16 | type PhantomProvider = { |
| #17 | isPhantom?: boolean; |
| #18 | connect: (opts?: { onlyIfTrusted?: boolean }) => Promise<{ publicKey: { toString: () => string } }>; |
| #19 | disconnect: () => Promise<void>; |
| #20 | on: (event: string, cb: (...args: unknown[]) => void) => void; |
| #21 | off: (event: string, cb: (...args: unknown[]) => void) => void; |
| #22 | publicKey: { toString: () => string } | null; |
| #23 | isConnected: boolean; |
| #24 | }; |
| #25 | |
| #26 | type SolflareProvider = { |
| #27 | isSolflare?: boolean; |
| #28 | connect: (opts?: { onlyIfTrusted?: boolean }) => Promise<{ publicKey: { toString: () => string } }>; |
| #29 | disconnect: () => Promise<void>; |
| #30 | on: (event: string, cb: (...args: unknown[]) => void) => void; |
| #31 | off: (event: string, cb: (...args: unknown[]) => void) => void; |
| #32 | publicKey: { toString: () => string } | null; |
| #33 | isConnected: boolean; |
| #34 | }; |
| #35 | |
| #36 | function getProvider(): PhantomProvider | SolflareProvider | null { |
| #37 | if (typeof window === "undefined") return null; |
| #38 | const w = window as unknown as Record<string, unknown>; |
| #39 | |
| #40 | // Phantom |
| #41 | if ("phantom" in w) { |
| #42 | const phantom = w.phantom as Record<string, unknown>; |
| #43 | if (phantom.solana && (phantom.solana as PhantomProvider).isPhantom) { |
| #44 | return phantom.solana as PhantomProvider; |
| #45 | } |
| #46 | } |
| #47 | |
| #48 | // Solflare |
| #49 | if ("solflare" in w && (w.solflare as SolflareProvider).isSolflare) { |
| #50 | return w.solflare as SolflareProvider; |
| #51 | } |
| #52 | |
| #53 | // Backwards compat: window.solana |
| #54 | if ("solana" in w && ((w.solana as PhantomProvider).isPhantom || (w.solana as SolflareProvider).isSolflare)) { |
| #55 | return w.solana as PhantomProvider; |
| #56 | } |
| #57 | |
| #58 | return null; |
| #59 | } |
| #60 | |
| #61 | const RPC_URL = "https://api.mainnet-beta.solana.com"; |
| #62 | |
| #63 | async function fetchBalance(address: string): Promise<number> { |
| #64 | try { |
| #65 | const resp = await fetch(RPC_URL, { |
| #66 | method: "POST", |
| #67 | headers: { "Content-Type": "application/json" }, |
| #68 | body: JSON.stringify({ |
| #69 | jsonrpc: "2.0", |
| #70 | id: 1, |
| #71 | method: "getBalance", |
| #72 | params: [address], |
| #73 | }), |
| #74 | }); |
| #75 | const data = await resp.json(); |
| #76 | if (data.result?.value !== undefined) { |
| #77 | return data.result.value / 1e9; // lamports -> SOL |
| #78 | } |
| #79 | } catch { /* */ } |
| #80 | return 0; |
| #81 | } |
| #82 | |
| #83 | export function useSolanaWallet() { |
| #84 | const [state, setState] = useState<WalletState>({ |
| #85 | connected: false, |
| #86 | connecting: false, |
| #87 | publicKey: null, |
| #88 | balance: null, |
| #89 | walletName: null, |
| #90 | }); |
| #91 | const providerRef = useRef<PhantomProvider | SolflareProvider | null>(null); |
| #92 | |
| #93 | // Try auto-connect on mount |
| #94 | useEffect(() => { |
| #95 | const provider = getProvider(); |
| #96 | if (!provider) return; |
| #97 | providerRef.current = provider; |
| #98 | |
| #99 | const tryAutoConnect = async () => { |
| #100 | try { |
| #101 | // Only auto-connect if previously trusted |
| #102 | const resp = await provider.connect({ onlyIfTrusted: true }); |
| #103 | const pk = resp.publicKey.toString(); |
| #104 | const bal = await fetchBalance(pk); |
| #105 | setState({ |
| #106 | connected: true, |
| #107 | connecting: false, |
| #108 | publicKey: pk, |
| #109 | balance: bal, |
| #110 | walletName: "isPhantom" in provider && provider.isPhantom ? "Phantom" : "Solflare", |
| #111 | }); |
| #112 | } catch { |
| #113 | // Not previously authorized — that's fine |
| #114 | } |
| #115 | }; |
| #116 | tryAutoConnect(); |
| #117 | |
| #118 | // Listen for account changes |
| #119 | const onAccountChanged = async (...args: unknown[]) => { |
| #120 | const newPk = args[0] as { toString: () => string } | null; |
| #121 | if (newPk) { |
| #122 | const pk = newPk.toString(); |
| #123 | const bal = await fetchBalance(pk); |
| #124 | setState((s) => ({ ...s, publicKey: pk, balance: bal, connected: true })); |
| #125 | } else { |
| #126 | setState({ connected: false, connecting: false, publicKey: null, balance: null, walletName: null }); |
| #127 | } |
| #128 | }; |
| #129 | |
| #130 | const onDisconnect = () => { |
| #131 | setState({ connected: false, connecting: false, publicKey: null, balance: null, walletName: null }); |
| #132 | }; |
| #133 | |
| #134 | provider.on("accountChanged", onAccountChanged); |
| #135 | provider.on("disconnect", onDisconnect); |
| #136 | |
| #137 | return () => { |
| #138 | provider.off("accountChanged", onAccountChanged); |
| #139 | provider.off("disconnect", onDisconnect); |
| #140 | }; |
| #141 | }, []); |
| #142 | |
| #143 | const connect = useCallback(async () => { |
| #144 | const provider = getProvider(); |
| #145 | if (!provider) { |
| #146 | // No wallet installed — open install page |
| #147 | window.open("https://phantom.app/", "_blank"); |
| #148 | return; |
| #149 | } |
| #150 | providerRef.current = provider; |
| #151 | setState((s) => ({ ...s, connecting: true })); |
| #152 | |
| #153 | try { |
| #154 | const resp = await provider.connect(); |
| #155 | const pk = resp.publicKey.toString(); |
| #156 | const bal = await fetchBalance(pk); |
| #157 | setState({ |
| #158 | connected: true, |
| #159 | connecting: false, |
| #160 | publicKey: pk, |
| #161 | balance: bal, |
| #162 | walletName: "isPhantom" in provider && provider.isPhantom ? "Phantom" : "Solflare", |
| #163 | }); |
| #164 | } catch { |
| #165 | setState((s) => ({ ...s, connecting: false })); |
| #166 | } |
| #167 | }, []); |
| #168 | |
| #169 | const disconnect = useCallback(async () => { |
| #170 | const provider = providerRef.current; |
| #171 | if (provider) { |
| #172 | try { await provider.disconnect(); } catch { /* */ } |
| #173 | } |
| #174 | setState({ connected: false, connecting: false, publicKey: null, balance: null, walletName: null }); |
| #175 | }, []); |
| #176 | |
| #177 | const refreshBalance = useCallback(async () => { |
| #178 | if (!state.publicKey) return; |
| #179 | const bal = await fetchBalance(state.publicKey); |
| #180 | setState((s) => ({ ...s, balance: bal })); |
| #181 | }, [state.publicKey]); |
| #182 | |
| #183 | return { ...state, connect, disconnect, refreshBalance }; |
| #184 | } |
| #185 |