[ ARKIV DOCS ]

Everything you need to build with Arkiv. From quick start guides to comprehensive API documentation.

Last updated: February 2026 ยท SDK v0.6.0

๐Ÿ“š

Guides & Tutorials

Step-by-step tutorials and examples

Guides & Tutorials

Step-by-step guides to help you build with Arkiv using real examples.

Step-by-Step Tutorials

Real-Time Crypto Dashboard

Follow the end-to-end tutorial to build a real-time cryptocurrency dashboard using Arkiv as the data layer.

Crypto Dashboard

Read the guide

Sketch App with MetaMask

Build a sketching application that connects to MetaMask and stores drawings on the Arkiv blockchain using p5.js.

Sketch App

Read the guide

Quickstart Projects

1. Build a Decentralized Note-Taking App

Create a simple note-taking app with automatic expiration and attributes.

typescriptExample
1import { createWalletClient, http } from "@arkiv-network/sdk"
2import { privateKeyToAccount } from "@arkiv-network/sdk/accounts"
3import { kaolin } from "@arkiv-network/sdk/chains"
4import { ExpirationTime, jsonToPayload } from "@arkiv-network/sdk/utils"
5import { randomUUID } from 'crypto'
6
7const walletClient = createWalletClient({
8  chain: kaolin,
9  transport: http(),
10  account: privateKeyToAccount(process.env.PRIVATE_KEY),
11})
12
13// Create a note with 12-hour expiration
14const noteId = randomUUID()
15const noteData = {
16  title: "My First Note",
17  content: "This note will expire in 12 hours",
18  created: Date.now()
19}
20
21const { entityKey, txHash } = await walletClient.createEntity({
22  payload: jsonToPayload(noteData),
23  contentType: 'application/json',
24  attributes: [
25    { key: 'type', value: 'note' },
26    { key: 'noteId', value: noteId },
27    { key: 'title', value: noteData.title },
28    { key: 'created', value: noteData.created }
29  ],
30  expiresIn: ExpirationTime.fromHours(12),
31})
32
33console.log('Note created:', entityKey)

2. Cross-Device Clipboard

Build a clipboard that syncs across devices using temporary storage.

typescriptExample
1import { createWalletClient, createPublicClient, http } from "@arkiv-network/sdk"
2import { privateKeyToAccount } from "@arkiv-network/sdk/accounts"
3import { kaolin } from "@arkiv-network/sdk/chains"
4import { ExpirationTime } from "@arkiv-network/sdk/utils"
5import { eq, and } from "@arkiv-network/sdk/query"
6import { randomUUID } from 'crypto'
7
8// Setup clients
9const walletClient = createWalletClient({
10  chain: kaolin,
11  transport: http(),
12  account: privateKeyToAccount(process.env.PRIVATE_KEY),
13})
14
15const publicClient = createPublicClient({
16  chain: kaolin,
17  transport: http(),
18})
19
20// Copy to clipboard (store data)
21async function copyToClipboard(text: string, deviceId: string) {
22  const clipId = randomUUID()
23  const timestamp = Date.now()
24
25  const { entityKey } = await walletClient.createEntity({
26    payload: stringToPayload(text),
27    contentType: 'text/plain',
28    attributes: [
29      { key: 'type', value: 'clipboard' },
30      { key: 'deviceId', value: deviceId },
31      { key: 'clipId', value: clipId },
32      { key: 'timestamp', value: timestamp }
33    ],
34    expiresIn: ExpirationTime.fromHours(1),
35  })
36
37  return clipId
38}
39
40// Paste from clipboard (retrieve latest)
41async function getLatestClip(deviceId: string) {
42  const query = publicClient.buildQuery()
43  const clips = await query
44    .where(eq('type', 'clipboard'))
45    .where(eq('deviceId', deviceId))
46    .withPayload(true)
47    .withAttributes(true)
48    .fetch()
49
50  // Sort by timestamp (newest first)
51  clips.sort((a, b) => {
52    const aTime = a.attributes?.find(an => an.key === "timestamp")?.value || 0
53    const bTime = b.attributes?.find(an => an.key === "timestamp")?.value || 0
54    return Number(bTime) - Number(aTime)
55  })
56
57  if (clips.length > 0) {
58    return payloadToString(clips[0].payload)
59  }
60  return null
61}
62
63// Usage
64const myDeviceId = "device-" + randomUUID()
65await copyToClipboard("Hello from device A!", myDeviceId)
66const clipText = await getLatestClip(myDeviceId)
67console.log("Pasted:", clipText)

3. Real-time Data Sync

Sync data in real-time across multiple clients using event subscriptions.

typescriptExample
1import { createWalletClient, createPublicClient, http, webSocket } from "@arkiv-network/sdk"
2import { privateKeyToAccount } from "@arkiv-network/sdk/accounts"
3import { kaolin } from "@arkiv-network/sdk/chains"
4import { ExpirationTime } from "@arkiv-network/sdk/utils"
5
6// Client 1: Watcher (uses WebSocket for real-time events)
7const watcherClient = createPublicClient({
8  chain: kaolin,
9  transport: webSocket('wss://kaolin.hoodi.arkiv.network/rpc/ws'),
10})
11
12// Watch for new entities
13const unwatch = watcherClient.watchEntityCreated({
14  onCreated: async (event) => {
15    // Fetch entity details
16    const entity = await watcherClient.getEntity(event.entityKey)
17
18    const attributes = entity.attributes || []
19    const typeAttr = attributes.find(a => a.key === 'type')
20
21    if (typeAttr?.value === 'message') {
22      const payload = entity.payload
23      console.log("New message:", payloadToString(payload))
24      // Update UI, trigger notifications, etc.
25    }
26  }
27})
28
29// Client 2: Publisher
30const publisherClient = createWalletClient({
31  chain: kaolin,
32  transport: http(),
33  account: privateKeyToAccount(process.env.PUBLISHER_KEY),
34})
35
36// Post a message
37const { entityKey } = await publisherClient.createEntity({
38  payload: stringToPayload("Hello everyone!"),
39  contentType: 'text/plain',
40  attributes: [
41    { key: 'type', value: 'message' },
42    { key: 'channel', value: 'general' },
43    { key: 'timestamp', value: Date.now() }
44  ],
45  expiresIn: ExpirationTime.fromHours(2),
46})
47
48// Client 1 will receive the event in real-time
49
50// Cleanup when done
51// unwatch()

Advanced Guides

Performance Optimization

Best practices for optimizing query performance and reducing costs.

1. Use Specific Queries

typescriptExample
1// โŒ Bad: Returns all notes
2const query1 = publicClient.buildQuery()
3const allNotes = await query1
4  .where(eq('type', 'note'))
5  .fetch()
6
7// โœ… Good: Filter by additional criteria
8const oneDayAgo = Date.now() - 86400000
9const query2 = publicClient.buildQuery()
10const recentNotes = await query2
11  .where(eq('type', 'note'))
12  .where(gt('created', oneDayAgo))
13  .where(gt('priority', 3))
14  .fetch()

2. Batch Operations

typescriptExample
1// โŒ Bad: Multiple individual creates
2for (const item of items) {
3  await walletClient.createEntity(item) // Slow!
4}
5
6// โœ… Good: Single batch create
7const { entityKeys } = await walletClient.batchCreateEntities(items) // Fast!

3. Choose Appropriate expiresIn

typescriptExample
1// Match data lifetime to use case using ExpirationTime helper
2const sessionData = { expiresIn: ExpirationTime.fromMinutes(30) }  // 30 min for sessions
3const cacheData = { expiresIn: ExpirationTime.fromHours(1) }       // 1 hour for cache
4const tempFiles = { expiresIn: ExpirationTime.fromHours(24) }      // 24 hours for temp files
5const weeklyData = { expiresIn: ExpirationTime.fromDays(7) }       // 7 days for weekly data
6
7// Don't over-allocate storage time

4. Optimize Attributes

typescriptExample
1// โœ… Good: Use proper types for attributes
2{ key: 'priority', value: 5 }  // Numeric - can use gt(), lt() operators
3
4// โŒ Bad: Using string for numbers
5{ key: 'priority', value: '5' }  // String - only eq() checks
6
7// Numeric attributes enable range queries
8const query = publicClient.buildQuery()
9const highPriority = await query
10  .where(gt('priority', 3))  // Works with numeric values
11  .fetch()

Security Best Practices

Ensure your applications follow security best practices.

1. Never Expose Private Keys

typescriptExample
1// โœ… Good: Use environment variables
2const privateKey = process.env.PRIVATE_KEY
3
4// โŒ Bad: Hardcoded keys
5const privateKey = "0x1234..." // NEVER DO THIS!

2. Validate User Input

typescriptExample
1// โœ… Good: Sanitize and validate
2function createNote(userInput: string) {
3  // Validate input
4  if (!userInput || userInput.length > 10000) {
5    throw new Error("Invalid input")
6  }
7
8  // Sanitize
9  const sanitized = userInput.trim()
10
11  return walletClient.createEntity({
12    payload: stringToPayload(sanitized),
13    contentType: 'text/plain',
14    attributes: [{ key: 'type', value: 'note' }],
15    expiresIn: ExpirationTime.fromHours(12),
16  })
17}

3. Use Read-Only Clients for Queries

typescriptExample
1import { createPublicClient, http } from "@arkiv-network/sdk"
2import { kaolin } from "@arkiv-network/sdk/chains"
3import { eq } from "@arkiv-network/sdk/query"
4
5// For public data queries, use a public client
6// This prevents accidental writes and doesn't require private key
7const publicClient = createPublicClient({
8  chain: kaolin,
9  transport: http(),
10})
11
12// Safe for public use - query only, no write operations
13const query = publicClient.buildQuery()
14const publicData = await query
15  .where(eq('type', 'public'))
16  .fetch()

4. Implement Rate Limiting

typescriptExample
1// Prevent abuse with rate limiting
2class RateLimiter {
3  private requests = new Map<string, number[]>()
4
5  canMakeRequest(userId: string, maxRequests = 10, windowMs = 60000) {
6    const now = Date.now()
7    const userRequests = this.requests.get(userId) || []
8
9    // Remove old requests outside the window
10    const recentRequests = userRequests.filter(time => now - time < windowMs)
11
12    if (recentRequests.length >= maxRequests) {
13      return false
14    }
15
16    recentRequests.push(now)
17    this.requests.set(userId, recentRequests)
18    return true
19  }
20}
21
22const limiter = new RateLimiter()
23
24async function createEntity(userId: string, data: any) {
25  if (!limiter.canMakeRequest(userId)) {
26    throw new Error("Rate limit exceeded")
27  }
28
29  return walletClient.createEntity(data)
30}

Image & File Storage Guide

Store large files efficiently using chunking strategy.

typescriptExample
1import { createWalletClient, createPublicClient, http } from "@arkiv-network/sdk"
2import { privateKeyToAccount } from "@arkiv-network/sdk/accounts"
3import { kaolin } from "@arkiv-network/sdk/chains"
4import { ExpirationTime } from "@arkiv-network/sdk/utils"
5import { eq, and } from "@arkiv-network/sdk/query"
6import { randomUUID } from 'crypto'
7import * as fs from 'fs'
8
9const walletClient = createWalletClient({
10  chain: kaolin,
11  transport: http(),
12  account: privateKeyToAccount(process.env.PRIVATE_KEY),
13})
14
15const publicClient = createPublicClient({
16  chain: kaolin,
17  transport: http(),
18})
19
20// Chunk large files for efficient storage
21const CHUNK_SIZE = 64 * 1024 // 64KB chunks
22
23async function uploadFile(file: Buffer, fileName: string) {
24  const fileId = randomUUID()
25  const totalChunks = Math.ceil(file.length / CHUNK_SIZE)
26  const entities = []
27
28  // Split file into chunks
29  for (let i = 0; i < file.length; i += CHUNK_SIZE) {
30    const chunk = file.slice(i, i + CHUNK_SIZE)
31    const chunkIndex = Math.floor(i / CHUNK_SIZE)
32
33    entities.push({
34      payload: chunk,
35      contentType: 'application/octet-stream',
36      attributes: [
37        { key: 'type', value: 'file-chunk' },
38        { key: 'fileId', value: fileId },
39        { key: 'fileName', value: fileName },
40        { key: 'chunkIndex', value: chunkIndex },
41        { key: 'totalChunks', value: totalChunks }
42      ],
43      expiresIn: ExpirationTime.fromDays(7),
44    })
45  }
46
47  // Upload all chunks in batch
48  await walletClient.batchCreateEntities(entities)
49
50  return fileId
51}
52
53async function downloadFile(fileId: string): Promise<Buffer> {
54  // Query all chunks for this file
55  const query = publicClient.buildQuery()
56  const chunks = await query
57    .where(eq('type', 'file-chunk'))
58    .where(eq('fileId', fileId))
59    .withPayload(true)
60    .withAttributes(true)
61    .fetch()
62
63  // Sort by chunk index
64  chunks.sort((a, b) => {
65    const aIdx = a.attributes?.find(an => an.key === "chunkIndex")?.value || 0
66    const bIdx = b.attributes?.find(an => an.key === "chunkIndex")?.value || 0
67    return Number(aIdx) - Number(bIdx)
68  })
69
70  // Combine chunks
71  const buffers = chunks.map(chunk => Buffer.from(chunk.payload))
72  return Buffer.concat(buffers)
73}
74
75// Usage
76const imageBuffer = fs.readFileSync("photo.jpg")
77const fileId = await uploadFile(imageBuffer, "photo.jpg")
78console.log("Uploaded file:", fileId)
79
80// Later, download it
81const downloaded = await downloadFile(fileId)
82fs.writeFileSync("downloaded.jpg", downloaded)

Integration Examples

Next.js Integration

typescriptExample
1// pages/api/store.ts
2import { createWalletClient, http } from "@arkiv-network/sdk"
3import { privateKeyToAccount } from "@arkiv-network/sdk/accounts"
4import { kaolin } from "@arkiv-network/sdk/chains"
5import { ExpirationTime, jsonToPayload } from "@arkiv-network/sdk/utils"
6
7export default async function handler(req, res) {
8  if (req.method === 'POST') {
9    const walletClient = createWalletClient({
10      chain: kaolin,
11      transport: http(),
12      account: privateKeyToAccount(process.env.PRIVATE_KEY!),
13    })
14
15    const { entityKey, txHash } = await walletClient.createEntity({
16      payload: jsonToPayload(req.body),
17      contentType: 'application/json',
18      attributes: [{ key: 'type', value: 'api-data' }],
19      expiresIn: ExpirationTime.fromHours(24),
20    })
21
22    res.json({ entityKey, txHash })
23  }
24}

Express.js Integration

typescriptExample
1import express from 'express'
2import { createWalletClient, http } from "@arkiv-network/sdk"
3import { privateKeyToAccount } from "@arkiv-network/sdk/accounts"
4import { kaolin } from "@arkiv-network/sdk/chains"
5import { ExpirationTime, jsonToPayload } from "@arkiv-network/sdk/utils"
6
7const app = express()
8app.use(express.json())
9
10// Initialize wallet client once
11const walletClient = createWalletClient({
12  chain: kaolin,
13  transport: http(),
14  account: privateKeyToAccount(process.env.PRIVATE_KEY!),
15})
16
17app.post('/store', async (req, res) => {
18  try {
19    const { entityKey, txHash } = await walletClient.createEntity({
20      payload: jsonToPayload(req.body),
21      contentType: 'application/json',
22      attributes: [{ key: 'type', value: 'api-data' }],
23      expiresIn: ExpirationTime.fromHours(24),
24    })
25    res.json({ entityKey, txHash })
26  } catch (error) {
27    res.status(500).json({ error: error.message })
28  }
29})
30
31app.listen(3000, () => console.log('Server running on port 3000'))

React Hook (Backend Only)

typescriptExample
1import { useState, useEffect } from 'react'
2import { createPublicClient, http } from "@arkiv-network/sdk"
3import { kaolin } from "@arkiv-network/sdk/chains"
4import { ExpirationTime, jsonToPayload } from "@arkiv-network/sdk/utils"
5
6export function useArkiv() {
7  const [publicClient, setPublicClient] = useState(null)
8  const [isReady, setIsReady] = useState(false)
9
10  useEffect(() => {
11    // Initialize public client for queries
12    const client = createPublicClient({
13      chain: kaolin,
14      transport: http(),
15    })
16    setPublicClient(client)
17    setIsReady(true)
18  }, [])
19
20  const store = async (data: any, expiresInHours: number = 24) => {
21    // Write operations should go through your backend API
22    const response = await fetch('/api/store', {
23      method: 'POST',
24      headers: { 'Content-Type': 'application/json' },
25      body: JSON.stringify({
26        data,
27        expiresInHours
28      })
29    })
30    const { entityKey } = await response.json()
31    return entityKey
32  }
33
34  const get = async (entityKey: string) => {
35    if (!publicClient) throw new Error('Arkiv client not ready')
36    const entity = await publicClient.getEntity(entityKey)
37    return JSON.parse(payloadToString(entity.payload))
38  }
39
40  return { store, get, publicClient, isReady }
41}
42
43// Backend API route: /api/store
44// See Next.js Integration example above

Testing & Development

Unit Testing with Jest

Write comprehensive tests for your Arkiv applications.

typescriptExample
1// __tests__/arkiv.test.ts
2import { createWalletClient, createPublicClient, http } from "@arkiv-network/sdk"
3import { privateKeyToAccount } from "@arkiv-network/sdk/accounts"
4import { kaolin } from "@arkiv-network/sdk/chains"
5import { ExpirationTime } from "@arkiv-network/sdk/utils"
6import { eq, gt } from "@arkiv-network/sdk/query"
7
8describe('Arkiv SDK', () => {
9  let walletClient: any
10  let publicClient: any
11
12  beforeAll(async () => {
13    walletClient = createWalletClient({
14      chain: kaolin,
15      transport: http(),
16      account: privateKeyToAccount(process.env.TEST_PRIVATE_KEY!),
17    })
18
19    publicClient = createPublicClient({
20      chain: kaolin,
21      transport: http(),
22    })
23  })
24
25  test('should create and retrieve entity', async () => {
26    const testData = "Test data for Jest"
27
28    // Create entity
29    const { entityKey, txHash } = await walletClient.createEntity({
30      payload: stringToPayload(testData),
31      contentType: 'text/plain',
32      attributes: [{ key: 'type', value: 'test' }],
33      expiresIn: ExpirationTime.fromHours(1),
34    })
35
36    expect(entityKey).toBeDefined()
37    expect(txHash).toBeDefined()
38
39    // Retrieve entity
40    const entity = await publicClient.getEntity(entityKey)
41    const retrieved = payloadToString(entity.payload)
42
43    expect(retrieved).toBe(testData)
44  })
45
46  test('should query entities by attributes', async () => {
47    // Create test entities
48    const { entityKeys } = await walletClient.batchCreateEntities([
49      {
50        payload: stringToPayload("Test 1"),
51        contentType: 'text/plain',
52        attributes: [
53          { key: 'type', value: 'test-query' },
54          { key: 'priority', value: 5 }
55        ],
56        expiresIn: ExpirationTime.fromHours(1),
57      },
58      {
59        payload: stringToPayload("Test 2"),
60        contentType: 'text/plain',
61        attributes: [
62          { key: 'type', value: 'test-query' },
63          { key: 'priority', value: 3 }
64        ],
65        expiresIn: ExpirationTime.fromHours(1),
66      }
67    ])
68
69    // Query high priority items
70    const query = publicClient.buildQuery()
71    const results = await query
72      .where(eq('type', 'test-query'))
73      .where(gt('priority', 4))
74      .fetch()
75
76    expect(results.length).toBeGreaterThanOrEqual(1)
77  })
78})

Deployment Strategies

Best practices for deploying Arkiv applications to production.

1. Environment Configuration

bashExample
1# .env.production
2PRIVATE_KEY=${VAULT_PRIVATE_KEY}
3ARKIV_CHAIN_ID=60138453025
4ARKIV_RPC_URL=https://kaolin.hoodi.arkiv.network/rpc
5ARKIV_WS_URL=wss://kaolin.hoodi.arkiv.network/rpc/ws
6NODE_ENV=production

2. Docker Deployment

dockerfileExample
1# Dockerfile
2FROM oven/bun:1-alpine
3
4WORKDIR /app
5
6COPY package.json bun.lockb ./
7RUN bun install --production
8
9COPY . .
10
11ENV NODE_ENV=production
12
13CMD ["bun", "run", "dist/index.js"]

3. Health Checks

typescriptExample
1// health.ts
2import { createPublicClient, http } from "@arkiv-network/sdk"
3import { kaolin } from "@arkiv-network/sdk/chains"
4import { eq } from "@arkiv-network/sdk/query"
5
6export async function healthCheck() {
7  try {
8    const publicClient = createPublicClient({
9      chain: kaolin,
10      transport: http(),
11    })
12
13    // Try a simple query
14    const query = publicClient.buildQuery()
15    await query
16      .where(eq('type', 'health-check'))
17      .limit(1)
18      .fetch()
19
20    return { status: 'healthy', timestamp: Date.now() }
21  } catch (error) {
22    return { status: 'unhealthy', error: error.message }
23  }
24}

4. Monitoring & Logging

typescriptExample
1import winston from 'winston'
2
3const logger = winston.createLogger({
4  level: 'info',
5  format: winston.format.json(),
6  transports: [
7    new winston.transports.File({ filename: 'error.log', level: 'error' }),
8    new winston.transports.File({ filename: 'combined.log' })
9  ]
10})
11
12// Log all Arkiv operations
13async function createEntityWithLogging(data: any) {
14  try {
15    logger.info('Creating entity', { data })
16    const { entityKey, txHash } = await walletClient.createEntity(data)
17    logger.info('Entity created', { entityKey, txHash })
18    return { entityKey, txHash }
19  } catch (error) {
20    logger.error('Failed to create entity', { error, data })
21    throw error
22  }
23}