Repository navigation
Releases
v0.25.51 PQ CORE SmartHoldem
🐞 Fixed:
• PQ delegates no longer lose their PQ status - announces are verified against the originating node, not the last relay hop
• A node no longer "forgets" a peer after a failed first connection
✨ Improved:
• ⚡ At startup the node picks the fastest relay and skips a dead one instead of waiting 30s
• 🔍 Metrics now show why a delegate is not PQ: no announce, stale announce, bad signature, clock skew…
• 🕸 More direct neighbours in the gossip mesh
• 🧰 Build on 2–4 GB RAM machines: cargo build --profile release-lowmem -j 2
🛠 Upgrade: stop the node -> replace the binary -> start. No config or database changes.
Download
wget http://smartholdem.io/download/core/sth-core
Mandatory Network Upgrade
Important
This is a mandatory (consensus-breaking) release. All delegates, relay nodes, exchanges, explorers and wallet
backends must upgrade to v0.25.50 before block height 12,500,000. Nodes running an older version will reject
blocks produced under the new rules and will fork off the network.
| Version | 0.25.50 |
| Activation height | 12,500,000 |
| Minimum core version after activation | 0.25.50 |
| Upgrade type | Hard fork (milestone activation) |
| Urgency | 🔴 High required for all node operators |
Block time is 8 seconds (~10,800 blocks per day). Check the current height via
curl -s $API/api/blockchain | jq .data.block.height and plan your upgrade with a safe margin.
✨ Highlights
At block 12,500,000 the SmartHoldem mainnet activates a major set of protocol features that turn STH into a
multi-asset network with a built-in on-chain market.
🪙 Native Tokens (typeGroup 3, SHIP-14)
First-class tokens issued directly on the SmartHoldem blockchain no smart contracts, no extra layers.
- TokenInit register a token with its ticker, decimals, initial supply, supply cap and
mintable/burnableflags. - TokenTransfer send tokens to one or many recipients in a single transaction.
- TokenMint issue additional supply (only if the token is mintable, never above the cap).
- TokenBurn permanently destroy tokens (only if the token is burnable).
- TokenMeta publish an on-chain token manifest (name, description, logo, links).
📈 Token Market on-chain DEX (SHIP-43)
A native order book for tokens, settled in STH by consensus.
- TokenSell place a sell order.
- TokenBuy fill an order; tokens and STH are swapped atomically in the same block.
- TokenCancel withdraw an open order.
- Up to 64 open orders per seller per token.
🧩 SmartObjects V2
ntfryDatanow accepts any UTF-8 payload up to 255 bytes.- SmartObject names of type 5 act as token tickers (unique ticker registry).
- SmartObjects can be transferred, listed for sale and bought directly on-chain.
🛡️ Strict Balance Enforcement
- A block containing any transaction whose
amount + feeis not covered by the sender's balance is rejected
(InsufficientBalanceError). This closes edge cases inherited from the legacy core.
🔥 Token Economics & Burn
Registering a token is intentionally priced as a premium action to protect the ticker namespace from spam and to make
STH deflationary.
- Token registration (TokenInit) costs
5,000 STH. - 50% of the registration fee
2,500 STHis burned forever (sent to the network burn address and removed
from circulating supply). The remaining 50% goes to the forging delegate. - The burn share (
tokenFees.initBurnPercent) is a milestone parameter (SHIP-17) and can be changed by a future
milestone without a code fork. - The total burned supply is visible on the node API and in
sth-cli status.
Fee schedule after block 12,500,000
| Operation | Fee (STH) | Notes |
|---|---|---|
| TokenInit (token registration) | 5,000 | 50% burned (2,500 STH) |
| TokenTransfer | 0.1 | + 0.01 per additional recipient |
| TokenMint | 1 | |
| TokenBurn | 0.1 | |
| TokenMeta (on-chain manifest) | 100 | |
| TokenSell (place order) | 1 | |
| TokenBuy (fill order) | 0.1 | |
| TokenCancel (cancel order) | 0.1 |
Fees are exact (static): a transaction paying a different fee is rejected with StaticFeeMismatchError.
⚙️ Changes Since v0.25.49
- Mainnet activation height moved from
12,365,000to12,500,000. - Default token registration fee (
tokenFees.init) raised from 500 STH -> 5,000 STH. - TokenMeta fee raised from 10 STH -> 100 STH.
minCoreVersionbumped to0.25.50.
Activation milestone (embedded in the binary, network/mainnet/milestones.json):
{
"height": 12500000,
"tokens": true,
"sobjV2": true,
"strictBalance": true,
"tokenFees": { "meta": 10000000000 },
"tokenMarket": { "enabled": true, "maxOpenOrders": 64 },
"minCoreVersion": "0.25.50"
}Warning
If your node uses a custom network_dir in node.yaml, you must replace the old 12365000 milestone in your
own milestones.json with the entry above. A mismatch will fork your node.
🛠️ Upgrade Instructions
- Stop the node.
- Replace the binary with v0.25.50 (or rebuild from source:
cargo build --release). - Update your custom
milestones.jsonif you usenetwork_dir(see above). - Start the node and verify the version and milestone:
Before height 12,500,000 the flags are
curl -s $API/api/node/configuration | jq '.data.constants | {tokens, sobjV2, strictBalance, minCoreVersion}'
false; after activation they becometrue. - Delegates: confirm on
/metrics-> Delegates that your node is shown as up to date (no "outdated" badge).
No database resync is required.
Who must upgrade
| Role | Action |
|---|---|
| Forging delegates | Required otherwise blocks are rejected and slots are missed |
| Relay / API nodes | Required otherwise the node forks off |
| Exchanges & custodians | Required update deposit/withdrawal monitoring for typeGroup 3 transactions |
| Explorers & wallets | Required support the new token and market transaction types |
| Legacy Node.js relays | Must be replaced with Rust nodes they cannot process typeGroup 3 blocks |
⚠️ Compatibility Notes
- Nothing is created automatically at the activation height; the first token is registered by a regular TokenInit
transaction. - Transactions of typeGroup 3 sent before height 12,500,000 are rejected.
- REST API remains 100% backward compatible; new endpoints cover tokens and the token market.
✅ Verification
cargo test --lib config::tests::mainnet_tokens_market_at_12_500_000
cargo testAll 209 tests pass on this release.
Thank you to all delegates and node operators for keeping the SmartHoldem network secure.
Please upgrade early and report any issues in the GitHub issue tracker.
v0.25.50 PQ CORE SmartHoldem
Mandatory Network Upgrade
Important
This is a mandatory (consensus-breaking) release. All delegates, relay nodes, exchanges, explorers and wallet
backends must upgrade to v0.25.50 before block height 12,500,000. Nodes running an older version will reject
blocks produced under the new rules and will fork off the network.
| Version | 0.25.50 |
| Activation height | 12,500,000 |
| Minimum core version after activation | 0.25.50 |
| Upgrade type | Hard fork (milestone activation) |
| Urgency | 🔴 High required for all node operators |
Block time is 8 seconds (~10,800 blocks per day). Check the current height via
curl -s $API/api/blockchain | jq .data.block.height and plan your upgrade with a safe margin.
✨ Highlights
At block 12,500,000 the SmartHoldem mainnet activates a major set of protocol features that turn STH into a
multi-asset network with a built-in on-chain market.
🪙 Native Tokens (typeGroup 3, SHIP-14)
First-class tokens issued directly on the SmartHoldem blockchain no smart contracts, no extra layers.
- TokenInit register a token with its ticker, decimals, initial supply, supply cap and
mintable/burnableflags. - TokenTransfer send tokens to one or many recipients in a single transaction.
- TokenMint issue additional supply (only if the token is mintable, never above the cap).
- TokenBurn permanently destroy tokens (only if the token is burnable).
- TokenMeta publish an on-chain token manifest (name, description, logo, links).
📈 Token Market on-chain DEX (SHIP-43)
A native order book for tokens, settled in STH by consensus.
- TokenSell place a sell order.
- TokenBuy fill an order; tokens and STH are swapped atomically in the same block.
- TokenCancel withdraw an open order.
- Up to 64 open orders per seller per token.
🧩 SmartObjects V2
ntfryDatanow accepts any UTF-8 payload up to 255 bytes.- SmartObject names of type 5 act as token tickers (unique ticker registry).
- SmartObjects can be transferred, listed for sale and bought directly on-chain.
🛡️ Strict Balance Enforcement
- A block containing any transaction whose
amount + feeis not covered by the sender's balance is rejected
(InsufficientBalanceError). This closes edge cases inherited from the legacy core.
🔥 Token Economics & Burn
Registering a token is intentionally priced as a premium action to protect the ticker namespace from spam and to make
STH deflationary.
- Token registration (TokenInit) costs
5,000 STH. - 50% of the registration fee
2,500 STHis burned forever (sent to the network burn address and removed
from circulating supply). The remaining 50% goes to the forging delegate. - The burn share (
tokenFees.initBurnPercent) is a milestone parameter (SHIP-17) and can be changed by a future
milestone without a code fork. - The total burned supply is visible on the node API and in
sth-cli status.
Fee schedule after block 12,500,000
| Operation | Fee (STH) | Notes |
|---|---|---|
| TokenInit (token registration) | 5,000 | 50% burned (2,500 STH) |
| TokenTransfer | 0.1 | + 0.01 per additional recipient |
| TokenMint | 1 | |
| TokenBurn | 0.1 | |
| TokenMeta (on-chain manifest) | 100 | |
| TokenSell (place order) | 1 | |
| TokenBuy (fill order) | 0.1 | |
| TokenCancel (cancel order) | 0.1 |
Fees are exact (static): a transaction paying a different fee is rejected with StaticFeeMismatchError.
⚙️ Changes Since v0.25.49
- Mainnet activation height moved from
12,365,000to12,500,000. - Default token registration fee (
tokenFees.init) raised from 500 STH -> 5,000 STH. - TokenMeta fee raised from 10 STH -> 100 STH.
minCoreVersionbumped to0.25.50.
Activation milestone (embedded in the binary, network/mainnet/milestones.json):
{
"height": 12500000,
"tokens": true,
"sobjV2": true,
"strictBalance": true,
"tokenFees": { "meta": 10000000000 },
"tokenMarket": { "enabled": true, "maxOpenOrders": 64 },
"minCoreVersion": "0.25.50"
}Warning
If your node uses a custom network_dir in node.yaml, you must replace the old 12365000 milestone in your
own milestones.json with the entry above. A mismatch will fork your node.
🛠️ Upgrade Instructions
- Stop the node.
- Replace the binary with v0.25.50 (or rebuild from source:
cargo build --release). - Update your custom
milestones.jsonif you usenetwork_dir(see above). - Start the node and verify the version and milestone:
Before height 12,500,000 the flags are
curl -s $API/api/node/configuration | jq '.data.constants | {tokens, sobjV2, strictBalance, minCoreVersion}'
false; after activation they becometrue. - Delegates: confirm on
/metrics-> Delegates that your node is shown as up to date (no "outdated" badge).
No database resync is required.
Who must upgrade
| Role | Action |
|---|---|
| Forging delegates | Required otherwise blocks are rejected and slots are missed |
| Relay / API nodes | Required otherwise the node forks off |
| Exchanges & custodians | Required update deposit/withdrawal monitoring for typeGroup 3 transactions |
| Explorers & wallets | Required support the new token and market transaction types |
| Legacy Node.js relays | Must be replaced with Rust nodes they cannot process typeGroup 3 blocks |
⚠️ Compatibility Notes
- Nothing is created automatically at the activation height; the first token is registered by a regular TokenInit
transaction. - Transactions of typeGroup 3 sent before height 12,500,000 are rejected.
- REST API remains 100% backward compatible; new endpoints cover tokens and the token market.
✅ Verification
cargo test --lib config::tests::mainnet_tokens_market_at_12_500_000
cargo testAll 209 tests pass on this release.
Thank you to all delegates and node operators for keeping the SmartHoldem network secure.
Please upgrade early and report any issues in the GitHub issue tracker.
Download
wget http://smartholdem.io/download/core/sth-core
v0.25.48 PQ CORE SmartHoldem
v0.25.45 PQ CORE SmartHoldem
v0.25.44 SmartHoldem PQ CORE
Changed metrics page: a resolved fork no longer screams
- The forging card showed
⚠ SAME-HEIGHT FORK … other side must roll backuntil restart. Once finality has passed
the height (or the chain is 21+ blocks beyond it) the line turns into a dimfork at H (A vs B) resolved by the majority chain, finalized · 3h ago, and disappears after 24 h. The wording no longer talks about legacy rollbacks.
Changed sth-core init defaults for an all-PQ network
discovery.listen_port: 4004(was 0 = random): a fixed public UDP port gives stable direct dial info across restarts
and makes relay-down mode effective. Open UDP 4004 in the firewall.p2p.legacy_listen: "0.0.0.0:4001"(was empty): every node serves blocks over the legacy TCP port too the second,
relay-independent transport that carried the chain through the gossip outage. Open TCP 4001. A bind failure is
logged, not fatal. Existing node.yaml files are not changed set both values by hand.
Changed SAME-HEIGHT FORK alert names the culprit
- The alert now says which block came first and therefore whose node failed to spread it (
X's block (slot …) did not reach Y within 8s, so Y forged the same height), that the majority chain wins and the minority rolls back on its own,
and what to check on X's node (gossip neighbours,peers[].path = direct, record on the PQ MAP, clock). The legacy
rollback hint is gone there are no legacy forgers left.
First Start
sth-core init
sth-core run --p2pv0.25.41 PQ CORE SmartHoldem
Full Changelog: 0.25.23...0.25.41
v0.25.26 PQ CORE SmartHoldem
live audit, mempool antispam, security audit fixes
This release prepares the network for the final step of the transition: all 21 forging delegates on the Rust PQ node,
legacy Node.js nodes remaining as relay / API only. It adds operator tooling that no longer requires stopping a delegate,
a spam policy for the transaction pool, and closes the findings of a static security audit of the whole codebase.
Highlights
audit-all without stopping the node
sth-core audit-all [--fix] used to need systemctl stop because Sled holds an exclusive lock on the database - for a
forging delegate that meant missed slots. Now the CLI detects a running node and asks it to audit itself through the local
API (POST /api/ntfry/audit, loopback only, refused behind a reverse proxy) and follows the progress:
node is running -> live audit through http://127.0.0.1:4003 (forging is not interrupted)
scanned 10,000 of 11,990,120 blocks…
height 11,990,131 · wallets 61,204 · compared 61,190 · skipped 14 · drifted 0 · 143.2s (live)
OK: every wallet matches the chain history
The full chain walk and the wallet comparison run without any lock; the block-apply lock is held only for milliseconds to
book the blocks applied meanwhile and re-check the candidates. A fork rollback during the scan aborts the audit safely.
--fix repairs drifted wallets and rebuilds the vote index in the same critical section. Nightly cron no longer needs a
stop/start pair: sth-core audit-all --fix --json.
sth-core alerts-test
Validates the Telegram bot token, lists the chats the bot has seen when telegram_chat_id is empty (ready to paste into
node.yaml), otherwise sends a test message to Telegram and the webhook and explains any refusal (chat not found ->
send the bot /start).
mempool.antispam - pool policy against cheap-transaction floods
Never consensus: blocks are not judged by it. All values are defaults in node.yaml:
mempool:
antispam:
enabled: true
max_per_sender: 64 # pending transactions per address (a same-nonce replacement does not count)
max_bytes_per_sender: 65536
peer_tx_per_min: 600 # per legacy IP / iroh EndpointId; the rest of a batch is dropped before verification
peer_invalid_per_min: 20 # bad signatures / bytes / network (nonce faults are NOT counted) -> ban for ban_secs
ban_secs: 3600 # transactions only - blocks from that peer are still accepted
replace_by_fee: true # full pool: higher fee-per-byte evicts the cheapest pending (a sender's last nonce only)
deny_peers: [] # IPs / EndpointIds always dropped
alert_drops_per_min: 500 # alert `spam-wave`Metrics: mempool.antispam { counters, banned[], topSenders[] } in /api/ntfry/metrics and a line in the mempool card.
New rejection code ERR_SPAM_SENDER.
Security audit
A static (read-only) security review of the codebase was performed before the delegate migration. Consensus-critical
controls were confirmed: a block cannot be applied twice or off the tip (apply lock + in-transaction tip check), every
network intake path verifies signatures before touching state, lock poisoning cannot panic the node, the iroh address
book is signed and bounded, balance arithmetic is checked i128. No fund-theft or consensus fault was found. Three
availability findings were fixed in this release:
| Severity | Finding | Fix |
|---|---|---|
| High | Legacy P2P postBlock / getBlocks used the header's numberOfTransactions for Vec::with_capacity before any check - a peer could make the node reserve gigabytes and abort |
count bounded by the milestone maxTransactions and by the payload size before allocating |
| Medium | Legacy P2P postTransactions accepted unlimited transactions per request (unbounded ML-DSA verification); WebSocket messages up to 64 MiB |
40 transactions per request (legacy maxTransactionsPerRequest), 20 MiB message / frame cap (legacy maxPayload) |
| Medium | The new live audit held the apply lock across a full wallet scan | comparison moved out of the lock; only candidates re-checked under it |
| Low | &block_id[..8] could panic on a short id |
get(..8) |
Upgrade
systemctl stop sth-core && cp sth-core /usr/local/bin/ && systemctl start sth-core
sth-core alerts-test # optional: verify Telegram / webhook
sth-core audit-all # optional: state check on the running node
No database migration; node.yaml gains the mempool.antispam section with defaults (nothing to change).
Versions 0.25.24 -> 0.25.26 (0.25.25 = live audit + alerts-test, 0.25.26 = antispam + audit fixes).
For Update on Linux:
pm2 stop all # stop legacy node
pm2 delete all
pm2 delete 0
pm2 save
mkdir pq-node
cd pq-node
wget http://smartholdem.io/download/core/sth-core
chmod +x sth-core
wget http://smartholdem.io/download/core/snapshot/data.tgz
tar -zxvf data.tgz
rm data.tgz
./sth-core init
./sth-core run
v0.25.23
First work release
for update
pm2 stop all # stop legacy node
pm2 delete all
pm2 delete 0
pm2 save
mkdir pq-node
cd pq-node
wget http://smartholdem.io/download/core/sth-core
chmod +x sth-core
wget http://smartholdem.io/download/core/snapshot/data.tgz
tar zxvf data.tgz
rm data.tgz
./sth-core init
./sth-core run
SmartHoldem CORE v0.9.0
SmartHoldem Core on Rust
sth-core init
sth-core run --p2p
or
CORE_API_HOST=0.0.0.0 CORE_API_PORT=4003 nohup ./sth-core --db-path ./data run --p2p &
tail -f nohup.outNETFORY WEB4 Decentralized Network based on SmartHoldem
SmartHoldem CORE v0.8.1
First implementation SmartHoldem Core on Rust
sth-core init
sth-core run --p2p
or
CORE_API_HOST=0.0.0.0 CORE_API_PORT=4003 nohup ./sth-core --db-path ./data run --p2p &
tail -f nohup.out