Skip to content

Store and query your first entity

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

In this tutorial you write a blog post to Arkiv and read it back with a query. It takes one TypeScript file and a test account with some test GLM.

Before you start, you need:

  • Node.js 22.10 or later, or Bun
  • The private key of a test account. Fund it with test GLM (tGLM) from the faucet
  • An access key. Arkiv’s public RPC is heavily rate limited to prevent abuse, so get a free key from the Arkiv Hub
  1. Create a folder with a package.json that marks it as an ES module:

    Terminal window
    mkdir arkiv-hello && cd arkiv-hello
    echo '{ "type": "module" }' > package.json
  2. Install the SDK and viem, which the SDK builds on:

    Terminal window
    bun add @arkiv-network/sdk viem
  3. Put your private key and access key in environment variables:

    Terminal window
    export PRIVATE_KEY=your_private_key_here
    export ARKIV_ACCESS_KEY=your_access_key_here

Create hello.ts. Start with a schema, which lists the attributes a post has and their types. The indexed() ones are the ones you can filter on:

hello.ts
import { addr, bytes, dec, defineSchema, str } from "@arkiv-network/sdk/schema"
const Posts = defineSchema("my-app.posts", {
author: addr().indexed(),
category: str().indexed(),
score: dec().indexed(),
title: str(),
body: bytes(),
summary: str().optional(),
})

The name tags every post you write, and a query through Posts only sees entities with that tag. Arkiv is a public database that other projects write to as well, so replace my-app with a name unique to your project. The rest of the docs use this schema.

Add two clients. The public client reads, and the wallet client signs and sends writes with your account:

hello.ts
import { createPublicClient, createWalletClient } from "@arkiv-network/sdk"
import { rabdi } from "@arkiv-network/sdk/chains"
import { http, type Hex } from "viem"
import { privateKeyToAccount } from "viem/accounts"
const chain = rabdi({ accessKey: process.env.ARKIV_ACCESS_KEY ?? "" })
const account = privateKeyToAccount(process.env.PRIVATE_KEY as Hex)
const publicClient = createPublicClient({ chain, transport: http() })
const walletClient = createWalletClient({ chain, transport: http(), account })

rabdi puts your access key in the RPC URL, so http() with no URL sends it. Without ARKIV_ACCESS_KEY, the chain uses the public RPC and logs a warning.

Use of the public RPC is subject to the Arkiv Terms of Use and Privacy Policy.

createEntity takes the schema, the post’s values and how long the post should live. It returns the new entity’s key:

hello.ts
import { ExpirationTime } from "@arkiv-network/sdk"
import { stringToBytes } from "viem"
const { entityKey } = await walletClient.createEntity(
Posts,
{
author: addr(account.address),
category: "intro",
score: dec("4.5"),
title: "Hello, Arkiv",
body: stringToBytes("My first entity."),
},
{ expiration: ExpirationTime.fromDays(1) },
)
console.log("Created", entityKey)

Each value has to match its column. "intro" and "Hello, Arkiv" are strings, so they’re str values as they are. A dec or an addr has no plain JavaScript form, so you wrap those in dec(...) and addr(...). summary is optional, so you can leave it out.

Select the fields you want, name the schema and keep only the posts your account owns:

hello.ts
import { hexToString } from "viem"
const { entities } = await publicClient
.select({ $key: true, title: true, body: true })
.from(Posts)
.ownedBy(account.address)
.fetch()
for (const post of entities) {
console.log(post.$key, post.title.value, hexToString(post.body.value))
}

Anyone can write an entity with your schema’s tag, so ownedBy keeps out posts your account doesn’t own. Each row holds the fields you selected. $key is the entity’s key, and every name without a $ is a column. A column comes back as a typed value, so you read it through .value.

Terminal window
bun hello.ts

The script prints the key of the new post, then the post itself:

Created 0x5f1e…
0x5f1e… Hello, Arkiv My first entity.

If the create fails because of insufficient funds, the account has no tGLM yet. Get some from the faucet and run the script again.