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

@titon-network/fortuna-sdk

v0.2.0

Published

TypeScript SDK for Fortuna — TON-native threshold-BLS VRF. Request verifiable randomness, decode events, integrate as a consumer.

Readme

@titon-network/fortuna-sdk

TypeScript SDK for Fortuna — TON-native threshold-BLS VRF. One on-chain pairing per randomness, verified in TVM.

Use this SDK if you're building a contract that needs unbiasable randomness — gaming outcomes, NFT trait rolls, lottery payouts, shuffled queue order, any fair-pick problem. Fortuna runs an off-chain DKG'd operator network (staked through ForgeTON), assigns one aggregator per request, verifies the threshold-aggregated BLS signature on chain, and delivers a unique 256-bit output via a standard VrfCallback opcode.

Think Chainlink VRF, sovereign and TON-native.

npm install @titon-network/fortuna-sdk @ton/core

@ton/core is a peer dependency — bring your own version (>= 0.63.0).

Surfaces

┌─────────────────────────────────────────────────────────────┐
│ explainError   FortunaError   summarizeTx   formatTxSummary │  diagnostics
├─────────────────────────────────────────────────────────────┤
│ decodeEvent    decodeEvents    tryDecodeEvent               │  events
├─────────────────────────────────────────────────────────────┤
│ Fortuna        newOracle       FORTUNA_DEFAULTS             │  contract + factory
├─────────────────────────────────────────────────────────────┤
│ OP   ERR   loadFortunaCode   FORTUNA_TESTNET                │  constants + artifacts
└─────────────────────────────────────────────────────────────┘

No façade, no fluent builder — the surface is small by design. Fortuna has one role (threshold-BLS VRF); integrate at the ABI level.

Make your LLM Fortuna-aware (one-time, 5 seconds)

If you're building with Claude Code / Cursor / Aider / Cline / any AI coding assistant, run:

npx fortuna init

It writes FORTUNA.md to your repo root — a dense, AI-optimised brief covering the integration pattern, critical constants, callback-safety checklist, and common-error table. Reference it from your existing CLAUDE.md / .cursorrules / AGENTS.md and your assistant will generate correct Fortuna code on the first try.

The same content powers llms.txt (follows the llmstxt.org convention) and AGENTS.md (full surface map).

The consumer-contract playbook

You're building a product that needs randomness. Here's the shape:

  1. Deploy your consumer contract with a handler for VrfCallback (opcode 0x50). Run npx fortuna generate consumer --type <kind> to scaffold a starter:
  2. Call RequestRandomness from your consumer with a queryId (your internal identifier) and a seed (any uint256 — mixed into alpha for request personalisation). Attach fortuna.getRequiredRequestValue(callbackGas) + callbackGas TON. Use new QueryIdStream() from @titon-network/fortuna-sdk for collision-free ids.
  3. Wait for the callback. Fortuna assigns an aggregator (round-robin over pkShare-registered automatons); on fulfillment your contract receives VrfCallback { queryId, beta } from the Fortuna address. beta is your 256-bit VRF output — use it as entropy.
  4. (Optional) Reclaim if the aggregator stalls. After req.deadline elapses, anyone can call ReclaimRequest — Fortuna slashes the aggregator via ForgeTON and reassigns (first reclaim) or refunds the consumer (subsequent reclaims).

That's it. Operator set, DKG, BLS aggregation, threshold verification, slashing — all handled by the pool + oracle.

What does a request cost?

Defaults — override via UpdateFortunaConfig, or read live with fortuna.getConfig():

| Item | Default | Paid to | |------|---------|---------| | minRequestFee | 0.5 TON | Fortuna (split: 70% operator reward on fulfill, 30% protocol) | | callbackGas (you size) | 0.02–1 TON | Your consumer (returned via VrfCallback) | | minGasForRequest | 0.02 TON | Burned to process the request receiver | | Minimum total value: | ~0.54 TON | (+ small buffer recommended) |

On reclaim after deadline:

  • First reclaim: aggregator slashed at ForgeTON (1 TON from their stake); request reassigned; fee stays locked pending the retry.
  • Subsequent reclaims / stale-epoch / pool-below-threshold: consumer refunded minRequestFee + callbackGas (no slash — capacity-aware, not aggregator fault).

Use await fortuna.getRequiredRequestValue(callbackGas) to size value: at runtime — it reads live config so an owner-side UpdateFortunaConfig can't break hardcoded callers.

Quickstart

Deploy a fresh Fortuna

import { TonClient } from '@ton/ton';
import { toNano } from '@ton/core';
import { newOracle } from '@titon-network/fortuna-sdk';

const tonClient = new TonClient({ endpoint: 'https://testnet.toncenter.com/api/v2/jsonRPC' });
const fortuna = tonClient.open(newOracle({
    owner: ownerAddress,
    forgeton: forgetonPoolAddress,  // the ForgeTON pool Fortuna will stake/slash through
}));

await fortuna.sendDeploy(ownerSender, toNano('1'));
console.log('Fortuna deployed at', fortuna.address.toString());

Post-deploy:

  1. ForgeTON owner must admit Fortuna as a consumer: forgeton.sendSetConsumer(ownerSender, { value, contract: fortuna.address, isActive: true })
  2. Run the off-chain DKG ceremony across your automaton set. The ceremony outputs the aggregate groupPk (G1, 48 bytes) + per-automaton sk_i + pkShare_i.
  3. Publish groupPk to Fortuna: fortuna.sendPublishGroupKey(ownerSender, { value, threshold, memberCount, groupPk: cellFromBytes(pk) })
  4. Each automaton calls fortuna.sendRegisterBlsShare with its own pkShare.

Request randomness from your consumer

On-chain (Tolk) — inside your consumer's receiver:

import "./messages"  // your copy of RequestRandomness from examples/consumer-template.tolk

val req = createMessage({
    bounce: BounceMode.NoBounce,
    dest: storage.fortuna,
    value: ton("0.6"),  // ≥ minRequestFee (0.5 TON default) + callbackGas + minGasForRequest + buffer
    body: RequestRandomness {
        queryId: myQueryId,
        seed: mySeed,
        callbackGas: ton("0.05"),
    },
});
req.send(SEND_MODE_REGULAR);

Off-chain (TypeScript) — pre-compute the value for any external trigger:

import { Fortuna } from '@titon-network/fortuna-sdk';

const fortuna = tonClient.open(Fortuna.createFromAddress(FORTUNA_TESTNET.fortuna));
const value = await fortuna.getRequiredRequestValue(toNano('0.05'));
// → minRequestFee + callbackGas + minGasForRequest

Implement the callback

Inside your consumer's onInternalMessage:

struct (0x00000050) VrfCallback {
    queryId: uint64
    beta:    uint256
}

fun handleVrfCallback(msg: VrfCallback, sender: address) {
    assert (sender == storage.fortuna) throw E_NOT_FORTUNA;
    // `msg.beta` is your 256-bit VRF output — use it.
    // Example: pick a winner from an enumerable set.
    val winnerIdx = msg.beta % storage.participantCount;
    // ...
}

See examples/consumer-template.tolk for a complete, compilable consumer skeleton.

Decode Fortuna events

import { decodeEvents, FortunaEvent } from '@titon-network/fortuna-sdk';

for (const tx of result.transactions) {
    const externalOuts = [...tx.outMessages.values()]
        .filter(m => m.info.type === 'external-out')
        .map(m => m.body);

    for (const ev of decodeEvents(externalOuts)) {
        switch (ev.kind) {
            case 'RandomnessRequested':
                console.log(`queryId=${ev.queryId} aggregator=${ev.aggregator}`);
                break;
            case 'RandomnessFulfilled':
                console.log(`queryId=${ev.queryId} beta=0x${ev.beta.toString(16)}`);
                break;
        }
    }
}

Or use the one-shot summarizer:

import { summarizeTxs, formatTxSummary } from '@titon-network/fortuna-sdk';
for (const s of summarizeTxs(result.transactions)) {
    console.log(formatTxSummary(s));
    // [ok] events: RandomnessRequested
    // [fail] exit 205 InsufficientFee — RequestRandomness msgValue below ...
}

Interpret exit codes

import { explainError } from '@titon-network/fortuna-sdk';

const e = explainError(209);
// {
//   code: 209,
//   origin: 'fortuna',
//   name: 'StaleEpoch',
//   message: 'Fulfill for a request whose groupEpoch is older than the current storage.groupEpoch.',
//   hint: 'A rotation happened after this request was created...'
// }

Fortuna also surfaces ForgeTON bounce-back codes (160 NotAuthorizedConsumer, 179 SlashBudgetExceeded) for consumers debugging slash failures, and TVM's common codes (9 CellUnderflow, 13 OutOfGas, 0xFFFF UnknownOpcode).

CLI

The SDK ships a small fortuna CLI for local introspection + project scaffolding. All commands accept --json for machine-readable output.

Scaffolding

$ npx fortuna init                                   # write FORTUNA.md (agent context)
$ npx fortuna generate consumer --type raffle        # scaffold a Tolk consumer
$ npx fortuna generate consumer --type lottery --out myLottery.tolk --force

Introspection (no network)

$ fortuna explain 244
exit 244 (fortuna): InvalidG1Point
  groupPk/pkShare failed BLS_G1_INGROUP subgroup check.
  hint: Point is either not on the curve or not in the prime-order subgroup...

$ fortuna schema
SDK expects:
  FORTUNA_STORAGE_VERSION:     1
  ...
  BLS:
    pubkey bytes:  48 (G1)
    sig bytes:     96 (G2)
    DST:           BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_

$ fortuna hash
hex:    <bundled artifact code hash>

$ fortuna decode <hex-boc>             # decode an emitted FortunaEvent cell

Live-oracle (needs npm install @ton/ton)

$ fortuna info <fortuna-addr> --testnet
$ fortuna estimate request --oracle <addr> --callback-gas 0.05 --testnet
$ fortuna verify --testnet                 # drift-check SDK vs canonical deploy

Sandbox testing (subpath import)

import { Blockchain } from '@ton/sandbox';
import { deployFortunaFixture } from '@titon-network/fortuna-sdk/testing';

const blockchain = await Blockchain.create();
const { fortuna, automaton, fulfill } = await deployFortunaFixture(blockchain);
// fortuna is live, operator registered — ready for RequestRandomness

One line to a ready-to-request Fortuna. Under the hood: deploy + generate BLS keys + publish group key + register share. See src/testing/index.ts.

The BLS ciphersuite (for aggregators)

Fortuna's on-chain BLS_VERIFY uses the min-pk variant:

  • Pubkeys (G1): 48 bytes compressed
  • Signatures (G2): 96 bytes compressed
  • Domain-separation tag: BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_

@noble/curves/bls12-381's default DST is _NUL_ — that does NOT match TVM. Off-chain signers must pass BLS_DST_G2_POP (exported from @titon-network/fortuna-sdk) explicitly to bls.longSignatures.hash().

import { bls12_381 as bls } from '@noble/curves/bls12-381';
import { BLS_DST_G2_POP } from '@titon-network/fortuna-sdk';

const msgPoint = bls.longSignatures.hash(alphaBytes, BLS_DST_G2_POP);
const sig = bls.longSignatures.sign(msgPoint, sk).toBytes(true);  // 96-byte G2

See examples/aggregator-skeleton.ts for a reference off-chain signer.

What's where

| File | Purpose | |------|---------| | src/contracts/Fortuna.ts | Fortuna class — ABI wrapper with send/get methods, SchemaDriftError, validateConfig, validateAgainstLive | | src/opcodes.ts | OP + ERR constants + schema versions + BLS ciphersuite constants | | src/errors.ts | explainError(code) + FortunaError — covers Fortuna (100-269), ForgeTON bounce-backs (160, 179), TVM (0-99, 0xFFFF) | | src/events/ | Typed FortunaEvent union + decodeEvent / decodeEvents / tryDecodeEvent | | src/factory.ts | newOracle({ owner, forgeton }) — one-line deploy handle | | src/query-id.ts | QueryIdStream — collision-free u64 queryId generator | | src/solo.ts | generateGroupKey + signAlpha + computeAlpha — BLS helpers for solo / aggregator signing with the correct DST | | src/testing/ | deployFortunaFixture — one-line sandbox setup. Imported via @titon-network/fortuna-sdk/testing subpath | | src/diagnostics.ts | summarizeTx / formatTxSummary — collapse a Transaction to its useful fields | | src/artifacts/loader.ts | loadFortunaCode() + FORTUNA_CODE_HASH | | src/deployments.ts | FORTUNA_TESTNET / FORTUNA_MAINNET canonical addresses + assertDeployment() loud-error helper | | src/cli.ts | fortuna CLI — explain / decode / hash / schema / info / estimate / verify / init / generate | | ERRORS.md | Flat Markdown table of every exit code (Fortuna + ForgeTON + TVM). Generated. | | OPCODES.md | Flat Markdown table of every wire opcode. Generated. | | llms.txt | Single-page AI-assistant context (llmstxt.org convention) | | templates/ | Shipped assets for fortuna init (agent-context.md) | | examples/ | Runnable TS examples + four Tolk consumer templates (raffle / NFT / lottery / shuffle) | | skills/ | AI-assistant playbooks — 11 persona-grouped skills (deploy, request, receiver, lifecycle, scaffold, gas-sizing, integration-test, monitor-events, aggregator-setup, debug-exit-code, owner-ops) |

Schema drift

Persistent structs (FortunaStorage, OperatorInfo, RequestInfo, etc.) carry a schemaVersion: uint8 first field. The SDK's getters verify this version and throw SchemaDriftError on mismatch — that's the signal to upgrade either the SDK or the contract. See src/contracts/Fortuna.ts and the fortuna schema CLI command.

Links

License

MIT.