guide · smart wallets

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.

live in this app

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

  1. Open dashboard.zerodev.app and Create Project.
  2. Enable Show Testnets and tick Avalanche Fuji (43113). Save.
  3. Open the project → Gas Policies → select chain Avalanche Fuji → toggle Sponsor all transactions → confirm Enable.
  4. 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

  1. Open dashboard.privy.io → your app → Wallet infrastructureSmart wallets.
  2. 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:

Name
Avalanche Fuji
ID number (chainId)
43113
RPC URL
https://avax-fuji.g.alchemy.com/v2/<KEY>
your Alchemy Fuji endpoint
Bundler URL
https://rpc.zerodev.app/api/v3/<PROJECT_ID>/chain/43113
ZeroDev → project → RPC tab
Paymaster URL
https://rpc.zerodev.app/api/v3/<PROJECT_ID>/chain/43113
ZeroDev → project → RPC tab

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>
gotcha · stub chain

{ 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.

reading the smart address

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 appId is in VITE_PRIVY_APP_ID.
  • · Chain missing / "wrong network" popups defaultChain was a { id, name } stub. Pass the real viem avalancheFuji in both defaultChain and supportedChains.
  • · Smart address never appears after login → you're reading useSmartWallets().client.account.address. Read from user.linkedAccounts instead (see section 4).