Documentation Index
Fetch the complete documentation index at: https://docs.rhinestone.dev/llms.txt
Use this file to discover all available pages before exploring further.
Overview
Dynamic provides embedded wallet infrastructure that enables seamless user onboarding and wallet management. This guide shows you how to integrate Dynamic signers with Rhinestone smart accounts to create a unified, cross-chain wallet experience.
How it works: Dynamic handles user authentication and wallet connections, providing wagmi-compatible clients. We use wagmi hooks to access these clients and pass them to Rhinestone’s SDK, which wraps them with cross-chain capabilities.
Prerequisites
- A Dynamic account and project
- Dynamic Environment ID
- React application setup
Install Dependencies
Install the required dependencies:npm install @dynamic-labs/sdk-react-core @dynamic-labs/ethereum @dynamic-labs/wagmi-connector @tanstack/react-query @rhinestone/sdk viem wagmi
Set Up Dynamic Provider
Set up the Dynamic provider with wagmi and TanStack Query in your React application:import { DynamicContextProvider } from '@dynamic-labs/sdk-react-core'
import { EthereumWalletConnectors } from '@dynamic-labs/ethereum'
import { DynamicWagmiConnector } from '@dynamic-labs/wagmi-connector'
import { createConfig, WagmiProvider } from 'wagmi'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { http } from 'viem'
import { mainnet, polygon, arbitrum, base } from 'viem/chains'
const wagmiConfig = createConfig({
chains: [mainnet, polygon, arbitrum, base],
multiInjectedProviderDiscovery: false,
transports: {
[mainnet.id]: http(),
[polygon.id]: http(),
[arbitrum.id]: http(),
[base.id]: http(),
},
})
const queryClient = new QueryClient()
function App() {
return (
<DynamicContextProvider
settings={{
environmentId: process.env.NEXT_PUBLIC_DYNAMIC_ENVIRONMENT_ID,
walletConnectors: [EthereumWalletConnectors],
}}
>
<WagmiProvider config={wagmiConfig}>
<QueryClientProvider client={queryClient}>
<DynamicWagmiConnector>
{/* Your app components */}
</DynamicWagmiConnector>
</QueryClientProvider>
</WagmiProvider>
</DynamicContextProvider>
)
}
Create Integration Hook
Create a production-ready hook that integrates Dynamic wallets with Rhinestone accounts. This demonstrates the core pattern: get the wallet client from wagmi, then pass it to Rhinestone.Dynamic automatically populates wagmi’s useAccount() and useWalletClient() hooks when users connect their wallets.
import { useState, useEffect } from 'react'
import { useAccount, useWalletClient } from "wagmi"
import { RhinestoneSDK } from "@rhinestone/sdk"
import { walletClientToAccount } from "@rhinestone/sdk/utils"
interface GlobalWalletState {
rhinestoneAccount: any | null
address: string | null
isLoading: boolean
error: string | null
isConnected: boolean
}
export function useGlobalWallet(): GlobalWalletState & {
reconnect: () => Promise<void>
} {
const { address, isConnected } = useAccount()
const { data: walletClient } = useWalletClient()
const [state, setState] = useState<GlobalWalletState>({
rhinestoneAccount: null,
address: null,
isLoading: false,
error: null,
isConnected: false,
})
const initializeAccount = async () => {
if (!isConnected || !address || !walletClient) {
setState(prev => ({
...prev,
rhinestoneAccount: null,
address: null,
isConnected: false,
error: null,
}))
return
}
setState(prev => ({ ...prev, isLoading: true, error: null }))
try {
// Ensure wallet client has address property for Rhinestone compatibility
const wrappedWalletClient = walletClientToAccount(walletClient)
const apiKey = process.env.NEXT_PUBLIC_RHINESTONE_API_KEY
if (!apiKey) {
throw new Error('NEXT_PUBLIC_RHINESTONE_API_KEY is not configured')
}
// Create Rhinestone account using Dynamic's wallet client
const rhinestone = new RhinestoneSDK({
apiKey,
})
const account = await rhinestone.createAccount({
owners: {
type: "ecdsa",
accounts: [wrappedWalletClient],
},
})
setState(prev => ({
...prev,
rhinestoneAccount: account,
address,
isConnected: true,
isLoading: false,
}))
} catch (error) {
console.error('Failed to initialize Rhinestone account:', error)
setState(prev => ({
...prev,
error: error instanceof Error ? error.message : 'Failed to initialize account',
isLoading: false,
isConnected: false,
}))
}
}
useEffect(() => {
initializeAccount()
}, [isConnected, address, walletClient])
return {
...state,
reconnect: initializeAccount,
}
}
Usage
Basic Component Integration
Use the enhanced hook in your React components with proper loading and error states:
import { useGlobalWallet } from './hooks/useGlobalWallet'
import { useDynamicContext } from '@dynamic-labs/sdk-react-core'
function WalletDashboard() {
const { setShowAuthFlow } = useDynamicContext()
const {
rhinestoneAccount,
address,
isLoading,
error,
isConnected,
reconnect
} = useGlobalWallet()
// Handle loading state
if (isLoading) {
return (
<div className="flex items-center justify-center p-8">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
<span className="ml-2">Setting up your global wallet...</span>
</div>
)
}
// Handle error state
if (error) {
return (
<div className="bg-red-50 border border-red-200 rounded-lg p-4">
<h3 className="text-red-800 font-medium">Wallet Setup Error</h3>
<p className="text-red-600 text-sm mt-1">{error}</p>
<button
onClick={reconnect}
className="mt-2 px-3 py-1 bg-red-600 text-white rounded text-sm hover:bg-red-700"
>
Try Again
</button>
</div>
)
}
// Handle disconnected state
if (!isConnected || !rhinestoneAccount) {
return (
<div className="text-center p-8">
<h2 className="text-xl font-semibold text-gray-900 mb-4">
Connect Your Wallet
</h2>
<p className="text-gray-600 mb-6">
Connect with Dynamic to access cross-chain functionality
</p>
<button
onClick={() => setShowAuthFlow(true)}
className="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
>
Connect Wallet
</button>
</div>
)
}
// Connected state
return (
<div className="bg-green-50 border border-green-200 rounded-lg p-6">
<h2 className="text-lg font-semibold text-green-900 mb-2">
Wallet Connected
</h2>
<div className="space-y-2 text-sm">
<p><strong>EOA Address:</strong> {address}</p>
<p><strong>Smart Account:</strong> {rhinestoneAccount.getAddress()}</p>
<p className="text-green-700">Ready for cross-chain transactions!</p>
</div>
</div>
)
}
Cross-Chain Transactions
Send transactions using the Dynamic-connected wallet with proper error handling:
import { useState } from 'react'
import { encodeFunctionData, parseUnits, erc20Abi } from 'viem'
import { baseSepolia, arbitrumSepolia } from 'viem/chains'
function CrossChainTransfer({ rhinestoneAccount }) {
const [isTransacting, setIsTransacting] = useState(false)
const [txResult, setTxResult] = useState(null)
const [error, setError] = useState(null)
const handleCrossChainTransfer = async () => {
if (!rhinestoneAccount) return
setIsTransacting(true)
setError(null)
setTxResult(null)
try {
const prepared = await rhinestoneAccount.prepareTransaction({
sourceChains: [baseSepolia],
targetChain: arbitrumSepolia,
calls: [
{
to: "USDC", // This resolves to the USDC address on the target chain
data: encodeFunctionData({
abi: erc20Abi,
functionName: "transfer",
args: ["0xrecipient", parseUnits("10", 6)],
}),
},
],
tokenRequests: [
{
address: "USDC",
amount: parseUnits("10", 6),
},
],
})
const signed = await rhinestoneAccount.signTransaction(prepared)
const transaction = await rhinestoneAccount.submitTransaction(signed)
// Wait for transaction execution
const result = await rhinestoneAccount.waitForExecution(transaction)
setTxResult({
id: transaction.id,
hash: result.transactionHash,
status: 'success',
})
} catch (err) {
console.error('Transaction failed:', err)
setError(err instanceof Error ? err.message : 'Transaction failed')
} finally {
setIsTransacting(false)
}
}
return (
<div className="space-y-4">
<button
onClick={handleCrossChainTransfer}
disabled={isTransacting || !rhinestoneAccount}
className="px-4 py-2 bg-blue-600 text-white rounded disabled:opacity-50"
>
{isTransacting ? 'Sending...' : 'Send 10 USDC (Base → Arbitrum)'}
</button>
{error && (
<div className="text-red-600 text-sm">
Error: {error}
</div>
)}
{txResult && (
<div className="text-green-600 text-sm">
Transaction successful!
<a
href={`https://arbiscan.io/tx/${txResult.hash}`}
target="_blank"
rel="noopener noreferrer"
className="underline ml-1"
>
View on Arbiscan
</a>
</div>
)}
</div>
)
}
Environment Variables
Make sure to set the following environment variables:
NEXT_PUBLIC_DYNAMIC_ENVIRONMENT_ID=your_dynamic_environment_id
NEXT_PUBLIC_RHINESTONE_API_KEY=your_rhinestone_api_key
Complete Example
Try the full integration in our example repository:
git clone https://github.com/rhinestonewtf/e2e-examples.git
cd e2e-examples/dynamic
npm install && npm run dev
The example demonstrates:
- Dynamic wallet connection and authentication
- Rhinestone smart account creation
- Cross-chain transactions
- Error handling and best practices
EIP-7702 Support
For EIP-7702 with Dynamic, you need to create a custom viem account that wraps Dynamic’s signer.
Get the Dynamic signer from the connector, then use viem’s toAccount to create a compatible account:
import { useDynamicContext } from "@dynamic-labs/sdk-react-core";
import { toAccount } from "viem/accounts";
const { primaryWallet } = useDynamicContext();
const connector = primaryWallet.connector;
connector.setActiveAccount(primaryWallet.address);
const dynamicSigner = await connector.getSigner();
const eoaAccount = toAccount({
address: primaryWallet.address,
signMessage: ({ message }) => dynamicSigner.signMessage({ message }),
signTransaction: (tx) => dynamicSigner.signTransaction(tx),
signTypedData: (typedData) => dynamicSigner.signTypedData(typedData),
signAuthorization: (authorization) => dynamicSigner.signAuthorization(authorization),
});
Pass this account to Rhinestone with the eoa parameter:
const rhinestoneAccount = await sdk.createAccount({
owners: { type: "ecdsa", accounts: [eoaAccount] },
eoa: eoaAccount,
});
Next Steps