[ 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.
Sketch App with MetaMask
Build a sketching application that connects to MetaMask and stores drawings on the Arkiv blockchain using p5.js.
Quickstart Projects
1. Build a Decentralized Note-Taking App
Create a simple note-taking app with automatic expiration and attributes.
typescriptExample1import { 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.
typescriptExample1import { 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.
typescriptExample1import { 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
typescriptExample1// โ 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
typescriptExample1// โ 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
typescriptExample1// 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
typescriptExample1// โ 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
typescriptExample1// โ 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
typescriptExample1// โ 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
typescriptExample1import { 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
typescriptExample1// 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.
typescriptExample1import { 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
typescriptExample1// 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
typescriptExample1import 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)
typescriptExample1import { 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.
typescriptExample1// __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
bashExample1# .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
dockerfileExample1# 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
typescriptExample1// 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
typescriptExample1import 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}