@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 ethersor with yarn:
yarn add @kirobo/offchain-triggers ethersor with pnpm:
pnpm add @kirobo/offchain-triggers ethersDeno
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: 0x114e0011faa5b73076d891c6b6c015a927a2f8e4d2cf1544f43b836f6a5a77b1Deno 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); // trueNode.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 providedError- If any value cannot be converted to a valid uint256Error- If any value is negativeError- 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:
- First hash:
keccak256(abi.encodePacked(uint256(v0), uint256(v1))) - 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 installBuild
npm run buildTest
npm testLint
npm run lintFormat
npm run formatPublishing
Publish to npm
npm publishPublish to JSR (Deno registry)
npx jsr publishLicense
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.
