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:
- Provider Setup: Configure the embedded wallet provider (Openfort, Dynamic, Privy, etc.)
- Get Wallet Client: Use the provider’s SDK to access the authenticated wallet and create a viem wallet client
- Pass to Rhinestone: Create a Rhinestone account using the wallet client as a signer
- 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