Skip to content
Agentmart
Submit a tool

arkiv-query

Read Arkiv entities with the SDK select builder or raw arkiv_query JSON-RPC.

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-query

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

Read Arkiv entities with the SDK select builder or raw arkiv_query JSON-RPC. Use for typed predicates, escaping, creator scoping, all-page pagination, cursor recovery, filtered counts, historical block arguments and raw native-operation encoding for non-TypeScript clients. For an existing error string, use arkiv-troubleshooting first.

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-query

What's inside

Skill name
arkiv-query
Description
Read Arkiv entities with the SDK select builder or raw arkiv_query JSON-RPC. Use for typed predicates, escaping, creator scoping, all-page pagination, cursor recovery, filtered counts, historical block arguments and raw native-operation encoding for non-TypeScript clients. For an existing error string, use arkiv-troubleshooting first.
License
MIT
Metadata
network: tiramisu · verified: 2026-10-08 · arkiv-sdk: >=0.8.1 <0.9
Instructions
189 lines in SKILL.md · view on GitHub

Before installing

  • Instructions call the network

Files (2)

  • SKILL.md
  • json-rpc.md · references/

SKILL.md (first lines)

# Query a consistent set of Arkiv entities

## When to use

Use this skill to turn a known entity model into a read query. Use arkiv-data-modeling when field encodings or relationships are undecided, arkiv-security-trust for publisher/auth decisions, and arkiv-troubleshooting first when the task includes an existing error.

Inspect the installed SDK version and declarations. This guidance targets SDK 0.8.1 and Tiramisu; SDK exports and node capabilities differ. For Python, Rust or tools without this SDK, read [json-rpc.md](references/json-rpc.md) for the current wire protocol instead of adapting an older SDK API from memory.

## Rules that change the result

- `.fetch()` requires at least one filter: `.where(...)`, `.ownedBy(...)` or `.createdBy(...)`. Raw `arkiv_query` also accepts `*`; use a bounded, scoped query for application reads rather than scanning the shared database.
- `.where()` accumulates predicates with AND. Repeated `.ownedBy()` or `.createdBy()` replaces that respective filter. Use `or(...)` for alternatives.
- Pair every predicate with its write constructor. A bare number is `i32`, bigint is `u256`, string is `str`, boolean is `bool`. `u64` timestamps and expiry blocks must remain `u64` in queries.
- Millisecond timestamps such as `Date.now()` exceed the signed `i32` range (-2147483648 to 2147483647), even though they fit a JavaScript safe integer. SDK 0.8.1 rejects a bare timestamp predicate with `InvalidValueError` before sending a query; do not promise an empty result. Import `u64` from `@arkiv-network/sdk/attr` and preserve the stored type, for example `eq('created_at', u64(timestamp))`. Exact equality also requires the stored timestamp value, not a newly sampled clock value.
- A typed predicate matches only cells stored with that type. `gte('price_minor', u256(0n))` also excludes legacy `i32` prices; a bare-number predicate does the reverse. Do not replace this with unsupported `EXISTS` or `TYPEOF`.
- Use `.createdBy(trustedAddress)` or `$creator = addr(...)` for original publisher scope. `$owner` is current mutation authority and can change after transfer. `project` and `entity_type` are namespaces, not authentication.
- Pin a block from `getBlockNumber({ cacheTime: 0 })` with `.atBlock(snapshot)` for a complete walk. Preserve query, projection and snapshot across its opaque cursors. `page.next()` returns a new immutable page; reassign the variable. The previous page stays readable.
- Pages hold at most 200 entities; `.limit(n)` sets the size of each page, not the total matches across a walk. A short or full page does not prove completion: check `hasNextPage()`. Bound requests and restarts; return complete results or an explicit error, never silently treat a partial set as complete.
- A typed `QueryError` with `kind === 'cursor'` can restart the complete walk. Discard accumulated rows. For a current-state read, choose a fresh snapshot; for an explicitly historical read, keep its requested block. A block error must preserve that historical intent and surface the failure.
- Current Tiramisu returns matches newest first by creation block, with descending keys within a block. For the latest N by creation, a first page with `limit(N)` suffices when N is at most 200. There is no arbitrary attribute ordering or general aggregate API: fetch every page in scope and sort a copy for attribute-based top-N. A raw filtered count is available; SDK `getEntityCount()` is chain-wide.

## Worked all-page read with bounded recovery

The page/restart budgets below are application policies, not node limits. Configure a request timeout on the supplied transport. The provided client factory uses eight seconds and no transport retries; viem otherwise retries `-32005` as `LimitExceededRpcError`, delaying cursor recovery. The walker permits one cursor restart by default. It never retries parse, type, literal, block, quota or ordinary network errors. An optional access key stays server-side and is sent only to the factory's fixed official Tiramisu RPC; redirects fail closed.

```typescript
import { createPublicClient } from '@arkiv-network/sdk';
import { addr, bool, str, u256 } from '@arkiv-network/sdk/attr';
import { tiramisu } from '@arkiv-network/sdk/chains';
import { and, eq, gte, render, QueryError } from '@arkiv-network/sdk/query';
import { http, type Address } from 'viem';

type Public = ReturnType<typeof createPublicClient>;
type ReadPolicy = { maxPages?: number; maxRestarts?: number; historicalBlock?: bigint };

export function queryClient(accessKey?: string) {
  return createPublicClient({
    chain: tiramisu,
    cacheTime: 0,
    transport: http(tiramisu.rpcUrls.default.http[0], {
      timeout: 8_000,
      retryCount: 0,
      fetchOptions: { cache: 'no-store', redirect: 'error',
        ...(accessKey ? { headers: { 'X-API-KEY': accessKey } } : {}) },
    }),
  });
}

export function listingPredicates(creator: Address) {
  return [eq('project', str('example_marketplace')), eq('entity_type', str('listing')),
    eq('active', bool(true)), gte('price_minor', u256(0n)), eq('$creator', addr(creator))];
}

export function listingExpression(creator: Address) {
  return render(and(...listingPredicates(creator)));
}

export async function countListings(client: Public, creator: Address, snapshot: bigint) {
  if (snapshot < 0n || snapshot > BigInt(Number.MAX_SAFE_INTEGER)) throw new Error('Unsafe count block');
  // SDK 0.8.1's count schema exposes only empty params. Widen this one raw boundary.
  const request = client.request as unknown as (args: {
    method: 'arkiv_getEntityCount'; params: [{ query: string; block: number }];
  }) => Promise<unknown>;
  const count = await request({ method: 'arkiv_getEntityCount',
    params: [{ query: listingExpression(creator), block: Number(snapshot) }] });
  if (typeof count !== 'number' || !Number.isSafeInteger(count) || count < 0) {
    throw new Error('Invalid filtered count response');
  }
  return count;
}

export async function fetchAllListings(
  client: Public,
  creator: Address,
  { maxPages = 50, maxRestarts = 1, historicalBlock }: ReadPolicy = {},
) {
  if (!Number.isInteger(maxPages) || maxPages < 1) throw new Error('Invalid page budget');
  if (!Number.isInteger(maxRestarts) || maxRestarts < 0 || maxRestarts > 3) {
    throw new Error('Invalid cursor restart budget');
  }
  let requests = 0; // Total page requests across all attempts, including failed ones.
  for (let attempt = 0; ; attempt++) {
    const snapshot = historicalBlock ?? await client.getBlockNumber({ cacheTime: 0 });
    try {
      if (requests >= maxPages) throw new Error('Query page budget exhausted; discard incomplete results');
      requests++;
      let page = await client.select({ key: true, attributes: true })
        .where(...listingPredicates(creator)).atBlock(snapshot).limit(100).fetch();
      if (page.blockNumber !== snapshot) throw new Error('Query snapshot mismatch');
      const entities = [...page.entities];
      while (page.hasNextPage()) {
        if (requests >= maxPages) throw new Error('Query page budget exhausted; discard incomplete results');
        requests++;
        page = await page.next();
        if (page.blockNumber !== snapshot) throw new Error('Query snapshot mismatch');
        entities.push(...page.entities);
      }
      return { snapshot, entities };
    } catch (error) {
      if (!(error instanceof QueryError && error.kind === 'cursor')) throw error;
      if (attempt >= maxRestarts) throw new Error('Cursor restart budget exhausted', { cause: error });
      // A new attempt owns a new array; no rows from this partial walk escape.
    }
  }
}
```

If the budget is too small for the workload, fail and let the application choose a larger authorized budget or an incremental workflow that explicitly exposes incompleteness. Do not automatically fall back to an unscoped query.

Use the returned complete set for a global top-N within that scope. Compare exact typed values; do not convert large prices to JavaScript numbers:

```typescript
import { createPublicClient } from '@arkiv-network/sdk';

type ListingRow = {
  key: `0x${string}`;
  attributes: Awaited<ReturnType<ReturnType<typeof createPublicClient>['getEntity']>>['attributes'];
};

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 (12 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-query skill. Agent Mart. https://agentmart.dev/skills/arkiv-network-arkiv-query (last updated 2026-10-24)