Smart Wallets on Base: A Developer’s Guide to Better Onchain UX

What is a Smart Wallet?

A smart wallet is a non-custodial crypto wallet powered by smart contracts on the blockchain. It makes it possible for your users to create an onchain account in seconds, without any app or extension. One of the best and most friendly ways to enable users to interact with onchain applications.

How Does Smart Wallet Work?

Smart wallets rely on passkeys, stored on users’ devices which can be used on the Coinbase website (keys.coinbase.com) and mobile app. Developers get to integrate an SDK that uses keys.coinbase.com popups to allow users to see requests and sign with their passkey. The SDK and the popup use cross-domain messaging to share information back to the app.

Coinbase SDK and client support the following networks:

  1. Base

  2. Optimism

  3. Polygon

  4. BNB Chain

  5. Arbitrum

  6. Avalanche

  7. Zora

  8. Ethereum Mainnet (very expensive)

  9. Ethereum Sepolia (Testnet)

  10. Base Sepolia (Testnet)

  11. Optimism Sepolia (Testnet)

Why Smart Wallet?

  1. Users can get free sponsored transactions: Use Paymaster to sponsor transactions and you could qualify for up to $15k in gas credits.

  2. Support Sub-Accounts: Let your app create and manage extra onchain accounts for users, all controlled by their main smart wallet — no more constant transaction pop-ups or signatures for every small action.

  3. Batch Operations: You can now perform multiple operations in a single transaction with the help of a smart wallet

  4. ERC-20 Paymaster: Let Users Pay Gas with Your App Token.
    Instead of forcing users to top up ETH just to use your app, ERC-20 paymasters let them pay gas fees using your app's native token (e.g., $VIBES, $AFRICOIN).

Adding Smart Wallet To An Existing Next.js Project

This guide would help you add smart wallet to an existing Next.js application using Wagmi.

Step 1: Install Dependencies

First navigate into your project directory and install the required dependencies

npm install @coinbase/wallet-sdk wagmi viem @tanstack/react-query

Step 2: Create Wagmi Config

Your project should have a wagmi.ts file, if it does not have one then you should proceed to create one in the root directory of your project.

import { http, createConfig } from "wagmi";
import { baseSepolia } from "wagmi/chains";
import { coinbaseWallet } from "wagmi/connectors";

export const cbWalletConnector = coinbaseWallet({
  appName: "Wagmi Smart Wallet",
  preference: "smartWalletOnly",
});

export const config = createConfig({
  chains: [baseSepolia],
  // turn off injected provider discovery
  multiInjectedProviderDiscovery: false,
  connectors: [cbWalletConnector],
  ssr: true,
  transports: {
    [baseSepolia.id]: http(),
  },
});

declare module "wagmi" {
  interface Register {
    config: typeof config;
  }
}

We just had to modify the wagmi config to include the Smart Wallet connector cbWalletConnector

Step 3: Create Providers Component

Create a file called providers.tsx in the app/ directory:

"use client";

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { useState, type ReactNode } from "react";
import { WagmiProvider } from "wagmi";

import { config } from "@/wagmi";

export function Providers(props: { children: ReactNode }) {
  const [queryClient] = useState(() => new QueryClient());

  return (
    <WagmiProvider config={config}>
      <QueryClientProvider client={queryClient}>
        {props.children}
      </QueryClientProvider>
    </WagmiProvider>
  );
}

Step 4: Add Providers to the Root Layout

Update the root layout file (app/layout.tsx):

import "./globals.css";
import type { Metadata } from "next";
import type { ReactNode } from "react";

import { Providers } from "./providers";

export const metadata: Metadata = {
  title: "Smart Wallet App",
  description: "Smart Wallet Next.js integration",
};

export default function RootLayout(props: { children: ReactNode }) {
  return (
    <html lang="en">
      <body>
        <Providers>{props.children}</Providers>
      </body>
    </html>
  );
}

Step 5: Set Up Wallet Connection + SIWE (Sign-In With Ethereum)

Now let’s create a component that handles:

  • Connecting the Smart Wallet using Coinbase’s connector

  • Triggering Sign-In With Ethereum (SIWE) for authentication

Create a file called ConnectAndSIWE.tsx in your components/ directory and set up the logic to:

  • Connect to the smart wallet

  • Prepare and sign the SIWE message

  • Verify the signature

import { useCallback, useEffect, useState } from "react";
import type { Hex } from "viem";
import {
  useAccount,
  useConnect,
  usePublicClient,
  useSignMessage,
} from "wagmi";
import { SiweMessage } from "siwe";
import { cbWalletConnector } from "@/wagmi";

export function ConnectAndSIWE() {
  const { connect } = useConnect({
    mutation: {
      onSuccess: (data) => {
        const address = data.accounts[0];
        const chainId = data.chainId;
        const m = new SiweMessage({
          domain: document.location.host,
          address,
          chainId,
          uri: document.location.origin,
          version: "1",
          statement: "Smart Wallet SIWE Example",
          nonce: "12345678", // Replace with a real nonce in production
        });
        setMessage(m);
        signMessage({ message: m.prepareMessage() });
      },
    },
  });

  const account = useAccount();
  const client = usePublicClient();
  const [signature, setSignature] = useState<Hex | undefined>();
  const { signMessage } = useSignMessage({
    mutation: { onSuccess: (sig) => setSignature(sig) },
  });

  const [message, setMessage] = useState<SiweMessage | undefined>();
  const [valid, setValid] = useState<boolean | undefined>();

  const checkValid = useCallback(async () => {
    if (!signature || !account.address || !client || !message) return;
    const v = await client.verifyMessage({
      address: account.address,
      message: message.prepareMessage(),
      signature,
    });
    setValid(v);
  }, [signature, account.address, client, message]);

  useEffect(() => {
    checkValid();
  }, [signature]);

  return (
    <div>
      <button onClick={() => connect({ connector: cbWalletConnector })}>
        Connect + SIWE
      </button>
      {valid !== undefined && <p>Is valid: {valid.toString()}</p>}
    </div>
  );
}

Step 6: Use the Component in a Page

Add the component to app/page.tsx:

import { ConnectAndSIWE } from '../components/ConnectAndSIWE'

export default function Home() {
  return (
    <main>
      <h1>Smart Wallet Integration</h1>
      <ConnectAndSIWE />
    </main>
  )
}
0
Subscribe to my newsletter

Read articles from Ogedengbe Israel directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Ogedengbe Israel
Ogedengbe Israel

Blockchain developer && Advocate