Decentralized mining pools, federations and confederations for BTC, DOGE, BCH and LTC. FROST threshold signatures, on-chain SPV verification, and CrossCurve integration for native BTC yield.
SatoshiFi is a system of decentralized mining pools and BTC federations built on smart
contracts deployed to Ethereum (or compatible EVM networks). The project combines two layers:
local mining pools (MiningPoolDAO) governed by a DAO of operators and miners,
and global coordination through MultiPoolDAO for each native network
(Bitcoin, Dogecoin, Bitcoin Cash, Litecoin).
All critical processes — reward distribution, UTXO verification, signature formation — happen on-chain. No single point of failure, no custodial model in the traditional sense: native BTC remains on federation addresses secured by threshold signatures, while ERC-20 wrappers (mpToken and sToken) circulate freely in DeFi.
Each pool is an independent DAO, unified through MultiPoolDAO for global coordination. Governance is enforced through NFT/SBT roles.
On-chain verification of every UTXO and block header. Reward distribution through open calculators: PPS, FPPS, PPLNS, Score.
FROST threshold signatures (Schnorr/BIP340), Distributed Key Generation on-chain, staking and slashing for oracles.
BTC (SimpleSPVContract), Dogecoin (SPVContractDogecoin), BCH, LTC — each network with its own sToken (SBTC, SDOGE, SBCH, SLTC).
IDistributionScheme interface.
The system is built as a modular stack of contracts on Ethereum. Each layer addresses a
specific concern and exposes an interface to the next. Separating local pool management
(MiningPoolDAO) from global coordination (MultiPoolDAO) is the
principal architectural decision that allows the system to scale across networks and pools.
| Layer | Responsibility |
|---|---|
| Bitcoin / SPV | Block header validation, coinbase maturity, Merkle inclusion, and pool UTXO registry |
| Pool | BTC reward registration, MP token minting, redemption escrow and refund |
| MultiPool | MP token aggregation, share token issuance, per-pool balance tracking |
| Federation | DKG, FROST authorization, pool management, governance |
| Confederation | Federation aggregation plus custodian threshold |
| Fees | Wrap/unwrap fee calculation, NFT discounts, fee distribution among participants |
Parsing and verification of Bitcoin structures: blocks, headers, target calculations, Merkle proofs.
Single-call deployment of pools, mpToken, sToken, FROST multisigs and MultiPool contracts.
ERC-20 tokens with 1:1 backing to the native asset. Access gated by PoolMembershipNFT.
Implementations of IDistributionScheme tracked by a single CalculatorRegistry with whitelisting.
Share-data collection and validation from Stratum providers, with staking and slashing to enforce honest behavior.
Coordination of Schnorr/BIP340 threshold signatures. Bootstrapped via cryptographic FROST after DKG.
SimpleSPVContract verifies block maturity (≥ 100 confirmations) and Merkle inclusion of the transaction.
MiningPoolDAO records the confirmed coinbase UTXO in its internal registry.
PoolMpToken.mint issues mpTokens.
depositMpToken. SafeERC20 transfers tokens, with an optional wrap fee withheld.
constructRedemptionTx builds a Bitcoin transaction; an off-chain MPC signer broadcasts it on the native network.
All contracts are written in Solidity ^0.8.19 using OpenZeppelin libraries
(AccessControl, ReentrancyGuard, Pausable, SafeERC20).
Below is a summary of the key contracts and their public APIs.
Local pool management: share intake, SPV block verification, reward distribution through
calculators, mpToken issuance, and interaction with MultiPoolDAO for sToken conversion.
Key data structures:
UTXO — txId, vout, amount, spentLockedTokens — networkId, owner, amount, powScript, txId, createdAt, deadline, statusPoolInfo — poolId, networkId, payoutScript, activeNetworkConfig — active, spv, sTokenBacking — totalReceived, reserved, availablePrimary functions:
initialize(address frostAddress, bytes groupPub, uint64 redemptionTimeout, address slashRecv)
setNetwork(uint8 networkId, address spvAddr, address sToken, bool active)
registerPool(bytes32 poolId, uint8 networkId, bytes payoutScript, address operator)
receiveReward(bytes32 poolId, bytes blockHeaderRaw, bytes txRaw, uint32 vout,
bytes32[] merkleProof, uint8[] directions)
mintSTokenWithProof(bytes32 poolId, bytes blockHeaderRaw, bytes txRaw, uint32 vout,
bytes32[] merkleProof, uint8[] directions, uint256 amount, address recipient)
burnAndRedeem(uint8 networkId, uint256 amount, bytes powScript)
withdrawFees(uint8 networkId, address to, uint256 amount)
Stores block headers, tracks the mainchain by cumulative work, and verifies transaction
inclusion through Merkle proofs. It also owns the pool UTXO registry. Admin functions are
restricted to spvAdmin; test helpers can be permanently disabled via
disableTestHelpers().
function addBlockHeader(bytes calldata rawHeader) public
function disableTestHelpers() external
function setPoolContract(address poolContract) external
function transferSPVAdmin(address newAdmin) external
function isMature(bytes32 blockHash) external view returns (bool)
function isMatureWithDepth(bytes32 blockHash, uint32 confirmationDepth) external view returns (bool)
function checkTxInclusion(bytes32 blockHash, bytes32 txid, bytes32[] calldata siblings, uint256 index)
external view returns (bool)
function registerPoolUTXO(bytes32 txid, uint32 vout) external
function markPoolUTXOSpent(bytes32 txid, uint32 vout) external
The authorization adapter for federation and confederation contracts. It operates in two modes: legacy counter mode (for compatibility and tests) and strict cryptographic mode (production). Once cryptographic FROST is bootstrapped, legacy counter approvals are permanently disabled.
function bootstrapCryptographicFROST(
address verifier,
bytes calldata groupPubkey,
string calldata signatureType
) external
function approveOperationWithAggregateSignature(
bytes32 operationHash,
address target,
bytes calldata aggregateSignature
) external
All privileged operations use target-bound hashes that include the domain, chainid, FROST contract address, target contract, function selector, parameter hash, and nonce.
function hashOperation(
address target,
bytes4 selector,
bytes32 paramsHash,
uint256 nonce
) external view returns (bytes32)
Target contracts expose helper functions: getAddPoolOperationHash,
getSetFeeManagerOperationHash, getProcessWithdrawalOperationHash, and others.
Callers should always use these helpers instead of constructing hashes by hand.
The system supports six primary interaction scenarios for two base roles: miners and pool operators. Additional flows cover oracles and FROST participants.
StratumDataAggregator.MiningPoolDAO.SPVContract verifies Merkle proof and block maturity (≥ 100 confirmations).CalculatorRegistry distributes shares across WorkerData/MemberData.PoolMpToken.mint issues mpToken to recipients.
The miner can keep mpToken in an ERC-20 wallet for unrestricted transfer, use them in DeFi,
or convert them to sToken via MultiPoolDAO.depositMpToken. Conversion involves
a re-verification of the underlying UTXO through SPV and burns the mpToken.
burnAndRedeem(networkId, amount, powScript).PoolSToken.burnFrom burns the sTokens and moves mpToken into escrow.constructRedemptionTx builds a BTC transaction with explicit miner fee and an RBF-compatible sequence.verifyBurnViaSPV confirms inclusion; escrow is burned and reserves settled.
The operator calls MiningPoolFactory.createPool and receives a deployed clone of
MiningPoolDAO together with a mpToken. The pool is then registered in
MultiPoolDAO, a calculator is selected through CalculatorRegistry,
and a payout policy is configured.
Oracles register with StratumOracleRegistry, receive ORACLE_ROLE,
and submit data validated by StratumDataValidator. FROST sessions are created
for each redemption operation and finalized with an aggregated Schnorr signature, verified
through FROSTVerifier.
Token economics revolve around two token types: mpToken (Mining Pool Token) and sToken (SBTC, SDOGE, SBCH, SLTC). Both are ERC-20 and both are backed 1:1 by their native assets.
Issued by PoolMpToken for a specific pool. It is a wrapped version of the
native asset (1 mpBTC = 1 BTC). It is minted after SPV verification of the coinbase and
execution of the calculator. mpToken can be held in any ERC-20 wallet, transferred between
addresses, used in DeFi, or burned during conversion to sToken.
Issued by MultiPoolDAO after a second UTXO verification. SBTC for BTC, SDOGE
for Dogecoin, and so on. Used for redemption to native assets through FROST or for DeFi.
Backed by pool reserves (Backing.available).
| Model | Contract | Key parameter | Strength | Trade-off |
|---|---|---|---|---|
| PPS | PPSCalculator | baseRate | Simplicity, predictability | Does not account for fees |
| FPPS | FPPSCalculator | baseRate, feeBonus | Includes tx fees | More expensive to compute |
| PPLNS | PPLNSCalculator | windowSize | Anti pool-hopping | Less predictable payouts |
| Score | ScoreCalculator | decayFactor | Encourages loyalty | More configuration to tune |
StratumDataValidator enforce honest reporting.Backing reserves guarantee 1:1 collateralization, defaultRedemptionTimeout bounds large withdrawals.FROST (Flexible Round-Optimized Schnorr Threshold Signatures) is the foundation of the federation's cryptographic guarantees. The private key never exists in whole form: each participant holds a share, and signing requires collecting a threshold (t-of-n) of partial signatures.
DKG in SatoshiFi does not generate secrets on-chain. The DKGCoordinator
contract coordinates the protocol: collecting VSS commitments, share hashes, public key
shares, and threshold attestations for the final group public key.
The safe path isfinalizeDKG(uint256, bytes calldata groupPubkey)with externally generated group key material. The argumentlessfinalizeDKG(uint256)is deliberately disabled with an explicit revert because it generated secrets on-chain.
Group public key:
abi.encodePacked(uint256 pubX, uint256 pubY)Signatures:
(Rx, z) with even-Y lift, or 96 bytes (Rx, Ry, z)(r, s, v)Schnorr verification rejects odd-Y group keys and odd-Y R points for the BIP340 path.
After bootstrapCryptographicFROST or configureCryptographicFROST is
called, the counterApprovalsEnabled flag becomes permanently false. All
subsequent approvals must go through approveOperationWithAggregateSignature
with a real aggregate signature, checked by cryptographicVerifier.verify.
Legacy counter mode remains in the contracts for tests and backward compatibility but
is not the production security boundary. Production deployments always
use the strict path deployFederationWithCryptographicFROST(...) or
deployConfederationWithCryptographicFROST(...).
Simplified Payment Verification is a deliberate trade-off between completeness and efficiency. SatoshiFi does not run a full Bitcoin node on-chain; instead it verifies transaction inclusion through block headers and Merkle paths.
bits; block hash ≤ target.
The standard maturity depth is 100 confirmations (Bitcoin coinbase maturity). In production
this parameter should be raised above the demo constant. isMatureWithDepth
allows the depth to be set explicitly. Chain reorganizations of up to 10 blocks (BTC) are
supported; Dogecoin uses a chain-length heuristic due to its high block rate.
The SPV contract also owns the pool UTXO registry. Pool contracts are registered through
setPoolContract (only spvAdmin) and then register their UTXOs via
registerPoolUTXO(txid, vout). Test helpers (addBlockWithHash,
setBlockMerkleRoot) must be permanently disabled in production through
disableTestHelpers().
SIGHASH_ALL | SIGHASH_ANYONECANPAY for fee bumping.| Network | Coinbase maturity | Standard tx confirmations | Time to finality |
|---|---|---|---|
| Bitcoin (BTC) | 100 blocks | 6 confirmations | ≈ 1 hour |
| Bitcoin Cash (BCH) | 100 blocks | 6 confirmations | ≈ 1 hour |
| Litecoin (LTC) | 240 blocks | 6 confirmations | ≈ 15 min |
| Dogecoin (DOGE) | 600 blocks | 6 confirmations | ≈ 6 min |
A federation is a group of participants that jointly hold shares of the BTC address key through DKG. A confederation extends a federation with institutional custodians: critical operations require both a FROST threshold and a custodian threshold.
The strict production path deployFederationWithCryptographicFROST(...) atomically
deploys and initializes:
SimpleFROSTMultisig with cryptographic FROST already bootstrappedSimpleMultiPool for MP-token aggregationMultiPoolShareToken (the federation's sToken)
deployConfederationWithCryptographicFROST(...) additionally deploys
CustodianValidator. All MultiPool operations in a confederation require two
independent approvals: a FROST threshold for the target-bound operation hash and a custodian
threshold for the same hash. The custodian set is managed inside CustodianValidator,
not only in a governance wrapper.
SimpleMultiPool and ConfederationMultiPool track per-user and
per-MP-token balances:
userPoolBalances[user][mpToken]pendingUserPoolShares[user][mpToken]PoolInfo.exists — preserves accounting on reactivation
Withdrawals lock shares in pending accounting first and burn them only when the withdrawal
is processed. A user can cancel after WITHDRAWAL_CANCEL_DELAY, and an approved
reject path can release pending shares. This model prevents the use of S-tokens from one
MP pool to withdraw from another MP pool.
Addresses are generated through DKG in FROSTCoordinator. The public key (xpub)
is created on-chain; derivation follows BIP-32 (HD wallets) adapted for P2WSH (BTC, BCH, LTC)
or Scrypt-compatible addresses (DOGE). The address list is stored in BridgeInbox
and exposed via AddressGenerated events.
Key rotation is initiated by DAO vote or on schedule (≈ 6 months, ≈ 262,800 blocks for BTC). A new DKG runs, old keys are invalidated, and funds are moved to the new address through a multisig transaction. The rotation is confirmed on-chain through SPV.
BridgeInbox.federationAddresses, with UTXOUpdated events.TokenMinted on UTXO receipt, TokenBurned on redemption. The invariant: totalSupply(sToken) ≤ totalUTXO.BridgeInbox data.
A custodian is a trusted participant from MultiPoolDAO who holds a share of the
cryptographic key through FROST and guarantees that every pool member can withdraw their
share independently. Misbehavior is punished by slashing the custodian's deposit.
Redemption requires a collective FROST signature from DAO members (t-of-n). Pro: low fees, full intra-pool decentralization. Con: redemption depends on majority cooperation; refusal to sign can delay payouts.
Each member can initiate a withdrawal of their own share independently. The custodian verifies the request and provides their signature share. Pro: independent withdrawal, slashing as economic guarantee. Con: additional custodian fee.
The custodian posts a deposit in the network's native currency (ETH, BTC, USDC) or in a dedicated DAO token. The deposit is locked for the entire participation period and slashed on proven violations: signing an invalid request, refusing to sign a valid request, or colluding with other participants.
Optional fee tied to deposit size (incentivizes larger deposits):
f = k / (d + c) × r
k — risk constant (e.g., 1e18)
d — custodian deposit
c — minimum offset (e.g., 1e15)
r — total pool reward for the period
Alternative — fixed custodian percentage:
f = p × r
p — agreed percentage (e.g., 0.005 for 0.5%)
r — total pool reward for the period
Slashing on violation:
s = min(p × d, m)
p — slashing percentage (e.g., 0.1 for 10%)
d — custodian deposit
m — cap, to avoid zeroing the deposit entirely
In pools backed by a custodian, miners can rent out their share (mpToken or aggregated hashrate). The custodian acts as an arbitrator: it guarantees redemption to the renter, resolves disputes, and bears responsibility through its slashable deposit.
rentShare(amount, renter, duration, fee), delegating the share and recording the terms.StratumDataAggregator.redeemRentalRewards; the custodian verifies and signs.Rental fee:
a = p × s × d
p — rental percentage (e.g., 0.05 for 5%)
s — share size in mpToken
d — rental duration in periods
Slashing in dispute:
s = min(v × dep, max_s)
v — violation percentage (e.g., 0.2 for 20%)
dep — violator's deposit
max_s — maximum slashing (cap)
Share rental adds liquidity to the system and turns a stake in a pool into a tradable asset, similar in spirit to decentralized hashrate marketplaces but fully on-chain.
The fee system handles wrap/deposit and unwrap/withdrawal fees for federation and confederation pools. Every change requires FROST approval, and confederations additionally require a custodian threshold.
| Contract | Purpose |
|---|---|
FederationFeeManager | Fee configs, fee calculation, NFT discount consumption, authorized fee collectors |
FeeDistributionCalculator | Equal or weighted distribution between members and custodians |
ProjectWalletRegistry | Per-federation project wallets with FROST-gated changes |
GlobalProjectWalletRegistry | Owner-managed global native-fee distribution |
FederationNFTModifier | ERC721 fee discount NFTs with expiration and volume limits |
FeeSystemDeployer | Single-entry deployment of all fee contracts |
Fixed percentage:
fee = amount × fixedBps / 10000
Dynamic:
fee = dynamicMinFee + amount × dynamicCoeffBps / 10000
The current implementation caps the computed fee at amount, so a bad dynamic
input cannot underflow pool accounting. Config validation rejects
fixedBps > 10000 and dynamicCoeffBps > 10000.
Two fee APIs are available:
function calculateFeeWithDiscount(
bool isWrap, uint256 amount, uint256 discountBps
) external view returns (uint256 fee)
function calculateFeeWithNFT(
bool isWrap, uint256 amount, uint256 nftTokenId, address account
) external returns (uint256 fee)
calculateFeeWithNFT consumes allowance through
FederationNFTModifier.useDiscountFor. The NFT contract checks ownership,
expiration, remaining volume, and increments usedVolume.
enum DistributionMode { Equal, ByPercentage }
struct DistributionConfig {
uint256 memberShareBps;
uint256 custodianShareBps;
DistributionMode memberMode;
DistributionMode custodianMode;
}
Validation requires memberShareBps + custodianShareBps ≤ 10000. Equal mode
splits the amount across active recipients; ByPercentage mode uses weights.
Security is built on eight independent layers of defense. No single layer is sufficient on its own — an attacker would have to defeat them all simultaneously.
Roles ADMIN_ROLE, POOL_ROLE, ORACLE_ROLE, MINTER_ROLE, BURNER_ROLE. Assigned only during initialize.
Every function interacting with tokens or reserves is protected by nonReentrant.
Emergency stop for receiveReward, mintSToken, burnAndRedeem through ADMIN.
No single-party signatures. t-of-n with t > n/2, plus custodian threshold for confederations.
Maturity check (≥ 100 confirmations), Merkle proof for every UTXO, reorg handling up to 10 blocks.
1:1 backing of mpToken/sToken to the native asset. Minting is bounded by Backing.available.
Oracle deposits are slashed for invalid data. Whitelist enforced through StratumOracleRegistry.
MIN_GAS_ESTIMATE = 50k, MAX_GAS_ESTIMATE = 5M, MAX_WORKERS = 1000–2000.
| Risk | Mitigation |
|---|---|
| Oracle attacks (forged shares) | ORACLE_ROLE, multi-validation, reputation, staking/slashing |
| SPV verification errors | ≥ 100 confirmations, reorg support, audited libraries, regular audits |
| FROST share compromise | t > n/2, BIP340 validation, redemption timeout, geographic distribution |
| Reentrancy via DeFi | ReentrancyGuard, Backing.available check before mint |
| Calculator errors | DistributionMath, input validation, whitelist in CalculatorRegistry |
| ADMIN role abuse | Multisig (Gnosis Safe), DAO via NFT/SBT, event logging |
| Chain reorganization | isMature with high depth, reorg up to 10 blocks, oracle monitoring |
deployFederationWithCryptographicFROST or deployConfederationWithCryptographicFROST.SimpleFROSTMultisig.counterApprovalsEnabled() == false after bootstrap.disableTestHelpers().ADMIN_ROLE to a multisig or DAO.
web3/test/CriticalTests.t.sol currently contains 71 critical tests. Coverage
includes: DKG phases and group key validation, strict FROST aggregate approval, BIP340
verifier vector, federation/confederation deploy guards, SPV admin and test-helper
protections, MultiPool per-pool accounting and withdrawal locks, redemption escrow/refund
and BTC transaction checks, fee config validation and authorized fee collectors, NFT
ownership and project wallet share validation.
BTCyield is a product layer on top of SatoshiFi infrastructure, built jointly with CrossCurve. SatoshiFi provides cross-chain wrapping and custodial infrastructure through FROST. CrossCurve runs the yield-routing layer via Yield Basis and Silo Finance.
Garden Solver manages the cbBTC/sBTC peg.
Curator routes yield across strategies.
Integration with Yield Basis (cyb-tokens) and Silo Finance (lending).
Audited smart contracts (MixBytes).
Federation Protocol — threshold BTC wrapping.
FROST signatures with DKG ceremony.
sBTC (sToken) — confederation wrapper.
Cross-chain Bitcoin ↔ Ethereum, Custodian DAO and governance.
Risk: minimal. Full BTC control. Create your own confederation. Participants remain owners of their bitcoins. The custodian algorithmically cannot block withdrawal of burned wrappers — their job is solely to defend against key compromise.
Risk: medium. Rent from the entire system TVL. Wrap BTC into WBTC, cbBTC or tBTC and provide liquidity to Solver (peg management) and Curator (yield routing). You earn against the full system TVL — all confederations + solver + curator.
Risk: strategic. Strategic participation. Each of the 11 Custodian DAO seats grants 1/11 of the total revenue from all confederation operations: wrap fees + LP yield + the confederations' share of Curator yield from sBTC pairs.
| Strategy | Platform | Target yield |
|---|---|---|
| LP cbBTC/sBTC peg management | DEX + Solver | Base layer + fees |
| cyb-WBTC / cyb-cbBTC vaults | Yield Basis | 5–6% APY |
| BTC wrapper lending | Silo Finance | 7–15% APY |
| yb-token collateral → crvUSD | Silo Finance | Leverage |
| Stablecoin deposit | Silo Finance | from 6% APY |
Seat composition:
Confirmed liquidity:
| Confederation | Solver Capital | Custodian DAO | |
|---|---|---|---|
| BTC control | Full | Partial (wrapped) | Not required |
| Target yield | ~4.5% APY | 7–15% APY | 1/11 of system |
| Risk level | Minimal | Medium | Strategic |
| Minimum entry | 3+ participants | BTC for wrapping | Seat purchase |
| Income type | Stable yield | Rent from TVL | Passive |
| Best fit | BTC holders, family offices | Experienced DeFi participants | Institutional investors |
IDistributionScheme interface, including calculate,
calculateForMembers, validateInput, validateMemberInput,
getSchemeInfo, and estimateGas. Register the contract in
CalculatorRegistry via registerCalculator, providing scheme type,
name, version, and gas estimate. Only authorized authors can register; usage requires a
whitelist entry through whitelistCalculator.
MultiPoolDAO (e.g.
MultiPoolDAO_BTC for BTC). The contract re-verifies the associated UTXO
through SPVContract or SPVContractDogecoin. After verification it
calls PoolSToken.mint to issue sToken (SBTC, SDOGE, SBCH, SLTC).
SPVContract rejects the block and no rewards are distributed. The miner must
supply correct data: block header, txRaw, Merkle proof. This is the baseline defense
against forged UTXOs and verification attacks.
MultiPoolDAO is only required for converting mpToken to sToken
for global liquidity or redemption into native assets.
PoolMembershipNFT (Soulbound Token) confirming participation.
The SBT is issued by MiningPoolDAO and is required for submitting shares and
receiving mpToken. Additional roles (admin, validator) are granted via PoolRoleBadgeNFT.
FROSTCoordinator and
FROSTVerifier. The user calls burnAndRedeem, burning sToken.
FROST produces a Schnorr (BIP340) signature, verified before the native transaction is
constructed. Confederations additionally require a custodian threshold.
Pausable. An administrator with
DEFAULT_ADMIN_ROLE can pause receiveReward,
mintSToken, and burnAndRedeem. In production this role should be
transferred to a multisig or DAO.
SPVContract), Dogecoin (SPVContractDogecoin), BCH, and LTC.
Each network has its own MultiPoolDAO and sToken: SBTC, SDOGE, SBCH, SLTC.
FROSTCoordinator, old keys are invalidated, and funds
are moved through a multisig transaction. The rotation is confirmed on-chain via SPV.
BridgeOutbox builds
the transaction with an explicit miner fee). A pool may subsidize fees through DAO vote
for small transactions. The user can speed up the transaction via RBF, since
SIGHASH_ALL | SIGHASH_ANYONECANPAY is used.
MultiPoolDAO). An additional insurance pool is in development.
MultiPoolDAO) is decentralized and not bound to a single
jurisdiction. Each pool may select applicable law through DAO vote. sToken may be
classified differently across jurisdictions (e-money, security, utility); exact status
requires legal counsel at the scaling stage.
MiningPoolDAO. Freely transferable, redeemable to the native network, or convertible to sToken.MultiPoolDAO per network. Provides liquidity and redemption to native assets.StratumDataAggregator and StratumDataValidator.MultiPoolDAO holding a key share through FROST. Guarantees honesty through a slashable deposit.totalReceived, reserved, available. sToken issuance is bounded by available.BridgeInbox and events.
Smart contract code and documentation are distributed under the MIT
license (SPDX-License-Identifier: MIT). Dependencies (OpenZeppelin Contracts
and the project's own libraries — BlockHeader, BitcoinUtils,
MerkleProofLib) are also MIT.