guide · smart wallets

Privy + ZeroDev smart wallets on Aeneid

DATA Foundation Aeneid (chainId 1315) isn't in Privy's preset chain list, so we 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 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 record() tx to 0x5f48C4e58CF195B090cE3572D46BFb439faF0e4f.

What you'll set up

  • · A ZeroDev project scoped to Aeneid with Sponsor all transactions enabled.
  • · A Privy app with the Kernel (ZeroDev) smart-wallet provider active.
  • · Aeneid registered as a Privy custom chain (id 1315), pointed at your Alchemy/Story RPC + the ZeroDev bundler and paymaster URLs.

1 · ZeroDev dashboard

  1. Open dashboard.zerodev.appCreate Project.
  2. Enable Show Testnets and tick DATA Foundation Aeneid (1315). Save.
  3. Open the project → Gas Policies → chain Aeneid → 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

Aeneid isn't in Privy's preset list, so add it as a Custom chain and fill in exactly:

custom chain
NAME         DATA Foundation Aeneid
ID (chainId) 1315
RPC URL      https://aeneid.storyrpc.io   (or your Alchemy endpoint)
BUNDLER URL  From ZeroDev dashboard → project → RPC tab
PAYMASTER    From ZeroDev dashboard → project → RPC tab
EXPLORER     https://aeneid.datanetscan.io

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. Mount this file behind <ClientOnly> + lazy()@privy-io/react-auth is not SSR-safe.

src/components/privy-client-entry.tsx
// 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 aeneid = defineChain({
  id: 1315,
  name: "DATA Foundation Aeneid",
  nativeCurrency: { name: "IP", symbol: "IP", decimals: 18 },
  rpcUrls: { default: { http: [
    import.meta.env.VITE_AENEID_RPC_URL || "https://aeneid.storyrpc.io",
  ] } },
  blockExplorers: { default: {
    name: "DataNetScan",
    url: "https://aeneid.datanetscan.io",
  } },
  testnet: true,
});

<PrivyProvider
  appId={import.meta.env.VITE_PRIVY_APP_ID}
  config={{
    loginMethods: ["google", "email"],
    embeddedWallets: { ethereum: { createOnLogin: "users-without-wallets" } },
    defaultChain: aeneid,
    supportedChains: [aeneid],
    appearance: { theme: "dark" },
  }}
>
  <SmartWalletsProvider>
    <App />
  </SmartWalletsProvider>
</PrivyProvider>

5 · Send a sponsored transaction

any component
// Sponsored tx via the ZeroDev-backed Kernel smart wallet
import { useSmartWallets } from "@privy-io/react-auth/smart-wallets";
import { encodeFunctionData } from "viem";
import ledger from "@/data/choreo-ledger.json";

const { client } = useSmartWallets();

const hash = await client!.sendTransaction({
  to: ledger.address as `0x${string}`,
  data: encodeFunctionData({
    abi: ledger.abi,
    functionName: "record",
    args: ["choreo:plie"],
  }),
});
// Gas is sponsored by the ZeroDev paymaster — Privy popup reads US$0.00.

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 Aeneid 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 1315, 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 appId is in VITE_PRIVY_APP_ID.
what's deployed

ChoreoLedger at 0x5f48C4e58CF195B090cE3572D46BFb439faF0e4f, deploy tx 0xc0edde7eb7084907, source verified on DataNetScan.