Skip to content

React Integration

This guide targets the Tiramisu testnet. Check Networks for current testnets and faucets.

The Arkiv SDK works in the browser out of the box. This page covers the two most common React patterns:

  1. Reading data: query entities with a public client, cached via TanStack Query
  2. Writing data: sign transactions with the user’s wallet via wagmi

Use a public client for queries: no private key required, safe to run in the browser.

First, define fetcher functions separate from hooks so they are reusable and testable:

lib/arkiv-queries.ts
import { createPublicClient } from "@arkiv-network/sdk"
import { tiramisu } from "@arkiv-network/sdk/chains"
import { eq } from "@arkiv-network/sdk/query"
import { http } from "viem"
const PROJECT_ATTRIBUTE = { key: "project", value: "my-app" }
export const publicClient = createPublicClient({
chain: tiramisu,
transport: http(),
})
export async function fetchEntitiesByType<T>(
entityType: string
): Promise<(T & { arkivEntityKey: string })[]> {
const result = await publicClient
.select({ key: true, payload: true })
.where(
eq(PROJECT_ATTRIBUTE.key, PROJECT_ATTRIBUTE.value),
eq("entity_type", entityType),
)
.limit(50)
.fetch()
return result.entities
.map((entity) => {
try {
return { arkivEntityKey: entity.key, ...entity.toJson() }
} catch {
return null
}
})
.filter((item): item is T & { arkivEntityKey: string } => item !== null)
}
export async function fetchEntityByKey<T>(entityKey: string): Promise<T> {
const entity = await publicClient.getEntity(entityKey)
return entity.toJson()
}

Wrap the fetchers with TanStack Query for caching, deduplication, and background refetching:

Terminal window
npm install @tanstack/react-query
hooks/useArkivQuery.ts
import { useQuery } from "@tanstack/react-query"
import { fetchEntitiesByType, fetchEntityByKey } from "@/lib/arkiv-queries"
export function useArkivQuery<T>(entityType: string) {
return useQuery<(T & { arkivEntityKey: string })[]>({
queryKey: ["arkiv", "entities", entityType],
queryFn: () => fetchEntitiesByType<T>(entityType),
})
}
export function useArkivEntity<T>(entityKey: string | null) {
return useQuery<T>({
queryKey: ["arkiv", "entity", entityKey],
queryFn: () => fetchEntityByKey<T>(entityKey!),
enabled: !!entityKey,
})
}
import { useArkivQuery } from "@/hooks/useArkivQuery"
interface Post {
title: string
content: string
}
function PostList() {
const { data: posts, isLoading, error } = useArkivQuery<Post>("post")
if (isLoading) return <p>Loading...</p>
if (error) return <p>Error: {error.message}</p>
return (
<ul>
{posts?.map((post) => (
<li key={post.arkivEntityKey}>{post.title}</li>
))}
</ul>
)
}

Your project can use wagmi, or a wagmi-powered kit such as RainbowKit or ConnectKit. You can then derive an Arkiv wallet client directly from the wagmi wallet client. This avoids duplicate wallet connection logic.

import { useAccount, useWalletClient } from "wagmi"
import { createWalletClient as createArkivWalletClient } from "@arkiv-network/sdk"
import { tiramisu } from "@arkiv-network/sdk/chains"
import { custom } from "viem"
function useArkivWalletClient() {
const { address } = useAccount()
const { data: wagmiWalletClient } = useWalletClient()
if (!wagmiWalletClient || !address) return null
return createArkivWalletClient({
chain: tiramisu,
transport: custom(wagmiWalletClient.transport),
account: address,
})
}

Then use the returned client like any other Arkiv wallet client:

import { jsonToPayload, ExpirationTime } from "@arkiv-network/sdk"
function CreatePostButton() {
const arkivClient = useArkivWalletClient()
async function handleCreate() {
if (!arkivClient) return
const { entityKey, txHash } = await arkivClient.createEntity({
payload: jsonToPayload({ title: "My Post", content: "Hello!" }),
contentType: "application/json",
attributes: {
project: "my-app",
entity_type: "post",
},
expires: ExpirationTime.fromDays(30),
})
console.log("Created:", entityKey, txHash)
}
return (
<button onClick={handleCreate} disabled={!arkivClient}>
Create Post
</button>
)
}

Register the Arkiv Tiramisu testnet in your wagmi config so wallet switching works:

wagmi.ts
import { http, createConfig } from "wagmi"
import { tiramisu } from "@arkiv-network/sdk/chains"
export const config = createConfig({
chains: [tiramisu],
transports: {
[tiramisu.id]: http(),
},
})

If your app supports multiple chains, add tiramisu alongside them:

import { mainnet } from "wagmi/chains"
import { tiramisu } from "@arkiv-network/sdk/chains"
export const config = createConfig({
chains: [mainnet, tiramisu],
transports: {
[mainnet.id]: http(),
[tiramisu.id]: http(),
},
})

If you do not use wagmi, you can connect MetaMask directly:

import { createWalletClient } from "@arkiv-network/sdk"
import { tiramisu } from "@arkiv-network/sdk/chains"
import { custom } from "viem"
// Prompt MetaMask to add the network (no effect if it is already added)
await window.ethereum.request({
method: "wallet_addEthereumChain",
params: [{
chainId: `0x${tiramisu.id.toString(16)}`,
chainName: tiramisu.name,
nativeCurrency: tiramisu.nativeCurrency,
rpcUrls: tiramisu.rpcUrls.default.http,
blockExplorerUrls: ["https://tiramisu.explorer.arkiv.network"],
}],
})
// Connect
const [address] = await window.ethereum.request({
method: "eth_requestAccounts",
})
const walletClient = createWalletClient({
chain: tiramisu,
transport: custom(window.ethereum),
})