Skip to main content

Overview

Privy provides simple, secure wallet infrastructure that enables seamless user authentication and wallet management. This guide shows you how to integrate Privy signers with Rhinestone smart accounts to create a smooth, cross-chain wallet experience. How it works: Privy handles user authentication and wallet creation, then provides a wagmi-compatible client that we wrap with Rhinestone’s smart account functionality. You get the best of both worlds - Privy’s seamless onboarding and Rhinestone’s cross-chain capabilities.

Prerequisites

  • A Privy account and application
  • Privy App ID
  • React application setup
1

Install Dependencies

Install the required dependencies:
npm install @privy-io/react-auth @privy-io/wagmi @tanstack/react-query @rhinestone/sdk viem wagmi
Make sure to import createConfig and WagmiProvider from @privy-io/wagmi rather than from wagmi directly. Privy’s wrappers ensure the two libraries stay in sync.
2

Set Up Providers

Set up the Privy, wagmi, and TanStack Query providers in your React application:
import { PrivyProvider } from '@privy-io/react-auth'
import { createConfig, WagmiProvider } from '@privy-io/wagmi'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { http } from 'wagmi'
import { mainnet, polygon, arbitrum, base } from 'viem/chains'

const queryClient = new QueryClient()

const wagmiConfig = createConfig({
  chains: [mainnet, polygon, arbitrum, base],
  transports: {
    [mainnet.id]: http(),
    [polygon.id]: http(),
    [arbitrum.id]: http(),
    [base.id]: http(),
  },
})

function App() {
  return (
    <PrivyProvider
      appId={process.env.NEXT_PUBLIC_PRIVY_APP_ID}
      config={{
        loginMethods: ['email', 'wallet', 'google', 'twitter'],
        appearance: {
          theme: 'light',
          accentColor: '#676FFF',
        },
        embeddedWallets: {
          ethereum: {
            createOnLogin: 'users-without-wallets',
          },
        },
        defaultChain: base,
        supportedChains: [mainnet, polygon, arbitrum, base],
      }}
    >
      <QueryClientProvider client={queryClient}>
        <WagmiProvider config={wagmiConfig}>
          {/* Your app components */}
        </WagmiProvider>
      </QueryClientProvider>
    </PrivyProvider>
  )
}
3

Create Integration Hook

Create a custom hook that integrates Privy authentication with Rhinestone accounts. This hook demonstrates the key pattern: use wagmi to get the wallet client from Privy, then pass it to Rhinestone as a signer.
The useWalletClient() hook from wagmi automatically connects to Privy’s embedded wallets and external wallet connections.
import { useState, useEffect } from 'react'
import { usePrivy, useWallets } from "@privy-io/react-auth"
import { useWalletClient, useAccount } from "wagmi"
import { RhinestoneSDK, walletClientToAccount } from "@rhinestone/sdk"

interface PrivyWalletState {
  rhinestoneAccount: any | null
  isLoading: boolean
  error: string | null
}

export function usePrivyWallet() {
  const { ready, authenticated, login, logout } = usePrivy()
  const { wallets } = useWallets()
  const { address } = useAccount()
  const { data: walletClient } = useWalletClient()
  const [state, setState] = useState<PrivyWalletState>({
    rhinestoneAccount: null,
    isLoading: false,
    error: null,
  })

  const initializeAccount = async () => {
    if (!ready || !authenticated || !address || !walletClient) {
      setState({ rhinestoneAccount: null, isLoading: false, error: null })
      return
    }

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

    try {
      // wrap the wagmi client for the sdk
      const wrappedWalletClient = walletClientToAccount(walletClient)

      // create the rhinestone account
      const rhinestone = new RhinestoneSDK({
        apiKey: process.env.NEXT_PUBLIC_RHINESTONE_API_KEY,
      })
      const account = await rhinestone.createAccount({
        owners: {
          type: "ecdsa",
          accounts: [wrappedWalletClient],
        },
      })

      setState({ rhinestoneAccount: account, isLoading: false, error: null })
    } catch (error) {
      console.error('Failed to initialize Rhinestone account:', error)
      setState({
        rhinestoneAccount: null,
        isLoading: false,
        error: error instanceof Error ? error.message : 'Failed to initialize account',
      })
    }
  }

  useEffect(() => {
    initializeAccount()
  }, [ready, authenticated, address, walletClient])

  return {
    rhinestoneAccount: state.rhinestoneAccount,
    isLoading: state.isLoading,
    error: state.error,
    authenticated,
    login,
    logout,
    reconnect: initializeAccount,
  }
}

Usage

Basic Authentication Flow

Use the hook to handle Privy authentication and smart account creation:
import { usePrivyWallet } from './hooks/usePrivyWallet'

function PrivyWalletDashboard() {
  const { authenticated, login, logout, rhinestoneAccount, isLoading, error, reconnect } = usePrivyWallet()

  if (isLoading) {
    return <p>Setting up your wallet...</p>
  }

  if (error) {
    return (
      <div>
        <p>Error: {error}</p>
        <button onClick={reconnect}>Try Again</button>
      </div>
    )
  }

  if (!authenticated) {
    return (
      <div>
        <h2>Welcome to Rhinestone</h2>
        <button onClick={login}>Sign in with Privy</button>
      </div>
    )
  }

  return (
    <div>
      <h2>Connected!</h2>
      <p>Smart Account: {rhinestoneAccount?.getAddress()}</p>
      <button onClick={logout}>Logout</button>
    </div>
  )
}

Cross-Chain Transactions

Send transactions using the Privy-connected wallet:
import { encodeFunctionData, parseUnits } from 'viem'
import { erc20Abi } from 'viem'
import { baseSepolia, arbitrumSepolia } from 'viem/chains'

async function handleCrossChainTransfer(rhinestoneAccount) {
  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),
      },
    ],
  })
}

The Integration Pattern

All embedded signer integrations follow the same pattern:
  1. Provider Setup: Configure the embedded wallet provider (Privy, Dynamic, Turnkey)
  2. Get Wallet Client: Use wagmi hooks (or viem accounts for Turnkey) to access the authenticated wallet
  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

Privy supports multiple authentication methods out of the box:
  • Email: Users can sign in with their email address
  • Social: Google, Twitter, Discord, and other OAuth providers
  • Wallet: Connect external wallets like MetaMask
  • SMS: Phone number verification
The login() function will show Privy’s authentication modal with all configured methods.

Environment Variables

Make sure to set the following environment variables:
NEXT_PUBLIC_PRIVY_APP_ID=your_privy_app_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/privy
npm install && npm run dev
The example demonstrates:
  • Privy authentication flows (email, social, wallet)
  • Embedded wallet creation and management
  • Rhinestone smart account integration
  • Cross-chain transaction execution

Next Steps