Wallet Integration
Aomi widgets use the Para + wagmi provider tree directly. The registry install
gives you the wallet UI plus RuntimeTxHandler; you provide the Para and wagmi
providers in your host app.
Setup
Install Dependencies
If you installed the widget through npx shadcn add https://aomi.dev/r/aomi-frame.json,
the wallet UI is already included. Use the setup below when you want to wire
Para-based auth and external wallets in your host app.
npm install @getpara/react-sdk @getpara/evm-wallet-connectors wagmi viem @tanstack/react-queryConfigure Para
"use client";
import "@getpara/react-sdk/styles.css";
import { ParaProvider, Environment } from "@getpara/react-sdk";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { defineChain, http } from "viem";
import { mainnet, polygon, arbitrum, base } from "wagmi/chains";
const queryClient = new QueryClient();
const walletConnectProjectId =
process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID ??
process.env.NEXT_PUBLIC_PROJECT_ID;
const localhost = defineChain({
id: 31337,
name: "Localhost",
nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
rpcUrls: {
default: {
http: ["http://127.0.0.1:8545"],
},
},
});
const useAnvilForWallet = process.env.NEXT_PUBLIC_ANVIL_FOR_WALLET === "true";
const networks = useAnvilForWallet
? [localhost, mainnet, polygon, arbitrum, base]
: [mainnet, polygon, arbitrum, base];
const transports = Object.fromEntries(
networks.map((chain) => [chain.id, http(chain.rpcUrls.default.http[0])]),
);
export function Providers({ children }: { children: React.ReactNode }) {
return (
<QueryClientProvider client={queryClient}>
<ParaProvider
paraClientConfig={{
apiKey: process.env.NEXT_PUBLIC_PARA_API_KEY!,
env:
(process.env.NEXT_PUBLIC_PARA_ENVIRONMENT as
| Environment
| undefined) ?? Environment.BETA,
}}
config={{ appName: "Your App" }}
paraModalConfig={{
disableEmailLogin: true,
oAuthMethods: ["GOOGLE"],
}}
externalWalletConfig={{
appDescription: "Your app description",
appUrl: "https://your-app.com",
wallets: [
"WALLETCONNECT",
"METAMASK",
"COINBASE",
"RAINBOW",
"RABBY",
],
walletConnect: {
projectId: walletConnectProjectId!,
},
evmConnector: {
config: {
chains: networks,
transports,
ssr: true,
},
},
}}
>
{children}
</ParaProvider>
</QueryClientProvider>
);
}Supported Chains
Configure which chains are available by passing them to Para's EVM connector:
import { mainnet, polygon, arbitrum, base, optimism } from "wagmi/chains";
externalWalletConfig={{
evmConnector: {
config: {
chains: [mainnet, polygon, arbitrum, base, optimism],
transports: {
[mainnet.id]: http(),
[polygon.id]: http(),
[arbitrum.id]: http(),
[base.id]: http(),
[optimism.id]: http(),
},
},
},
}}The NetworkSelect component in the widget (or getChainInfo / SUPPORTED_CHAINS in the headless library) will reflect the configured chains.
Transaction Signing Flow
When the AI assistant determines a transaction is needed, the following flow occurs:
1. LLM emits wallet_tx_request tool call
2. Backend sends wallet_tx_request event via SSE
3. Frontend receives event via useWalletHandler
4. UI prompts user to review and sign
5. User signs with their wallet (wagmi)
6. Frontend sends wallet:tx_complete event back to backend
7. Backend injects transaction result into LLM context
8. LLM continues the conversation with the resultIn the Widget
The widget includes RuntimeTxHandler automatically. When a transaction
request arrives, it executes through wagmi inside your Para provider tree and
prompts the user to sign.
In the Headless Library
Handle transaction requests manually:
import { useWalletHandler, useAomiRuntime } from "@aomi-labs/react";
import { useSendTransaction, useSignTypedData } from "wagmi";
function WalletRequestHandler() {
const { currentThreadId } = useAomiRuntime();
const { pendingRequests, startProcessing, resolveRequest, rejectRequest } =
useWalletHandler({ sessionId: currentThreadId });
const { sendTransactionAsync } = useSendTransaction();
const { signTypedDataAsync } = useSignTypedData();
const handleRequest = async (request: WalletRequest) => {
startProcessing(request.id);
try {
if (request.kind === "transaction") {
const payload = request.payload as WalletTxPayload;
const hash = await sendTransactionAsync({
to: payload.to,
value: BigInt(payload.value ?? "0"),
data: payload.data,
});
resolveRequest(request.id, { txHash: hash });
}
if (request.kind === "eip712_sign") {
const payload = request.payload as WalletEip712Payload;
const signature = await signTypedDataAsync(payload.typedData);
resolveRequest(request.id, { signature });
}
} catch (error) {
rejectRequest(request.id, error.message);
}
};
return (
<div>
{pendingRequests.map((req) => (
<div key={req.id}>
<p>Transaction request: {req.kind}</p>
<button onClick={() => handleRequest(req)}>Approve</button>
<button onClick={() => rejectRequest(req.id, "User rejected")}>
Reject
</button>
</div>
))}
</div>
);
}EIP-712 Typed Data Signing
The AI can also request EIP-712 typed data signatures (used for gasless transactions, permit approvals, etc.). The flow is identical to transaction signing but uses signTypedData instead of sendTransaction.
Network Switching
In the Widget
The NetworkSelect component provides a dropdown for switching chains. When the user selects a network, it calls useSwitchChain from wagmi and updates the Aomi runtime.
Programmatically
import { useSwitchChain } from "wagmi";
import { useUser } from "@aomi-labs/react";
function NetworkSwitcher() {
const { switchChain } = useSwitchChain();
const { setUser } = useUser();
const handleSwitch = async (chainId: number) => {
await switchChain({ chainId });
// The widget auth bridge auto-syncs, but you can also set manually:
setUser({ chainId });
};
}ConnectButton Component (Widget)
The widget's ConnectButton component handles the full lifecycle:
- Renders a connect/manage account button
- On connect: opens the Para auth modal
- On successful connection: syncs address, chainId, and connection status to
useUser - When already connected: opens the Para account modal
import { ConnectButton } from "@/components/control-bar";
<ConnectButton
connectLabel="Connect Wallet"
onConnectionChange={(isConnected) => {
console.log("Wallet connected:", isConnected);
}}
/>;Utilities
The headless library provides wallet-related utilities:
import {
formatAddress,
getNetworkName,
getChainInfo,
SUPPORTED_CHAINS,
} from "@aomi-labs/react";
formatAddress("0x1234567890abcdef1234567890abcdef12345678");
// "0x1234...5678"
getNetworkName(1);
// "Ethereum"
getChainInfo(137);
// { name: "Polygon", ticker: "POL", ... }
SUPPORTED_CHAINS;
// [{ chainId: 1, name: "Ethereum", ... }, ...]