Skip to content

Get Started: Wagmi Apps

Estimated time to read: 6 minutes

Enable unified-balance and chain abstracted transactions in Web3 apps built with the Wagmi library by integrating with the Arcana CA Wagmi SDK.

Arcana CA Wagmi SDK lets you add chain abstracted bridge and transfer functions in wagmi apps. Replace the useSendTransaction and useWriteContract hooks of the Wagmi library with those provided by the SDK to enable chain abstraction in a transparent manner.

Wagmi Plug & Play Widget

The Arcana CA Wagmi SDK supports a plug-and-play UI modal that displays the unified balance within the Wagmi app context. Authenticated app users can view the unified balance via the plug-and-play widget and issue blockchain transactions through any browser-based third-party wallets connected to the Wagmi app.

1. Install

npm install --save @arcana/ca-wagmi
yarn add @arcana/ca-wagmi

2. Integrate

Use the CAProvider component from Arcana CA Wagmi SDK in the app code. Make sure you import the following functions from the ca-wagmi and not from the wagmi SDK.

  • useBalance - Unify the specified token balance across chains - USDC, USDT, ETH
  • useSendTransaction - Chain abstracted Send Transaction
  • useWriteContract - Chain abstracted Write Contract
  • useUnifiedBalance - Get unified balance for all tokens across all chains

The SDK also offers additional hooks:

  • useBalanceModal - Display a plug and play widget containing the unified balance
  • useCAFn - Allows chain abstracted bridge and transfer functions

For details, see Arcana CA Wagmi SDK Reference.

Refer to the following sample integration code and hook usage.

import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { WagmiProvider } from 'wagmi'
import { CAProvider } from '@arcana/ca-wagmi'
import App from "./App.tsx";
import { config } from "./utils/config";

const queryClient = new QueryClient()

createRoot(document.getElementById("root")!).render(
    <StrictMode>
        <WagmiProvider config={config}>
            <QueryClientProvider client={queryClient}>
                <CAProvider>
                    <App />
                </CAProvider>
            </QueryClientProvider>
        </WagmiProvider>
    </StrictMode>
);
import "./App.css";
import { useAccount } from "wagmi";

import { Account } from "./account";
import { WalletOptions } from "./wallet-options";

function ConnectWallet() {
    const { isConnected } = useAccount();
    if (isConnected) return <Account />;
    return <WalletOptions />;
}

function App() {
return (
    <div className="min-h-screen bg-white dark:bg-gray-900 flex">
    <div className=" align-center m-auto min-w-md max-w-md p-6 bg-white border border-gray-200 rounded-lg shadow-sm dark:bg-gray-800 dark:border-gray-700">
        <ConnectWallet />
    </div>
    </div>
);
}

export default App;
import {
    useAccount,
    useDisconnect,
    useEnsName,
    useSwitchChain,
// useSendTransaction //DO NOT use from the wagmi SDK
} from "wagmi";
import {
    useBalance,
    useSendTransaction, //Note: Use from ca-wagmi SDK
    useWriteContract,
    useUnifiedBalance,
} from "@arcana/ca-wagmi";

import { useState } from "react";
import Decimal from "decimal.js";
import { erc20Abi } from "viem";

export function Account() {
    const { sendTransaction } = useSendTransaction();
    const [allLoading, setLoading] = useState(false);
    const { address } = useAccount();
    const { disconnect } = useDisconnect();
    const { data: ensName } = useEnsName({ address });
    const { showBalance } = useBalance();
    const { loading, getAssetBalance } = useUnifiedBalance();
    if (!loading) {
        console.log({ assetBalance: getAssetBalance("ETH") });
    }
    const { switchChainAsync } = useSwitchChain();
    const { writeContract } = useWriteContract();

    const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault();
        setLoading(true);
        const form = e.currentTarget;
        try {
            const formData = new FormData(form);

            const toFV = formData.get("to");
            const chainFV = formData.get("chain");
            const assetFV = formData.get("asset");
            const amountFV = formData.get("amount");
            if (!toFV || !chainFV || !assetFV || !amountFV) {
                throw new Error("missing params");
            }
            const to = toFV as `0x${string}`;
            const chain = Number(chainFV);
            const asset = assetFV as "usdc" | "usdt" | "eth";

            await switchChainAsync({ chainId: chain });

            let amount = new Decimal(amountFV as string);
            if (asset.toLowerCase() === "ETH".toLowerCase()) {
                amount = amount.mul(new Decimal(10).pow(18));
                const value = BigInt(amount.toString());
                sendTransaction(
                    {
                        to,
                        value,
                    },
                    {
                        onSuccess(hash) {
                            createSuccessToast(chain, hash);
                            form.reset();
                            setLoading(false);
                            console.log("success");
                        },
                        onSettled() {
                            console.log("settled");
                        },
                        onError(error) {
                            console.log({ error });
                            form.reset();
                            setLoading(false);
                        },
                    }
                );
            } else {
                const chainData = chainToCurrency[chain];
                const s = chainData[asset === "usdc" ? 0 : 1];
                if (!s) {
                    throw new Error("asset not supported");
                }
                writeContract(
                    {
                        address: s,
                        abi: erc20Abi,
                        functionName: "transfer",
                        args: [to, BigInt(amount.mul(new Decimal(10).pow(6)).toString())],
                    },
                    {
                        onSuccess(hash) {
                            createSuccessToast(chain, hash);
                            form.reset();
                            setLoading(false);
                            console.log("success");
                        },
                        onError(error) {
                            form.reset();
                            setLoading(false);
                            console.log({ error });
                        },
                    }
                );
            }
        } catch (e) {
            form.reset();
            console.log({ e });
            setLoading(false);
        }
    };

    return (
        <>
            {loading ? (
                <div>
                    <p>Loading...</p>
                </div>   
            ) : (
                <>
                    <p>
                        {address && ensName ? `${ensName} (${address})` : address}
                    </p>
                    <div>
                        <button onClick={() => disconnect()}>
                            Disconnect
                        </button>
                        <button onClick={() => showModal()}> //Display Unified Balance
                            Show balances
                        </button>
                    </div>
                    <div className="mb-4 m-auto"></div>
                    <form onSubmit={handleSubmit}>
                        <div>
                            <label>
                                To
                            </label>
                            <input
                                name="to"
                                type="text"
                                placeholder="0x..."
                                required
                            />
                        </div>
                        <div>
                            <label>
                                Destination Chain
                            </label>
                            <select
                                required
                                name="chain"
                                id="chain"
                                defaultValue={""}
                            >
                                <option value="" disabled>
                                    Select a chain
                                </option>
                                <option value="42161">Arbitrum One</option>
                                <option value="59144">Linea</option>
                                <option value="534352">Scroll</option>
                                <option value="10">Optimism</option>
                                <option value="8453">Base</option>
                                <option value="1">Ethereum</option>
                                <option value="137">Polygon POS</option>
                            </select>
                        </div>
                        <div>
                            <label>
                                Asset
                            </label>
                            <select
                                name="asset"
                                id="asset"
                                defaultValue={""}
                            >
                                <option value="" disabled>
                                    Select an asset
                                </option>
                                <option value="usdt">USDT</option>
                                <option value="usdc">USDC</option>
                                <option value="eth">ETH</option>
                            </select>
                        </div>
                        <div className="mb-5">
                            <label>
                                Amount
                            </label>
                            <input
                                name="amount"
                                type="text"
                                id="amount"
                                required
                            />
                        </div>
                        <button
                            type="submit"
                            disabled={allLoading}
                        >
                            {allLoading ? "Loading..." : "Submit"}
                        </button>
                    </form>
                </>
            )}
        </>
    );
}      
import { http, createConfig } from "wagmi";
import {
    mainnet,
    optimism,
    base,
    arbitrum,
    scroll,
    linea,
    polygon,
} from "wagmi/chains";
import { injected } from "wagmi/connectors";

export const config = createConfig({
    chains: [mainnet, optimism, arbitrum, base, scroll, linea, polygon],
    connectors: [injected()],
    transports: {
        [mainnet.id]: http(),
        [optimism.id]: http(),
        [arbitrum.id]: http(),
        [base.id]: http(),
        [scroll.id]: http(),
        [linea.id]: http(),
        [polygon.id]: http(),
    },
});
import * as React from "react";
import { Connector, useConnect } from "wagmi";

function WalletOption({
connector,
onClick,
}: {
connector: Connector;
onClick: () => void;
}) {
const [ready, setReady] = React.useState(false);

React.useEffect(() => {
    (async () => {
    const provider = await connector.getProvider();
    setReady(!!provider);
    })();
}, [connector]);

return (
    <>
    <div>
        <button
        disabled={!ready}
        type="button"
        onClick={onClick}
        >
        <img
            src={connector.icon}
            aria-hidden="true"
        />
        {connector.name}
        </button>
    </div>
    </>
);
}

export function WalletOptions() {
const { connectors, connect } = useConnect();

return (
    <>
    <h3>
        Wallets
    </h3>
    <hr></hr>
    {connectors
        .filter((c) => c.id !== "injected")
        .map((connector) => (
        <WalletOption
            key={connector.uid}
            connector={connector}
            onClick={() => connect({ connector })}
        />
        ))}
    </>
);
}

Hooks

The Arcana CA Wagmi SDK replaces useSendTranaction and useWriteContract hooks of the wagmi library. In addition, it provides hooks for managing unified balance.

Wagmi Hooks

Replace the following hooks used in the app from the Wagmi library with those from the Arcana CA Wagmi SDK package:

import { useSendTransaction, useWriteContract } from "@arcana/ca-wagmi";

// Replace the `wagmi` APIs `useSendTransaction` and `useSendTransactionAsync`
const { sendTransaction, sendTransactionAsync } = useSendTransaction(); 

// Replace the wagmi APIs `useWriteContract` and `useWriteContractAsync`
const { writeContract, writeContractAsync } = useWriteContract(); 

Arcana Hooks

Use these Arcana hooks to access unified balance via the plug-and-play widget. Additionally, you can enable chain abstracted bridge and transfer functions through useCAFns.

  • useBalance - to get the unified balance value across all supported chains for the specified token string
  • useBalances - to get the unified balance values across all supported chains for all supported tokens associated with the EOA
  • useBalanceModal - to display or hide the unified balance popup widget
  • useCAFn() - for chain abstracted bridging and token transfer functionality

useBalance

useBalance({ symbol: string })

symbol: Required parameter of type string with the value equal to one of the supported currency/token symbol.

useBalance() returns response contains the following fields:

Parameter Type
loading boolean
value { symbol: string, decimals: number, formatted: string, value: bigint} \| null
error Error \| null
import { useBalance } from "@arcana/ca-wagmi"

const balance = useBalance({ symbol: "eth" })
Sample useBalance Response
{
  loading: false,
  value: {
    symbol: "ETH",
    decimals: 18,
    formatted: "0.000785657313049966",
    value: 785657313049966n
  },
  error: null
}

useBalances

useBalances() returns response contains the following fields:

Parameter Type
loading boolean
value UseBalanceValue[] \| null
error Error \| null
import { useBalances } from "@arcana/ca-wagmi"

const balances = useBalances()
Sample useBalances Response
{
  loading: false,
  value: [{
    symbol: "ETH",
    decimals: 18,
    formatted: "0.000785657313049966"
    value: 785657313049966n,
    breakdown: [{
      chain: {
        id: 1,
        name: "Ethereum",
        logo: "..."
      },
      formatted: "0.000785657313049966",
      address: "0x0000000000000000000000000000000000000000",
      value: 785657313049966n
    }]
  }],
  error: null
} 

useBalanceModal

useBalanceModal() returns response contains the following fields:

Field Type
showModal () => void
hideModal () => void
import { useBalanceModal } from "@arcana/ca-wagmi"

const { showModal, hideModal } = useBalanceModal()
With CA
Plug & Play Unified Balance Widget

useCAFn

The useCAFn() response contains the following fields:

Parameter Type
bridge ({ token: string, amount: string, chain: number }) => Promise<unknown>
transfer ({ token: string, amount: string, chain: number, to: "0x${string}" }) => Promise<unknown>
  import { useCAFn } from "@arcana/ca-wagmi"

  const { bridge, transfer } = useCAFn()

  await bridge({
  token: "usdt",
  amount: "1.5",
  chain: 42161
  })

  const hash = await transfer({to: "0x80129F3d408545e51d051a6D3e194983EB7801e8",
  token: "usdt",
  amount: "1.5",
  chain: 10
  })
With CA
`useCAFn`: Chain Abstracted Bridge and Transfer

See Also

Try CA Wagmi SDK CodeSandbox


Last update: April 18, 2025 by shaloo