Prerequisites

  • A Dynamic account and project (with Ethereum enabled)
  • React application setup

Installation

Install the required dependencies:
npm install @dynamic-labs/sdk-react-core @dynamic-labs/ethereum @rhinestone/sdk

Setup Dynamic Provider

First, set up the Dynamic provider in your React application:
import { DynamicContextProvider } from '@dynamic-labs/sdk-react-core'
import { EthereumWalletConnectors } from '@dynamic-labs/ethereum'

function App() {
  return (
    <DynamicContextProvider
      settings={{
        environmentId: process.env.NEXT_PUBLIC_DYNAMIC_ENVIRONMENT_ID,
        walletConnectors: [EthereumWalletConnectors],
      }}
    >
      {/* Your app components */}
    </DynamicContextProvider>
  )
}

Create a Dynamic Integration Hook

Create a custom hook that integrates Dynamic wallets with Rhinestone accounts. This demonstrates the core pattern: get the wallet client, then pass it to Rhinestone.
import { useDynamicContext } from '@dynamic-labs/sdk-react-core'
import { createRhinestoneAccount } from "@rhinestone/sdk"
import { isEthereumWallet } from '@dynamic-labs/ethereum'
import { useState, useEffect, useMemo } from 'react'

export function useGlobalWallet() {
  const { primaryWallet } = useDynamicContext();
  const [rhinestoneAccount, setRhinestoneAccount] = useState(null)

  // Memoize the wallet address to use as a stable dependency
  const walletAddress = useMemo(() => {
    return primaryWallet?.address
  }, [primaryWallet?.address])

  useEffect(() => {
    let isMounted = true

    async function initializeAccount() {
      if (!primaryWallet || !isEthereumWallet(primaryWallet) || !walletAddress) {
        setRhinestoneAccount(null)
        return
      }

      try {
        // Get the wallet client asynchronously
        const walletClient = await primaryWallet.getWalletClient()
        
        if (!isMounted) return

        // Dynamic sometimes needs address explicitly added to the client
        const walletClientWithAddress = {
          ...walletClient,
          address: walletAddress,
        };

        // Pass the wallet client (from Dynamic) to Rhinestone
        // Rhinestone wraps it with cross-chain transaction capabilities
        const account = await createRhinestoneAccount({
          owners: {
            type: "ecdsa",
            accounts: [walletClientWithAddress as any], // client from Dynamic
          },
          rhinestoneApiKey: import.meta.env.VITE_RHINESTONE_API_KEY,
        })

        if (isMounted) {
          setRhinestoneAccount(account)
        }
      } catch (error) {
        console.error('Failed to create Rhinestone account:', error)
        if (isMounted) {
          setRhinestoneAccount(null)
        }
      }
    }

    initializeAccount()

    // Cleanup function to prevent setting state on unmounted component
    return () => {
      isMounted = false
    }
  }, [primaryWallet?.address])

  return { rhinestoneAccount, address: primaryWallet?.address }
}

Usage

Basic Component Integration

Use the hook in your React components:
import { useGlobalWallet } from './hooks/useGlobalWallet'
import { useDynamicContext } from '@dynamic-labs/sdk-react-core'

function WalletDashboard() {
  const { setShowAuthFlow } = useDynamicContext()
  const { rhinestoneAccount, address } = useGlobalWallet()

  if (!rhinestoneAccount) {
    return (
      <button onClick={() => setShowAuthFlow(true)}>
        Connect Wallet
      </button>
    )
  }

  return (
    <div>
      <h2>Connected: {address}</h2>
      <p>Smart Account: {rhinestoneAccount.getAddress()}</p>
    </div>
  )
}

Cross-Chain Transactions

Send transactions using the Dynamic-connected wallet:
async function handleCrossChainTransfer() {
  const transaction = await rhinestoneAccount.sendTransaction({
    sourceChains: [baseSepolia],
    targetChain: arbitrumSepolia,
    calls: [
      {
        to: "USDC",
        data: encodeFunctionData({
          abi: erc20Abi,
          functionName: "transfer",
          args: ["0xrecipient", parseUnits("10", 6)],
        }),
      },
    ],
    tokenRequests: [
      {
        address: "USDC",
        amount: parseUnits("10", 6),
      },
    ],
  })
}

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

Next Steps