diff --git a/src/components/Demo.tsx b/src/components/Demo.tsx index 1f81c58..526c57e 100644 --- a/src/components/Demo.tsx +++ b/src/components/Demo.tsx @@ -17,6 +17,11 @@ import { useSwitchChain, useChainId, } from "wagmi"; +import { + useConnection as useSolanaConnection, + useWallet as useSolanaWallet, +} from '@solana/wallet-adapter-react'; +import { useHasSolanaProvider } from "./providers/SafeFarcasterSolanaProvider"; import { config } from "~/components/providers/WagmiProvider"; import { Button } from "~/components/ui/Button"; @@ -38,6 +43,13 @@ export default function Demo( const { address, isConnected } = useAccount(); const chainId = useChainId(); + const hasSolanaProvider = useHasSolanaProvider(); + let solanaWallet, solanaPublicKey, solanaSignMessage, solanaAddress; + if (hasSolanaProvider) { + solanaWallet = useSolanaWallet(); + ({ publicKey: solanaPublicKey, signMessage: solanaSignMessage } = solanaWallet); + solanaAddress = solanaPublicKey?.toBase58(); + } useEffect(() => { console.log("isSDKLoaded", isSDKLoaded); @@ -413,11 +425,65 @@ export default function Demo( )} + + {solanaAddress && ( +
+

Solana

+
+ Address:
{truncateAddress(solanaAddress)}
+
+ +
+ )} ); } +function SignSolanaMessage({ signMessage }: { signMessage?: (message: Uint8Array) => Promise }) { + const [signature, setSignature] = useState(); + const [signError, setSignError] = useState(); + const [signPending, setSignPending] = useState(false); + + const handleSignMessage = useCallback(async () => { + setSignPending(true); + try { + if (!signMessage) { + throw new Error('no Solana signMessage'); + } + const input = Buffer.from("Hello from Solana!", "utf8"); + const signatureBytes = await signMessage(input); + const signature = Buffer.from(signatureBytes).toString("base64"); + setSignature(signature); + setSignError(undefined); + } catch (e) { + if (e instanceof Error) { + setSignError(e); + } + } finally { + setSignPending(false); + } + }, [signMessage]); + + return ( + <> + + {signError && renderError(signError)} + {signature && ( +
+
Signature: {signature}
+
+ )} + + ); +} + function SignMessage() { const { isConnected } = useAccount(); const { connectAsync } = useConnect(); diff --git a/src/components/SolanaWalletDemo.tsx b/src/components/SolanaWalletDemo.tsx deleted file mode 100644 index c68f77f..0000000 --- a/src/components/SolanaWalletDemo.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import * as React from "react"; -import { useWallet } from "@solana/wallet-adapter-react"; - -export function SolanaWalletDemo() { - const { publicKey } = useWallet(); - const solanaAddress = publicKey?.toBase58() ?? ""; - return ( -
-

Solana Wallet Demo

-
- Solana Address: - {solanaAddress || "Not connected"} -
-
- ); -} diff --git a/src/components/providers/SafeFarcasterSolanaProvider.tsx b/src/components/providers/SafeFarcasterSolanaProvider.tsx new file mode 100644 index 0000000..a63761c --- /dev/null +++ b/src/components/providers/SafeFarcasterSolanaProvider.tsx @@ -0,0 +1,86 @@ +import * as React from "react"; +import dynamic from "next/dynamic"; +import { sdk } from '@farcaster/frame-sdk'; + +const FarcasterSolanaProvider = dynamic( + () => import('@farcaster/mini-app-solana').then(mod => mod.FarcasterSolanaProvider), + { ssr: false } +); + +type SafeFarcasterSolanaProviderProps = { + endpoint: string; + children: React.ReactNode; +}; + +const SolanaProviderContext = React.createContext<{ hasSolanaProvider: boolean }>({ hasSolanaProvider: false }); + +export function SafeFarcasterSolanaProvider({ endpoint, children }: SafeFarcasterSolanaProviderProps) { + const isClient = typeof window !== "undefined"; + const [hasSolanaProvider, setHasSolanaProvider] = React.useState(false); + const [checked, setChecked] = React.useState(false); + + React.useEffect(() => { + if (!isClient) return; + let cancelled = false; + (async () => { + try { + const provider = await sdk.wallet.getSolanaProvider(); + if (!cancelled) { + setHasSolanaProvider(!!provider); + } + } catch { + if (!cancelled) { + setHasSolanaProvider(false); + } + } finally { + if (!cancelled) { + setChecked(true); + } + } + })(); + return () => { + cancelled = true; + }; + }, [isClient]); + + React.useEffect(() => { + let errorShown = false; + const origError = console.error; + console.error = (...args) => { + if ( + typeof args[0] === "string" && + args[0].includes("WalletConnectionError: could not get Solana provider") + ) { + if (!errorShown) { + origError(...args); + errorShown = true; + } + return; + } + origError(...args); + }; + return () => { + console.error = origError; + }; + }, []); + + if (!isClient || !checked) { + return null; + } + + return ( + + {hasSolanaProvider ? ( + + {children} + + ) : ( + <>{children} + )} + + ); +} + +export function useHasSolanaProvider() { + return React.useContext(SolanaProviderContext).hasSolanaProvider; +}