Wagmi Integration
Using RISE Wallet with Wagmi React hooks
This guide covers integrating RISE Wallet with Wagmi, the popular collection of React Hooks for Ethereum.
Overview
RISE Wallet implements a Wagmi connector and provides custom React hooks that map directly to the wallet's capabilities. The integration is designed to feel natural to developers already familiar with Wagmi.
Installation
npm i rise-wallet wagmi viem @tanstack/react-querypnpm add rise-wallet wagmi viem @tanstack/react-queryyarn add rise-wallet wagmi viem @tanstack/react-querybun add rise-wallet wagmi viem @tanstack/react-queryBasic Setup
1. Configure the RISE Wallet Connector
import { Chains, RiseWallet } from "rise-wallet";
import { riseWallet } from "rise-wallet/wagmi";
import { createConfig, http } from "wagmi";
// Export the connector for advanced usage
export const rwConnector = riseWallet(RiseWallet.defaultConfig);
// Create wagmi config
export const config = createConfig({
chains: [Chains.riseTestnet],
connectors: [rwConnector],
transports: {
[Chains.riseTestnet.id]: http("https://testnet.riselabs.xyz"),
},
});2. Set Up Providers
"use client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { WagmiProvider } from "wagmi";
import { config } from "@/config/wagmi";
import { useState } from "react";
export function Providers({ children }: { children: React.ReactNode }) {
const [queryClient] = useState(() => new QueryClient());
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
</WagmiProvider>
);
}3. Wrap Your App in the Provider
Add the Providers component to your root layout:
import { Providers } from "./providers";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}Standard Wagmi Hooks
All standard Wagmi hooks work seamlessly with RISE Wallet:
Connection Management
import { useConnect, useConnection, useDisconnect, useConnectors } from "wagmi";
export function WalletButton() {
const { address, isConnected } = useConnection();
const connect = useConnect();
const disconnect = useDisconnect();
const connectors = useConnectors();
if (isConnected) {
return (
<div>
<span>{address}</span>
<button onClick={() => disconnect.mutate()}>Disconnect</button>
</div>
);
}
const rwConnector = connectors.find(c => c.id === "com.risechain.wallet");
if (!rwConnector) return null;
return (
<button onClick={() => connect.mutate({ connector: rwConnector })}>
Connect with Passkey
</button>
);
}Sending Transactions
import { useSendCalls } from "wagmi";
import { parseEther } from "viem";
export function SendTransaction() {
const sendCalls = useSendCalls();
//for sending 0.001 ETH to some address
const handleSend = async () => {
const result = await sendCalls.mutateAsync({
calls: [{
to: "0x...",
value: parseEther("0.001"),
}],
});
console.log("Transaction sent:", result);
};
return (
<button onClick={handleSend} disabled={sendCalls.isPending}>
{sendCalls.isPending ? "Sending..." : "Send 0.001 ETH"}
</button>
);
}RISE Wallet Specific Hooks
Import custom hooks via the Hooks namespace:
import { Hooks } from "rise-wallet/wagmi";or individually:
import { useGrantPermissions } from "rise-wallet/wagmi/Hooks";useGrantPermissions
Create session keys with specific permissions:
import { parseEther } from "viem";
import { P256, PublicKey } from "ox";
const grantPermissions = Hooks.useGrantPermissions();
const createSession = async () => {
// 1. Generate a local key pair
const privateKey = P256.randomPrivateKey();
const publicKey = PublicKey.toHex(P256.getPublicKey({ privateKey }), {
includePrefix: false,
});
// 2. Grant permissions to the session key
await grantPermissions.mutateAsync({
key: { publicKey, type: "p256" },
expiry: Math.floor(Date.now() / 1000) + 3600, // 1 hour
feeToken: null, // use native token for fees, or { limit: "0.01", symbol: "ETH" }
// the permissions you want the session key to have
permissions: {
calls: [{ // function calls
to: "0x...", // address of the contract on which the function is being called
signature: "0x...", // function signature. you can get this via: cast sig "transfer(address,uint256)"
}],
// token spend limits
spend: [{
token: "0x...", // token contract address
limit: parseEther("50"), // max amount (bigint)
period: "minute", // time window (eg. "minute", "hour", "day")
}],
},
});
// 3. Store the private key to sign future transactions without popup
localStorage.setItem("session_key", privateKey);
};usePermissions
View current permissions for the connected account:
const { data: permissions } = Hooks.usePermissions();
if (permissions) {
console.log("Active permissions:", permissions);
}useRevokePermissions
Revoke previously granted permissions:
const revokePermissions = Hooks.useRevokePermissions();
const revokeSession = async (keyId: string) => {
await revokePermissions.mutateAsync({
keyId,
});
};useAssets
Get user's token balances and assets:
const { data: assets } = Hooks.useAssets();
return (
<div>
{assets?.map(asset => (
<div key={asset.address}>
{asset.symbol}: {asset.balance}
</div>
))}
</div>
);Polling Transaction Status
When using session keys with wallet_sendPreparedCalls, you need to manually poll for transaction status using wallet_getCallsStatus:
import { P256, Signature } from "ox";
async function sendWithSessionKey(calls: any[], sessionPrivateKey: string, sessionPublicKey: string) {
const provider = await connector.getProvider();
// 1. Prepare the calls
const { digest, ...request } = await provider.request({
method: "wallet_prepareCalls",
params: [{
calls,
chainId: "0x...",
from: address,
key: { publicKey: sessionPublicKey, type: "p256" },
}],
});
// 2. Sign with session key
const signature = Signature.toHex(
P256.sign({ payload: digest, privateKey: sessionPrivateKey })
);
// 3. Send signed calls
const result = await provider.request({
method: "wallet_sendPreparedCalls",
params: [{ ...request, signature }],
});
// 4. Extract bundle ID
const bundleId = Array.isArray(result) ? result[0].id : result.id;
// 5. Poll for transaction status
const status = await provider.request({
method: "wallet_getCallsStatus",
params: [bundleId],
});
// 6. Check status
if (status.status !== 200) {
throw new Error(`Transaction failed with status ${status.status}`);
}
// 7. Return transaction hash
return status.receipts[0].transactionHash;
}Status Codes
The status field in the response indicates the current state of the transaction bundle:
| Code | Description |
|---|---|
| 100 | Pending - Bundle received but not executed onchain yet |
| 200 | Confirmed - Bundle included onchain without reverts, receipts available |
| 300 | Failed (Offchain) - Bundle not included onchain, wallet will not retry |
| 400 | Reverted (Complete) - Bundle completely reverted, only gas charges may be onchain |
| 500 | Reverted (Partial) - Bundle partially reverted, some changes may be onchain |
Response Structure
type GetCallsStatusResponse = {
id: string, // Bundle ID
status: number, // Status code (100-500)
receipts: {
logs: {
chainId: string,
address: string,
data: string,
topics: string[],
}[],
status: string, // "0x1" for success, "0x0" for failure
blockHash?: string,
blockNumber?: string,
gasUsed: string,
transactionHash: string, // The actual transaction hash
}[],
capabilities?: {
interopStatus?: 'pending' | 'confirmed' | 'failed',
},
}Note: Standard Wagmi hooks like useSendCalls handle polling internally using sendCallsSync. You only need to manually poll wallet_getCallsStatus when using session keys with wallet_sendPreparedCalls.
Advanced Patterns
Batched Transactions
Execute multiple operations atomically:
const sendCalls = useSendCalls();
// Approve and swap in one transaction
await sendCalls.mutateAsync({
calls: [
{
to: TOKEN_ADDRESS,
data: approveCalldata,
},
{
to: DEX_ADDRESS,
data: swapCalldata,
},
],
// Atomic execution - all succeed or all fail
atomicRequired: true,
});Reading Transaction Status
Monitor transaction progress:
import { useWaitForTransactionReceipt } from "wagmi";
const { data: receipt, isLoading } = useWaitForTransactionReceipt({
hash: transactionHash,
});
if (receipt) {
console.log("Transaction confirmed:", receipt);
}Error Handling
Properly handle wallet errors:
import { BaseError } from "wagmi";
const sendCalls = useSendCalls();
try {
await sendCalls.mutateAsync({...});
} catch (error) {
if (error instanceof BaseError) {
// Handle specific error types
if (error.shortMessage.includes("rejected")) {
console.log("User rejected the request");
}
}
}TypeScript Support
RISE Wallet provides full TypeScript support:
import type { RiseWallet } from "rise-wallet";
import type { Config } from "wagmi";
// Config is fully typed
const config: Config = createConfig({
chains: [Chains.riseTestnet],
connectors: [rwConnector],
transports: {
[Chains.riseTestnet.id]: http(),
},
});Best Practices
1. Check Connection State
Always verify the connection state before attempting transactions:
const { isConnected } = useConnection();
if (!isConnected) {
return <ConnectButton />;
}2. Handle Loading States
Provide feedback during async operations:
const sendCalls = useSendCalls();
// Access: sendCalls.isPending, sendCalls.isSuccess, sendCalls.isError3. Use Error Boundaries
Wrap your app with error boundaries to catch unexpected errors:
import { ErrorBoundary } from "react-error-boundary";
<ErrorBoundary fallback={<ErrorFallback />}>
<App />
</ErrorBoundary>Examples
For complete working examples, check out: