npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@livo-build/runtime

v0.2.21

Published

Livo runtime — chain signing/reads, D1 state, and logging for keepers, servers, and bots.

Readme

@livo-build/runtime

The standard library for Livo compute targets that bundle npm deps at deploy — keepers (cron Workers) and bots (Telegram/Twitter Workers). (Servers / Durable Objects deploy as a single un-bundled module today, so they can't import this yet — use fetch + Web Crypto there; their INDEXER_<NAME>_URL env binding is still injected.)

It deletes the most-repeated, most-error-prone code these targets hand-roll: signing and sending on-chain transactions, reading contract state, querying the project's subgraph, the Telegram webhook dance, talking to D1, and basic logging/retry. No more hand-bundling secp256k1 to fit a single Worker module.

At a glance: Chain (read/sign/send + RPC failover), Relayer (managed bot signing), Wallet (per-user custodial wallets, server-side custody), bindContracts (call contracts by name), Indexer (typed subgraph queries), Telegram (webhook plumbing in one call), Store (D1 KV + idempotency), verifyMessage (prove wallet ownership), requireSecret, queue, retry, log.

import { Chain, Store, log } from "@livo-build/runtime";

export default {
  async scheduled(event, env, ctx) {
    const chain = new Chain(env); // reads RPC_URL + KEEPER_PRIVATE_KEY from env
    const count = await chain.call(FORUM, "topicCount()(uint256)");
    const hash = await chain.send({ address: FORUM, abi: FORUM_ABI, functionName: "createPost", args: [count, "gm"] });
    const receipt = await chain.waitForReceipt(hash);
    log.info("posted", { hash, status: receipt.status });
  },
};

Delivery model — explicit import, registry-installed, bundled at deploy

You write normal imported code that is locally runnable and type-checked. The Livo deploy step produces the self-contained single-module Worker the platform requires. There are no ambient globals — what you read in the source is what runs.

@livo-build/runtime is published to npm (like @livo-build/livo). A keeper/bot/server that imports it ships a package.json declaring the dependency:

  • On deploy, the presence of a package.json with deps routes the target through the platform's existing build container (npm install + esbuild), which resolves @livo-build/runtime from the registry and bundles it — plus its audited @noble crypto — into one self-contained Worker module. No hand-bundling.
  • Targets with no package.json deploy exactly as before — the build step is opt-in by the presence of a manifest. Existing keepers keep working untouched.
  • Versions pin in package.json ("@livo-build/runtime": "^0.2.3"), so a runtime update can't silently change a working keeper.

Scaffold the common path with create_keeper --template runtime-keeper.

Layer 1 — Chain

Reads need no key; writes need a signer key on env.

const chain = new Chain(env, {
  rpcUrlSecret: "RPC_URL",               // default
  signerKeySecret: "KEEPER_PRIVATE_KEY", // omit for read-only
});

// reads
await chain.call(address, "balanceOf(address)(uint256)", [who]);
await chain.readContract({ address, abi, functionName, args });
await chain.getLogs({ address, event: "Transfer(address indexed from, address indexed to, uint256 value)", args: { from }, fromBlock });
await chain.getBalance(addr);
chain.address; // signer address (null when read-only)

// writes — encode → nonce(pending) → fees → estimateGas(+20%) → sign EIP-1559 → broadcast
const hash = await chain.send({ address, abi, functionName, args });
const receipt = await chain.waitForReceipt(hash, { timeoutMs, pollMs });

// reliable write — nonce resolved at broadcast, retry-on-collision + replace-by-fee
const h = await chain.sendReliable({ address, abi, functionName, args, confirmMs: 30_000 });

// batch reads — ONE eth_call via Multicall3 (allowFailure defaults true → null on revert)
const [supply, mine] = await chain.multicall([
  { address: token, abi, functionName: "totalSupply" },
  { address: token, abi, functionName: "balanceOf", args: [chain.address] },
]);

// deploy a contract — to:null creation, waits for the receipt's contractAddress
const { address: deployed } = await chain.deploy({ abi, bytecode, args: [param] });

// escape hatches
chain.encodeFunction(abi, fn, args); // 0x calldata
chain.signTx(txFields);              // raw 0x EIP-1559 tx
chain.rpc(method, params);           // raw JSON-RPC

Defaults (configurable): nonce pending; tip = 1.5 gwei; maxFee = baseFee*2 + tip; estimateGas * 1.2 with a 500_000 fallback when the node reverts the estimate. A funding-less signer fails loudly: "signer wallet 0x… can't afford this tx — needs ~N wei, has M wei."

Encoding and signing are validated byte-for-byte against viem in CI (test/abi.test.ts, test/tx.test.ts). EIP-1559 (type-2) only for v1.

Layer 2 — Contract bindings

sync_contract_bindings emits a pure-data bindings module — keepers/_livo/contracts.js — from the same canonical pointer (pickCanonical) the frontend's interface/src/livo/contracts.ts uses, so runtime and UI can never watch different deployments. Feed it to bindContracts for name-addressable, typed contract handles:

import { Chain, bindContracts } from "@livo-build/runtime";
import { addresses, abis } from "../_livo/contracts.js"; // generated, canonical

const contracts = bindContracts(addresses, abis);
const forum = contracts.Forum.connect(chain); // address resolved from the canonical registry
await forum.read.topicCount();
const hash = await forum.write.createPost(topicId, body);

(The module is pure data and dependency-free so it resolves in the deploy build container without the keeper's node_modules. @livo-build/runtime/contracts exports bindContracts and the binding types.)

RPC failover

Chain accepts several endpoints and rotates past transport failures (network error, 5xx, 429) — a deterministic chain error (revert/nonce) is surfaced, not retried elsewhere:

new Chain(env, { rpcUrls: ["https://a", "https://b"] });
new Chain(env); // or set RPC_URL to a comma/whitespace-separated list

Indexer — query the project's subgraph

INDEXER_<NAME>_URL is injected at deploy as the subgraph's stable live alias, so the worker never holds a version-pinned URL that breaks on the next sync_indexers/reindex:

import { indexer } from "@livo-build/runtime";
const { keepers } = await indexer(env, "hearth").query(
  `query($m:Int!){ keepers(where:{count_gte:$m}){ id count } }`, { m: 3 });
await indexer(env, "hearth").meta(); // { block, hasIndexingErrors }

Telegram — webhook plumbing in one call

import { Telegram } from "@livo-build/runtime";
export default {
  fetch(req, env) {
    return new Telegram(env).handleUpdate(req, (msg) =>
      msg.text === "/start" ? "👋 hi " + msg.handle : "you said " + msg.text);
  },
};

handleUpdate verifies the WEBHOOK_SECRET secret-token, short-circuits Livo's __livo_test harness (returns the computed reply without calling Telegram), parses the update, and sends the reply. Lower-level verify / parse / sendMessage / setMyCommands are exposed too.

Layer 3 — State + utilities

const store = new Store(env.DB);     // typed KV over D1; auto-creates `livo_kv`
await store.get("heartbeat");        // string | null
await store.set("heartbeat", "3");
await store.getJSON<Config>("config");
await store.setJSON("config", obj);

// Idempotency: true only the FIRST time a key is seen — a retried cron tick or a
// redelivered webhook won't double-fire.
if (!(await store.once(`block:${n}`))) return;

log.info("posted", { tx, topicId }); // structured, consistently prefixed
log.error("send failed", err);

import { requireSecret, retry } from "@livo-build/runtime";
const apiKey = requireSecret(env, "SOME_API_KEY"); // loud error if missing, not silent undefined
await retry(() => chain.send(...), { tries: 3, backoffMs: 500 }); // flaky RPCs

Store replaces the hand-rolled CREATE TABLE IF NOT EXISTS kv (k,v). The table is livo_kv(k TEXT PRIMARY KEY, v TEXT, updated_at INTEGER) — stable and documented.

Watching events — watchLogs

A Worker has no long-lived process, so this is catch-up per scheduled tick, not a websocket subscription. It reads new logs since a Store-persisted block cursor, up to the (confirmed) head, in chunked eth_getLogs windows:

import { watchLogs } from "@livo-build/runtime";

await watchLogs(chain, store, {
  event: "Transfer(address indexed from, address indexed to, uint256 value)",
  address: token,                 // optional filter
  cursorKey: "transfers",         // namespaced Store cursor
  confirmations: 2n,              // reorg safety (default 0)
  chunkSize: 2000n,               // match your RPC's eth_getLogs range cap
  onLogs: async (logs, range) => { /* idempotent! at-least-once delivery */ },
});

The cursor advances after each successful onLogs, so a throw retries that window on the next tick — onLogs must be idempotent. First run defaults to "from head" (new logs only); pass fromBlock to backfill.

Bots — Relayer

Relayer extends Chain: same read/write/multicall surface, but it signs with the custodied RELAYER_PRIVATE_KEY (not the keeper key) and — when the platform injects RELAYER_NONCE_URL + RELAYER_NONCE_TOKEN — serializes nonces through Convex so concurrent bot invocations sharing one managed key don't collide (falling back to RPC pending otherwise).

import { Relayer, log } from "@livo-build/runtime";
const relayer = new Relayer(env);                 // RELAYER_PRIVATE_KEY
const hash = await relayer.send({ address, abi, functionName, args });

Bots — Wallet (per-user custodial wallets)

Wallet gives each end user their own wallet(s). The bot signs on a user's behalf without ever holding key material: every call hits the platform (WALLET_API_URL + WALLET_API_TOKEN, auto-injected), which custodies a per-(project, user) seed and signs server-side. Generated wallets derive from the user's own seed (isolated from the project's platform mnemonic and from other users); imported wallets store an encrypted raw key. This is the sanctioned wallet primitive — don't hand-roll key storage. Scaffold with create_bot --template wallet-bot (the default).

import { Wallet } from "@livo-build/runtime";
const acct = new Wallet(env).user(msg.from.id); // a handle for one end-user
await acct.address();                  // active wallet (auto-created on first call)
await acct.list();                     // [{ label, address, active, imported }]
await acct.create("trading");          // new HD wallet from the user's own seed
await acct.import("0x<key>", "main");  // bring your own key
await acct.use("trading");             // switch active
const hash = await acct.send({ address, abi, functionName, args }); // platform signs

Prove wallet ownership — verifyMessage

Verify a "prove you own this wallet" signature server-side (EIP-191 personal_sign, the basis of wallet-link / SIWE login) — no hand-rolled secp256k1. The pattern: a website has the user connect a wallet and personal_sign a nonce; a bot/keeper then verifies it and links the recovered address to the user's account.

import { verifyMessage, recoverMessageAddress } from "@livo-build/runtime";

// true iff `address` produced `signature` over `message` (never throws)
if (verifyMessage({ address, message: "Link my wallet · nonce: " + nonce, signature })) {
  // ownership proven — link `address` to the user
}
recoverMessageAddress(message, signature); // → the signer's checksummed address

Scaffold the full flow (bot serves the connect+sign page, verifies, links, and gates a private group; a keeper boots members who fall below) with create_bot --template wallet-link-bot.

Queues

Publish to a per-project queue from any deployable that declares produces: [name] (the platform injects QUEUE_<NAME>_URL + QUEUE_<NAME>_TOKEN):

import { queue } from "@livo-build/runtime";       // or new Queue(env, "settler")
await queue(env, "settler").send({ orderId });
await queue(env, "settler").sendBatch([{ a: 1 }, { a: 2 }]);

Local testing

Everything runs in Node with a mocked env — no platform involved. See test/keeper-harness.test.ts for the template: a fake JSON-RPC handler, an in-memory D1, and a real keeper handler driven end-to-end.

npm test            # vitest: viem-equivalence + keeper harness
npm run build       # tsc → dist/*.js + .d.ts (what npm consumers install)

Out of scope (v1)

Legacy pre-1559 txs, multi-chain abstraction beyond a chainId, account abstraction / paymasters, anything non-EVM. Added later only when a second real use case demands it.

Agent-facing copy of this reference lives at the MCP resource livo://skill/runtime; test/skill-coverage.test.ts fails CI if a public export here isn't documented there, so the two can't drift.