titon-network-fortuna-sdk
Python SDK for Fortuna — TON's threshold-BLS VRF. Verifiable randomness for TON, operator-bonded via ForgeTON shared-security and group-key-rotated via Atlas.
1:1 surface parity with @titon-network/fortuna-sdk (TS),
so docs translate by transliteration. Contract wrapper, VRF primitives, event
decoder, error explainer, bundled compiled artifact, live-deployment constants.
Install
pip install titon-network-fortuna-sdk
Requires Python ≥ 3.11.
Connect — testnet vs mainnet (the only difference)
The SDK is network-agnostic. You pick the network in two lines: the LiteServer config and which deployment constant you import.
import asyncio
from pytoniq import LiteBalancer
from fortuna_sdk import Fortuna, FORTUNA_TESTNET
async def main():
# ── testnet ──────────────────────────────────────────────────────
client = LiteBalancer.from_testnet_config(trust_level=2)
await client.start_up()
fortuna = Fortuna.create_from_address(FORTUNA_TESTNET.fortuna, client=client)
health = await fortuna.get_contract_health()
print(f"connected to testnet — paused={health.paused} fee_accumulated={health.config.fee_accumulated}")
await client.close_all()
asyncio.run(main())
For mainnet, change exactly two lines (once Fortuna mainnet is deployed — see Live deployments below):
# ── mainnet ──────────────────────────────────────────────────────────
client = LiteBalancer.from_mainnet_config(trust_level=2) # ← was from_testnet_config
await client.start_up()
fortuna = Fortuna.create_from_address(FORTUNA_MAINNET.fortuna, # ← was FORTUNA_TESTNET
client=client)
That's it. The same wheel, same bytecode, same code paths work on either
network — only the LiteServer config and the deployment constant change.
Pointing at a custom deployment (your own fork, a private chain) is just
Fortuna.create_from_address("0Q...", client=client) — no constant required.
Prefer the safe-by-default helper for scripts: assert_deployment("testnet" | "mainnet") returns the populated FortunaDeployment or raises an actionable
error if the requested network isn't shipped yet (mainnet today).
Three surfaces
1. Contract wrapper
from fortuna_sdk import Fortuna, new_fortuna
# Existing deploy:
fortuna = Fortuna.create_from_address(addr, client=client)
config = await fortuna.get_config()
# Fresh deploy:
fortuna = new_fortuna(
owner=owner_addr, forgeton=forgeton_addr, atlas=atlas_addr, client=client
)
# fortuna.address is derived from the bundled code + init data.
await fortuna.send_deploy(wallet, value=200_000_000) # 0.2 TON
Every on-chain receiver has a send_* method on the wrapper:
send_request_randomness, send_reclaim_request, send_fulfill_randomness,
send_pause, send_unpause, send_update_config, send_withdraw_fees,
send_sync_atlas, send_propose_code_upgrade, send_execute_code_upgrade,
send_cancel_code_upgrade, send_prune_expired_request, send_fortuna_opt_in,
send_fortuna_opt_out. Every getter has a get_* method:
get_ownership, get_paused, get_config, get_schema_versions,
get_operator, get_group_key, get_request, get_pending_upgrade,
get_contract_health.
2. VRF primitives
from fortuna_sdk import compute_alpha, compute_beta, sign_alpha, aggregate_signatures
# Off-chain operator signing:
alpha = compute_alpha(consumer, query_id, seed, creation_lt)
sig = sign_alpha(operator_sk, alpha)
# Off-chain aggregate (threshold ceremony):
agg = aggregate_signatures([sig1, sig2, ...])
# Consumer-side beta verification (after VrfCallback):
beta = compute_beta(agg, consumer, query_id, seed, creation_lt)
assert beta == event_beta_bytes # byte-identical to on-chain
compute_alpha / compute_beta / compute_req_key are byte-identical to the
Tolk on-chain helpers. sign_alpha / aggregate_signatures use
py_ecc.bls.G2ProofOfPossession (min-pk + POP DST), interoperable with the
TS SDK's @noble/curves/bls12-381 longSignatures.
3. Event decoding + error explaining
from fortuna_sdk import decode_events, explain_error, FortunaError
# Decode external-out bodies into the typed FortunaEvent union:
for ev in decode_events(external_out_bodies):
if ev.kind == "RequestFulfilled":
print(f"req {ev.req_key:x} via {ev.submitter}, beta={ev.beta:x}")
# Explain an exit code with structured metadata:
e = explain_error(161)
# ErrorExplanation(code=161, origin='fortuna', name='InvalidBlsSignature', ...)
# Or catch the typed exception:
try:
await fortuna.send_fulfill_randomness(...)
except FortunaError as err:
print(err.code, err.origin, err.hint)
Common task → what to import (cheat sheet)
| Task | Import |
|---|---|
Send RequestRandomness |
from fortuna_sdk import Fortuna, FORTUNA_TESTNET, QueryIdStream |
| Decode events from a tx | from fortuna_sdk import decode_events, RequestCreatedEvent, RequestFulfilledEvent |
| Verify beta client-side | from fortuna_sdk import compute_beta |
| Compute alpha for off-chain signing | from fortuna_sdk import compute_alpha, sign_alpha, aggregate_signatures |
| Owner ops (pause / config / withdraw) | from fortuna_sdk import Fortuna (every receiver has a send_*) |
| Code-upgrade timelock | from fortuna_sdk import Fortuna, MIN_UPGRADE_DELAY_SECONDS |
| Look up an exit code | from fortuna_sdk import explain_error, format_error_explanation |
| Catch SDK-side reverts | from fortuna_sdk import FortunaError, SchemaDriftError |
| Schema drift pre-flight | await fortuna.validate_against_live() |
| Bundle a tx into success/failure/events | from fortuna_sdk import summarize_tx_from_parts, format_tx_summary |
| Live testnet address book | from fortuna_sdk import FORTUNA_TESTNET, FORTUNA_MAINNET, assert_deployment |
| Bundled compiled BoC for a fresh deploy | from fortuna_sdk import load_fortuna_code, fortuna_code_hash, new_fortuna |
⚠️ Deploy from Python is partial. new_fortuna(...) builds the init
cell + bundles the BoC, and you can drive send_deploy from any
pytoniq.BaseWallet. But admitting Fortuna at Atlas + ForgeTON
requires their TS SDKs (no Python ports yet) — finish the wire-up
from ../../scripts/deployFortunaScripted.ts + wireFortunaScripted.ts.
⚠️ Operator off-chain signer is TS-only. Python ships the crypto
primitives (sign_alpha, aggregate_signatures), but the production
operator daemon, keystore, metrics, and event drain live in
automaton (TypeScript). Don't roll your own in
Python unless you have a strong reason.
Surface parity with TypeScript
Same names (snake_case mirror), same shapes. Translate any TS docs/example by mechanical transliteration:
| TypeScript | Python |
|---|---|
Fortuna.createFromAddress(addr) |
Fortuna.create_from_address(addr, client=client) |
Fortuna.createFromConfig(cfg, code) |
Fortuna.create_from_config(cfg, code, client=client) |
fortuna.sendRequestRandomness(via, opts) |
await fortuna.send_request_randomness(wallet, **opts) |
fortuna.getRequest(consumer, queryId) |
await fortuna.get_request(consumer, query_id) |
computeAlpha(consumer, q, seed, lt) |
compute_alpha(consumer, q, seed, lt) |
decodeEvents(bodies) |
decode_events(bodies) |
explainError(161) |
explain_error(161) |
summarizeTx(tx) |
summarize_tx(tx) |
FORTUNA_TESTNET.fortuna |
FORTUNA_TESTNET.fortuna |
assertDeployment("testnet") |
assert_deployment("testnet") |
See AGENTS.md for the full surface map + skills/ for persona-grouped recipes (consumer / owner / debug).
Project links
- Fortuna contract source
- TS SDK — canonical, this is a port
fortuna/CLAUDE.md— repo-wide AI development guidefortuna/DESIGN.md— architecture decisions D-001..D-019
🛡️ Audit posture. Built on the Fortuna contract that cleared TSA static
analysis with zero findings — see
AUDIT_REPORT.md.
The SDK is a transliteration of the audited TS SDK; same byte-for-byte alpha /
beta / request-key derivation.
Metadata
Release files for titon-network-fortuna-sdk 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| titon_network_fortuna_sdk-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Release files / titon_network_fortuna_sdk-0.4.0-py3-none-any.whl
| Download URL | titon_network_fortuna_sdk-0.4.0-py3-none-any.whl |
|---|---|
| Size | 41.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b5fcd49cee1bc4dea7192a77bae091c61593975f3440881b6388d90ddc1dc409
|
|
BLAKE2b-256 checksum How to use checksums |
443385ec2b676234709149b8b6d418610b3a19b7c8de4faec0c135fb5a8c7ac6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.3
|