create-dynamic-app
Version:
CLI tool to generate sample applications using Dynamic's web3 authentication
278 lines (249 loc) • 7.31 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);
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}`
}