Testnet only (Arkiv Tiramisu). OpenTrigger 0.1 has not had an outside audit.
OpenTrigger

Read and watch

List rules and receipts, read a rule's audit trail, follow workers, and get live updates over one WebSocket.

Reading needs no wallet: createTriggers() with no options can read everything.

Rules and their status

const rules = await triggers.listRules({ creator: myAddress })
// each: rule fields + status ('active' | 'fired' | 'failed') + deadline (block) + receipts

A rule with an ok or skipped receipt is fired; one with only failed receipts is failed; anything else is active. deadline is the block it fires at (for expire rules, from the target's current expiry).

One rule's audit trail

const trail = await triggers.getRule(key)
// { rule, status, deadline, targetExpiresAt, claims, receipts }

claims are the live 60-second leases (a worker is firing it right now); receipts are what happened.

Receipts

await triggers.listReceipts({ creator: myAddress })   // every receipt for your rules, newest first
await triggers.listReceipts({ rule: key })            // one rule
await triggers.listReceipts({ creator, sinceBlock })  // only newer ones

Workers

const workers = await triggers.listWorkers()
// worker, online, mode, actions, lastBlock, version, sealKey, operator, operatorVerified

Live updates

const stop = triggers.watch({
  creator: myAddress,
  onReceipt: (r) => {},   // a worker fired one of your rules
  onRule: (r) => {},      // you created a rule (from any app)
  onHeartbeat: (w) => {}, // a worker appeared or renewed its heartbeat
  onStatus: (s) => {},    // 'live' | 'reconnecting'
  minIntervalMs: 8000,
})
// later: stop()

watch opens one WebSocket. Arkiv's event feed is unfiltered and carries no attributes, so each burst of events triggers a few small filtered queries ("receipts since the last one I saw") instead of fetching every new entity. It reconnects by itself, once per drop, and re-reads on every reconnect so nothing is missed.

Times and blocks

const clock = await triggers.clock()            // { block, timeMs, blockSeconds }
const date = await triggers.toDate(rule.deadline)
const block = await triggers.toBlock(new Date())

The pure toDate(block, clock) and toBlock(date, clock) exports do the same without a network call, which is handy for countdowns.

Reading from a browser

A browser cannot hold an access key, so it reads on Arkiv's anonymous quota. Two habits keep a page light:

  • Create the public client with http(url, { retryCount: 0 }). With retries, a rate-limited read waits out Retry-After and looks frozen; without them it fails in about a second with HttpRequestError (details: "ANON_COST_LIMITED"), and you can say so and retry later.
  • Use minIntervalMs of 5 to 10 seconds on watch, and poll the clock rarely (count blocks locally).
import { createPublicClient } from '@arkiv-network/sdk'
import { tiramisu } from '@arkiv-network/sdk/chains'
import { http } from 'viem'

const publicClient = createPublicClient({ chain: tiramisu, transport: http(undefined, { retryCount: 0 }) })
const triggers = createTriggers({ publicClient })

See Access keys and quota for a server-side relay that adds a key only when a browser is rate-limited.

On this page