Sova Docs
  • Sova Developer Docs
  • Documentation
    • Intro
    • How It Works
    • Node Design & Architecture
    • Sova Whitepaper
  • TECHNOLOGY
    • Bitcoin Precompiles
    • Double Spend Protection
    • For Frontend Developers
    • For Solidity Developers
  • Contributing
  • NETWORK INFORMATION
    • Connecting to Sova
    • OP Chain Configuration
    • Sova Contracts
  • Node Operators
    • Running Sova
  • Community & Support
    • Frequently Asked Questions (FAQ)
Powered by GitBook
On this page
  • Precompile Library
  • Custom Smart Contract Example
  1. TECHNOLOGY

For Solidity Developers

The tools we use to work on Sova are the same as any other EVM network. Developers can use Foundry, Tenderly, common web3 frontend libraries, and much more. This is one of the primary advantages of the network. Sova can easily integrate with any other EVM network while at the same time benefiting from the hard work of the largest Blockchain dev community in the world.

Precompile Library

SovaBitcoin.sol

// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

/**
 * @title SovaBitcoin
 * @author Sova Labs
 *
 * A library for integrating with Bitcoin precompiles on Sova.
 */
library SovaBitcoin {
    /// @notice Bitcoin precompile address
    address public constant BTC_PRECOMPILE = address(0x999);

    /// @notice Bitcoin context contract address
    address public constant SOVA_L1_BLOCK_ADDRESS = 0x2100000000000000000000000000000000000015;
    /// @notice Native Bitcoin wrapper address
    address public constant UBTC_ADDRESS = 0x2100000000000000000000000000000000000020;

    /// @notice Bitcoin precompile selectors
    bytes4 public constant BROADCAST_BYTES = 0x00000001;
    bytes4 public constant DECODE_BYTES = 0x00000002;
    bytes4 public constant CHECKSIG_BYTES = 0x00000003;
    bytes4 public constant ADDRESS_CONVERT_LEADING_BYTES = 0x00000004;
    bytes4 public constant UBTC_SIGN_TX_BYTES = 0x00000005;

    struct Output {
        string addr;
        uint256 value;
        bytes script;
    }

    struct Input {
        bytes32 prevTxHash;
        uint32 outputIndex;
        bytes scriptSig;
        bytes[] witness;
    }

    struct BitcoinTx {
        bytes32 txid;
        Output[] outputs;
        Input[] inputs;
        uint256 locktime;
    }

    error PrecompileCallFailed();
    error InvalidOutput(string expected, string actual);
    error InvalidDeposit();
    error InvalidAmount();
    error InsufficientInput();
    error InvalidLocktime();

    /// @custom:semver 0.1.0-beta.1
    function version() public pure returns (string memory) {
        return "0.1.0-beta.1";
    }

    /**
     * @notice Decodes a raw Bitcoin transaction into a structured format
     *
     * @param signedTx          The raw signed Bitcoin transaction
     *
     * @return BitcoinTx        Object containing the decoded transaction data
     */
    function decodeBitcoinTx(bytes memory signedTx) internal view returns (BitcoinTx memory) {
        (bool success, bytes memory returndata) = BTC_PRECOMPILE.staticcall(abi.encodePacked(DECODE_BYTES, signedTx));
        if (!success) revert PrecompileCallFailed();
        return abi.decode(returndata, (BitcoinTx));
    }

    /**
     * @notice Verifies the signatures in a Bitcoin transaction
     *
     * @param signedTx       The raw signed Bitcoin transaction
     *
     * @return success       Boolean indicating if payload is valid
     */
    function checkSignature(bytes calldata signedTx) internal view returns (bool success) {
        (success,) = BTC_PRECOMPILE.staticcall(abi.encodePacked(CHECKSIG_BYTES, signedTx));
    }

    /**
     * @notice Retrieves the unique deposit address for the corresponding EVM address
     *
     * @param addr             The EVM address to convert
     *
     * @return returnData      The Bitcoin deposit address in bytes format
     */
    function convertToBtcAddress(address addr) internal returns (bytes memory) {
        (bool success, bytes memory returnData) =
            BTC_PRECOMPILE.call(abi.encodePacked(ADDRESS_CONVERT_LEADING_BYTES, addr));
        if (!success) revert PrecompileCallFailed();
        return returnData;
    }

    /**
     * @notice Broadcasts a signed Bitcoin transaction to the Bitcoin network
     *
     * @param signedTx         The raw signed Bitcoin transaction to broadcast
     */
    function broadcastBitcoinTx(bytes memory signedTx) internal {
        (bool success,) = BTC_PRECOMPILE.call(abi.encodePacked(BROADCAST_BYTES, signedTx));
        if (!success) revert PrecompileCallFailed();
    }

    /**
     * @notice Validates a Bitcoin transaction for deposit purposes and returns the decoded transaction
     * @dev Performs a series of checks on the transaction structure and content:
     *      1. Verifies the transaction has between 1 and 3 outputs
     *      2. Confirms the first output value meets the minimum amount
     *      3. Ensures the transaction has at least one input
     *      4. Validates the locktime is not in the future
     *      5. Checks that the first output address matches the network's receive address
     *
     * @param signedTx          The raw signed Bitcoin transaction
     * @param amount            The minimum expected amount in satoshis
     *
     * @return btcTx            The decoded Bitcoin transaction
     */
    function isValidDeposit(bytes memory signedTx, uint256 amount) internal returns (BitcoinTx memory) {
        BitcoinTx memory btcTx = decodeBitcoinTx(signedTx);

        if (btcTx.outputs.length < 1 || btcTx.outputs.length > 3) {
            revert InvalidDeposit();
        }

        if (btcTx.outputs[0].value != amount) {
            revert InvalidAmount();
        }

        if (btcTx.inputs.length < 1) {
            revert InsufficientInput();
        }

        if (btcTx.locktime > block.timestamp) {
            revert InvalidLocktime();
        }

        // Recover the callers unique bitcoin deposit address
        bytes memory convertedBtcAddress = SovaBitcoin.convertToBtcAddress(msg.sender);

        if (keccak256(convertedBtcAddress) != keccak256(bytes(btcTx.outputs[0].addr))) {
            revert InvalidOutput(btcTx.outputs[0].addr, string(convertedBtcAddress));
        }

        return btcTx;
    }
}

Custom Smart Contract Example

Broadcast a Bitcoin Transaction

// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

import {SovaBitcoin} from "@sova-network/src/lib/SovaBitcoin.sol";

contract BitcoinSender {
    function broadcastBitcoinTx(bytes memory rawTransaction) public returns (bytes32) {
        // Decode the transaction first to get the txid
        SovaBitcoin.BitcoinTx memory btcTx = SovaBitcoin.decodeBitcoinTx(rawTransaction);
        
        // Broadcast the transaction
        SovaBitcoin.broadcastBitcoinTx(rawTransaction);
        
        return btcTx.txid;
    }
}

PreviousFor Frontend DevelopersNextContributing

Last updated 2 days ago