Skip to content
Agentmart
Submit a tool

arkiv-first-write

Set up @arkiv-network/sdk on a verified Arkiv testnet and make a first entity write with funding, safe key custody, explicit expiration, seed reconciliation…

Skill by Arkiv-Network

IndexedMIT★ 1
Write a review

Install

Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.

npx agentmarket install skill/arkiv-network-arkiv-first-write

Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

Set up @arkiv-network/sdk on a verified Arkiv testnet and make a first entity write with funding, safe key custody, explicit expiration, seed reconciliation and query read-back. Tiramisu is the default. Use for a first write, quickstart or minimal SDK example; use arkiv-app-integration for authenticated application endpoints.

Use cases: writing

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

Permissions

What this skill can do and what it asks for, from its SKILL.md. Checked Oct 25, 2026.

Tools
No tools found
Secrets
None asked for
Install scripts
None found

Network hosts it talks to 1

  • codeload.github.com · found in install command

Files it touches

  • ~/.claude/skills/arkiv-first-write

What's inside

Skill name
arkiv-first-write
Description
Set up @arkiv-network/sdk on a verified Arkiv testnet and make a first entity write with funding, safe key custody, explicit expiration, seed reconciliation and query read-back. Tiramisu is the default. Use for a first write, quickstart or minimal SDK example; use arkiv-app-integration for authenticated application endpoints.
License
MIT
Metadata
network: tiramisu · verified: 2026-10-08 · arkiv-sdk: >=0.8.1 <0.9
Instructions
188 lines in SKILL.md · view on GitHub

Before installing

  • Instructions call the network

Files (3)

  • SKILL.md
  • estimate.md · references/
  • testing.md · references/

SKILL.md (first lines)

# First Arkiv write

Use `arkiv` for orientation. This skill produces one verified entity, not a production signing API. Read `arkiv-write-safety` before bulk imports or recovery from an ambiguous submission.

## Prepare the project

Before producing executable SDK code, observe the project's installed SDK version and inspect its installed declarations. If either is unavailable, request that evidence or approval to obtain it before giving executable SDK code; an "unverified" label does not satisfy this gate. Recommend SDK `0.8.1` exactly or state the supported range literally as `>=0.8.1 <0.9`; never describe `0.8.x` as a compatibility guarantee. If the project uses SDK `0.8.0`, stop integration and ask for approval to upgrade, without forcing or bypassing peers. Report only files you actually inspected.

For an explanation-only request, still identify the current API shapes: object-valued `attributes` and `expires` constructed with `ExpirationTime`, such as `ExpirationTime.fromDays(1)`. Distinguish that relative input from the entity's resulting absolute `expiresAt` block. Describe awaiting the create result, checking its receipt and reading the entity back. These explanations do not claim installed-declaration inspection or authorize execution.

Inspect the existing package manager and installed SDK declarations. Explain a dependency installation before running it. For a new npm project:

```bash
npm install @arkiv-network/sdk@^0.8.1 viem
```

Configure `ARKIV_PRIVATE_KEY` locally in a server-only environment. Never request its value in chat. Use a dedicated development EOA, not a production account. An access key, if needed, goes in a server-side `X-API-KEY` header bound to the official RPC origin; it does not fund transactions. The helpers below reject access keys for other origins and refuse redirects.

Announce **Tiramisu, chain 7738577**, and the proposed entity before sending. A live write spends test GLM: get authorization for that write or a bounded smoke-test budget. Check the balance and estimate the actual transaction; a positive balance alone does not establish enough gas. Use the [Hub faucet](https://hub.arkiv.network/faucet) without promising an amount or cooldown. If spending is not authorized, stop at preparation and estimation.

For another Arkiv chain, announce its verified name, chain ID and RPC instead. SDK 0.8.1 exports Tiramisu and localhost; an RPC URL alone does not change a client's chain. Build a viem `Chain` from verified network coordinates and pass it explicitly to the helper and the [dry run](references/estimate.md). Check funding on that chain. Never rename Tiramisu or invent an SDK chain export.

SDK 0.8.1 has no public create-estimator method. Use the guarded [dry run](references/estimate.md) with the same create parameters: it captures the SDK's gas estimate and blocks every broadcast. It is not a live write/read-back test.

## Create, reconcile a seed, and read back

The following server-only helper assumes the write has been authorized. Pass a locally loaded `LocalAccount` from viem's `privateKeyToAccount`; validate that the configured value is a 32-byte hex key without logging it. No environment secret is read at import time.

Use an exclusive writer for this creator while running the seed. Query-before-create prevents routine repeated runs; it is **not atomic uniqueness** across processes. A reset chain or expired seed means a new key, so rebuild references. This example makes its note readonly; its owner can still delete, transfer or extend it.

Save the helper as `first-note.ts` and the [guarded estimator](references/estimate.md) as `estimate-create.ts`. Import their exports in your server entrypoint and call `ensureFirstNote(account, rpcUrl, chain, { accessKey, estimateGas: data => estimateCreate(account, data, rpcUrl, chain, accessKey) })`. The callback estimates the same frozen parameters without broadcasting. A fresh seed fails closed if it is missing; seed reuse needs no estimator.

```typescript
import {
  createPublicClient, createWalletClient, EntityMutationError,
  ExpirationTime, jsonToPayload, type CreateEntityParameters,
} from "@arkiv-network/sdk";
import { key, u64 } from "@arkiv-network/sdk/attr";
import { tiramisu } from "@arkiv-network/sdk/chains";
import { eq } from "@arkiv-network/sdk/query";
import { http, type Chain, type LocalAccount, type TransactionReceipt } from "viem";
import { estimateFeesPerGas } from "viem/actions";

type FirstWriteOptions = {
  accessKey?: string;
  estimateGas?: (data: CreateEntityParameters) => Promise<bigint>;
};

export function firstNoteParameters(): CreateEntityParameters {
  return {
    payload: jsonToPayload({ text: "Hello, Arkiv" }), contentType: "application/json",
    attributes: {
      project: "first_note", entity_type: "note", seed_id: "welcome_v1",
      created_at: u64(Date.now()),
    },
    expires: ExpirationTime.fromDays(1),
    flags: { readonly: true, permissionlessExtension: false },
  };
}

export async function ensureFirstNote(
  account: LocalAccount,
  rpcUrl: string = tiramisu.rpcUrls.default.http[0],
  chain: Chain = tiramisu,
  options: FirstWriteOptions = {},
) {
  if (options.accessKey && new URL(rpcUrl).origin !== new URL(tiramisu.rpcUrls.default.http[0]).origin) {
    throw new Error("Access key origin mismatch");
  }
  const fetchOptions = { cache: "no-store" as const, redirect: "error" as const,
    ...(options.accessKey ? { headers: { "X-API-KEY": options.accessKey } } : {}) };
  const publicClient = createPublicClient({
    chain, cacheTime: 0,
    transport: http(rpcUrl, { fetchOptions, retryCount: 0, timeout: 10_000 }),
  });
  if (await publicClient.getChainId() !== chain.id) throw new Error("Wrong chain");
  const expected = jsonToPayload({ text: "Hello, Arkiv" });
  const matches = (payload: Uint8Array) =>
    payload.length === expected.length && payload.every((value, i) => value === expected[i]);
  const prior = await publicClient
    .select({ key: true, payload: true, expiresAt: true, creationFlags: true,
      contentType: true, attributes: true })
    .where(eq("project", "first_note"), eq("entity_type", "note"), eq("seed_id", "welcome_v1"))
    .createdBy(account.address)
    .atBlock(await publicClient.getBlockNumber({ cacheTime: 0 }))
    .limit(2)
    .fetch();
  if (prior.entities.length > 1 || prior.hasNextPage()) throw new Error("Duplicate seed: reconcile");
  const existing = prior.entities[0];
  if (existing) {
    if (!existing.creationFlags.readonly || existing.creationFlags.permissionlessExtension
        || existing.contentType !== "application/json" || !matches(existing.payload)
        || existing.attributes.created_at?.type !== "u64") {
      throw new Error("Seed differs: reconcile instead of overwriting");
    }
    return { entityKey: existing.key, expiresAt: existing.expiresAt.toString(), reused: true };
  }
  const balance = await publicClient.getBalance({ address: account.address });
  if (balance === 0n) {
    throw new Error("Fund the signer before writing");
  }
  if (typeof options.estimateGas !== "function") throw new Error("Configure guarded estimateCreate before writing");
  const parameters = firstNoteParameters();
  const estimated = await options.estimateGas(parameters);
  if (typeof estimated !== "bigint" || estimated <= 0n) throw new Error("Invalid gas estimate");
  const gas = (estimated * 12n + 9n) / 10n; // Round up this helper's 20% gas margin.
  const fees = await estimateFeesPerGas(publicClient);
  if (balance < gas * fees.maxFeePerGas) throw new Error("Insufficient funds for estimated gas and fee cap");
  const walletClient = createWalletClient({
    account, chain, cacheTime: 0,
    transport: http(rpcUrl, { fetchOptions, retryCount: 0, timeout: 10_000 }),
  });
  let created;
  try {
    // createEntity returns { entityKey, txHash, expiresAt }, without a receipt.
    created = await walletClient.createEntity(parameters, { gas, ...fees });
  } catch (error) {
    if (error instanceof EntityMutationError && error.txHash) {
      // This may be a revert, a receipt-decode failure, or a timeout with the tx still pending.

From Arkiv-Network/skills, MIT.

More from Arkiv-Network/skills

Signals

Stars
1
Forks
1
Open issues
9
Last commit (this folder)
Oct 16, 2026 (13 days ago)

From GitHub (fetched Oct 24, 2026).

Details

License
MIT
First seen
Oct 24, 2026
Last checked
Oct 24, 2026

More from Arkiv-Network

Cite this page

arkiv-first-write skill. Agent Mart. https://agentmart.dev/skills/arkiv-network-arkiv-first-write (last updated 2026-10-24)