diff --git a/docs/apps/index.mdx b/docs/apps/index.mdx deleted file mode 100644 index b2253affa..000000000 --- a/docs/apps/index.mdx +++ /dev/null @@ -1,547 +0,0 @@ ---- -title: "Build an app on Base" -description: "A step-by-step guide to building a Next.js tally app on Base using wagmi and viem, with wallet connection, contract reads and writes, and batch transaction support." ---- - -This guide walks you through building an onchain tally app on Base from scratch. You will connect wallets, read and write to a smart contract, detect wallet capabilities, and fall back gracefully for wallets that do not support batching. - -## What you'll build - -- A Next.js app that connects wallets and handles connection state -- Contract reads and writes against a deployed counter on Base Sepolia -- Batch transaction support for smart wallets via EIP-5792 -- A graceful fallback for wallets that do not support batching - - -Base is a fast, low-cost Ethereum L2 built to bring the next billion users onchain. Low gas fees make batch transactions practical and real-time UX possible. Every pattern in this guide works on any EVM chain. - - -## Steps - - - - Create a new Next.js app and install the required dependencies. - - ```bash Terminal - npx create-next-app@latest my-base-app --typescript --tailwind --app - cd my-base-app - npm install wagmi viem @tanstack/react-query @base-org/account - ``` - - - - Create the Wagmi config with Base Sepolia, then wrap your app in the required providers. - - ```typescript config/wagmi.ts lines expandable - import { http, createConfig, createStorage, cookieStorage } from 'wagmi' - import { baseSepolia } from 'wagmi/chains' - import { baseAccount, injected } from 'wagmi/connectors' - - export const config = createConfig({ - chains: [baseSepolia], - connectors: [ - injected(), - baseAccount({ - appName: 'My Base App', - }), - ], - storage: createStorage({ storage: cookieStorage }), - ssr: true, - transports: { - [baseSepolia.id]: http('https://sepolia.base.org'), - }, - }) - - declare module 'wagmi' { - interface Register { - config: typeof config - } - } - ``` - - - `ssr: true` combined with `cookieStorage` prevents Next.js hydration mismatches. The `baseAccount` connector connects users via the [Base Account SDK](/base-account/overview/what-is-base-account) smart wallet — you will detect its capabilities in step 7. The `injected` connector handles browser extension wallets like MetaMask. - - - ```typescript app/providers.tsx lines expandable - 'use client' - - import { WagmiProvider } from 'wagmi' - import { QueryClient, QueryClientProvider } from '@tanstack/react-query' - import { type ReactNode } from 'react' - import { config } from '@/config/wagmi' - - const queryClient = new QueryClient() - - export function Providers({ children }: { children: ReactNode }) { - return ( - - - {children} - - - ) - } - ``` - - Wrap your root layout with ``. - - - - Create a component that handles all four wallet connection states. - - ```typescript components/ConnectWallet.tsx lines expandable - 'use client' - - import { useAccount, useConnect, useDisconnect } from 'wagmi' - - export function ConnectWallet() { - const { address, isConnected, isConnecting, isReconnecting } = useAccount() - const { connect, connectors } = useConnect() - const { disconnect } = useDisconnect() - - if (isReconnecting) return
Reconnecting...
- - if (!isConnected) { - return ( -
- {connectors.map((connector) => ( - - ))} -
- ) - } - - return ( -
- - {address?.slice(0, 6)}...{address?.slice(-4)} - - -
- ) - } - ``` - - - `useAccount` exposes four states: `isConnecting`, `isReconnecting`, `isConnected`, and `isDisconnected`. Checking only `isConnected` causes UI flashes on page load — handle all four. - -
- - - Install Foundry and initialize a contracts directory inside your project. - - ```bash Terminal - mkdir contracts && cd contracts - curl -L https://foundry.paradigm.xyz | bash - foundryup - forge init --no-git - ``` - - - The `--no-git` flag prevents Foundry from initialising a nested git repository inside your project. - - - Configure Base Sepolia in your environment file. - - ```bash contracts/.env - BASE_SEPOLIA_RPC_URL="https://sepolia.base.org" - ``` - - - If `https://sepolia.base.org` is unreachable, use an alternative public endpoint such as `https://base-sepolia-rpc.publicnode.com`. For production apps, use a dedicated RPC provider. - - - Load the variable and import your deployer key securely. - - ```bash Terminal - source .env - cast wallet import deployer --interactive - ``` - - - Never share or commit your private key. `cast wallet import` stores it in `~/.foundry/keystores`, which is not tracked by git. - - - - `cast wallet import --interactive` requires a TTY (interactive terminal). In scripted or CI environments, pass the key directly instead: - - ```bash Terminal - forge create ./src/Counter.sol:Counter \ - --rpc-url $BASE_SEPOLIA_RPC_URL \ - --private-key $DEPLOYER_PRIVATE_KEY - ``` - - - Deploy the contract. - - ```bash Terminal - forge create ./src/Counter.sol:Counter \ - --rpc-url $BASE_SEPOLIA_RPC_URL \ - --account deployer - ``` - - Verify the deployment by reading the initial counter value. - - ```bash Terminal - cast call "number()(uint256)" --rpc-url $BASE_SEPOLIA_RPC_URL - ``` - - You need testnet ETH to pay for deployment. Get free Base Sepolia ETH from one of the [network faucets](/base-chain/network-information/network-faucets). - - - - Define your contract address and ABI, then read the current counter value. - - ```typescript config/counter.ts lines expandable - export const COUNTER_ADDRESS = '0x...' as const - - export const counterAbi = [ - { - type: 'function', - name: 'number', - inputs: [], - outputs: [{ name: '', type: 'uint256' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'increment', - inputs: [], - outputs: [], - stateMutability: 'nonpayable', - }, - ] as const - ``` - - - `as const` is required. Without it, wagmi cannot infer function names, argument types, or return types from the ABI. - - - ```typescript components/CounterDisplay.tsx lines expandable - 'use client' - - import { useReadContract } from 'wagmi' - import { baseSepolia } from 'wagmi/chains' - import { COUNTER_ADDRESS, counterAbi } from '@/config/counter' - - export function CounterDisplay() { - const { data: count, isLoading, isError } = useReadContract({ - address: COUNTER_ADDRESS, - abi: counterAbi, - functionName: 'number', - chainId: baseSepolia.id, - }) - - if (isLoading && count === undefined) return

Loading...

- if (isError && count === undefined) return

Failed to read contract

- - return

{count?.toString()}

- } - ``` - - - `isError` can be `true` while `data` still holds a valid cached value from a previous successful fetch. Always gate error renders on `data === undefined` so stale data is preferred over an error message. - -
- - - Send a transaction and surface all three confirmation states to the user. - - ```typescript components/IncrementButton.tsx lines expandable - 'use client' - - import { useEffect } from 'react' - import { - useWriteContract, - useWaitForTransactionReceipt, - useChainId, - useSwitchChain, - } from 'wagmi' - import { readContractQueryOptions } from 'wagmi/query' - import { useQueryClient } from '@tanstack/react-query' - import { baseSepolia } from 'wagmi/chains' - import { config } from '@/config/wagmi' - import { COUNTER_ADDRESS, counterAbi } from '@/config/counter' - - export function IncrementButton() { - const chainId = useChainId() - const { switchChain, isPending: isSwitching } = useSwitchChain() - const { data: hash, isPending, writeContract } = useWriteContract() - const { isLoading: isConfirming, isSuccess } = - useWaitForTransactionReceipt({ hash }) - const queryClient = useQueryClient() - - useEffect(() => { - if (isSuccess) { - queryClient.invalidateQueries({ - queryKey: readContractQueryOptions(config, { - address: COUNTER_ADDRESS, - abi: counterAbi, - functionName: 'number', - chainId: baseSepolia.id, - }).queryKey, - }) - } - }, [isSuccess, queryClient]) - - if (chainId !== baseSepolia.id) { - return ( - - ) - } - - return ( -
- - {isSuccess &&

Confirmed!

} - {hash && ( - - View on Basescan - - )} -
- ) - } - ``` - - - `useReadContract` caches its result and does not automatically refetch after a write. Use `queryClient.invalidateQueries` with the read's query key to trigger a single refetch when a transaction confirms. - - - Surface three states to the user: waiting for wallet signature, waiting for on-chain confirmation, and success. - - - Without `useSwitchChain`, calling `writeContract` while the wallet is on the wrong network causes wagmi to attempt a background chain switch. If the user misses or dismisses the wallet popup, the button stays at "Confirm in Wallet..." indefinitely with no error and no recovery path. - -
- - - Smart wallets support batch transactions via EIP-5792. EOAs do not. Detect support before attempting to batch. - - ```typescript hooks/useWalletCapabilities.ts lines expandable - import { useCapabilities } from 'wagmi' - import { baseSepolia } from 'wagmi/chains' - import { useMemo } from 'react' - - export function useWalletCapabilities() { - const { data: capabilities } = useCapabilities() - - const supportsBatching = useMemo(() => { - const atomic = capabilities?.[baseSepolia.id]?.atomic - return atomic?.status === 'ready' || atomic?.status === 'supported' - }, [capabilities]) - - const supportsPaymaster = useMemo(() => { - return capabilities?.[baseSepolia.id]?.paymasterService?.supported === true - }, [capabilities]) - - return { supportsBatching, supportsPaymaster } - } - ``` - - - `useChainId()` returns the wallet's current chain, not your deployment chain. A MetaMask user on Ethereum mainnet would get incorrect capability results. Always check capabilities against the chain where your contract is deployed. - - - See [Batch Transactions with Wagmi](/base-account/framework-integrations/wagmi/batch-transactions) for a deeper look at EIP-5792 capability detection. - - - - Use `useSendCalls` for smart wallets and `useWriteContract` for EOAs. The component detects which path to take at render time. - - ```typescript components/BatchIncrement.tsx lines expandable - 'use client' - - import { useEffect } from 'react' - import { - useSendCalls, - useWaitForCallsStatus, - useWriteContract, - useWaitForTransactionReceipt, - useAccount, - useChainId, - useSwitchChain, - } from 'wagmi' - import { readContractQueryOptions } from 'wagmi/query' - import { useQueryClient } from '@tanstack/react-query' - import { encodeFunctionData } from 'viem' - import { baseSepolia } from 'wagmi/chains' - import { config } from '@/config/wagmi' - import { useWalletCapabilities } from '@/hooks/useWalletCapabilities' - import { COUNTER_ADDRESS, counterAbi } from '@/config/counter' - - const counterQueryKey = readContractQueryOptions(config, { - address: COUNTER_ADDRESS, - abi: counterAbi, - functionName: 'number', - chainId: baseSepolia.id, - }).queryKey - - export function BatchIncrement() { - const { isConnected } = useAccount() - const { supportsBatching } = useWalletCapabilities() - - if (!isConnected) return

Connect your wallet first.

- - return supportsBatching ? : - } - - function BatchFlow() { - const chainId = useChainId() - const { switchChain, isPending: isSwitching } = useSwitchChain() - const { data, sendCalls, isPending } = useSendCalls() - const { isLoading: isConfirming, isSuccess } = useWaitForCallsStatus({ - id: data?.id, - }) - const queryClient = useQueryClient() - - useEffect(() => { - if (isSuccess) { - queryClient.invalidateQueries({ queryKey: counterQueryKey }) - } - }, [isSuccess, queryClient]) - - if (chainId !== baseSepolia.id) { - return ( - - ) - } - - const incrementData = encodeFunctionData({ - abi: counterAbi, - functionName: 'increment', - }) - - return ( -
- - {isSuccess &&

Batch confirmed!

} -
- ) - } - - function SequentialFlow() { - const chainId = useChainId() - const { switchChain, isPending: isSwitching } = useSwitchChain() - const { data: hash, isPending, writeContract } = useWriteContract() - const { isLoading: isConfirming, isSuccess } = - useWaitForTransactionReceipt({ hash }) - const queryClient = useQueryClient() - - useEffect(() => { - if (isSuccess) { - queryClient.invalidateQueries({ queryKey: counterQueryKey }) - } - }, [isSuccess, queryClient]) - - if (chainId !== baseSepolia.id) { - return ( - - ) - } - - return ( - - ) - } - ``` - - - Never call `useSendCalls` without first confirming `supportsBatching` is `true`. Calling it against an EOA will throw. - -
- - - Compose the components into a single page. - - ```typescript app/page.tsx lines expandable - import { ConnectWallet } from '@/components/ConnectWallet' - import { CounterDisplay } from '@/components/CounterDisplay' - import { BatchIncrement } from '@/components/BatchIncrement' - - export default function Home() { - return ( -
-

Onchain Tally

- - - -
- ) - } - ``` - - Start the development server. - - ```bash Terminal - npm run dev - ``` -
-
- -## Next steps - -- **Go to mainnet** — add `base` to your `chains` array and transports in `config/wagmi.ts`, redeploy your contract to Base mainnet, and update `COUNTER_ADDRESS`. -- **Sponsor gas** — use the `paymasterService` capability with `useSendCalls` to cover your users' transaction fees. See [Sponsor Gas](/base-account/improve-ux/sponsor-gas/paymasters). -- **Send notifications** — use the [Notifications guide](/apps/technical-guides/base-notifications) to fetch opted-in wallet addresses and send in-app notifications. -- **Batch read calls** — reduce RPC round trips by batching reads via viem's `multicall`. -- **Optimistic updates** — update the UI before confirmation using TanStack Query's `onMutate` callback. -- **Wagmi setup reference** — review the full [Wagmi setup guide](/base-account/framework-integrations/wagmi/setup) for additional configuration options. diff --git a/docs/docs.json b/docs/docs.json index 2d43d8395..e437955b8 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -758,6 +758,14 @@ ] }, "redirects": [ + { + "source": "/apps", + "destination": "/apps/quickstart/build-app" + }, + { + "source": "/apps/index", + "destination": "/apps/quickstart/build-app" + }, { "source": "/base-chain/node-operators/base-v1-upgrade", "destination": "/base-chain/specs/upgrades/azul/node-upgrade" diff --git a/docs/llms-full.txt b/docs/llms-full.txt index fe9652da4..8f6976bbb 100644 --- a/docs/llms-full.txt +++ b/docs/llms-full.txt @@ -395,7 +395,6 @@ const client = createPublicClient({ chain: base, transport: http() }) - [Base MCP Skill](https://docs.base.org/ai-agents/skills/SKILL): Base MCP — gives your AI assistant access to a Base Account via the Base MCP server (mcp.base.org). Wallet, portfolio, sending, swapping, signing, x402 payments, batched contract calls, and transaction history across supported chains. ## Apps -- [Build an app on Base](https://docs.base.org/apps/index): A step-by-step guide to building a Next.js tally app on Base using wagmi and viem, with wallet connection, contract reads and writes, and batch transaction support. - [Builder Codes for Agent Developers](https://docs.base.org/apps/builder-codes/agent-developers): Attribute your AI agent's onchain transactions to your identity on Base and unlock analytics and leaderboard features. - [Builder Codes for App Developers](https://docs.base.org/apps/builder-codes/app-developers): Integrate Builder Codes into your app using Wagmi or Viem to attribute onchain activity. - [Base Builder Codes](https://docs.base.org/apps/builder-codes/builder-codes): Attribute onchain activity to your app, wallet or agent with Builder Codes. diff --git a/docs/llms.txt b/docs/llms.txt index 7b55e59fe..679f53e12 100644 --- a/docs/llms.txt +++ b/docs/llms.txt @@ -307,7 +307,6 @@ - [Base MCP Skill](https://docs.base.org/ai-agents/skills/SKILL): Base MCP — gives your AI assistant access to a Base Account via the Base MCP server (mcp.base.org). Wallet, portfolio, sending, swapping, signing, x402 payments, batched contract calls, and transaction history across supported chains. ## Apps -- [Build an app on Base](https://docs.base.org/apps/index): A step-by-step guide to building a Next.js tally app on Base using wagmi and viem, with wallet connection, contract reads and writes, and batch transaction support. - [Builder Codes for Agent Developers](https://docs.base.org/apps/builder-codes/agent-developers): Attribute your AI agent's onchain transactions to your identity on Base and unlock analytics and leaderboard features. - [Builder Codes for App Developers](https://docs.base.org/apps/builder-codes/app-developers): Integrate Builder Codes into your app using Wagmi or Viem to attribute onchain activity. - [Base Builder Codes](https://docs.base.org/apps/builder-codes/builder-codes): Attribute onchain activity to your app, wallet or agent with Builder Codes.