[V136] feat: add BNB Smart Chain (BSC)
Summary
Part 4 of the V136 chain stack (00 to 06), tracked in #325. This MR targets v136/02-bifrost-evm-hardening. Review only the commits on this branch; the parent branches have their own MRs.
This branch adds BNB Smart Chain: chain BSC, gas asset BSC.BNB, router MAYAChain_Router 4.1, and a BSC client on the generic EVM client. BSC is the first chain behind the chain-activation gate from branch 00. Every BSC path stays inert until the network version reaches 1.136.0.
It also creates the shared V136 address functions (NewAddressV136, IsChainV136, getChainV136, getNetworkV136), which branches 04 to 06 extend.
Changes
Consensus ([V136])
6b630ab74common:BSCChainandBNBBEP20Asset(BSC.BNB); the legacy BeaconBNBChain/BNB.BNBare untouched.- Every BSC case in an unversioned predicate is reached only when
BSCChain.IsActivated():GetEVMChains(soIsEVM,AddressPrefix),ChainsThatCanBundleMultipleBlocks,GetGasAsset,GetGasUnits(centigwei, 1e11),DustThreshold(10_000 = 0.0001 BNB),ApproximateBlockMilliseconds(450),InboundNotes. PubKey.GetAddressreturnsNoAddressfor an inactive chain, before the cache.- V136 address set with 1.136.0 dispatch. BSC needs no new parsing rule, so three functions delegate to the V135 bodies;
getChainV136adds BSC after ETH.NewAddressV130,IsChainV130,getChainV129,getNetworkV130move unchanged toaddress_archive.go. migrateLegacy1e8TransactionFeeRate(v127 migrations only) skips inactive chains, so a replay with BSC inAllChainsis identical.
ff6a4fe88Token list: empty below 1.136.0. From 1.136.0: Binance-Peg USDT0x55d398326f99059fF775485246999027B3197955and USDC0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d, both 18 decimals (same as THORNode's list).29f882e30SUPPORT_CHAINS_VCURRENTgains BSC (gated at 1.136.0 by 00).getRouterChainsadds BSC at 1.136.0. BSC routers ship empty on every network; mainnet/stagenet are filled in a later release that gates the address on the network version, and the first router is then set withMimirUpgradeContractBSC=1, as for ARB.MaxBSCGas= 0.5 BNB.a3b389663handler_tsshandleV136: a router chain without a chain contract stays out of a new vault. Without it, a keygen that reached consensus on BSC before the router was set would create a vault that ignores router deposits and cannot sign BSC outbounds.handleV135moves unchanged tohandler_tss_archive.go.40628aa48migrateStoreV136removes the stale mainnetBSC.BNBpool: Staged, zero balances, 20000000 pending CACAO (0.002 CACAO) from one LP. It acts only on that exact shape, refunds the pending CACAO from Asgard, deletes the records and emits the events, in one cache context. Mainnet, stagenet and mocknet run it; regtest only with mimirV136TestStalePool.333c1338bgetChainV136lists BSC only once it is activated, so an unrecognised address never maps to BSC on a 1.135 network.
Bifrost ([Version-unspecific])
bc973cde2BSC client on the generic EVM client:- Finality:
finalizedtag (BEP-126) with 3 confirmations as fallback. - Chain id 56 on mainnet/stagenet, 1397 on mocknet.
- Receipts via
eth_getBlockReceipts. - Non-vault native transfers below dust are dropped (THORNode parity; only BSC has a non-zero EVM threshold).
- Config:
bscblock (disabled: true, reorg rescan 600 blocks, gas resolution 5 centigwei, USDT/USDC whitelist), env binds, metrics label. - BSC is removed from
haltchain_known_thorchain_chains. - A coinless observation is split on its own chain, not the legacy
BNBChain.
- Finality:
31eede9fcBSC confirmations are a built-in rule (finalized, 3), so branch 02's pinning stops an operator override on mainnet/stagenet. THORNode hardcodes 3.
Mocknet, simulation, docs, tests
eba2153beinit_chainrestoredIFSto the literal "IFS", so later expansions split on those letters. BSC was the first chain name to hit it (bug since 2022).a6a56ab55Mocknetbinance-smartservice:ethereum/client-go:v1.16.3pinned by digest, dev genesis with chain id 1397. Router 4.1 is deployed at genesis.32b85ac92BSC in the static simulation suite.b6d0cea4bdocs/chains/bsc.md(parameters, activation, launch runbook, user notes),docs/mimir.md(MAXGAS-<CHAIN>, first-router rule),docs/newchain.md.21d04142fBSC mainnet header hash fixture for the reorg check.8b5aaf2ceTHORChain halts of BSC no longer propagate.a9ae3c5ddThebscconfig block resetsusername/password, so BSC does not inherit the BTC daemon credentials (see Review follow-up).bc7523a34Regression suitesbsc/bsc-swaps,bsc/bsc-activation,bsc/bsc-stale-pool, and regenerated exports.
Versioning / consensus impact
[V136]commits change state-machine behaviour from 1.136.0: chain predicates, address functions, token list, support and router chains,handler_tss, router upgrade (upgradeContractV136), store migration 136.- Archived unchanged:
NewAddressV130,IsChainV130,getChainV129,getNetworkV130(common/address_archive.go);handleV135(x/mayachain/handler_tss_archive.go);upgradeContractV109(x/mayachain/router_upgrade_controller_archive.go). No#check-lint-warning. - Below 1.136.0, every BSC predicate answers as for an unknown chain, and BSC observations fail
ValidateBasic(code 6). - Export changes all come from the genesis single-node vault, which is built from
SUPPORT_CHAINS_VCURRENTand now listsBSC. Affected: the vault chain list, the resulting block hashes, andgas_used+15 to +360 on txs touching that vault. Exports were regenerated withEXPORT=1, not hand-edited.
THORNode references and parity
- Parity audit of 02 + 03 against THORNode
cd6c065108. The bifrost findings are fixed on branch 02 (A10fced2f7b4, A2cc8097283b, A389c6fc0f64, A55294ac17fc, A61df4d86490, D1-D3). The BSC confirmations rule is fixed here. - BSC confirmations: THOR
4a8c22818hardcodes 3. MAYA waits for thefinalizedtag and votes 3. - Dust filter matches THOR
6a474b90c5. - Separate MRs on
develop: signer-cache expiry (!880 (merged), THORa759cb4f99; BSC is covered throughevm/) and RPC auth (!881 (merged), THOR337d8f9317). - Intentional divergences:
- router 4.1: no direct-ERC20 inbound, no memoless outbound;
centigweigas units, resolution 5;- cancel txs observed as
ETH.ETH, the same as ARB; - a two-token whitelist, no aggregators, no short codes.
- MAYA-only:
handler_tssrouter guard;- stale-pool migration;
- activation gate;
- a unique mocknet chain id;
- BSC in the simulation (THOR excludes it).
Tests
- Unit tests:
- activation-rule table: every predicate for BSC at 1.135.0 with enforcement on, and with the version unset, equals the answer for an unknown chain;
- V136 address functions and the
GetAddressguard; MsgObservedTxIn/Outfail with code 6 at 1.135.0 and pass at 1.136.0;- token list, support and router chains,
handler_tssguard; migrateStoreV136: refund, idempotent rerun, untouched other shapes, Asgard shortfall;- BSC client: dust, receipts method, finality, chain id, header hash.
make test, lint (gofumpt, versions, handlers, managers, analyze, erc20s) pass.- Regression, full
EXPORT=1run on this branch: 144/144, 0 failures, exports unchanged from the committed ones. - Mocknet: full static simulation passes (214 actors, 46 min), including the BSC actors. Run on 2026-10-17. It ran before the THOR-parity fixes; the regression run above includes them. The run found the
IFSbug and a--networkidflag geth rejects with--dev; both are fixed here.
Deferred / open items
- Keygen chain-set preflight and churn-out (THOR
25989b1f7e,161ba2adaf): consensus, planned for V137. Meanwhile, the runbook checks that BSC is enabled on every Ready node before the first BSC churn. - "Missing tx out" security-event exemption for BSC failed-outbound placeholders: consensus event, V137. Until then each reverted BSC outbound emits a security event (pre-existing for ARB).
- Scanner prefetch for 0.45 s blocks (THOR
bd66c5254,f6abd5730): measure scan lag on a mainnet node first. - Mainnet/stagenet
bscNewRouterafter deployment, gated on the network version of that release. node-launcher and Midgard changes. - Quote confirmations for BSC (
querier_quotes.gois ETH-only), hardhat fee-on-transfer tests, smokebsc.py. - When !881 (merged) lands: the
bscconfig section needsusername: ""andpassword: "", like the other EVM chains.
Operator notes
Launch runbook in docs/chains/bsc.md, in order:
- Before the release is published, set
HaltBSCChain=1andHaltBSCTrading=1and verify them on/mayachain/mimir(both are unset on mainnet and stagenet today). - The network reaches 1.136.0; verify both halts are still set and clear the stale
HALTBSCCHAINAGG. - Deploy and verify the router, ship
bscNewRouterin a release that gates it on the network version, then setMimirUpgradeContractBSC=1. - Set
MAXGAS-BSC=1000000. - Operators set
BSC_DISABLED=false(chain id 56). - Churn, unhalt, seed the pool, unhalt trading.
migrateStoreV136 refunds the mainnet stale-pool LP 0.002 CACAO automatically at the upgrade.
CI follow-up
0f00294a1drops the duplicate H1 fromdocs/chains/bsc.md(markdownlint MD025, the front matter has the title) and applies prettier.
Review follow-up
a9ae3c5ddresetsusername/passwordin thebscconfig block. The default-chain anchor carries the BTC daemon credentials, and with the EVM RPC auth from !881 (merged) an EVM chain that keeps them sends them as basic auth. The !881 (merged) config test (EVM chains fromcommon.GetEVMChains) covers BSC once both branches are merged.- The
docs/newchain.mdactivation intro suggestion is in !883 (4a628e537).
Review follow-up (round 4)
0effed2b3:upgradeContractV136: from 1.136.0 aMimirUpgradeContract<CHAIN>for a router chain with no new router in the binary is deleted instead of staying set until a later release fills in the address (BSC everywhere, AVAX on mainnet/stagenet).upgradeContractV109moves unchanged to the archive. ThebscNewRoutercomment and the docs require the address release to be version-gated.8220b85f3: runbook: set and verifyHaltBSCChain=1/HaltBSCTrading=1before the release and before BSC is enabled (they are unset on mainnet and stagenet). The later step only verifies them and clearsHALTBSCCHAINAGG.HaltBSCChainstops observation but not the first BSC churn.a5b2dee6a: BSC reorg recovery and restart tests on the shipped BSC scanner config (eth_getBlockReceipts, block-hash check, 600-block rescan). The reorg and version-lookup fixes they exercise are on !885 and !883.
Follow-up after review round 4
572b65740:upgradeContractV136also deletesMimirUpgradeContract<CHAIN>when the new router is already the chain's router (nothing left to do) and when the old router is not the chain's router (this release cannot upgrade it; set the mimir again after a release with the right old router). Before, the mimir stayed set and the check logged every block. Below 1.136.0 nothing changes. Test:TestRouterUpgradeStuckMimir(1.135.0 keeps the mimir, 1.136.0 deletes it, in both cases).