Skip to main content

Overview

Openfort provides embedded wallet infrastructure that enables seamless user authentication and wallet management with built-in Shield security. This guide shows you how to integrate Openfort signers with Rhinestone smart accounts to create a secure, cross-chain wallet experience. How it works: Openfort handles user authentication and embedded wallet creation with Shield security, 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

  • An Openfort account
  • Openfort publishable key
  • Shield publishable key
  • React application setup

Installation

Install the required dependencies:
npm install @openfort/react @tanstack/react-query @rhinestone/sdk viem wagmi

Setup Openfort Provider

First, set up the Openfort provider with wagmi and React Query in your React application:
import { OpenfortProvider, getDefaultConfig, AccountTypeEnum, AuthProvider } from '@openfort/react'
import { WagmiProvider, createConfig } from 'wagmi'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { sepolia } from 'viem/chains'

// Configure wagmi
const wagmiConfig = createConfig(
  getDefaultConfig({
    appName: 'My App',
    chains: [sepolia],
    ssr: true
  })
)

// Create query client
const queryClient = new QueryClient()

function App() {
  return (
    <WagmiProvider config={wagmiConfig}>
      <QueryClientProvider client={queryClient}>
        <OpenfortProvider
          publishableKey={process.env.NEXT_PUBLIC_OPENFORT_PUBLISHABLE_KEY}
          walletConfig={{
            shieldPublishableKey: process.env.NEXT_PUBLIC_SHIELD_PUBLISHABLE_KEY,
            accountType: AccountTypeEnum.EOA
          }}
          uiConfig={{
            authProviders: [
              AuthProvider.EMAIL,
              AuthProvider.GOOGLE,
              AuthProvider.GUEST
            ]
          }}
        >
          {/* Your app components */}
        </OpenfortProvider>
      </QueryClientProvider>
    </WagmiProvider>
  )
}

Create an Openfort Integration Hook

Create a production-ready hook that integrates Openfort wallets with Rhinestone accounts. This demonstrates the core pattern: get the wallet client from Openfort’s embedded wallet, then pass it to Rhinestone.
Openfort provides embedded wallets with Shield security that can be accessed via a viem-compatible provider.
import { useState, useEffect, useCallback } from 'react'
import { useUser, useWallets, useOpenfort } from '@openfort/react'
import { createWalletClient, custom } from 'viem'
import { sepolia } from 'viem/chains'
import { RhinestoneSDK, walletClientToAccount } from '@rhinestone/sdk'

interface OpenfortWalletState {
  rhinestoneAccount: any | null
  address: string | null
  isLoading: boolean
  error: string | null
  isAuthenticated: boolean
}

export function useOpenfortWallet(): OpenfortWalletState & {
  reconnect: () => Promise<void>
} {
  const { isAuthenticated } = useUser()
  const { wallets } = useWallets()
  const { client } = useOpenfort()
  const [state, setState] = useState<OpenfortWalletState>({
    rhinestoneAccount: null,
    address: null,
    isLoading: false,
    error: null,
    isAuthenticated: false,
  })

  const initializeAccount = useCallback(async () => {
    if (!isAuthenticated || !wallets || wallets.length === 0) {
      setState(prev => ({
        ...prev,
        rhinestoneAccount: null,
        address: null,
        isAuthenticated: false,
        error: null,
      }))
      return
    }

    setState(prev => ({ ...prev, isLoading: true, error: null }))

    try {
      // Get Ethereum provider from Openfort's embedded wallet
      const ethereumProvider = await client.embeddedWallet.getEthereumProvider()

      // Get signer address from Openfort wallet
      const signerAddress = wallets[0].address as `0x${string}`

      // Create wallet client with viem
      const walletClient = createWalletClient({
        chain: sepolia,
        transport: custom(ethereumProvider),
        account: signerAddress
      })

      // Wrap wallet client 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 Openfort's wallet client
      const rhinestone = new RhinestoneSDK({
        apiKey,
      })
      const account = await rhinestone.createAccount({
        owners: {
          type: "ecdsa",
          accounts: [wrappedWalletClient],
        },
      })

      setState(prev => ({
        ...prev,
        rhinestoneAccount: account,
        address: signerAddress,
        isAuthenticated: 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,
        isAuthenticated: false,
      }))
    }
  }, [isAuthenticated, wallets, client])

  useEffect(() => {
    initializeAccount()
  }, [initializeAccount])

  return {
    ...state,
    reconnect: initializeAccount,
  }
}

Usage

Basic Component Integration

Use the enhanced hook in your React components with Openfort’s pre-built authentication UI:
import { useOpenfortWallet } from './hooks/useOpenfortWallet'
import { OpenfortButton } from '@openfort/react'

function WalletDashboard() {
  const {
    rhinestoneAccount,
    address,
    isLoading,
    error,
    isAuthenticated,
    reconnect
  } = useOpenfortWallet()

  // Handle loading state
  if (isLoading) {
    return (
      <div>
        <div></div>
        <span>Setting up your wallet...</span>
      </div>
    )
  }

  // Handle error state
  if (error) {
    return (
      <div>
        <h3>Wallet Setup Error</h3>
        <p>{error}</p>
        <button onClick={reconnect}>
          Try Again
        </button>
      </div>
    )
  }

  // Handle disconnected state - use OpenfortButton for auth
  if (!isAuthenticated || !rhinestoneAccount) {
    return (
      <div>
        <h2>
          Connect Your Wallet
        </h2>
        <p>
          Connect with Openfort to access cross-chain functionality
        </p>
        <OpenfortButton />
      </div>
    )
  }

  // Connected state
  return (
    <div>
      <h2>
        Wallet Connected
      </h2>
      <div>
        <p><strong>EOA Address:</strong> {address}</p>
        <p><strong>Smart Account:</strong> {rhinestoneAccount.getAddress()}</p>
        <p>Ready for cross-chain transactions!</p>
      </div>
      <div>
        <OpenfortButton />
      </div>
    </div>
  )
}

Cross-Chain Transactions

Send transactions using the Openfort-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 transaction = await rhinestoneAccount.sendTransaction({
        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),
          },
        ],
      })

      // 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>
      <button
        onClick={handleCrossChainTransfer}
        disabled={isTransacting || !rhinestoneAccount}
      >
        {isTransacting ? 'Sending...' : 'Send 10 USDC (Base → Arbitrum)'}
      </button>

      {error && (
        <div>
          Error: {error}
        </div>
      )}

      {txResult && (
        <div>
          Transaction successful!
          <a
            href={`https://arbiscan.io/tx/${txResult.hash}`}
            target="_blank"
            rel="noopener noreferrer"
          >
            View on Arbiscan
          </a>
        </div>
      )}
    </div>
  )
}

The Integration Pattern

All embedded signer integrations follow the same pattern:
  1. Provider Setup: Configure the embedded wallet provider (Openfort, Dynamic, Privy, etc.)
  2. Get Wallet Client: Use the provider’s SDK to access the authenticated wallet and create a viem wallet client
  3. Pass to Rhinestone: Create a Rhinestone account using the wallet client as a signer
  4. Use Cross-Chain Features: The resulting account has all of Rhinestone’s capabilities
This pattern means you can easily switch between providers or support multiple providers in the same app.

Authentication Methods

Openfort supports multiple authentication methods out of the box:
  • Email: Users can sign in with their email address
  • Social: Google and other OAuth providers
  • Guest: Anonymous authentication for quick onboarding
The <OpenfortButton /> component automatically handles the authentication flow with all configured methods.

Shield Security

Openfort’s Shield provides additional security for embedded wallets:
  • Key Management: Secure key storage and recovery
  • Transaction Protection: Advanced fraud detection
  • Recovery Options: User-friendly account recovery flows
Configure Shield in your OpenfortProvider with the shieldPublishableKey.

Environment Variables

Make sure to set the following environment variables:
NEXT_PUBLIC_OPENFORT_PUBLISHABLE_KEY=your_openfort_publishable_key
NEXT_PUBLIC_SHIELD_PUBLISHABLE_KEY=your_shield_publishable_key
NEXT_PUBLIC_RHINESTONE_API_KEY=your_rhinestone_api_key

Next Steps