Skip to content

React Integration

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’re reusable and testable:

lib/arkiv-queries.ts
import { createPublicClient, http } from "@arkiv-network/sdk"
import { kaolin } from "@arkiv-network/sdk/chains"
import { eq } from "@arkiv-network/sdk/query"
const PROJECT_ATTRIBUTE = { key: "project", value: "my-app" }
export const publicClient = createPublicClient({
chain: kaolin,
transport: http(),
})
export async function fetchEntitiesByType<T>(
entityType: string
): Promise<(T & { arkivEntityKey: string })[]> {
const query = publicClient.buildQuery()
const result = await query
.where([
eq(PROJECT_ATTRIBUTE.key, PROJECT_ATTRIBUTE.value),
eq("entityType", entityType),
])
.withPayload(true)
.withMetadata(true)
.limit(50)
.fetch()
return result.entities
.map((entity: any) => {
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>
)
}

If your project uses wagmi (or a wagmi-powered kit like RainbowKit or ConnectKit), you can 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,
custom,
} from "@arkiv-network/sdk"
import { kaolin } from "@arkiv-network/sdk/chains"
function useArkivWalletClient() {
const { address } = useAccount()
const { data: wagmiWalletClient } = useWalletClient()
if (!wagmiWalletClient || !address) return null
return createArkivWalletClient({
chain: kaolin,
transport: custom(wagmiWalletClient.transport),
account: address,
})
}

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

import { jsonToPayload, ExpirationTime } from "@arkiv-network/sdk/utils"
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: [
{ key: "project", value: "my-app" },
{ key: "entityType", value: "post" },
],
expiresIn: ExpirationTime.fromDays(30),
})
console.log("Created:", entityKey, txHash)
}
return (
<button onClick={handleCreate} disabled={!arkivClient}>
Create Post
</button>
)
}

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

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

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

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

If you’re not using wagmi, you can connect MetaMask directly:

import { createWalletClient, custom } from "@arkiv-network/sdk"
import { kaolin } from "@arkiv-network/sdk/chains"
// Prompt MetaMask to add the network
await window.ethereum.request({
method: "wallet_addEthereumChain",
params: [{
chainId: "0xe0087f821",
chainName: "Arkiv Kaolin Testnet",
nativeCurrency: { name: "ETH", symbol: "ETH", decimals: 18 },
rpcUrls: ["https://kaolin.hoodi.arkiv.network/rpc"],
blockExplorerUrls: ["https://explorer.kaolin.hoodi.arkiv.network"],
}],
})
// Connect
const [address] = await window.ethereum.request({
method: "eth_requestAccounts",
})
const walletClient = createWalletClient({
chain: kaolin,
transport: custom(window.ethereum),
})