create-dynamic-app
Version:
CLI tool to generate sample applications using Dynamic's web3 authentication
277 lines (247 loc) • 7.19 kB
text/typescript
import type { Chain } from "../types"
interface MethodConfig {
name: string
method: string
isSigner?: boolean
}
interface ChainMethods {
[ ]:
| 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);
const result = await ${method}(primaryWallet);
setResult(safeStringify(result));
} catch (error) {
setResult(safeStringify({ error: error instanceof Error ? error.message : 'Unknown error occurred' }));
} finally {
setIsLoading(false);
}
}`
}
return `
async function fetch${chain}${name}() {
if (!primaryWallet || !is${chain}Wallet(primaryWallet)) return;
try {
setIsLoading(true);
const result = await primaryWallet.${method}(${isSigner ? '"Hello World"' : ""});
setResult(safeStringify(result));
} catch (error) {
setResult(safeStringify({ error: error instanceof Error ? error.message : 'Unknown error occurred' }));
} 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 generateMethodsContent = (
selectedChains: Chain[],
useViem: boolean
) => {
const walletTypeChecks = selectedChains
.filter((chain) => chain.name in chainMethods)
.map(
(chain) =>
`import { is${chain.name}Wallet } from '@dynamic-labs/${chain.name.toLowerCase()}'`
)
.join("\n")
const ethersImport =
!useViem && selectedChains.some((chain) => chain.name === "Ethereum")
? `import { getWeb3Provider, getSigner } from "@dynamic-labs/ethers-v6";\n`
: ""
const safeStringify = `
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
);
};
`
// 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")
return `'use client';
import { useState, useEffect } from 'react';
import { useDynamicContext, useIsLoggedIn, useUserWallets } from "@dynamic-labs/sdk-react-core";
${walletTypeChecks}
${ethersImport}
import './Methods.css';
interface DynamicMethodsProps {
isDarkMode: boolean;
}
export default function DynamicMethods({ isDarkMode }: DynamicMethodsProps) {
const isLoggedIn = useIsLoggedIn();
const { sdkHasLoaded, primaryWallet, user } = useDynamicContext();
const userWallets = useUserWallets();
const [isLoading, setIsLoading] = useState(true);
const [result, setResult] = useState('');
const [error, setError] = useState<string | null>(null);
${safeStringify}
useEffect(() => {
if (sdkHasLoaded && isLoggedIn && primaryWallet) {
setIsLoading(false);
} else {
setIsLoading(true);
}
}, [sdkHasLoaded, isLoggedIn, primaryWallet]);
function clearResult() {
setResult('');
setError(null);
}
function showUser() {
try {
setResult(safeStringify(user));
setError(null);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to stringify user data');
}
}
function showUserWallets() {
try {
setResult(safeStringify(userWallets));
setError(null);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to stringify wallet data');
}
}
${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}</pre>
)}
</div>
)}
{(result || error) && (
<div className="clear-container">
<button className="btn btn-primary" onClick={clearResult}>Clear</button>
</div>
)}
</div>
)}
</>
);
}`
}