Privy + ZeroDev smart wallets on Robinhood Chain
Robinhood Chain Testnet (chainId 46630) isn't in Privy's preset chain list yet, so you register it as a custom chain in the Privy dashboard and point the ZeroDev Kernel bundler + paymaster at it. Once wired, every logged-in user gets an ERC-4337 smart wallet with sponsored gas — no MetaMask, no faucet, no signature popup for gas fees.
The Choreo Ledger demo is wired to this exact setup. Sign in with Google → Privy provisions a Kernel smart wallet → ZeroDev sponsors the tx.
What you'll set up
- · A ZeroDev project scoped to Robinhood Testnet with "Sponsor all transactions" enabled.
- · A Privy app with the Kernel (ZeroDev) smart-wallet provider active.
- · Robinhood Testnet registered as a Privy custom chain (id 46630), with your Alchemy RPC + the ZeroDev bundler and paymaster URLs.
1 · ZeroDev dashboard
- Open dashboard.zerodev.app and Create Project.
- Enable Show Testnets and tick Robinhood Testnet (46630). Save.
- Open the project → Gas Policies → select chain Robinhood Testnet → toggle Sponsor all transactions → confirm Enable.
- Copy the Bundler URL and Paymaster URL from the project's RPC tab — you'll paste them into Privy.
2 · Privy dashboard — enable smart wallets
- Open dashboard.privy.io → your app → Wallet infrastructure → Smart wallets.
- Click Enable smart wallets for your app and pick Kernel (ZeroDev) as the provider.
3 · Configure chains → Add new chain → Custom chain
Robinhood Testnet isn't in Privy's preset list, so add it as a Custom chain and fill in exactly:
Save. Privy will show Robinhood Testnet alongside Sepolia / Tempo / Base Sepolia in Configure chains. That entry — not a preset — is what activates Kernel on chainId 46630.
4 · Client wiring
Wrap the app in SmartWalletsProvider so useSmartWallets() is available. Privy reads the provider + custom chain from the dashboard, so no bundler/paymaster URL belongs in code.
// src/components/privy-client-entry.tsx
import { PrivyProvider } from "@privy-io/react-auth";
import { SmartWalletsProvider } from "@privy-io/react-auth/smart-wallets";
import { defineChain } from "viem";
export const robinhoodTestnet = defineChain({
id: 46630,
name: "Robinhood Testnet",
nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
rpcUrls: {
default: {
http: [
import.meta.env.VITE_ROBINHOOD_TESTNET_RPC_URL ||
"https://rpc.testnet.chain.robinhood.com",
],
},
},
blockExplorers: { default: { name: "Blockscout", url: "https://explorer.testnet.chain.robinhood.com" } },
testnet: true,
});
<PrivyProvider
appId={import.meta.env.VITE_PRIVY_APP_ID}
config={{
loginMethods: ["google", "email"],
embeddedWallets: { ethereum: { createOnLogin: "users-without-wallets" } },
defaultChain: robinhoodTestnet,
supportedChains: [robinhoodTestnet],
}}
>
<SmartWalletsProvider>
<App />
</SmartWalletsProvider>
</PrivyProvider>
5 · Send a sponsored transaction
// Sponsored tx via the ZeroDev-backed smart wallet
import { useSmartWallets } from "@privy-io/react-auth/smart-wallets";
import { encodeFunctionData } from "viem";
import abi from "@/data/abi/ChoreoLedger.json";
import contract from "@/data/contract.json";
const { client } = useSmartWallets();
const hash = await client!.sendTransaction({
to: contract.address as `0x${string}`,
data: encodeFunctionData({ abi, functionName: "log", args: [cid] }),
});
// Gas is sponsored by ZeroDev paymaster — user pays $0.
The user signs a UserOp with their embedded EOA; ZeroDev's bundler submits it and the paymaster covers gas. Estimated fee in the Privy popup reads US$0.00.
Troubleshooting
- · "Add funds on Robinhood Chain Testnet to complete transaction" in the Privy popup means the wallet is sending as an EOA, not a smart wallet. Confirm Kernel is the provider and the custom chain is saved with the ZeroDev bundler + paymaster URLs.
- · Sponsorship policy denied → open ZeroDev → Gas Policies → make sure the toggle Sponsor all transactions is on for chain 46630, or add a chain-level policy that whitelists your contract.
- · Chain not shown in Privy → the custom chain is saved per-app; switch to the same app whose
appIdis inVITE_PRIVY_APP_ID.