Privy + ZeroDev smart wallets on Avalanche Fuji
Avalanche Fuji (chainId 43113) isn't in Privy's smart-wallet preset list, so you register it as a custom chain in the Privy dashboard and point Kernel (ZeroDev) at your Alchemy RPC + the ZeroDev bundler + paymaster. 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 Avalanche Fuji with Sponsor all transactions enabled.
- · A Privy app with the Kernel (ZeroDev) smart-wallet provider active.
- · Avalanche Fuji registered as a Privy custom chain (id 43113), 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 Avalanche Fuji (43113). Save.
- Open the project → Gas Policies → select chain Avalanche Fuji → 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
Avalanche Fuji isn't in Privy's preset list, so add it as a Custom chain and fill in exactly:
Saving this custom chain under the Kernel (ZeroDev) preset is what activates Kernel on chainId 43113.
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 { avalancheFuji } from "viem/chains";
<PrivyProvider
appId={privyCfg.appId}
config={{
loginMethods: ["google", "email"],
embeddedWallets: {
ethereum: { createOnLogin: "users-without-wallets" },
},
defaultChain: avalancheFuji, // real viem Chain — NOT { id, name }
supportedChains: [avalancheFuji],
}}
>
<SmartWalletsProvider>
<App />
</SmartWalletsProvider>
</PrivyProvider>{ id: 43113, name: "Avalanche Fuji" } as never compiles but silently breaks the smart-wallet client (no RPC / nativeCurrency → transport init fails). Pass the real avalancheFuji from viem/chains in both defaultChain and supportedChains.
useSmartWallets().client?.account?.address is null until Kernel finishes bootstrapping (and stays null when the custom chain isn't saved). The source of truth is the user's linked accounts:
const smartAddress = user?.linkedAccounts?.find(
(a): a is typeof a & { type: "smart_wallet"; address: string } =>
a.type === "smart_wallet",
)?.address ?? null;5 · Send a sponsored transaction
// Any component under <SmartWalletsProvider>
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] }),
chain: undefined as never,
account: client!.account,
});
// Gas is sponsored by ZeroDev — user pays $0, no signature popup for fees.The user signs a UserOp with their embedded EOA; ZeroDev's bundler submits it and the paymaster covers gas. The estimated fee in the Privy popup reads US$0.00.
Troubleshooting
- · "Add funds on Avalanche Fuji to complete transaction" in the Privy popup → 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 Sponsor all transactions is on for chain 43113, or add a chain-level policy that whitelists your contract.
- · Chain not shown in Privy → custom chains are saved per-app; switch to the same app whose
appIdis inVITE_PRIVY_APP_ID. - · Chain missing / "wrong network" popups →
defaultChainwas a{ id, name }stub. Pass the real viemavalancheFujiin bothdefaultChainandsupportedChains. - · Smart address never appears after login → you're reading
useSmartWallets().client.account.address. Read fromuser.linkedAccountsinstead (see section 4).