@titon-network/kronos-sdk
v0.8.0
Published
TypeScript SDK for the Kronos automation protocol on TON — register recurring on-chain jobs, run automatons, decode events. **TESTNET ONLY — NOT AUDITED.**
Maintainers
Readme
@titon-network/kronos-sdk
TypeScript SDK for the Kronos automation protocol on TON — Chainlink Automation for the TON ecosystem.
Register recurring on-chain jobs, decode events, maintain a local automaton mirror. Works in browsers, Node servers, and TON sandbox tests.
npm install @titon-network/kronos-sdk @ton/core30-second integration
Kronos is live on testnet. This block calls our deployed registry directly — copy, paste, run.
import { TonClient } from '@ton/ton';
import { Address, beginCell, toNano } from '@ton/core';
import {
KronosClient, KronosRegistry, KRONOS_TESTNET, registerJobOpts,
} from '@titon-network/kronos-sdk';
const tonClient = new TonClient({
endpoint: 'https://testnet.toncenter.com/api/v2/jsonRPC',
});
const registry = tonClient.open(
KronosRegistry.createFromAddress(KRONOS_TESTNET.registry),
);
const client = new KronosClient({ registry });
const cfg = await client.jobs.config();
await client.jobs.register(yourSender, registerJobOpts({
target: Address.parse('EQ...your-target-contract...'),
message: beginCell().storeUint(0x7001 /* OP_TICK */, 32).endCell(),
interval: 3_600, // every hour
reward: toNano('0.05'),
gasLimit: toNano('0.02'),
}, cfg)); // auto-funds 100 runsThat's it. An automaton picks up your job and calls your contract every hour.
For the target contract pattern (the four invariants every Kronos receiver must follow), see examples/contracts/counter/ — a complete reference Tolk contract + sandbox test you drop into your Blueprint project.
Set up your LLM assistant — three options
Kronos is built for LLM-assisted development. Pick the path that matches your tooling:
| Tool | Setup |
|---|---|
| Claude Code | npx @titon-network/kronos-sdk init — installs 11 skills (/kronos-register-job, /kronos-target-receiver, /kronos-scaffold, …) into .claude/skills/. Every future Claude session in this project knows Kronos. |
| Cursor / GPT / raw Claude API / Cline | Paste AGENT-PROMPT.md into your system prompt. ~3K tokens, self-contained, turns any LLM into a Kronos expert. |
| Claude Desktop / MCP-aware tooling | Install @titon-network/mcp — gives the LLM 17+ live tools: kronos.preview_economics, kronos.prepare_register_job, kronos.window_state, kronos.assigned_automaton, kronos.explain_exit_code, etc. |
| anything else | Fetch llms-full.txt — single-file context dump with the counter contract, full API table, every pitfall. Or llms.txt for the discoverable doc index per llmstxt.org. |
@ton/core is a peer dependency — bring your own version (>= 0.63.0). @ton/sandbox and @titon-network/forgeton-sdk are optional peers — only needed when you use @titon-network/kronos-sdk/testing or run automaton flows.
💡 Running an automaton? Install
@titon-network/forgeton-sdkalongside this one. Pool-side operations (stake, slash, pool events) live there. This SDK covers the Kronos registry only.
Three layers, pick what you need
┌──────────────────────────────────────────────────────┐
│ KronosClient registerJobOpts decodeEvent │ high-level
├──────────────────────────────────────────────────────┤
│ executionEconomics assignedAutomatonIndex isDue │ pure helpers
├──────────────────────────────────────────────────────┤
│ KronosRegistry │ contract wrapper (1:1 ABI)
└──────────────────────────────────────────────────────┘You can drop down any layer — they're all part of the public surface.
More recipes
Decode registry events from your indexer
import { decodeEvent } from '@titon-network/kronos-sdk';
// Each `Cell` is the body of an external-out message emitted by the registry.
// For pool-side events (AutomatonRegistered, AutomatonSlashed, …) use
// `decodeEvent` from @titon-network/forgeton-sdk on the pool's external bodies.
for (const body of externalBodies) {
const ev = decodeEvent(body);
if (ev === null) continue; // not a Kronos event — try @titon-network/forgeton-sdk next
switch (ev.kind) {
case 'JobExecuted':
console.log(`job ${ev.jobId} run ${ev.executionCount} by ${ev.automaton}`);
break;
case 'AssignedAutomatonMissed':
console.warn(`automaton ${ev.assigned} missed window for job ${ev.jobId}`);
break;
}
}Run an automaton (staking via @titon-network/forgeton-sdk)
Pool operations live in @titon-network/forgeton-sdk. Use both SDKs together:
import { KronosClient, KronosRegistry } from '@titon-network/kronos-sdk';
import { ForgeTON } from '@titon-network/forgeton-sdk';
const registry = tonClient.open(KronosRegistry.createFromAddress(REGISTRY_ADDRESS));
const pool = tonClient.open(ForgeTON.createFromAddress(FORGETON_ADDRESS));
const client = new KronosClient({ registry });
// Stake to become an automaton (pool-side; @titon-network/forgeton-sdk).
const poolCfg = await pool.getConfig();
const consumerCount = await pool.getConsumerCount();
await pool.sendRegisterAutomaton(walletSender, {
value: poolCfg.minStake
+ poolCfg.minGasForRegister
+ BigInt(consumerCount) * poolCfg.syncGasCost,
});
// Find the next due job and execute it (registry-side; @titon-network/kronos-sdk).
const jobCount = await client.jobs.count();
for (let i = 0n; i < jobCount; i++) {
if (!(await client.jobs.exists(i))) continue;
if (!(await client.jobs.isDue(i))) continue;
// Check if you're the assigned automaton for the primary window.
const assigned = await client.assignedAutomatonFor(i);
if (assigned && !assigned.equals(myAddress)) continue;
await client.jobs.execute(walletSender, { value: toNano('0.5'), jobId: i });
}Check if Kronos is operational
const activeAutomatons = await client.registry.getActiveAutomatonCount();
// → current operator capacity (denominator of the assignment formula)
if (activeAutomatons >= 2) {
// redundant capacity — safe to register new jobs
}For execution freshness, recent activity, and per-operator reliability, query the
Argus indexer's event stream rather than the contract — EvtJobExecuted +
EvtAssignedAutomatonMissed against the deterministic assignment formula give a
full audit trail without the gas cost of on-chain counters.
Inspect a job's window state
import { jobWindowState, isDue, nextExecutionAt } from '@titon-network/kronos-sdk';
const job = await client.jobs.get(jobId);
const cfg = await client.jobs.config();
const state = jobWindowState({
lastExecutedAt: job!.lastExecutedAt,
interval: job!.interval,
windowBefore: job!.windowBefore,
windowAfter: job!.windowAfter,
primaryWindowSeconds: cfg.primaryWindowSeconds,
expireAfter: job!.expireAfter,
});
// state.status: 'never-executed' | 'too-early' | 'primary' | 'fallback' | 'too-late' | 'expired'
// state.secondsToNext: countdown to next status changeReference
KronosClient
High-level façade over the registry. Construct with an already-opened contract handle:
const client = new KronosClient({
registry: tonClient.open(KronosRegistry.createFromAddress(REGISTRY_ADDRESS)),
});| Namespace | Methods |
|-----------|---------|
| client.jobs | register, execute, fund, ensureFunded, cancel, pause, resume, withdraw, update, updateFull, cleanupExpired, sweepDust, performHousekeeping, get, exists, isDue, nextDue, balance, balanceHealth, economics, count, config |
| client.mirror | snapshot, assignedFor, activeCount — registry-side reads against the dense automaton mirror maintained by inbound AutomatonSync |
| client.events | decode, decodeAll |
Plus convenience methods on the client itself: assignedAutomatonFor(jobId), windowFor(jobId).
For pool-side operations (stake, unstake, slash, pool events) use
@titon-network/forgeton-sdk'sForgeTONclass directly.
registerJobOpts(input, cfg?)
Helper that fills in sensible defaults (30s before / 10min after window, no expiry, unlimited runs) and, if cfg is supplied, auto-computes funding for 100 runs.
registerJobOpts(
{
target,
message,
interval: 3600,
reward: toNano('0.05'),
gasLimit: toNano('0.02'),
// Optional overrides — omit for defaults:
maxExecutions: 0, // unlimited
windowBefore: 30,
windowAfter: 600,
expireAfter: 0, // never
funding: toNano('1'), // or omit + pass cfg to auto-compute
},
cfg,
);decodeEvent(body: Cell): KronosEvent | null
Parses an external-log message body into a typed registry event. The discriminant is event.kind. Returns null when the opcode is not a Kronos event (try @titon-network/forgeton-sdk's decodeEvent next).
All registry event kinds:
JobRegistered JobExecuted JobFunded JobCancelled JobUpdated
JobPaused JobResumed JobWithdrawn JobExpired JobDustSwept
JobHousekeepingExecuted ForgetonSet AutomatonMirrorUpdated AssignedAutomatonMissed
ConfigUpdated TreasuryUpdated HousekeepingJobSet FeesWithdrawn
UpgradeProposed UpgradeCancelled CodeUpdated SlashRetriedPool-side events (AutomatonRegistered, AutomatonSlashed, …) are decoded by @titon-network/forgeton-sdk's own decodeEvent. Chain both decoders if you're watching both contracts.
Pure helpers
| Function | Purpose |
|----------|---------|
| executionEconomics({ reward, gasLimit, protocolFeeBps }) | Returns { totalCost, protocolFee, automatonReward, gasLimit }. Mirrors on-chain JobConfig.executionEconomics. |
| recommendedRegisterValue(...) | max(executions * totalCost, minFunding) + minGasReserve. The number you attach to RegisterJob. |
| previewJobCost({ reward, gasLimit, interval, executions? }, cfg) | One-call UI-friendly preview — { perExecutionCost, minFunding, recommended, protocolFee, burnRatePerDay, runsAtRecommended, … }. |
| estimateJobGas({ initCode, initData?, body }) or estimateJobGas({ blockchain, target, body }) | Sandbox-based gas estimator — { gasUsed, recommended } with 20% buffer. initCode mode deploys a fresh account; target mode needs the contract pre-deployed on the supplied blockchain. Requires @ton/sandbox (optional peer). |
| assignedAutomatonIndex({ jobId, executionCount, activeAutomatonCount }) | The on-chain assignment formula (jobId + execCount) % activeCount. |
| resolveAssignedAutomaton({ ..., automatonAddresses }) | Same, but resolves to an Address using a mirror snapshot. |
| jobWindowState({ lastExecutedAt, interval, ... }) | Full window inspection: status, deadlines, secondsToNext. |
| isDue(...), nextExecutionAt(...) | Convenience over jobWindowState — time/expiry only. |
| isExecutable({ ..., balance, perExecutionCost, isActive }) | Faithful preview of Execute success — also checks active + funded. Use this in automaton daemons. |
| validateRegisterOpts(opts, cfg) | Pre-flight foot-gun check: throws on contract-reject values (bad interval / reward / gasLimit / expireAfter), returns warnings[] for likely-buggy-but-legal inputs. Wired into registerJobOpts automatically when cfg is supplied — opt out with { validate: false }. |
JobPresets
Three spread-in profiles that collapse the windowBefore/windowAfter choice to a name:
import { registerJobOpts, JobPresets } from '@titon-network/kronos-sdk';
// Every minute, strict window.
registerJobOpts({ ..., interval: 60, ...JobPresets.tight }, cfg);
// Hourly (current defaults).
registerJobOpts({ ..., interval: 3600, ...JobPresets.default }, cfg);
// Daily / weekly, tolerant of late runs.
registerJobOpts({ ..., interval: 86_400, ...JobPresets.loose }, cfg);JobWatcher
Long-running subscription for a single job's events — polls the registry tx stream, filters by jobId, emits typed callbacks. Plug in any transaction source (TonClient, custom indexer, sandbox for tests):
import { JobWatcher } from '@titon-network/kronos-sdk';
const watcher = new JobWatcher(client, jobId, { source: myEventSource });
watcher
.on('JobExecuted', (ev) => console.log(`run ${ev.executionCount} by ${ev.automaton}`))
.on('JobFunded', (ev) => console.log(`topped up by ${ev.amount}`))
.on('LowBalance', (ev) => alertOps(ev.jobId, ev.runsRemaining));
const stop = watcher.start();
// ... later ...
await stop();Test harness — @titon-network/kronos-sdk/testing
Shrinks "deploy registry + register job + fast-forward + execute" to a one-liner. Requires @ton/sandbox installed:
import { SandboxKronos } from '@titon-network/kronos-sdk/testing';
const kronos = await SandboxKronos.deploy(blockchain, { owner, treasury, via: owner.getSender() });
const jobId = await kronos.register({ target, message, interval, reward, gasLimit, via });
kronos.fastForward(301);
const res = await kronos.executeNext(jobId, automaton.getSender());See /kronos-integration-test for the full API + patterns.
Error introspection
import { explainError, formatErrorExplanation, ERR } from '@titon-network/kronos-sdk';
const e = explainError(132);
// { code: 132, origin: 'kronos', name: 'NotJobOwner',
// message: 'Operation requires the job owner.',
// relatedSkill: '/kronos-job-lifecycle' }
// Single-line for logs or thrown Error messages.
throw new Error(formatErrorExplanation(e));
// → Error: [NotJobOwner] (132) Operation requires the job owner. See /kronos-job-lifecycleCovers every E_* from contracts/errors.tolk plus common TVM exit codes. Pool-side codes (160-199) route to origin: 'forgeton' with a pointer — install @titon-network/forgeton-sdk and call its explainError for the prose.
Bundled compiled contract
import { loadRegistryCode, KronosRegistry } from '@titon-network/kronos-sdk';
const code = loadRegistryCode();
const registry = KronosRegistry.createFromConfig({ owner, treasury }, code);
// → registry.init.code (BoC) + registry.address (deterministic from init)Ships with the compiled KronosRegistry.compiled.json under artifacts/. For the pool's BoC (ForgeTON.compiled.json), install @titon-network/forgeton-sdk and call its loadForgetonCode().
Examples
See examples/ for full programs:
01-register-job.ts— register a recurring counter-bumper job.02-automaton-register.ts— run an automaton (uses both @titon-network/kronos-sdk + @titon-network/forgeton-sdk).03-decode-events.ts— minimal indexer that prints every registry event.
Working with AI tools
This package ships AI-friendly assets alongside the runtime:
AGENTS.md— terse map of the SDK auto-discovered by Claude Code, Cursor, and other AI editors that follow the agents.md convention. Drop the SDK into your project and your assistant has context.skills/— drop-in.claude/skills/*.mdfiles for Claude Code:Job owner workflow
/kronos-register-job— guided job builder/kronos-job-lifecycle— update / pause / withdraw / cancel/kronos-monitor-events— indexer / alerting recipes
Contract dev workflow (writing the Kronos target)
/kronos-target-receiver— idiomatic Tolk receiver pattern/kronos-gas-sizing— empirically sizegasLimitwith a 20% buffer/kronos-integration-test—SandboxKronosharness patterns/kronos-scaffold— one-shot "bolt Kronos onto my contract"
Operator workflow
/kronos-automaton-setup— run an automaton (uses both SDKs)/kronos-deploy— self-host deployment + housekeeping setup
Debugging / admin
/kronos-debug-exit-code— exit-code triage/kronos-owner-ops— admin operations + timelocked upgrades
(Pool-side skills — stake an automaton, slash, admit a consumer — ship with
@titon-network/forgeton-sdk.)Install all of them in one step:
npx @titon-network/kronos-sdk initThis also prints a CLAUDE.md fragment you can paste into your project's instructions so the AI has day-one context.
JSDoc
@exampleblocks on every public method — your IDE's hover popups show runnable snippets.
Versioning
This SDK ships its own compiled BoC under artifacts/ (regenerated on each prepublishOnly). When the on-chain ABI changes, bump the SDK's major version. While we're pre-mainnet (v0.x), expect breaking changes alongside contract upgrades.
License
MIT.
