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

forgeton-sdk

v0.4.0

Published

TypeScript SDK for ForgeTON — the shared-security staking pool for TON. Stake automatons, slash, receive AutomatonSync pushes.

Readme

forgeton-sdk

TypeScript SDK for ForgeTON — the shared-security staking pool for TON. Stake once, get slashed by any admitted consumer.

Use this SDK if you're building an on-chain service that needs an off-chain operator network — executors, VRF signers, oracle reporters, keeper bots, anything where trust comes from stake-at-risk. ForgeTON already runs the operator set (automatons), handles staking + cooldowns, and broadcasts AutomatonSync pushes. Your product contract admits itself as a consumer, receives the sync stream, and slashes operators for product-specific misbehavior.

Think EigenLayer, purpose-built for TON.

npm install forgeton-sdk @ton/core

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

Surfaces

┌─────────────────────────────────────────────────────────────┐
│ explainError   ForgetonError   summarizeTx   formatTxSummary│  diagnostics
├─────────────────────────────────────────────────────────────┤
│ decodeEvent    decodeEvents    tryDecodeEvent               │  events
├─────────────────────────────────────────────────────────────┤
│ ForgeTON       newPool         FORGETON_DEFAULTS            │  contract + factory
├─────────────────────────────────────────────────────────────┤
│ OP   ERR   loadForgetonCode   FORGETON_TESTNET              │  constants + artifacts
└─────────────────────────────────────────────────────────────┘

No façade, no fluent builder — the surface is small by design. ForgeTON has one role (operator set + slashing); integrate at the ABI level.

The consumer-contract playbook

You're about to integrate ForgeTON into a new product. Here's the shape:

  1. Deploy your product contract. Implement a handleAutomatonSync(msg) receiver that maintains your local automaton mirror — and call forgeton.sendSlash when you detect misbehavior. See examples/consumer-template.tolk for a copy-paste-ready Tolk starter.
  2. Ask the ForgeTON owner to admit you via SetConsumer { contract: <you>, isActive: true } (opcode 0x18). Until you're admitted, Slash sends will bounce with E_NOT_AUTHORIZED_CONSUMER (160).
  3. Pick a reason tag. Any uint32 — it's a product-defined slash reason that shows up in the AutomatonSlashed event for off-chain attribution. Kronos uses 1 = MISSED_EXECUTION. Document yours.
  4. Pick a ctx payload. Any uint64 — typically your product's identifier (jobId / requestId / roundId). Sent on the inbound Slash only — ctx does not appear in the outbound AutomatonSlashed event. Indexers correlate by transaction hash if they need per-incident attribution. The event itself carries slasher + reason.

That's it. Stake, slashing, sync fan-out, upgrade timelock — all handled by the pool.

Quickstart

Deploy a fresh ForgeTON

import { TonClient } from '@ton/ton';
import { toNano } from '@ton/core';

import { newPool } from 'forgeton-sdk';

const tonClient = new TonClient({ endpoint: 'https://testnet.toncenter.com/api/v2/jsonRPC' });
const pool = tonClient.open(newPool({ owner: ownerAddress }));

await pool.sendDeploy(ownerSender, toNano('0.5'));
console.log('ForgeTON deployed at', pool.address.toString());

// Admit your consumer contract:
await pool.sendSetConsumer(ownerSender, {
    value: toNano('0.1'),
    contract: consumerAddress,
    isActive: true,
});

newPool({ owner }) is a one-line shortcut for ForgeTON.createFromConfig({ owner }, loadForgetonCode()). Use the long form if you want to swap the bundled code (e.g. running against a custom build).

Stake as an automaton

Automatons stake once and participate in every admitted consumer's product. The register call must also fund the sync fan-out: one push per admitted consumer.

import { ForgeTON, FORGETON_TESTNET } from 'forgeton-sdk';

const pool = tonClient.open(ForgeTON.createFromAddress(FORGETON_TESTNET.forgeton));

// getRegisterValue does the math for you (config + consumerCount + formula).
const value = await pool.getRegisterValue();
await pool.sendRegisterAutomaton(automatonSender, { value });

Manual formula (still useful if you want to add a buffer):

const cfg = await pool.getConfig();
const consumerCount = await pool.getConsumerCount();
const value = cfg.minStake + cfg.minGasForRegister + BigInt(consumerCount) * cfg.syncGasCost;

For unstake, getUnstakeValue({ crossingThreshold: true }) does the same — pass false for partial unstakes that don't trigger fan-out.

Slash a misbehaving automaton (from your consumer contract)

Consumer-side: build a Slash message. Only admitted consumers can send it; the pool enforces this on the Tolk side.

There is no shared Tolk package — every consumer copies the wire structs into its own messages.tolk. The bytes must be identical to ForgeTON's contracts/messages.tolk:167. Copy verbatim:

// In your consumer's messages.tolk — these MUST match forgeton/contracts/messages.tolk byte-for-byte:
struct (0x00000014) Slash {
    automaton: address
    reason:    uint32
    ctx:       uint64
}

struct (0x0000001A) AutomatonSync {
    automaton: address
    isActive:  bool
}

Then send:

val slashMsg = createMessage({
    bounce: BounceMode.NoBounce,
    dest: storage.forgeton,
    value: ton("0.05"),  // covers the pool's compute + storage delta
    body: Slash {
        automaton: offender,
        reason: REASON_MISSED_EXECUTION,  // your product-defined tag
        ctx: requestId,                    // your product's identifier
    },
});
slashMsg.send(SEND_MODE_REGULAR);

TypeScript-side (e.g., for tests, scripts, or external senders):

await pool.sendSlash(consumerSender, {
    value: toNano('0.05'),
    automaton: offenderAddress,
    reason: 1,
    ctx: 42n,
});

See examples/consumer-template.tolk for a complete starter consumer that you can deploy and customise.

Handle AutomatonSync pushes (consumer-side mirror)

On every lifecycle event (register, terminal unstake, terminal slash), the pool sends AutomatonSync { automaton, isActive } to every admitted consumer. You maintain your own mirror:

// In your consumer contract (Tolk):
fun handleAutomatonSync(msg: AutomatonSync, sender: address) {
    var storage = lazy ConsumerStorage.load();
    assert (sender == storage.forgeton) throw E_NOT_FORGETON;

    if (msg.isActive) {
        // Add to your mirror...
    } else {
        // Remove from your mirror (swap-and-pop for dense storage)...
    }
    storage.syncesReceived += 1;
    storage.save();
}

Per-consumer ConsumerInfo.syncesLanded on the pool is the sync drift counter — compare against your local syncesReceived to detect skipped pushes (tight-budget fan-out tail-skips). Owner can repair with ForceSync { automaton }.

Diagnose any tx in one call

import { summarizeTx, formatTxSummary } from 'forgeton-sdk';

const result = await pool.sendSlash(via, opts);
for (const tx of result.transactions) {
    const s = summarizeTx(tx);
    console.log(formatTxSummary(s));
    // [ok] events: AutomatonSlashed
    // [fail] exit 160 NotAuthorizedConsumer — Slash sender is not in ForgeTON's consumer set.
    if (!s.success) throw new Error(s.explanation?.hint ?? 'unknown failure');
}

summarizeTx(tx) returns { success, exitCode, actionResultCode, explanation, events, rawExternalBodies } — one call replaces hand-walking tx.description.computePhase.exitCode, then plucking external-out bodies, then decoding each.

Decode events from your indexer

import { decodeEvents } from 'forgeton-sdk';

// Each `Cell` is the body of an external-out message emitted by ForgeTON.
for (const ev of decodeEvents(externalBodies)) {
    switch (ev.kind) {
        case 'AutomatonRegistered':
            console.log(`+ automaton ${ev.automaton} staked ${ev.stake}`);
            break;
        case 'AutomatonSlashed':
            console.warn(`${ev.automaton} slashed -${ev.amount} by ${ev.slasher} (reason=${ev.reason})`);
            break;
        case 'ConsumerUpdated':
            console.log(`consumer ${ev.contract} ${ev.isActive ? 'admitted' : 'removed'} (count=${ev.consumerCount})`);
            break;
    }
}

For a working end-to-end indexer pattern (poll TonClient → extract bodies → decode), see examples/04-indexer.ts.

Reference

ForgeTON

Hand-written wrapper matching the on-chain ABI 1:1. send* methods for every inbound opcode, get* getters for every get fun.

Construct:

  • newPool({ owner, workchain? }) — one-liner, bundles loadForgetonCode().
  • ForgeTON.createFromConfig({ owner }, code, workchain?) — explicit, lets you swap the artifact.
  • ForgeTON.createFromAddress(addr) — handle for an existing deploy (no init data).

Send methods:

| Method | Who | Purpose | |--------|-----|---------| | sendDeploy(via, value) | anyone | Initial deploy. Note: unlike other send*, takes value as a positional arg (Blueprint convention). | | sendRegisterAutomaton | anyone | Stake as an automaton. Gas: minStake + minGasForRegister + consumerCount × syncGasCost — or use getRegisterValue(). | | sendIncreaseStake | the staked automaton | Top up stake. | | sendRequestUnstake | the staked automaton | Start cooldown. | | sendCancelUnstake | the staked automaton | Abort cooldown. | | sendUnstake | the staked automaton | Withdraw after cooldown elapsed. If cross-zero threshold: minGasForUnstake + consumerCount × syncGasCost — or use getUnstakeValue(). | | sendSlash | any admitted consumer | Slash an automaton. reason (uint32) surfaces in AutomatonSlashed; ctx (uint64) is inbound-only — does not appear in the event. | | sendSetConsumer | owner | Admit (isActive: true) or remove (isActive: false) a consumer. ≤ MAX_CONSUMERS = 16. | | sendForceSync | owner | Broadcast a single automaton's current state to every consumer (drift recovery). | | sendUpdateForgetonConfig | owner | Rewrite the 8-field config blob. Pre-validate locally with ForgeTON.validateConfig(opts). | | sendWithdrawSlashed | owner | Withdraw accrued slash funds. | | sendPruneAutomaton | owner | Emergency remove a stuck automaton. | | sendPause / sendUnpause | owner | Emergency halt on non-exit paths. | | sendProposeCodeUpgrade | owner | Propose new code. Either eta (absolute unix-seconds, ≥ now+24h) or delaySeconds (relative, ≥ 86400). Exactly one. | | sendExecuteCodeUpgrade | owner | Activate after eta. | | sendCancelCodeUpgrade | owner | Abort a pending proposal. |

Helpers (no I/O for validate*, one or two getter calls for estimate*):

| Method | Returns | Notes | |--------|---------|-------| | getRegisterValue() | Promise<bigint> | Live getConfig + getConsumerCount; applies the formula. Use for sendRegisterAutomaton. | | getUnstakeValue({ crossingThreshold }) | Promise<bigint> | crossingThreshold: true (default) includes the fan-out gas; false for partials. | | ForgeTON.validateConfig(opts) | void (throws) | Local pre-flight matching the contract's lower + upper bounds. Throws ForgetonError with the same exit code the contract would. |

Getters:

| Method | Returns | |--------|---------| | getStorageVersion() | FORGETON_STORAGE_VERSION — use to verify deployed schema matches SDK | | getOwner() | owner address | | getIsPaused() | pause flag | | getConfig() | full ForgetonConfigReply (8 fields incl. maxSlashPerConsumerPerDay) | | getAutomaton(addr) | AutomatonInfo \| null — throws SchemaDriftError on parse failure (contract / SDK version mismatch) | | getIsAutomaton(addr) | boolean — convenience over getAutomaton | | getActiveAutomatonCount() | active pool size | | getAutomatonCount() | zombie-inclusive count (incl. fully-slashed leftovers) | | getTotalStaked() | sum of all stakes | | getConsumer(addr) | ConsumerInfo \| null (incl. per-consumer syncesLanded) — throws SchemaDriftError on parse failure | | getIsConsumer(addr) | boolean — convenience over getConsumer | | getConsumerAt(index) | dense consumer-set lookup in [0, consumerCount) | | getConsumerCount() | admitted consumer count | | getSyncCursor() | round-robin starting index for the next pushSync — use to predict fan-out order under tight budget | | getSyncesAttempted() / getSyncesLanded() / getSlashesApplied() | drift counters (see table below) | | getPendingUpgrade() | { codeHash, eta }(0n, 0) when none pending |

Drift counters at a glance

Three counters with similar names — same word, different scopes:

| Counter | Where | Bumps on… | |---------|-------|-----------| | getSyncesAttempted() | global | every lifecycle event that would trigger a sync (register / terminal unstake / terminal slash / ForceSync), counted per-event regardless of fan-out | | getSyncesLanded() | global | every consumer push that actually fires (passes the per-iteration reserve check) — equals the sum of all per-consumer ConsumerInfo.syncesLanded | | ConsumerInfo.syncesLanded | per-consumer | a successful sync push to this consumer specifically — diff against the consumer's local syncesReceived to detect skips |

Indexer rule of thumb: syncesAttempted > syncesLanded ⇒ at least one consumer is being skipped (tight-budget tail). Owner repairs with sendForceSync({ automaton }).

Events

import { decodeEvent, decodeEvents, tryDecodeEvent } from 'forgeton-sdk';

decodeEvent(body)       // throws on unknown opcode — use when source is trusted
tryDecodeEvent(body)    // returns null on unknown opcode
decodeEvents(bodies)    // batch — silently drops unknown opcodes

All event kinds (typed discriminated union via event.kind):

AutomatonRegistered    StakeIncreased        UnstakeRequested      UnstakeCancelled
Unstaked               AutomatonSlashed      AutomatonPruned       ForgetonConfigUpdated
ConsumerUpdated        SlashedWithdrawn      PausedChanged         ForceSyncTriggered
UpgradeProposed        UpgradeCancelled      CodeUpdated

AutomatonSlashed carries slasher: Address and reason: number so indexers can attribute slashes to specific consumers / product-defined reasons. It does NOT carry ctx — if you need per-incident correlation (jobId / requestId / etc), correlate by transaction hash with the inbound Slash body.

Error introspection

import { explainError, ForgetonError, ERR } from 'forgeton-sdk';

const e = explainError(160);
// { code: 160, origin: 'forgeton', name: 'NotAuthorizedConsumer',
//   message: 'Slash sender is not in ForgeTON\'s consumer set.',
//   hint: 'Owner must SetConsumer { contract: <yourContract>, isActive: true } before that contract can send Slash.' }

throw new ForgetonError(ERR.NotAuthorizedConsumer, `tx ${txHash}`);

Covers every E_* from contracts/errors.tolk plus common TVM exit codes.

Diagnostics

import { summarizeTx, summarizeTxs, formatTxSummary } from 'forgeton-sdk';

const s = summarizeTx(tx);
// { success, exitCode, actionResultCode, explanation, events, rawExternalBodies }
formatTxSummary(s);
// '[ok] events: AutomatonSlashed'   |   '[fail] exit 160 NotAuthorizedConsumer — ...'

Pass any @ton/core Transaction (sandbox BlockchainTransaction works too). Events are decoded into the typed ForgetonEvent union; non-ForgeTON external-outs stay in rawExternalBodies if you need them.

Constants

| Constant | Value | Purpose | |----------|-------|---------| | FORGETON_DEFAULTS | { minStake, slashAmount, unstakeCooldown, syncGasCost, minStorageReserve, minGasForRegister, minGasForUnstake, maxSlashPerConsumerPerDay } | Default config values used at deploy. 8 fields — the per-consumer 24h slash circuit-breaker cap is the 8th. | | FORGETON_STORAGE_VERSION | 1 | Root-storage schema version — compare against getStorageVersion() | | FORGETON_CONFIG_BLOB_VERSION | 1 | Config-blob schema version | | AUTOMATON_INFO_VERSION | 1 | Map-value schema for AutomatonInfo | | CONSUMER_INFO_VERSION | 1 | Map-value schema for ConsumerInfo | | MAX_CONSUMERS | 16 | Hard cap on simultaneous admitted consumers | | MIN_UPGRADE_DELAY_SECONDS | 86400 | 24 h timelock floor between propose and execute | | FORGETON_CODE_HASH | { hex, base64 } | Code-hash of the bundled BoC; compare against on-chain code hash to detect drift |

Live deployments

import { FORGETON_TESTNET, ForgeTON } from 'forgeton-sdk';

console.log(FORGETON_TESTNET.forgeton.toString());      // address
console.log(FORGETON_TESTNET.expectedCodeHash);          // verify against on-chain
console.log(FORGETON_TESTNET.expectedSchema.storage);    // 2

const pool = tonClient.open(ForgeTON.createFromAddress(FORGETON_TESTNET.forgeton));

FORGETON_MAINNET will be added when the mainnet deploy lands.

Bundled compiled contract

import { loadForgetonCode, FORGETON_CODE_HASH, ForgeTON } from 'forgeton-sdk';

const code = loadForgetonCode();
const pool = ForgeTON.createFromConfig({ owner }, code);

// Verify the bundled code matches what's on-chain:
const liveHash = (await tonClient.getContractState(addr)).codeHash?.toString('hex');
if (liveHash !== FORGETON_CODE_HASH.hex) console.warn('SDK ↔ on-chain code drift');

Ships with the compiled ForgeTON.compiled.json under artifacts/. Useful for deploying your own pool instance or for sandbox tests.

Examples

Runnable scripts under examples/ — sandbox-based, so they execute offline. Each can be adapted to TonClient + a real wallet by swapping the Blockchain.create() setup at the top.

| Script | What it does | |--------|--------------| | 01-deploy-pool.ts | Deploy a fresh pool, verify config, admit a consumer. | | 02-register-as-automaton.ts | Use getRegisterValue to stake, walk full unstake cooldown. | | 03-slash-from-consumer.ts | Real-counterparty slash via TestConsumer, summarised with summarizeTx. | | 04-indexer.ts | Poll-loop pattern: extract external-out bodies → decodeEvents. | | operator-skeleton.ts | Minimal off-chain automaton operator: register, watch for deactivation, exit. | | consumer-template.tolk | Production-grade Tolk consumer starter — copy and customise. |

Run from sdk/: npx ts-node examples/01-deploy-pool.ts.

CLI

A small CLI ships with the package — useful for one-off introspection without writing a script:

npx forgeton explain 160                    # describe an exit code
npx forgeton decode <hex-event-body>        # decode an external-out body
npx forgeton info <pool-addr> --testnet     # pretty-print pool state
npx forgeton estimate register --testnet    # compute the right `value:` for register

Network commands (info, estimate) need @ton/ton installed (you probably already have it).

Versioning

This is a v0.x SDK. The on-chain ABI is still evolving; SDK majors track contract upgrades. Pin exact versions in production.

When a new schema version ships:

  • Increment the matching *_VERSION constant (FORGETON_STORAGE_VERSION, CONSUMER_INFO_VERSION, …).
  • Indexers compare getStorageVersion() against the exported constant — a mismatch means the SDK and the deployed contract disagree on storage layout. getAutomaton / getConsumer will throw SchemaDriftError rather than return malformed data. Redeploy or upgrade.

Working with AI tools

Drop this SDK into your project and your AI assistant picks it up:

  • AGENTS.md — terse map of the SDK auto-discovered by Claude Code, Cursor, and other editors that follow the agents.md convention.
  • skills/ — invocable skill files for common tasks (integrate-consumer, deploy-pool, run-operator, debug-exit-code). Compatible with Claude Code's skill system; useful as raw markdown for any LLM.

Known consumers

  • Kronos — decentralized automation (recurring on-chain jobs). Uses reason: 1 = MISSED_EXECUTION, ctx = jobId.
  • Future: Fortuna (VRF), oracles, functions.

License

MIT.