UNPKG

create-dynamic-app

Version:

CLI tool to generate sample applications using Dynamic's web3 authentication

278 lines (249 loc) 7.31 kB
import type { Chain } from "../types" interface MethodConfig { name: string method: string isSigner?: boolean } interface ChainMethods { [key: string]: | MethodConfig[] | { viem: MethodConfig[] ethers: MethodConfig[] } } // Method configurations for each chain const chainMethods: ChainMethods = { Ethereum: { viem: [ { name: "PublicClient", method: "getPublicClient" }, { name: "WalletClient", method: "getWalletClient" }, { name: "Message", method: "signMessage", isSigner: true }, ], ethers: [ { name: "Provider", method: "getWeb3Provider" }, { name: "Signer", method: "getSigner" }, { name: "Message", method: "signMessage", isSigner: true }, ], }, Solana: [ { name: "Connection", method: "getConnection" }, { name: "Signer", method: "getSigner" }, { name: "Message", method: "signMessage", isSigner: true }, ], Starknet: [{ name: "WalletAccount", method: "getWalletAccount" }], Algorand: [ { name: "Signer", method: "getSigner" }, { name: "Message", method: "signMessage", isSigner: true }, ], Cosmos: [ { name: "OfflineSigner", method: "getOfflineSigner" }, { name: "Provider", method: "getProvider" }, { name: "Message", method: "signMessage", isSigner: true }, ], Sui: [ { name: "Client", method: "getSuiClient" }, { name: "WalletAccount", method: "getWalletAccount" }, { name: "ActiveNetwork", method: "getActiveNetwork" }, ], } // Button generator const generateButton = (label: string, onClick: string) => ` <button type="button" className="btn btn-primary" onClick={${onClick}}> ${label} </button>` // Method generator const generateMethod = ( chain: string, name: string, method: string, isSigner = false ) => { if ( chain === "Ethereum" && (method === "getSigner" || method === "getWeb3Provider") ) { return ` async function fetch${chain}${name}() { if (!primaryWallet || !is${chain}Wallet(primaryWallet)) return; try { setIsLoading(true); setError(null); const result = await ${method}(primaryWallet); setResult(safeStringify(result)); } catch (error) { setError(error instanceof Error ? error.message : 'Unknown error occurred'); setResult(undefined); } finally { setIsLoading(false); } }` } return ` async function fetch${chain}${name}() { if (!primaryWallet || !is${chain}Wallet(primaryWallet)) return; try { setIsLoading(true); setError(null); const result = await primaryWallet.${method}(${isSigner ? '"Hello World"' : ""}); setResult(safeStringify(result)); } catch (error) { setError(error instanceof Error ? error.message : 'Unknown error occurred'); setResult(undefined); } finally { setIsLoading(false); } }` } // Chain button generator const generateChainButtons = (chain: Chain, useViem: boolean) => { const buttons: string[] = [] // Skip if chain not in configuration if (!(chain.name in chainMethods)) { return "" } const methods = chain.name === "Ethereum" ? ( chainMethods[chain.name] as { viem: MethodConfig[] ethers: MethodConfig[] } )[useViem ? "viem" : "ethers"] : (chainMethods[chain.name] as MethodConfig[]) for (const { name } of methods) { const methodName = `fetch${chain.name}${name}` buttons.push(generateButton(`Fetch ${name}`, methodName)) } if (buttons.length === 0) return "" return `{primaryWallet && is${chain.name}Wallet(primaryWallet) && ( <> ${buttons.join("\n")} </> )}` } export const generateMethodsTsxContent = ( selectedChains: Chain[], useViem: boolean ): string => { const imports = `import { useState, useEffect } from 'react'; import { useDynamicContext, useIsLoggedIn, useUserWallets } from "@dynamic-labs/sdk-react-core"; ${selectedChains .filter((chain) => chain.name in chainMethods) .map( (chain) => `import { is${chain.name}Wallet } from '@dynamic-labs/${chain.name.toLowerCase()}';` ) .join("\n")} ${ !useViem && selectedChains.some((chain) => chain.name === "Ethereum") ? `import { getWeb3Provider, getSigner } from "@dynamic-labs/ethers-v6";\n` : "" } import './Methods.css'; ` // Generate all methods for selected chains const generatedMethods = selectedChains .filter((chain) => chain.name in chainMethods) .map((chain) => { const methods = chain.name === "Ethereum" ? ( chainMethods[chain.name] as { viem: MethodConfig[] ethers: MethodConfig[] } )[useViem ? "viem" : "ethers"] : (chainMethods[chain.name] as MethodConfig[]) return methods .map(({ name, method, isSigner }) => generateMethod(chain.name, name, method, isSigner) ) .join("\n") }) .join("\n") const methodsCode = `export default function DynamicMethods({ isDarkMode }: { isDarkMode: boolean }) { const isLoggedIn = useIsLoggedIn(); const { sdkHasLoaded, primaryWallet, user } = useDynamicContext(); const userWallets = useUserWallets(); const [isLoading, setIsLoading] = useState(true); const [result, setResult] = useState<undefined | string>(undefined); const [error, setError] = useState<string | null>(null); const safeStringify = (obj: unknown): string => { const seen = new WeakSet(); return JSON.stringify(obj, (key, value) => { if (typeof value === 'object' && value !== null) { if (seen.has(value)) { return '[Circular]'; } seen.add(value); } return value; }, 2); }; useEffect(() => { if (sdkHasLoaded && isLoggedIn && primaryWallet) { setIsLoading(false); } else { setIsLoading(true); } }, [sdkHasLoaded, isLoggedIn, primaryWallet]); function clearResult() { setResult(undefined); setError(null); } function showUser() { try { setError(null); setResult(safeStringify(user)); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to stringify user data'); setResult(undefined); } } function showUserWallets() { try { setError(null); setResult(safeStringify(userWallets)); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to stringify wallet data'); setResult(undefined); } } ${generatedMethods} return ( <> {!isLoading && ( <div className="dynamic-methods" data-theme={isDarkMode ? 'dark' : 'light'}> <div className="methods-container"> <button className="btn btn-primary" onClick={showUser}>Fetch User</button> <button className="btn btn-primary" onClick={showUserWallets}>Fetch User Wallets</button> ${selectedChains.map((chain) => generateChainButtons(chain, useViem)).join("\n")} </div> {(result || error) && ( <div className="results-container"> {error ? ( <pre className="results-text error">{error}</pre> ) : ( <pre className="results-text"> {result && ( typeof result === "string" && result.startsWith("{") ? JSON.stringify(JSON.parse(result), null, 2) : result )} </pre> )} </div> )} {(result || error) && ( <div className="clear-container"> <button className="btn btn-primary" onClick={clearResult}>Clear</button> </div> )} </div> )} </> ); }` return `${imports}${methodsCode}` }