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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@kiroboio/offchain-triggers

v0.3.1

Published

Runtime-agnostic library for building chained uint256 hashes for Ethereum smart contracts

Readme

@kirobo/offchain-triggers

Runtime-agnostic library for building chained uint256 hashes for Ethereum smart contracts. Works seamlessly with Node.js, Deno, and Bun.

Features

  • Runtime agnostic (Node.js, Deno, Bun)
  • TypeScript support with full type definitions
  • Zero dependencies (ethers as peer dependency)
  • Comprehensive test coverage
  • ESM and CommonJS support
  • Published to both npm and JSR (Deno registry)

Installation

Node.js / Bun

npm install @kirobo/offchain-triggers ethers

or with yarn:

yarn add @kirobo/offchain-triggers ethers

or with pnpm:

pnpm add @kirobo/offchain-triggers ethers

Deno

import { buildChainedUint256Hash } from "jsr:@kirobo/offchain-triggers";

Usage

Basic Example

import { buildChainedUint256Hash } from "@kirobo/offchain-triggers";

// Build hash from array of uint256 values
const hash = buildChainedUint256Hash([1, 2, 3]);
console.log(hash);
// Output: 0x114e0011faa5b73076d891c6b6c015a927a2f8e4d2cf1544f43b836f6a5a77b1

Deno Edge Function Example

import { serve } from "https://deno.land/[email protected]/http/server.ts";
import { Wallet } from "npm:ethers@6";
import { buildChainedUint256Hash } from "jsr:@kirobo/offchain-triggers";

const PRIVATE_KEY = Deno.env.get("SIGNER_PRIVATE_KEY");
const wallet = new Wallet(PRIVATE_KEY);

serve(async (req) => {
  try {
    const body = await req.json();
    const values = body?.values;

    if (!Array.isArray(values) || values.length < 2) {
      return new Response("Invalid input", { status: 400 });
    }

    // Build the chained hash
    const hash = buildChainedUint256Hash(values);

    // Sign the hash
    const sig = wallet.signingKey.sign(hash);
    const { v, r, s } = sig;

    return new Response(
      JSON.stringify({
        hash,
        v,
        r,
        s,
        signerAddress: wallet.address,
      }),
      {
        status: 200,
        headers: { "Content-Type": "application/json" },
      }
    );
  } catch (err) {
    console.error(err);
    return new Response("Internal error", { status: 500 });
  }
});

Different Input Types

The function accepts bigint, string, or number values:

import { buildChainedUint256Hash } from "@kirobo/offchain-triggers";

// Using numbers
const hash1 = buildChainedUint256Hash([1, 2, 3]);

// Using bigints
const hash2 = buildChainedUint256Hash([1n, 2n, 3n]);

// Using strings
const hash3 = buildChainedUint256Hash(["1", "2", "3"]);

// Using hex strings
const hash4 = buildChainedUint256Hash(["0x1", "0x2", "0x3"]);

// Mixed types
const hash5 = buildChainedUint256Hash([1n, "2", 3, "0x4"]);

// All produce the same hash for equivalent values
console.log(hash1 === hash2); // true
console.log(hash2 === hash3); // true

Node.js Example

import { buildChainedUint256Hash } from "@kirobo/offchain-triggers";
import { Wallet } from "ethers";

async function signChainedHash(values: number[]) {
  const hash = buildChainedUint256Hash(values);
  const wallet = new Wallet(process.env.PRIVATE_KEY);
  const sig = wallet.signingKey.sign(hash);

  return {
    hash,
    signature: sig,
  };
}

const result = await signChainedHash([1, 2, 3, 4, 5]);
console.log(result);

Bun Example

import { buildChainedUint256Hash } from "@kirobo/offchain-triggers";

// Bun works exactly like Node.js
const hash = buildChainedUint256Hash([1, 2, 3]);
console.log(hash);

API

buildChainedUint256Hash(values: Uint256Value[]): string

Builds a chained hash from an array of uint256 values.

The hash is computed as follows:

h0 = keccak256(abi.encodePacked(v0, v1))
h1 = keccak256(abi.encodePacked(h0, v2))
h2 = keccak256(abi.encodePacked(h1, v3))
...

This matches Solidity's keccak256(abi.encodePacked(...)) behavior for chained hashing.

Parameters

  • values: Uint256Value[] - Array of at least 2 uint256 values (can be bigint, string, or number)

Returns

  • string - The final hash as a hex string (bytes32, 0x-prefixed)

Throws

  • Error - If less than 2 values are provided
  • Error - If any value cannot be converted to a valid uint256
  • Error - If any value is negative
  • Error - If any value exceeds uint256 max (2^256 - 1)

Type Definition

type Uint256Value = bigint | string | number;

How It Works

The library implements the same chained hashing algorithm used in Solidity:

  1. First hash: keccak256(abi.encodePacked(uint256(v0), uint256(v1)))
  2. Subsequent hashes: keccak256(abi.encodePacked(bytes32(h), uint256(vi)))

This creates a chain of hashes where each hash includes the previous hash and the next value, making it impossible to modify any value without invalidating the entire chain.

Solidity Verification

You can verify the hashes produced by this library match Solidity:

function verifyChainedHash(
    uint256[] memory values,
    bytes32 expectedHash
) public pure returns (bool) {
    require(values.length >= 2, "Need at least 2 values");

    bytes32 h = keccak256(abi.encodePacked(values[0], values[1]));

    for (uint256 i = 2; i < values.length; i++) {
        h = keccak256(abi.encodePacked(h, values[i]));
    }

    return h == expectedHash;
}

Development

Install Dependencies

npm install

Build

npm run build

Test

npm test

Lint

npm run lint

Format

npm run format

Publishing

Publish to npm

npm publish

Publish to JSR (Deno registry)

npx jsr publish

License

MIT

Contributing

Contributions are welcome! Please open an issue or submit a pull request.

Support

For issues and questions, please open an issue on GitHub.