SimpleMiningPool

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "contracts/src/demo/SimpleMiningPoolV2.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "./SimpleSPVContractV2.sol";
import "../tokens/PoolMpTokenV2.sol";

/**
 * @title SimpleMiningPool
 * @notice Minimal mining pool for demo - no FROST, no Factory, single owner
 * @dev Simplified flow: Bitcoin rewards → MP tokens → BTC redemption
 */
contract SimpleMiningPool is Ownable, ReentrancyGuard {
    // ======== STATE ========
    SimpleSPVContract public immutable spvContract;
    PoolMpToken public mpToken;

    string public poolId;
    bytes public payoutScript; // Bitcoin payout script

    // Rewards tracking
    struct RewardUTXO {
        bytes32 txid;
        uint32 vout;
        uint64 amountSat;
        bytes32 blockHash;
        bool isRegistered;
        bool isDistributed;
    }

    mapping(bytes32 => RewardUTXO) public rewards;
    uint256 public totalRewardsRegistered;
    uint256 public totalRewardsDistributed;

    // Redemptions tracking
    struct Redemption {
        address requester;
        uint64 amountSat;
        bytes btcScript;
        bool isConfirmed;
        uint256 timestamp;
    }

    mapping(uint256 => Redemption) public redemptions;
    uint256 public redemptionCounter;

    // ======== EVENTS ========
    event RewardRegistered(bytes32 indexed utxoKey, uint64 amountSat, bytes32 blockHash);
    event RewardDistributed(bytes32 indexed utxoKey, address[] recipients, uint256[] amounts);
    event RedemptionRequested(uint256 indexed redemptionId, address indexed requester, uint64 amountSat);
    event RedemptionConfirmed(uint256 indexed redemptionId, bool approved);

    // ======== CONSTRUCTOR ========
    constructor(
        address _spvContract,
        string memory _poolId,
        bytes memory _payoutScript
    ) Ownable(msg.sender) {
        require(_spvContract != address(0), "Invalid SPV");
        require(bytes(_poolId).length > 0, "Empty pool ID");

        spvContract = SimpleSPVContract(_spvContract);
        poolId = _poolId;
        payoutScript = _payoutScript;

        // Register this pool with SPV for source verification
        spvContract.setPoolContract(address(this));
    }

    // ======== SETUP ========

    function setMpToken(address _mpToken) external onlyOwner {
        require(address(mpToken) == address(0), "MP token already set");
        require(_mpToken != address(0), "Invalid MP token");
        mpToken = PoolMpToken(_mpToken);
    }

    function setPayoutScript(bytes calldata _script) external onlyOwner {
        payoutScript = _script;
    }

    // ======== REWARDS ========

    function registerReward(
        bytes32 txid,
        uint32 vout,
        uint64 amountSat,
        bytes32 blockHash,
        bytes32[] calldata siblings,
        uint256 index
    ) external onlyOwner returns (bytes32 utxoKey) {
        utxoKey = keccak256(abi.encodePacked(txid, vout));

        require(!rewards[utxoKey].isRegistered, "Already registered");
        require(amountSat > 0, "Zero amount");

        // Verify block exists and is mature (6 confirmations)
        require(spvContract.blockExists(blockHash), "Block not found");
        require(spvContract.isMature(blockHash), "Block not mature");

        // Verify transaction inclusion
        require(
            spvContract.checkTxInclusion(blockHash, txid, siblings, index),
            "TX not in block"
        );

        rewards[utxoKey] = RewardUTXO({
            txid: txid,
            vout: vout,
            amountSat: amountSat,
            blockHash: blockHash,
            isRegistered: true,
            isDistributed: false
        });

        totalRewardsRegistered += amountSat;

        // Register this UTXO in SPV for source verification
        spvContract.registerPoolUTXO(txid, vout);

        emit RewardRegistered(utxoKey, amountSat, blockHash);
    }

    function distributeReward(
        bytes32 utxoKey,
        address[] calldata recipients,
        uint256[] calldata amounts
    ) external onlyOwner nonReentrant {
        require(address(mpToken) != address(0), "MP token not set");
        require(recipients.length == amounts.length, "Length mismatch");
        require(recipients.length > 0, "No recipients");

        RewardUTXO storage reward = rewards[utxoKey];
        require(reward.isRegistered, "Not registered");
        require(!reward.isDistributed, "Already distributed");

        // Verify total amount matches
        uint256 totalAmount = 0;
        for (uint256 i = 0; i < amounts.length; i++) {
            totalAmount += amounts[i];
        }
        require(totalAmount == reward.amountSat, "Amount mismatch");

        // Mint MP tokens to recipients
        for (uint256 i = 0; i < recipients.length; i++) {
            if (amounts[i] > 0) {
                mpToken.mint(recipients[i], amounts[i]);
            }
        }

        reward.isDistributed = true;
        totalRewardsDistributed += reward.amountSat;

        emit RewardDistributed(utxoKey, recipients, amounts);
    }

    // ======== REDEMPTIONS ========

    function requestRedemption(
        uint64 amountSat,
        bytes calldata btcScript
    ) external nonReentrant returns (uint256 redemptionId) {
        require(address(mpToken) != address(0), "MP token not set");
        require(amountSat > 0, "Zero amount");
        require(btcScript.length > 0, "Empty script");

        // Burn MP tokens
        mpToken.burn(msg.sender, amountSat);

        redemptionId = redemptionCounter++;
        redemptions[redemptionId] = Redemption({
            requester: msg.sender,
            amountSat: amountSat,
            btcScript: btcScript,
            isConfirmed: false,
            timestamp: block.timestamp
        });

        emit RedemptionRequested(redemptionId, msg.sender, amountSat);
    }

    function confirmRedemption(uint256 redemptionId, bool approved) external onlyOwner {
        Redemption storage redemption = redemptions[redemptionId];
        require(redemption.amountSat > 0, "Redemption not found");
        require(!redemption.isConfirmed, "Already confirmed");

        redemption.isConfirmed = true;

        emit RedemptionConfirmed(redemptionId, approved);
    }

    // ======== VIEWS ========

    function getReward(bytes32 utxoKey) external view returns (RewardUTXO memory) {
        return rewards[utxoKey];
    }

    function getRedemption(uint256 redemptionId) external view returns (Redemption memory) {
        return redemptions[redemptionId];
    }

    function getMpTokenBalance(address account) external view returns (uint256) {
        if (address(mpToken) == address(0)) return 0;
        return mpToken.balanceOf(account);
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/access/Ownable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)

pragma solidity ^0.8.20;

import {Context} from "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * The initial owner is set to the address provided by the deployer. This can
 * later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    /**
     * @dev The caller account is not authorized to perform an operation.
     */
    error OwnableUnauthorizedAccount(address account);

    /**
     * @dev The owner is not a valid owner account. (eg. `address(0)`)
     */
    error OwnableInvalidOwner(address owner);

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
     */
    constructor(address initialOwner) {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        if (owner() != _msgSender()) {
            revert OwnableUnauthorizedAccount(_msgSender());
        }
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        if (newOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)

pragma solidity ^0.8.20;

import {StorageSlot} from "./StorageSlot.sol";

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,
 * consider using {ReentrancyGuardTransient} instead.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 *
 * IMPORTANT: Deprecated. This storage-based reentrancy guard will be removed and replaced
 * by the {ReentrancyGuardTransient} variant in v6.0.
 */
abstract contract ReentrancyGuard {
    using StorageSlot for bytes32;

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ReentrancyGuard")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant REENTRANCY_GUARD_STORAGE =
        0x9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00;

    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant NOT_ENTERED = 1;
    uint256 private constant ENTERED = 2;

    /**
     * @dev Unauthorized reentrant call.
     */
    error ReentrancyGuardReentrantCall();

    constructor() {
        _reentrancyGuardStorageSlot().getUint256Slot().value = NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        _nonReentrantBefore();
        _;
        _nonReentrantAfter();
    }

    /**
     * @dev A `view` only version of {nonReentrant}. Use to block view functions
     * from being called, preventing reading from inconsistent contract state.
     *
     * CAUTION: This is a "view" modifier and does not change the reentrancy
     * status. Use it only on view functions. For payable or non-payable functions,
     * use the standard {nonReentrant} modifier instead.
     */
    modifier nonReentrantView() {
        _nonReentrantBeforeView();
        _;
    }

    function _nonReentrantBeforeView() private view {
        if (_reentrancyGuardEntered()) {
            revert ReentrancyGuardReentrantCall();
        }
    }

    function _nonReentrantBefore() private {
        // On the first call to nonReentrant, _status will be NOT_ENTERED
        _nonReentrantBeforeView();

        // Any calls to nonReentrant after this point will fail
        _reentrancyGuardStorageSlot().getUint256Slot().value = ENTERED;
    }

    function _nonReentrantAfter() private {
        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _reentrancyGuardStorageSlot().getUint256Slot().value = NOT_ENTERED;
    }

    /**
     * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
     * `nonReentrant` function in the call stack.
     */
    function _reentrancyGuardEntered() internal view returns (bool) {
        return _reentrancyGuardStorageSlot().getUint256Slot().value == ENTERED;
    }

    function _reentrancyGuardStorageSlot() internal pure virtual returns (bytes32) {
        return REENTRANCY_GUARD_STORAGE;
    }
}
"
    },
    "contracts/src/demo/SimpleSPVContractV2.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "../libs/BlockHeader.sol";
import "../libs/TxMerkleProof.sol";
import "../libs/TargetsHelper.sol";
import "../core/BitcoinTxParser.sol";

/**
 * @title SimpleSPVContract
 * @notice Simplified SPV for demo: 6 confirmations instead of 100
 * @dev Minimal Bitcoin SPV verification without difficulty adjustment
 */
contract SimpleSPVContract {
    // DEMO: 6 confirmations instead of 100
    uint32 public constant COINBASE_MATURITY = 6;

    struct BlockData {
        bytes32 prev;
        bytes32 merkleRoot;
        uint32 version;
        uint32 time;
        uint32 nonce;
        uint32 bits;
        uint64 height;
        uint256 cumulativeWork;  // Total work up to this block
    }

    mapping(bytes32 => BlockData) public blocks;
    mapping(uint64 => bytes32) public height2hash;
    bytes32 public mainchainHead;

    // Trusted block configuration
    bytes32 public immutable trustedBlockHash;
    uint64 public immutable trustedBlockHeight;

    // Pool UTXO tracking for source verification
    mapping(bytes32 => mapping(uint32 => bool)) public poolUTXOs; // txid => vout => isPoolUTXO
    address public poolContract;

    event BlockHeaderAdded(uint64 height, bytes32 blockHash);
    event PoolUTXORegistered(bytes32 indexed txid, uint32 vout);
    event PoolUTXOSpent(bytes32 indexed txid, uint32 vout);
    event ReorgOccurred(bytes32 oldHead, bytes32 newHead, uint64 depth);
    event MainchainHeadUpdated(uint64 height, bytes32 head);

    error InvalidHeaderLength();
    error BlockAlreadyExists(bytes32);
    error PrevNotFound(bytes32);
    error OnlyPoolContract();
    error InvalidTxSource();
    error ReorgTooDeep();

    /**
     * @notice Constructor with optional trusted block
     * @param _trustedBlockHash Hash of the trusted block (or bytes32(0) for genesis)
     * @param _trustedBlockHeight Height of the trusted block
     * @param _trustedMerkleRoot Merkle root of the trusted block
     */
    constructor(
        bytes32 _trustedBlockHash,
        uint64 _trustedBlockHeight,
        bytes32 _trustedMerkleRoot
    ) {
        trustedBlockHash = _trustedBlockHash;
        trustedBlockHeight = _trustedBlockHeight;

        // If a trusted block is specified, add it to the chain
        if (_trustedBlockHash != bytes32(0)) {
            // Calculate work for trusted block
            uint256 target = TargetsHelper.bitsToTarget(0x1d00ffff);
            uint256 work = TargetsHelper.workFromTarget(target);

            blocks[_trustedBlockHash] = BlockData({
                prev: bytes32(0),
                merkleRoot: _trustedMerkleRoot,
                version: 1,
                time: uint32(block.timestamp),
                nonce: 0,
                bits: 0x1d00ffff,
                height: _trustedBlockHeight,
                cumulativeWork: work * _trustedBlockHeight  // Approximate cumulative work
            });
            height2hash[_trustedBlockHeight] = _trustedBlockHash;
            mainchainHead = _trustedBlockHash;
            emit BlockHeaderAdded(_trustedBlockHeight, _trustedBlockHash);
        }
    }

    /**
     * @notice Set the pool contract address (can only be set once)
     * @param _poolContract Address of the mining pool contract
     */
    function setPoolContract(address _poolContract) external {
        require(poolContract == address(0), "Pool already set");
        require(_poolContract != address(0), "Invalid pool address");
        poolContract = _poolContract;
    }

    modifier onlyPool() {
        if (msg.sender != poolContract) revert OnlyPoolContract();
        _;
    }

    // ======== Views ========

    function blockExists(bytes32 bh) public view returns (bool) {
        return blocks[bh].time != 0;
    }

    function isInMainchain(bytes32 bh) public view returns (bool) {
        uint64 h = blocks[bh].height;
        return h != 0 && height2hash[h] == bh;
    }

    function getBlockStatus(bytes32 bh) external view returns (bool isIn, uint64 confirmations) {
        uint64 h = blocks[bh].height;
        if (h == 0) return (false, 0);
        isIn = (height2hash[h] == bh);
        if (!isIn) return (false, 0);

        uint64 headH = blocks[mainchainHead].height;
        confirmations = headH >= h ? headH - h + 1 : 0;
    }

    function isMature(bytes32 bh) external view returns (bool) {
        uint64 h = blocks[bh].height;
        if (h == 0 || !isInMainchain(bh)) return false;
        uint64 head = blocks[mainchainHead].height;
        return head >= h + COINBASE_MATURITY;
    }

    function getBlockInfo(bytes32 bh) external view returns (
        BlockData memory mainBlockData,
        bool isInMainchainResult,
        uint256 cumulativeWork,
        bool exists
    ) {
        mainBlockData = blocks[bh];
        exists = mainBlockData.time != 0;
        isInMainchainResult = isInMainchain(bh);
        cumulativeWork = mainBlockData.cumulativeWork;
    }

    function getMainchainHead() external view returns (bytes32) {
        return mainchainHead;
    }

    function getMainchainHeight() external view returns (uint64) {
        return blocks[mainchainHead].height;
    }

    function getBlockMerkleRoot(bytes32 bh) external view returns (bytes32) {
        return blocks[bh].merkleRoot;
    }

    function getBlockHeight(bytes32 bh) external view returns (uint64) {
        return blocks[bh].height;
    }

    function getBlockHash(uint64 height) external view returns (bytes32) {
        return height2hash[height];
    }

    function checkTxInclusion(
        bytes32 blockHash,
        bytes32 txid,
        bytes32[] calldata siblings,
        uint256 index
    ) external view returns (bool) {
        if (!blockExists(blockHash)) return false;

        bytes32 merkleRoot = blocks[blockHash].merkleRoot;

        // Verify merkle proof
        return TxMerkleProof.verify(siblings, index, merkleRoot, txid);
    }

    /**
     * @notice Register a pool UTXO for source verification
     * @param txid Transaction ID (little-endian)
     * @param vout Output index
     */
    function registerPoolUTXO(bytes32 txid, uint32 vout) external onlyPool {
        poolUTXOs[txid][vout] = true;
        emit PoolUTXORegistered(txid, vout);
    }

    /**
     * @notice Mark a pool UTXO as spent
     * @param txid Transaction ID (little-endian)
     * @param vout Output index
     */
    function markPoolUTXOSpent(bytes32 txid, uint32 vout) external onlyPool {
        poolUTXOs[txid][vout] = false;
        emit PoolUTXOSpent(txid, vout);
    }

    /**
     * @notice Verify that a transaction spends from pool UTXOs
     * @param rawTx Raw Bitcoin transaction bytes
     * @return isValid True if transaction spends from at least one pool UTXO
     */
    function verifyTxSource(bytes calldata rawTx) external view returns (bool isValid) {
        // Parse all inputs from the transaction
        (bytes32[] memory prevTxIds, uint32[] memory vouts) = BitcoinTxParser.parseAllInputs(rawTx);

        // Check if any input spends from a registered pool UTXO
        for (uint256 i = 0; i < prevTxIds.length; i++) {
            // Flip to little-endian for lookup
            bytes32 prevTxIdLE = BitcoinTxParser.flipBytes32(prevTxIds[i]);
            if (poolUTXOs[prevTxIdLE][vouts[i]]) {
                return true;
            }
        }

        return false;
    }

    /**
     * @notice Check if a specific UTXO is registered as a pool UTXO
     * @param txid Transaction ID (little-endian)
     * @param vout Output index
     */
    function isPoolUTXO(bytes32 txid, uint32 vout) external view returns (bool) {
        return poolUTXOs[txid][vout];
    }

    // ======== Mutations (Simplified) ========

    function addBlockHeader(bytes calldata raw) public {
        if (raw.length != 80) revert InvalidHeaderLength();

        (BlockHeaderData memory h, bytes32 bh) = BlockHeader.parseHeader(raw);
        if (blocks[bh].time != 0) revert BlockAlreadyExists(bh);

        if (h.prevBlockHash != bytes32(0) && blocks[h.prevBlockHash].time == 0) {
            revert PrevNotFound(h.prevBlockHash);
        }

        uint64 height = (h.prevBlockHash == bytes32(0)) ? 0 : blocks[h.prevBlockHash].height + 1;

        // Calculate work and cumulative work
        uint256 target = TargetsHelper.bitsToTarget(h.bits);
        uint256 work = TargetsHelper.workFromTarget(target);
        uint256 cumulative = (h.prevBlockHash == bytes32(0))
            ? work
            : addmod(blocks[h.prevBlockHash].cumulativeWork, work, type(uint256).max);

        blocks[bh] = BlockData({
            prev: h.prevBlockHash,
            merkleRoot: h.merkleRoot,
            version: h.version,
            time: h.time,
            nonce: h.nonce,
            bits: h.bits,
            height: height,
            cumulativeWork: cumulative
        });

        // Check if this is the new mainchain head
        if (mainchainHead == bytes32(0)) {
            mainchainHead = bh;
            height2hash[height] = bh;
            emit MainchainHeadUpdated(height, bh);
        } else {
            if (blocks[bh].cumulativeWork > blocks[mainchainHead].cumulativeWork) {
                // This fork has more work - trigger reorg
                _reorgTo(bh, height);
            } else if (h.prevBlockHash == mainchainHead) {
                // Simple extension of mainchain
                mainchainHead = bh;
                height2hash[height] = bh;
                emit MainchainHeadUpdated(height, bh);
            }
            // else: this is a fork with less work, ignore
        }

        emit BlockHeaderAdded(height, bh);
    }

    /**
     * @notice Reorganize the mainchain to a new head with more cumulative work
     * @param newHead The new mainchain head block hash
     * @param height The height of the new head
     */
    function _reorgTo(bytes32 newHead, uint64 height) internal {
        uint64 maxReorgDepth = 10;
        bytes32 cursor = newHead;
        uint64 h = height;
        bytes32 oldHead = mainchainHead;
        uint64 reorgCount = 0;

        unchecked {
            while (true) {
                // Stop if we've reached a block already on mainchain
                if (height2hash[h] == cursor) break;

                // Update height mapping
                height2hash[h] = cursor;

                // Move to parent
                bytes32 p = blocks[cursor].prev;
                if (p == bytes32(0)) break;

                cursor = p;
                --h;
                ++reorgCount;

                if (reorgCount > maxReorgDepth) revert ReorgTooDeep();
            }
        }

        mainchainHead = newHead;
        emit ReorgOccurred(oldHead, newHead, reorgCount);
        emit MainchainHeadUpdated(height, newHead);
    }

    // ======== Test Helpers ========

    function addBlockWithHash(bytes32 targetHash, uint64 targetHeight) external {
        // Calculate work for test block
        uint256 target = TargetsHelper.bitsToTarget(0x1d00ffff);
        uint256 work = TargetsHelper.workFromTarget(target);

        blocks[targetHash] = BlockData({
            prev: bytes32(0),
            merkleRoot: bytes32(0),
            version: 1,
            time: uint32(block.timestamp),
            nonce: 0,
            bits: 0x1d00ffff,
            height: targetHeight,
            cumulativeWork: work * (targetHeight + 1)  // Approximate cumulative work
        });
        height2hash[targetHeight] = targetHash;
        mainchainHead = targetHash;
        emit BlockHeaderAdded(targetHeight, targetHash);
    }

    function setBlockMerkleRoot(bytes32 blockHash, bytes32 merkleRoot) external {
        require(blockExists(blockHash), "Block not found");
        blocks[blockHash].merkleRoot = merkleRoot;
    }
}
"
    },
    "contracts/src/tokens/PoolMpTokenV2.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";

/**
 * @title PoolMpToken
 * @notice Пул-специфичный mp-токен (например, mpBTC-pool001).
 *         Минт/бёрн — только DAO пула. Неизменяемо знает адрес DAO пула.
 *         Опционально может ограничивать трансферы (внутрипуловые переводы по вайтлисту).
 */
contract PoolMpToken is ERC20, ERC20Permit, AccessControl {
    // Роли (назначаются DAO пула в конструкторе)
    bytes32 public constant MINTER_ROLE         = keccak256("MINTER_ROLE");
    bytes32 public constant BURNER_ROLE         = keccak256("BURNER_ROLE");
    bytes32 public constant WHITELIST_MANAGER   = keccak256("WHITELIST_MANAGER");
    bytes32 public constant WHITELISTED_ACCOUNT = keccak256("WHITELISTED_ACCOUNT");

    /// @notice DAO пула, которому принадлежат админ-права
    address public immutable poolDAO;

    /// @notice Если true — включены ограничения трансферов (только вайтлист/DAO)
    bool public immutable restrictTransfer;

    event WhitelistUpdated(address indexed account, bool allowed);

    constructor(
        string memory name_,
        string memory symbol_,
        address poolDAO_,
        bool restrictTransfer_
    ) ERC20(name_, symbol_) ERC20Permit(name_) {
        require(poolDAO_ != address(0), "poolDAO=0");
        poolDAO = poolDAO_;
        restrictTransfer = restrictTransfer_;

        // Делегируем полный контроль DAO пула
        _grantRole(DEFAULT_ADMIN_ROLE, poolDAO_);
        _grantRole(MINTER_ROLE,         poolDAO_);
        _grantRole(BURNER_ROLE,         poolDAO_);
        _grantRole(WHITELIST_MANAGER,   poolDAO_);
    }

    // -------------------
    // DAO-ограниченные API
    // -------------------

    function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) {
        _mint(to, amount);
    }

    function burn(address from, uint256 amount) external onlyRole(BURNER_ROLE) {
        _burn(from, amount);
    }

    /// @notice DAO (или делегаты с ролью) управляют вайтлистом получателей/отправителей
    function addToWhitelist(address account) external onlyRole(WHITELIST_MANAGER) {
        _grantRole(WHITELISTED_ACCOUNT, account);
        emit WhitelistUpdated(account, true);
    }

    function removeFromWhitelist(address account) external onlyRole(WHITELIST_MANAGER) {
        _revokeRole(WHITELISTED_ACCOUNT, account);
        emit WhitelistUpdated(account, false);
    }

    // -------------------
    // Ограничения трансфера
    // -------------------

    /**
     * @dev Если включён режим restrictTransfer:
     *  - mint/burn всегда разрешены (from==0 / to==0)
     *  - любые переводы разрешены DAO пула
     *  - иначе обе стороны должны быть в вайтлисте (или одна из них — DAO пула)
     */
    function _update(address from, address to, uint256 value) internal override {
        if (restrictTransfer) {
            bool minting = from == address(0);
            bool burning = to   == address(0);
            if (!(minting || burning)) {
                if (from != poolDAO && to != poolDAO) {
                    // проверяем обе стороны
                    require(
                        hasRole(WHITELISTED_ACCOUNT, from) &&
                        hasRole(WHITELISTED_ACCOUNT, to),
                        "mp: restricted transfer"
                    );
                }
            }
        }
        super._update(from, to, value);
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/utils/Context.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)

pragma solidity ^0.8.20;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/utils/StorageSlot.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/StorageSlot.sol)
// This file was procedurally generated from scripts/generate/templates/StorageSlot.js.

pragma solidity ^0.8.20;

/**
 * @dev Library for reading and writing primitive types to specific storage slots.
 *
 * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
 * This library helps with reading and writing to such slots without the need for inline assembly.
 *
 * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
 *
 * Example usage to set ERC-1967 implementation slot:
 * ```solidity
 * contract ERC1967 {
 *     // Define the slot. Alternatively, use the SlotDerivation library to derive the slot.
 *     bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
 *
 *     function _getImplementation() internal view returns (address) {
 *         return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
 *     }
 *
 *     function _setImplementation(address newImplementation) internal {
 *         require(newImplementation.code.length > 0);
 *         StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
 *     }
 * }
 * ```
 *
 * TIP: Consider using this library along with {SlotDerivation}.
 */
library StorageSlot {
    struct AddressSlot {
        address value;
    }

    struct BooleanSlot {
        bool value;
    }

    struct Bytes32Slot {
        bytes32 value;
    }

    struct Uint256Slot {
        uint256 value;
    }

    struct Int256Slot {
        int256 value;
    }

    struct StringSlot {
        string value;
    }

    struct BytesSlot {
        bytes value;
    }

    /**
     * @dev Returns an `AddressSlot` with member `value` located at `slot`.
     */
    function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns a `BooleanSlot` with member `value` located at `slot`.
     */
    function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns a `Bytes32Slot` with member `value` located at `slot`.
     */
    function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns a `Uint256Slot` with member `value` located at `slot`.
     */
    function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns a `Int256Slot` with member `value` located at `slot`.
     */
    function getInt256Slot(bytes32 slot) internal pure returns (Int256Slot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns a `StringSlot` with member `value` located at `slot`.
     */
    function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `StringSlot` representation of the string storage pointer `store`.
     */
    function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {
        assembly ("memory-safe") {
            r.slot := store.slot
        }
    }

    /**
     * @dev Returns a `BytesSlot` with member `value` located at `slot`.
     */
    function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.
     */
    function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {
        assembly ("memory-safe") {
            r.slot := store.slot
        }
    }
}
"
    },
    "contracts/src/libs/BlockHeader.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import "./BytesUtils.sol";
import "../core/BitcoinHash.sol";

/// @notice Структура, представляющая разобранный 80-байтный Bitcoin block header
struct BlockHeaderData {
    uint32 version;
    bytes32 prevBlockHash; // wire format (32 bytes)
    bytes32 merkleRoot;    // wire format (32 bytes)
    uint32 time;
    uint32 bits;
    uint32 nonce;
}

library BlockHeader {
    using BytesUtils for bytes;

    error InvalidHeaderLength();

    /// @notice Парсит 80-байтный raw header (wire format) и возвращает структуру и blockHash (sha256d)
    /// @param raw 80-byte block header (calldata)
    /// @return header разобранный HeaderData
    /// @return blockHash double-sha256(header) — тот самый block hash (как в wire)
    function parseHeader(bytes calldata raw) internal pure returns (BlockHeaderData memory header, bytes32 blockHash) {
        if (raw.length != 80) revert InvalidHeaderLength();

        header.version = raw.toUint32LE(0);

        // prevBlockHash (32 bytes) at offset 4
        bytes32 prev;
        bytes32 merkle;
        assembly {
            // calldataload loads 32 bytes starting at position (raw.offset + 4)
            prev := calldataload(add(raw.offset, 4))
            merkle := calldataload(add(raw.offset, 36))
        }
        header.prevBlockHash = prev;
        header.merkleRoot = merkle;

        header.time = raw.toUint32LE(68);
        header.bits = raw.toUint32LE(72);
        header.nonce = raw.toUint32LE(76);

        // blockHash = doubleSha256(raw)
        blockHash = BitcoinHash.doubleSha256(raw);
    }
}
"
    },
    "contracts/src/libs/TxMerkleProof.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import "../core/BitcoinHash.sol";

/**
 * @notice Merkle proof verification for Bitcoin transaction id (txid).
 *
 * Условие: leaf и siblings должны быть в том же байтовом порядке (endianness),
 * что и merkleRoot, хранящийся в block header. В этом репозитории мы используем
 * wire-format double-sha256 output (sha256d) как стандартное представление.
 *
 * verify: берёт leaf, по списку siblings (от снизу к верх) и вычисляет root,
 * сравнивая с переданным root.
 */
library TxMerkleProof {
    /// @notice Проверяет inclusion proof.
    /// @param siblings массив sibling hashes (по уровню, снизу вверх)
    /// @param index позиция листа (начиная с 0) в уровне листьев (первоначальный индекс)
    /// @param root ожидаемый merkle root (как в block header)
    /// @param leaf txid (как bytes32)
    /// @return ok true если proof корректен
    function verify(bytes32[] calldata siblings, uint256 index, bytes32 root, bytes32 leaf) internal pure returns (bool ok) {
        bytes32 h = leaf;
        uint256 idx = index;

        for (uint256 i = 0; i < siblings.length; ++i) {
            bytes32 s = siblings[i];
            if ((idx & 1) == 0) {
                // current is left
                h = BitcoinHash.doubleSha256(abi.encodePacked(h, s));
            } else {
                // current is right
                h = BitcoinHash.doubleSha256(abi.encodePacked(s, h));
            }
            idx >>= 1;
        }
        return h == root;
    }
}
"
    },
    "contracts/src/libs/TargetsHelper.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

/**
 * @title TargetsHelper
 * @notice Helper library for Bitcoin difficulty and work calculations
 */
library TargetsHelper {
    uint32 internal constant DIFFICULTY_ADJUSTMENT_INTERVAL = 2016;
    uint32 internal constant TARGET_TIME_PER_BLOCK = 600;
    uint32 internal constant TARGET_TIMESPAN = TARGET_TIME_PER_BLOCK * DIFFICULTY_ADJUSTMENT_INTERVAL;

    /**
     * @notice Convert compact difficulty bits to full 256-bit target
     * @param bits Compact difficulty representation (nBits)
     * @return 256-bit target value
     */
    function bitsToTarget(uint32 bits) internal pure returns (uint256) {
        uint256 exponent = uint8(bits >> 24);
        uint256 mantissa = uint256(bits & 0xFFFFFF);
        if (exponent == 0) return 0;
        if (exponent <= 3) {
            return mantissa >> (8 * (3 - exponent));
        } else {
            return mantissa * (1 << (8 * (exponent - 3)));
        }
    }

    /**
     * @notice Convert 256-bit target to compact difficulty bits
     * @param target Full 256-bit target
     * @return Compact difficulty representation (nBits)
     */
    function targetToBits(uint256 target) internal pure returns (uint32) {
        if (target == 0) return 0;
        uint256 n = _bytesLen(target);
        uint256 exponent = n;
        uint256 mantissa;

        if (n <= 3) {
            mantissa = target << (8 * (3 - n));
            exponent = 3;
        } else {
            mantissa = target >> (8 * (n - 3));
            if ((mantissa & 0x800000) != 0) {
                mantissa >>= 8;
                exponent = n + 1;
            }
        }

        return uint32((exponent << 24) | (mantissa & 0xFFFFFF));
    }

    /**
     * @notice Calculate work from target (higher target = lower work)
     * @param target The difficulty target
     * @return Work value (inversely proportional to target)
     */
    function workFromTarget(uint256 target) internal pure returns (uint256) {
        unchecked {
            return type(uint256).max / (target + 1);
        }
    }

    /**
     * @notice Check if a block height is a difficulty adjustment block
     * @param height Block height
     * @return True if this is an adjustment block
     */
    function isAdjustmentBlock(uint64 height) internal pure returns (bool) {
        return height != 0 && (height % DIFFICULTY_ADJUSTMENT_INTERVAL == 0);
    }

    /**
     * @notice Calculate new difficulty target based on actual timespan
     * @param prevTarget Previous difficulty target
     * @param actualTimespan Actual time taken for the interval
     * @return New difficulty target
     */
    function retarget(uint256 prevTarget, uint32 actualTimespan) internal pure returns (uint256) {
        uint32 span = actualTimespan;
        uint32 minSpan = TARGET_TIMESPAN / 4;
        uint32 maxSpan = TARGET_TIMESPAN * 4;

        if (span < minSpan) span = minSpan;
        if (span > maxSpan) span = maxSpan;

        uint256 newTarget = (prevTarget * span) / TARGET_TIMESPAN;
        if (newTarget == 0) newTarget = 1;
        // Max target (minimum difficulty)
        if (newTarget > 0x00000000FFFF0000000000000000000000000000000000000000000000000000) {
            newTarget = 0x00000000FFFF0000000000000000000000000000000000000000000000000000;
        }
        return newTarget;
    }

    /**
     * @notice Calculate the number of bytes needed to represent a number
     * @param x The number
     * @return n Number of bytes
     */
    function _bytesLen(uint256 x) private pure returns (uint256 n) {
        if (x == 0) return 0;
        assembly {
            n := add(div(sub(0, and(sub(x, 1), add(not(sub(x, 1)), 1))), 255), 1)
        }
    }
}
"
    },
    "contracts/src/core/BitcoinTxParser.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

library BitcoinTxParser {
    error InvalidVarInt();
    error InvalidTxLength();

    struct VarInt { uint256 value; uint256 size; }

    function _readVarInt(bytes calldata raw, uint256 offset) internal pure returns (VarInt memory varInt) {
        if (offset >= raw.length) revert InvalidVarInt();
        uint8 fb = uint8(raw[offset]);
        if (fb < 0xFD) return VarInt(fb, 1);
        if (fb == 0xFD) {
            if (offset + 3 > raw.length) revert InvalidVarInt();
            uint16 val = uint16(uint8(raw[offset+1])) | (uint16(uint8(raw[offset+2])) << 8);
            return VarInt(val, 3);
        }
        if (fb == 0xFE) {
            if (offset + 5 > raw.length) revert InvalidVarInt();
            uint32 val = uint32(uint8(raw[offset+1])) |
                        (uint32(uint8(raw[offset+2])) << 8) |
                        (uint32(uint8(raw[offset+3])) << 16) |
                        (uint32(uint8(raw[offset+4])) << 24);
            return VarInt(val, 5);
        }
        if (offset + 9 > raw.length) revert InvalidVarInt();
        uint256 val;
        unchecked {
            for (uint i = 0; i < 8; ++i) {
                val |= uint256(uint8(raw[offset + 1 + i])) << (8 * i);
            }
        }
        return VarInt(val, 9);
    }

    /**
     * @notice Parse first input from Bitcoin transaction
     * @dev Extracts prevTxId and vout from the first input
     * @param raw Raw Bitcoin transaction bytes
     * @return prevTxIdLE Previous transaction ID (little-endian)
     * @return vout Output index being spent
     */
    function parseFirstInput(bytes calldata raw) internal pure returns (bytes32 prevTxIdLE, uint32 vout) {
        require(raw.length >= 41, "InvalidTxLength");
        uint256 offset = 4;

        // Check for witness flag
        if (offset + 2 <= raw.length && raw[offset] == 0x00 && raw[offset+1] == 0x01) {
            offset += 2;
        }

        // Read input count
        VarInt memory varInt = _readVarInt(raw, offset);
        require(varInt.value >= 1, "no inputs");
        offset += varInt.size;

        // Read prevTxId (32 bytes)
        assembly {
            prevTxIdLE := calldataload(add(raw.offset, offset))
        }
        offset += 32;

        // Read vout (4 bytes, little-endian)
        vout = uint32(uint8(raw[offset])) |
               (uint32(uint8(raw[offset+1])) << 8) |
               (uint32(uint8(raw[offset+2])) << 16) |
               (uint32(uint8(raw[offset+3])) << 24);
    }

    /**
     * @notice Parse all inputs from Bitcoin transaction
     * @dev Returns arrays of all prevTxIds and vouts
     * @param raw Raw Bitcoin transaction bytes
     * @return prevTxIds Array of previous transaction IDs (little-endian)
     * @return vouts Array of output indices
     */
    function parseAllInputs(bytes calldata raw) internal pure returns (bytes32[] memory prevTxIds, uint32[] memory vouts) {
        require(raw.length >= 10, "InvalidTxLength");
        uint256 offset = 4;

        // Check for witness flag
        if (offset + 2 <= raw.length && raw[offset] == 0x00 && raw[offset+1] == 0x01) {
            offset += 2;
        }

        // Read input count
        VarInt memory vinCount = _readVarInt(raw, offset);
        offset += vinCount.size;

        prevTxIds = new bytes32[](vinCount.value);
        vouts = new uint32[](vinCount.value);

        for (uint256 i = 0; i < vinCount.value; i++) {
            // Read prevTxId (32 bytes)
            bytes32 prevTxId;
            assembly {
                prevTxId := calldataload(add(raw.offset, offset))
            }
            prevTxIds[i] = prevTxId;
            offset += 32;

            // Read vout (4 bytes, little-endian)
            uint32 vout = uint32(uint8(raw[offset])) |
                         (uint32(uint8(raw[offset+1])) << 8) |
                         (uint32(uint8(raw[offset+2])) << 16) |
                         (uint32(uint8(raw[offset+3])) << 24);
            vouts[i] = vout;
            offset += 4;

            // Skip scriptSig
            VarInt memory scriptLen = _readVarInt(raw, offset);
            offset += scriptLen.size + scriptLen.value;

            // Skip sequence
            offset += 4;
        }
    }

    function doubleSha256(bytes memory data) internal pure returns (bytes32) {
        return sha256(abi.encodePacked(sha256(data)));
    }

    function flipBytes32(bytes32 x) internal pure returns (bytes32) {
        bytes32 y;
        unchecked {
            for (uint i = 0; i < 32; ++i) {
                y |= bytes32(uint256(uint8(x[i])) << ((31 - i) * 8));
            }
        }
        return y;
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/ERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "./IERC20.sol";
import {IERC20Metadata} from "./extensions/IERC20Metadata.sol";
import {Context} from "../../utils/Context.sol";
import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol";

/**
 * @dev Implementation of the {IERC20} interface.
 *
 * This implementation is agnostic to the way tokens are created. This means
 * that a supply mechanism has to be added in a derived contract using {_mint}.
 *
 * TIP: For a detailed writeup see our guide
 * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
 * to implement supply mechanisms].
 *
 * The default value of {decimals} is 18. To change this, you should override
 * this function so it returns a different value.
 *
 * We have followed general OpenZeppelin Contracts guidelines: functions revert
 * instead returning `false` on failure. This behavior is nonetheless
 * conventional and does not conflict with the expectations of ERC-20
 * applications.
 */
abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {
    mapping(address account => uint256) private _balances;

    mapping(address account => mapping(address spender => uint256)) private _allowances;

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;

    /**
     * @dev Sets the values for {name} and {symbol}.
     *
     * Both values are immutable: they can only be set once during construction.
     */
    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

    /**
     * @dev Returns the name of the token.
     */
    function name() public view virtual returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view virtual returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Returns the number of decimals used to get its user representation.
     * For example, if `decimals` equals `2`, a balance of `505` tokens should
     * be displayed to a user as `5.05` (`505 / 10 ** 2`).
     *
     * Tokens usually opt for a value of 18, imitating the relationship between
     * Ether and Wei. This is the default value returned by this function, unless
     * it's overridden.
     *
     * NOTE: This information is only used for _display_ purposes: it in
     * no way affects any of the arithmetic of the contract, including
     * {IERC20-balanceOf} and {IERC20-transfer}.
     */
    function decimals() public view virtual returns (uint8) {
        return 18;
    }

    /// @inheritdoc IERC20
    function totalSupply() public view virtual returns (uint256) {
        return _totalSupply;
    }

    /// @inheritdoc IERC20
    function balanceOf(address account) public view virtual returns (uint256) {
        return _balances[account];
    }

    /**
     * @dev See {IERC20-transfer}.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - the caller must have a balance of at least `value`.
     */
    function transfer(address to, uint256 value) public virtual returns (bool) {
        address owner = _msgSender();
        _transfer(owner, to, value);
        return true;
    }

    /// @inheritdoc IERC20
    function allowance(address owner, address spender) public view virtual returns (uint256) {
        return _allowances[owner][spender];
    }

    /**
     * @dev See {IERC20-approve}.
     *
     * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on
     * `transferFrom`. This is semantically equivalent to an infinite approval.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 value) public virtual returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, value);
        return true;
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Skips emitting an {Approval} event indicating an allowance update. This is not
     * required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve].
     *
     * NOTE: Does not update the allowance if the current allowance
     * is the maximum `uint256`.
     *
     * Requirements:
     *
     * - `from` and `to` cannot be the zero address.
     * - `from` must have a balance of at least `value`.
     * - the caller must have allowance for ``from``'s tokens of at least
     * `value`.
     */
    function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
        address spender = _msgSender();
        _spendAllowance(from, spender, value);
        _transfer(from, to, value);
        return true;
    }

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to`.
     *
     * This internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead.
     */
    function _transfer(address from, address to, uint256 value) internal {
        if (from == address(0)) {
            revert ERC20InvalidSender(address(0));
        }
        if (to == address(0)) {
            revert ERC20InvalidReceiver(address(0));
        }
        _update(from, to, value);
    }

    /**
     * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`
     * (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
     * this function.
     *
     * Emits a {Transfer} event.
     */
    function _update(address from, address to, uint256 value) internal virtual {
        if (from == address(0)) {
            // Overflow check required: The rest of the code assumes that totalSupply never overflows
            _totalSupply += value;
        } else {
            uint256 fromBalance = _balances[from];
            if (fromBalance < value) {
                revert ERC20InsufficientBalance(from, fromBalance, value);
            }
            unchecked {
                // Overflow not possible: value <= fromBalance <= totalSupply.
                _balances[from] = fromBalance - value;
            }
        }

        if (to == address(0)) {
            unchecked {
                // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
                _totalSupply -= value;
            }
        } else {
            unchecked {
                // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
                _balances[to] += value;
            }
        }

        emit Transfer(from, to, value);
    }

    /**
     * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
     * Relies on the `_update` mechanism
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead.
     */
    function _mint(address account, uint256 value) internal {
        if (account == address(0)) {
            revert ERC20InvalidReceiver(address(0));
        }
        _update(address(0), account, value);
    }

    /**
     * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.
     * Relies on the `_update` mechanism.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead
     */
    function _burn(address account, uint256 value) internal {
        if (account == address(0)) {
            revert ERC20InvalidSender(address(0));
        }
        _update(account, address(0), value);
    }

    /**
     * @dev Sets `value` as the allowance of `spender` over the `owner`'s tokens.
     *
     * This internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     *
     * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
     */
    function _approve(address owner, address spender, uint256 value) internal {
        _approve(owner, spender, value, true);
    }

    /**
     * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.
     *
     * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by
     * `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any
     * `Approval` event during `transferFrom` operations.
     *
     * Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to
     * true using the following override:
     *
     * ```solidity
     * function _approve(address owner, address spender, uint256 value, bool) internal virtual override {
     *     super._approve(owner, spender, value, true);
     * }
     * ```
     *
     * Requirements are the same as {_approve}.
     */
    function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
        if (owner == address(0)) {
            revert ERC20InvalidApprover(address(0));
        }
        if (spender == address(0)) {
            revert ERC20InvalidSpender(address(0));
        }
        _allowances[owner][spender] = value;
        if (emitEvent) {
            emit Approval(owner, spender, value);
        }
    }

    /**
     * @dev Updates `owner`'s allowance for `spender` based on spent `value`.
     *
     * Does not update the allowance value in case of infinite allowance.
     * Revert if not enough allowance is available.
     *
     * Does not emit an {Approval} event.
     */
    function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
        uint256 currentAllowance = allowance(owner, spender);
        if (currentAllowance < type(uint256).max) {
            if (currentAllowance < value) {
                revert ERC20InsufficientAllowance(spender, currentAllowance, value);
            }
            unchecked {
                _approve(owner, spender, currentAllowance - value, false);
            }
        }
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Permit.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/extensions/ERC20Permit.sol)

pragma solidity ^0.8.24;

import {IERC20Permit} from "./IERC20Permit.sol";
import {ERC20} from "../ERC20.sol";
import {ECDSA} from "../../../utils/cryptography/ECDSA.sol";
import {EIP712} from "../../../utils/cryptography/EIP712.sol";
import {Nonces} from "../../../utils/Nonces.sol";

/**
 * @dev Implementation of the ERC-20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[ERC-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC-20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on `{IERC20-approve}`, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 */
abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712, Nonces {
    bytes32 private constant PERMIT_TYPEHASH =
        keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");

    /**
     * @dev Permit deadline has expired.
     */
    error ERC2612ExpiredSignature(uint256 deadline);

    /**
     * @dev Mismatched signature.
     */
    error ERC2612InvalidSigner(address signer, address owner);

    /**
     * @dev Initializes the {EIP712} domain separator using the `name` parameter, and setting `version` to `"1"`.
     *
     * It's a good idea to use the same `name` that is defined as the ERC-20 token name.
     */
    constructor(string memory name) EIP712(name, "1") {}

    /// @inheritdoc IERC20Permit
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public virtual {
        if (block.timestamp > deadline) {
            revert ERC2612ExpiredSignature(deadline);
        }

        bytes32 structHash = keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline));

        bytes32 hash = _hashTypedDataV4(structHash);

        address signer = ECDSA.recover(hash, v, r, s);
        if (signer != owner) {
            revert ERC2612InvalidSigner(signer, owner);
        }

        _approve(owner, spender, value);
    }

    /// @inheritdoc IERC20Permit
    function nonces(address owner) public view virtual override(IERC20Permit, Nonces) returns (uint256) {
        return super.nonces(owner);
    }

    /// @inheritdoc IERC20Permit
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view virtual returns (bytes32) {
        return _domainSeparatorV4();
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/access/AccessControl.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (access/AccessControl.sol)

pragma solidity ^0.8.20;

import {IAccessControl} from "./IAccessControl.sol";
import {Context} from "../utils/Context.sol";
import {IERC165, ERC165} from "../utils/introspection/ERC165.sol";

/**
 * @dev Contract module that allows children to implement role-based access
 * control mechanisms. This is a lightweight version that doesn't allow enumerating role
 * members except through off-chain means by accessing the contract event logs. Some
 * applications may benefit from on-chain enumerability, for those cases see
 * {AccessControlEnumerable}.
 *
 * Roles are referred to by their `bytes32` identifier. These should be exposed
 * in the external API and be unique. The best way to achieve this is by
 * using `public constant` hash digests:
 *
 * ```solidity
 * bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
 * ```
 *
 * Roles can be used to represent a set of permissions. To restrict access to a
 * function call, use {hasRole}:
 *
 * ```solidity
 * function foo() public {
 *     require(hasRole(M

Tags:
ERC20, ERC165, Multisig, Mintable, Burnable, Swap, Upgradeable, Multi-Signature, Factory|addr:0xc60b7b50e81b22d2f0bee0cdee97e6149307b0ee|verified:true|block:23661376|tx:0x2604fe7a284171ee4b7aca5876d80f7f4c46f9a89b6658393d2864795fdf5adb|first_check:1761480320

Submitted on: 2025-10-26 13:05:21

Comments

Log in to comment.

No comments yet.