Blockchain / Global / Address

Contract

0x50AAEE5ECE41627F2bc4b64Ad29a4659a5C018cb

Balance
81.489501 SOMI
Transactions sent
1
Type
Contract · 8,915 bytes of code

SomniaVRFCoordinator Verified

Compiler
solc 0.8.30+commit.73712a01
Language
Solidity
Optimization
Yes, 200 runs
EVM version
shanghai
License
—
Verified
2026-10-12

src/SomniaVRFCoordinator.sol

and 6 more files

Solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

import {ISomniaVRFCoordinator} from "./interfaces/ISomniaVRFCoordinator.sol";
import {IVRFConsumer} from "./interfaces/IVRFConsumer.sol";
import {ISomniaEventHandler} from "./lib/reactivity/ISomniaEventHandler.sol";
import {ISomniaReactivityPrecompile} from "./lib/reactivity/ISomniaReactivityPrecompile.sol";
import {SomniaExtensions} from "./lib/reactivity/SomniaExtensions.sol";
import {
    IAgentRequester,
    IJsonApiAgent,
    Response as AgentResponse,
    Request as AgentRequestStruct,
    ResponseStatus as AgentResponseStatus
} from "./lib/agents/ISomniaAgents.sol";

/// @title SomniaVRFCoordinator
/// @notice Reactivity-native VRF coordinator for Somnia, with two entropy modes:
///
///         Mode A — blockhash-only. Cheap, fast, testnet-grade.
///         Mode B — drand mix (via Somnia Agents JSON API Request). Trust-minimized,
///                  publicly verifiable, ~150x the cost of Mode A.
///
///         The coordinator itself pays every request out of its own STT balance.
///         Consumers only pay their tx gas.
contract SomniaVRFCoordinator is ISomniaVRFCoordinator, ISomniaEventHandler {
    // ─── Constants ────────────────────────────────────────────────────────────

    uint16 public constant MIN_COMMIT_DELAY_BLOCKS = 16;
    uint16 public constant MAX_COMMIT_DELAY_BLOCKS = 200;
    uint32 public constant MAX_CALLBACK_GAS_LIMIT = 2_500_000;
    uint32 public constant MAX_NUM_WORDS = 500;

    address public constant SOMNIA_REACTIVITY_PRECOMPILE = address(0x0100);

    // Somnia Agents platform on Shannon testnet + JSON API Request agent id.
    // These are fixed per network; a mainnet-target coordinator would set the
    // platform address in the constructor instead of a constant.
    address public immutable AGENT_PLATFORM;
    uint256 public constant JSON_API_AGENT_ID = 13174292974160097713;
    uint256 public constant JSON_API_PRICE_PER_AGENT = 0.03 ether;
    uint256 public constant AGENT_SUBCOMMITTEE_SIZE = 3;

    // Mainnet deployments set this to `false` to reject Mode A (blockhash-only)
    // requests at request time. Testnet leaves it enabled so the coin-flip demo
    // can show both modes side by side. Immutable, set once at construction.
    bool public immutable FAST_MODE_ENABLED;

    // drand League of Entropy — default mainnet chain, /public/latest returns
    // the freshest round. The selector picks out the `randomness` field, a
    // 64-char hex string (32-byte BLS-derived hash). We keccak it with the
    // captured blockhash to form the final seed.
    string public constant DRAND_URL = "https://api.drand.sh/public/latest";
    string public constant DRAND_SELECTOR = "randomness";

    // ─── State ────────────────────────────────────────────────────────────────

    address public owner;
    uint256 public nextRequestId = 1;

    mapping(uint256 => Request) internal _requests;
    mapping(uint64 => uint256[]) internal _queuedAt;
    mapping(uint256 => uint256) internal _drandAgentToVrf; // agent requestId → VRF requestId

    // ─── Errors + events (owner) ──────────────────────────────────────────────

    error NotOwner();
    error TransferFailed();

    event OwnershipTransferred(address indexed previous, address indexed next);
    event CoordinatorFunded(address indexed from, uint256 amount, uint256 newBalance);
    event CoordinatorWithdrawn(address indexed to, uint256 amount);

    modifier onlyOwner() {
        if (msg.sender != owner) revert NotOwner();
        _;
    }

    // ─── Construction ─────────────────────────────────────────────────────────

    constructor(address _owner, address _agentPlatform, bool _fastModeEnabled) payable {
        owner = _owner;
        AGENT_PLATFORM = _agentPlatform;
        FAST_MODE_ENABLED = _fastModeEnabled;
        emit OwnershipTransferred(address(0), _owner);
        if (msg.value > 0) emit CoordinatorFunded(msg.sender, msg.value, address(this).balance);
    }

    // ─── Requests ─────────────────────────────────────────────────────────────

    function requestRandomWords(RandomWordsRequest calldata req)
        external
        override
        returns (uint256 requestId)
    {
        if (req.commitDelayBlocks < MIN_COMMIT_DELAY_BLOCKS) {
            revert CommitDelayTooShort(req.commitDelayBlocks, MIN_COMMIT_DELAY_BLOCKS);
        }
        if (req.commitDelayBlocks > MAX_COMMIT_DELAY_BLOCKS) {
            revert CommitDelayTooLong(req.commitDelayBlocks, MAX_COMMIT_DELAY_BLOCKS);
        }
        if (req.callbackGasLimit > MAX_CALLBACK_GAS_LIMIT) {
            revert CallbackGasTooHigh(req.callbackGasLimit, MAX_CALLBACK_GAS_LIMIT);
        }
        if (req.numWords == 0 || req.numWords > MAX_NUM_WORDS) {
            revert NumWordsOutOfRange(req.numWords, 1, MAX_NUM_WORDS);
        }
        if (!req.useVerifiableEntropy && !FAST_MODE_ENABLED) {
            revert FastModeDisabled();
        }

        requestId = nextRequestId++;
        uint64 revealBlock = uint64(block.number) + req.commitDelayBlocks;

        _requests[requestId] = Request({
            consumer: msg.sender,
            callbackGasLimit: req.callbackGasLimit,
            numWords: req.numWords,
            revealBlock: revealBlock,
            useVerifiableEntropy: req.useVerifiableEntropy,
            fulfilled: false,
            capturedBlockhash: bytes32(0)
        });

        _queuedAt[revealBlock].push(requestId);

        if (_queuedAt[revealBlock].length == 1) {
            SomniaExtensions.scheduleSubscriptionAtBlock(
                address(this),
                revealBlock,
                SomniaExtensions.SubscriptionOptions({
                    priorityFeePerGas: SomniaExtensions.DEFAULT_PRIORITY_FEE_PER_GAS,
                    maxFeePerGas: SomniaExtensions.DEFAULT_MAX_FEE_PER_GAS,
                    gasLimit: SomniaExtensions.DEFAULT_HANDLER_GAS_LIMIT
                })
            );
        }

        emit RandomWordsRequested(
            requestId,
            msg.sender,
            revealBlock,
            req.numWords,
            req.callbackGasLimit,
            req.useVerifiableEntropy
        );
    }

    // ─── Reactivity callback ──────────────────────────────────────────────────

    function onEvent(address, /* emitter */ bytes32[] calldata eventTopics, bytes calldata /* data */)
        external
        override
    {
        if (msg.sender != SOMNIA_REACTIVITY_PRECOMPILE) revert NotPrecompile(msg.sender);

        uint64 blockNumber = uint64(uint256(eventTopics[1]));
        uint256[] memory ids = _queuedAt[blockNumber];
        delete _queuedAt[blockNumber];

        for (uint256 i = 0; i < ids.length; i++) {
            _revealOrKickDrand(ids[i], blockNumber);
        }
    }

    function _revealOrKickDrand(uint256 requestId, uint64 blockNumber) internal {
        Request storage r = _requests[requestId];
        if (r.consumer == address(0)) revert UnknownRequest(requestId);
        if (r.fulfilled) return;

        bytes32 bh = blockhash(uint256(blockNumber) - 1);
        r.capturedBlockhash = bh;
        emit BlockhashCaptured(requestId, blockNumber, bh);

        if (!r.useVerifiableEntropy) {
            _fulfillFromSeed(requestId, keccak256(abi.encode(bh, requestId, r.consumer, blockNumber)));
        } else {
            _kickDrandRequest(requestId);
        }
    }

    function _kickDrandRequest(uint256 requestId) internal {
        bytes memory payload = abi.encodeWithSelector(
            IJsonApiAgent.fetchString.selector,
            DRAND_URL,
            DRAND_SELECTOR
        );

        IAgentRequester platform = IAgentRequester(AGENT_PLATFORM);
        uint256 deposit = platform.getRequestDeposit()
            + JSON_API_PRICE_PER_AGENT * AGENT_SUBCOMMITTEE_SIZE;

        // Best-effort; failure emits a fulfilled=false event so consumers can detect it.
        try platform.createRequest{value: deposit}(
            JSON_API_AGENT_ID,
            address(this),
            this.handleDrandResponse.selector,
            payload
        ) returns (uint256 agentReqId) {
            _drandAgentToVrf[agentReqId] = requestId;
            emit DrandRequested(requestId, agentReqId);
        } catch {
            Request storage r = _requests[requestId];
            r.fulfilled = true;
            emit RandomWordsFulfilled(requestId, r.consumer, r.revealBlock, false, true);
        }
    }

    // ─── Agents platform callback ─────────────────────────────────────────────

    /// @notice Called by the Somnia Agents platform when the JSON API validator
    ///         subcommittee reaches consensus on a drand fetch.
    function handleDrandResponse(
        uint256 agentRequestId,
        AgentResponse[] memory responses,
        AgentResponseStatus status,
        AgentRequestStruct memory /* details */
    ) external {
        if (msg.sender != AGENT_PLATFORM) revert NotAgentPlatform(msg.sender);

        uint256 vrfRequestId = _drandAgentToVrf[agentRequestId];
        if (vrfRequestId == 0) revert UnknownAgentRequest(agentRequestId);
        delete _drandAgentToVrf[agentRequestId];

        Request storage r = _requests[vrfRequestId];
        if (r.fulfilled) revert DrandAlreadyResolved(vrfRequestId);

        // Consensus must have succeeded; a failed drand fetch fulfils the VRF
        // as failed so consumers can retry rather than block indefinitely.
        if (status != AgentResponseStatus.Success || responses.length == 0) {
            r.fulfilled = true;
            emit RandomWordsFulfilled(vrfRequestId, r.consumer, r.revealBlock, false, true);
            return;
        }

        // Every validator ABI-encoded the same string; take the first entry.
        string memory drandHex = abi.decode(responses[0].result, (string));
        bytes memory drandBytes = bytes(drandHex);

        bytes32 seed = keccak256(
            abi.encode(
                drandBytes,
                r.capturedBlockhash,
                vrfRequestId,
                r.consumer,
                r.revealBlock
            )
        );

        _fulfillFromSeed(vrfRequestId, seed);
    }

    // ─── Fulfillment ──────────────────────────────────────────────────────────

    function _fulfillFromSeed(uint256 requestId, bytes32 seed) internal {
        Request storage r = _requests[requestId];
        r.fulfilled = true;

        uint256 n = r.numWords;
        uint256[] memory words = new uint256[](n);
        for (uint256 i = 0; i < n; i++) {
            words[i] = uint256(keccak256(abi.encode(seed, i)));
        }

        bool ok;
        try IVRFConsumer(r.consumer).rawFulfillRandomWords{gas: r.callbackGasLimit}(requestId, words) {
            ok = true;
        } catch {
            ok = false;
        }

        emit RandomWordsFulfilled(requestId, r.consumer, r.revealBlock, ok, r.useVerifiableEntropy);
    }

    // ─── Views ────────────────────────────────────────────────────────────────

    function getRequest(uint256 requestId) external view override returns (Request memory) {
        return _requests[requestId];
    }

    function pendingRequestsAt(uint64 blockNumber) external view returns (uint256[] memory) {
        return _queuedAt[blockNumber];
    }

    function vrfRequestOfDrand(uint256 agentRequestId) external view returns (uint256) {
        return _drandAgentToVrf[agentRequestId];
    }

    // ─── Funding and admin ────────────────────────────────────────────────────

    receive() external payable {
        emit CoordinatorFunded(msg.sender, msg.value, address(this).balance);
    }

    function withdraw(address payable to, uint256 amount) external onlyOwner {
        (bool ok,) = to.call{value: amount}("");
        if (!ok) revert TransferFailed();
        emit CoordinatorWithdrawn(to, amount);
    }

    function transferOwnership(address next) external onlyOwner {
        emit OwnershipTransferred(owner, next);
        owner = next;
    }
}

Other files

  • src/interfaces/ISomniaVRFCoordinator.sol
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.30;
    
    /// @title ISomniaVRFCoordinator
    /// @notice Reactivity-native VRF coordinator for Somnia. Consumers request
    ///         random words; the coordinator delivers them via a callback after
    ///         a commit delay.
    ///
    ///         Two entropy modes:
    ///
    ///         Mode A — fast (`useVerifiableEntropy = false`)
    ///           Entropy from `blockhash(revealBlock - 1)`. Cheap and low-latency,
    ///           but the block producer for the reveal block can influence it.
    ///           Testnet-grade only.
    ///
    ///         Mode B — verifiable (`useVerifiableEntropy = true`)
    ///           Entropy mixes drand (League of Entropy publicly-verifiable
    ///           beacon) with the blockhash. The coordinator fires a Somnia
    ///           Agents JSON API Request at reveal time; the resulting seed is
    ///           `keccak256(drand, blockhash, requestId, consumer, revealBlock)`.
    ///           Compromising either drand or the reveal-block producer alone
    ///           does not compromise the result.
    interface ISomniaVRFCoordinator {
        struct RandomWordsRequest {
            uint32 callbackGasLimit;
            uint16 commitDelayBlocks;
            uint32 numWords;
            bool useVerifiableEntropy;
        }
    
        struct Request {
            address consumer;
            uint32 callbackGasLimit;
            uint32 numWords;
            uint64 revealBlock;
            bool useVerifiableEntropy;
            bool fulfilled;
            bytes32 capturedBlockhash;
        }
    
        event RandomWordsRequested(
            uint256 indexed requestId,
            address indexed consumer,
            uint64 revealBlock,
            uint32 numWords,
            uint32 callbackGasLimit,
            bool useVerifiableEntropy
        );
    
        event BlockhashCaptured(
            uint256 indexed requestId,
            uint64 revealBlock,
            bytes32 blockhashValue
        );
    
        event DrandRequested(
            uint256 indexed requestId,
            uint256 indexed agentRequestId
        );
    
        event RandomWordsFulfilled(
            uint256 indexed requestId,
            address indexed consumer,
            uint64 revealBlock,
            bool callbackSuccess,
            bool usedVerifiableEntropy
        );
    
        error CommitDelayTooShort(uint16 provided, uint16 minimum);
        error CommitDelayTooLong(uint16 provided, uint16 maximum);
        error CallbackGasTooHigh(uint32 provided, uint32 maximum);
        error NumWordsOutOfRange(uint32 provided, uint32 minimum, uint32 maximum);
        error UnknownRequest(uint256 requestId);
        error UnknownAgentRequest(uint256 agentRequestId);
        error NotPrecompile(address caller);
        error NotAgentPlatform(address caller);
        error DrandAlreadyResolved(uint256 requestId);
        error DrandResponseFailed(uint8 status);
        error FastModeDisabled();
    
        /// @notice Request `numWords` uniformly random uint256 values, delivered via
        ///         the caller's `fulfillRandomWords(requestId, uint256[] words)` callback
        ///         after `commitDelayBlocks` blocks (Mode A) or after the drand round
        ///         following that block resolves (Mode B).
        function requestRandomWords(RandomWordsRequest calldata req)
            external
            returns (uint256 requestId);
    
        /// @notice Read a pending or historical request.
        function getRequest(uint256 requestId) external view returns (Request memory);
    }
    
  • src/interfaces/IVRFConsumer.sol
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.30;
    
    /// @title IVRFConsumer
    /// @notice Callback interface a VRF consumer must implement.
    ///         Signature mirrors Chainlink's VRFConsumerBaseV2Plus for portability.
    interface IVRFConsumer {
        function rawFulfillRandomWords(uint256 requestId, uint256[] calldata randomWords) external;
    }
    
  • src/lib/agents/ISomniaAgents.sol
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.30;
    
    /// @notice Types and interfaces for the Somnia Agents platform.
    ///         Vendored from somnia-chain/somnia-agents-docs so the coordinator can
    ///         fire an off-chain agent (here: JSON API Request → drand) alongside
    ///         the Reactivity BlockTick that carries the blockhash side of the seed.
    
    enum ConsensusType {
        Majority,
        Threshold
    }
    
    enum ResponseStatus {
        None,
        Pending,
        Success,
        Failed,
        TimedOut
    }
    
    struct Response {
        address validator;
        bytes result;
        ResponseStatus status;
        uint256 receipt;
        uint256 timestamp;
        uint256 executionCost;
    }
    
    struct Request {
        uint256 id;
        address requester;
        address callbackAddress;
        bytes4 callbackSelector;
        address[] subcommittee;
        Response[] responses;
        uint256 responseCount;
        uint256 failureCount;
        uint256 threshold;
        uint256 createdAt;
        uint256 deadline;
        ResponseStatus status;
        ConsensusType consensusType;
        uint256 remainingBudget;
        uint256 perAgentBudget;
    }
    
    interface IAgentRequester {
        function createRequest(
            uint256 agentId,
            address callbackAddress,
            bytes4 callbackSelector,
            bytes calldata payload
        ) external payable returns (uint256 requestId);
    
        function getRequestDeposit() external view returns (uint256);
    }
    
    /// @notice ABI of the JSON API Request base agent (agentId 13174292974160097713).
    interface IJsonApiAgent {
        function fetchString(string calldata url, string calldata selector)
            external
            returns (string memory);
    }
    
  • src/lib/reactivity/ISomniaEventHandler.sol
    // SPDX-License-Identifier: MIT
    // Copyright (c) 2026 Somnia Foundation
    
    pragma solidity 0.8.30;
    
    /// @notice Somnia event handler interface
    /// @title ISomniaEventHandler
    /// @author Somnia Foundation
    interface ISomniaEventHandler {
    
        /**
         * @notice Default function invoked for a matching reactivity subscription filter
         * @param emitter Smart contract that emitted the EVM event log(s)
         * @param eventTopics List of event topics associated with the event (event signature being first param)
         * @param data Event data for non-indexed event arguments. May be empty if event only has indexed params
         */
        function onEvent(
            address emitter,
            bytes32[] calldata eventTopics,
            bytes calldata data
        ) external;
    
    }
  • src/lib/reactivity/ISomniaReactivityPrecompile.sol
    // SPDX-License-Identifier: MIT
    // Copyright (c) 2026 Somnia Foundation
    
    pragma solidity 0.8.30;
    
    /// @notice Somnia reactivity precompile interface and related data structures
    /// @title ISomniaReactivityPrecompile
    /// @author Somnia Foundation
    interface ISomniaReactivityPrecompile {
        /// @notice Data structure representing a subscription
        struct SubscriptionData {
            // Topic filter, use 0x0 to indicate a wildcard or unused topic
            bytes32[4] eventTopics;
            //  Origin (tx.origin) filter, use address(0) to indicate a wildcard
            address origin;
            // Reserved for future use, use address(0)
            address caller;
            // Contract emitting the event filter, use address(0) to indicate a wildcard
            address emitter;
            // The address of the contract that will handle the event
            address handlerContractAddress;
            // The function selector in the handler contract, defaults to ISomniaEventHandler.onEvent
            bytes4 handlerFunctionSelector;
            // Extra fee per gas paid to validators to prioritize this event handling
            uint64 priorityFeePerGas;
            // Maximum fee per gas the subscriber is willing to pay (base fee + priority fee)
            uint64 maxFeePerGas;
            // Maximum gas that will be provisioned per subscription callback
            uint64 gasLimit;
            // Whether the event handling is guaranteed, i.e. moved to the next block if current is full
            bool isGuaranteed;
            // Whether multiple events can be coalesced into a single handling call per block
            bool isCoalesced;
        }
    
        /// @notice System tick emitted at the end of each block.
        /// @param blockNumber Current block number.
        event BlockTick(uint64 indexed blockNumber);
    
        /// @notice System tick emitted at epoch boundaries.
        /// @param epochNumber Current epoch number.
        /// @param blockNumber Current block number.
        event EpochTick(uint64 indexed epochNumber, uint64 indexed blockNumber);
    
        /// @notice Scheduled system event emitted at the requested timestamp.
        /// @param timestampMillis Timestamp in milliseconds.
        event Schedule(uint256 indexed timestampMillis);
    
        /// @notice Emitted when a new subscription is created
        /// @param subscriptionId New subscription identifier.
        /// @param owner Owner of the created subscription.
        /// @param subscriptionData Persisted subscription payload.
        event SubscriptionCreated(
            uint256 indexed subscriptionId,
            address indexed owner,
            SubscriptionData subscriptionData
        );
    
        /// @notice Emitted when a subscription is removed
        /// @param subscriptionId Removed subscription identifier.
        /// @param owner Owner of the removed subscription.
        event SubscriptionRemoved(
            uint256 indexed subscriptionId,
            address indexed owner
        );
    
        /**
         * @notice Creates a new Solidity subscription
         * @dev Cost: SUBSCRIPTION_MANAGEMENT_GAS_COST (~210k GAS)
         * @dev The caller becomes the owner of the subscription.
         * @dev The subscription starts being active immediately.
         * @param subscriptionData Defines the parameters for the subscription incl the handler contract
         * @return subscriptionId The subscription ID assigned to the new subscription
         */
        function subscribe(
            SubscriptionData calldata subscriptionData
        ) external returns (uint256 subscriptionId);
    
        /**
         * @notice Cancels a Solidity event subscription
         * @dev It can only be called by the owner
         * @dev Cost: SUBSCRIPTION_MANAGEMENT_GAS_COST (~210K GAS)
         * @param subscriptionId Unique subscription identifier
         */
        function unsubscribe(uint256 subscriptionId) external;
    
        /**
         * @notice Gets detailed information about a specific subscription
         * @param subscriptionId The unique subscription identifier
         * @return subscriptionData The recorded subscription information
         * @return owner The owner of the subscription that can make changes and pays for invocations
         */
        function getSubscriptionInfo(
            uint256 subscriptionId
        )
            external
            view
            returns (SubscriptionData memory subscriptionData, address owner);
    }
    
  • src/lib/reactivity/SomniaExtensions.sol
    // SPDX-License-Identifier: MIT
    // Copyright (c) 2026 Somnia Foundation
    
    pragma solidity 0.8.30;
    
    import {ISomniaReactivityPrecompile} from "./ISomniaReactivityPrecompile.sol";
    import {ISomniaEventHandler} from "./ISomniaEventHandler.sol";
    
    /// @notice The Somnia Reactivity Precompile is a privileged contract at a fixed address
    /// @title SomniaExtensions
    /// @author Somnia Foundation
    library SomniaExtensions {
        /// @notice Topic/origin/emitter matching criteria for a subscription.
        struct SubscriptionFilter {
            bytes32[4] eventTopics;
            address origin;
            address emitter;
        }
    
        /// @notice Gas and fee controls for subscription callback execution.
        struct SubscriptionOptions {
            uint64 priorityFeePerGas;
            uint64 maxFeePerGas;
            uint64 gasLimit;
        }
    
        /// @notice Fixed address for the Somnia Reactivity Precompile.
        address public constant SOMNIA_REACTIVITY_PRECOMPILE_ADDRESS =
            address(0x0100);
        /// @notice Minimum balance required by a subscription owner.
        uint256 public constant SUBSCRIPTION_OWNER_MINIMUM_BALANCE = 32 ether;
        /// @notice Minimum protocol base fee per gas used in fee validation.
        uint256 public constant MINIMUM_BASE_FEE_PER_GAS = 6 gwei;
        /// @notice Maximum callback gas limit allowed for a single subscription.
        uint64 public constant MAXIMUM_HANDLER_GAS_LIMIT = 200_000_000;
        /// @notice Default priority fee per gas used when callers opt into defaults.
        uint64 public constant DEFAULT_PRIORITY_FEE_PER_GAS = 0;
        /// @notice Default max fee per gas used when callers opt into defaults.
        uint64 public constant DEFAULT_MAX_FEE_PER_GAS = 20 gwei;
        /// @notice Default callback gas limit used when callers opt into defaults.
        uint64 public constant DEFAULT_HANDLER_GAS_LIMIT = 10_000_000;
    
        error HandlerZeroAddress();
        error EmptyFilter();
        error GasLimitZero();
        error GasLimitExceeded();
        error InvalidMaxFeePerGas();
        error InsufficientBalance();
        error TimestampInPast();
        error BlockInPast();
        error UnsubscribeFailed();
    
        /// @notice Creates a subscription owned by the caller using filter and gas options.
        /// @param handler Address of the handler contract that receives callbacks.
        /// @param filter Filter criteria for matching logs.
        /// @param options Fee and gas options for callback execution.
        /// @return subscriptionId Newly created subscription identifier.
        function subscribe(
            address handler,
            SubscriptionFilter memory filter,
            SubscriptionOptions memory options
        ) internal returns (uint256 subscriptionId) {
            return _subscribe(handler, filter, options);
        }
    
        /// @notice Creates a schedule subscription for an absolute millisecond timestamp.
        /// @param handler Address of the handler contract that receives callbacks.
        /// @param timestampMillis Absolute unix timestamp in milliseconds.
        /// @param options Fee and gas options for callback execution.
        /// @return subscriptionId Newly created subscription identifier.
        function scheduleSubscriptionAtTimestamp(
            address handler,
            uint256 timestampMillis,
            SubscriptionOptions memory options
        ) internal returns (uint256 subscriptionId) {
            if (timestampMillis < ((block.timestamp + 1) * 1000) + 1) {
                revert TimestampInPast();
            }
    
            SubscriptionFilter memory filter = SubscriptionFilter({
                eventTopics: [
                    ISomniaReactivityPrecompile.Schedule.selector,
                    bytes32(timestampMillis),
                    bytes32(0),
                    bytes32(0)
                ],
                origin: address(0),
                emitter: SOMNIA_REACTIVITY_PRECOMPILE_ADDRESS
            });
    
            return _subscribe(handler, filter, options);
        }
    
        /// @notice Creates a subscription triggered at a specific future block.
        /// @param handler Address of the handler contract that receives callbacks.
        /// @param blockNumber Future block number to trigger on.
        /// @param options Fee and gas options for callback execution.
        /// @return subscriptionId Newly created subscription identifier.
        function scheduleSubscriptionAtBlock(
            address handler,
            uint64 blockNumber,
            SubscriptionOptions memory options
        ) internal returns (uint256 subscriptionId) {
            if (blockNumber < block.number + 1) {
                revert BlockInPast();
            }
    
            SubscriptionFilter memory filter = SubscriptionFilter({
                eventTopics: [
                    ISomniaReactivityPrecompile.BlockTick.selector,
                    bytes32(uint256(blockNumber)),
                    bytes32(0),
                    bytes32(0)
                ],
                origin: address(0),
                emitter: SOMNIA_REACTIVITY_PRECOMPILE_ADDRESS
            });
    
            return _subscribe(handler, filter, options);
        }
    
        /// @notice Creates a subscription triggered at a specific epoch.
        /// @param handler Address of the handler contract that receives callbacks.
        /// @param epochNumber Epoch number to trigger on.
        /// @param options Fee and gas options for callback execution.
        /// @return subscriptionId Newly created subscription identifier.
        function scheduleSubscriptionAtEpoch(
            address handler,
            uint64 epochNumber,
            SubscriptionOptions memory options
        ) internal returns (uint256 subscriptionId) {
            SubscriptionFilter memory filter = SubscriptionFilter({
                eventTopics: [
                    ISomniaReactivityPrecompile.EpochTick.selector,
                    bytes32(uint256(epochNumber)),
                    bytes32(0),
                    bytes32(0)
                ],
                origin: address(0),
                emitter: SOMNIA_REACTIVITY_PRECOMPILE_ADDRESS
            });
    
            return _subscribe(handler, filter, options);
        }
    
        /// @notice Cancels a subscription.
        /// @param subscriptionId Existing subscription identifier.
        function unsubscribe(uint256 subscriptionId) internal {
            // Low-level call used to bypass false positives from Solidity safety checks
            // solhint-disable-next-line avoid-low-level-calls
            (bool success, ) = SOMNIA_REACTIVITY_PRECOMPILE_ADDRESS.call(
                abi.encodeWithSelector(ISomniaReactivityPrecompile.unsubscribe.selector, subscriptionId)
            );
            if (!success) revert UnsubscribeFailed();
        }
    
        /// @notice Fetches subscription details and owner for a subscription id.
        /// @param subscriptionId Existing subscription identifier.
        /// @return subscriptionData Stored subscription parameters.
        /// @return owner Address currently owning the subscription.
        function getSubscriptionInfo(
            uint256 subscriptionId
        )
            internal
            view
            returns (
                ISomniaReactivityPrecompile.SubscriptionData
                    memory subscriptionData,
                address owner
            )
        {
            return
                ISomniaReactivityPrecompile(SOMNIA_REACTIVITY_PRECOMPILE_ADDRESS)
                    .getSubscriptionInfo(subscriptionId);
        }
    
        /// @notice Returns the library default fee and gas options for subscriptions.
        /// @return options Default subscription options struct.
        function defaultSubscriptionOptions()
            internal
            pure
            returns (SubscriptionOptions memory)
        {
            return
                SubscriptionOptions({
                    priorityFeePerGas: DEFAULT_PRIORITY_FEE_PER_GAS,
                    maxFeePerGas: DEFAULT_MAX_FEE_PER_GAS,
                    gasLimit: DEFAULT_HANDLER_GAS_LIMIT
                });
        }
    
        /// @dev Internal helper used by all public subscription creation functions.
        function _subscribe(
            address handler,
            SubscriptionFilter memory filter,
            SubscriptionOptions memory options
        ) private returns (uint256 subscriptionId) {
            if (handler == address(0)) {
                revert HandlerZeroAddress();
            }
            if (!_hasAnyFilter(filter)) {
                revert EmptyFilter();
            }
            if (options.gasLimit == 0) {
                revert GasLimitZero();
            }
            if (options.gasLimit > MAXIMUM_HANDLER_GAS_LIMIT) {
                revert GasLimitExceeded();
            }
            if (
                options.maxFeePerGas != 0 &&
                options.priorityFeePerGas + MINIMUM_BASE_FEE_PER_GAS  >
                options.maxFeePerGas
            ) {
                revert InvalidMaxFeePerGas();
            }
            if (address(this).balance < SUBSCRIPTION_OWNER_MINIMUM_BALANCE) {
                revert InsufficientBalance();
            }
    
            return
                ISomniaReactivityPrecompile(SOMNIA_REACTIVITY_PRECOMPILE_ADDRESS)
                    .subscribe(_buildSubscriptionData(handler, filter, options));
        }
    
        /// @dev Returns true when at least one filter criterion is non-wildcard.
        function _hasAnyFilter(
            SubscriptionFilter memory filter
        ) private pure returns (bool hasAnyFilter) {
            return
                filter.origin != address(0) ||
                filter.emitter != address(0) ||
                filter.eventTopics[0] != bytes32(0) ||
                filter.eventTopics[1] != bytes32(0) ||
                filter.eventTopics[2] != bytes32(0) ||
                filter.eventTopics[3] != bytes32(0);
        }
    
        /// @dev Builds low-level precompile subscription payload from high-level inputs.
        function _buildSubscriptionData(
            address handler,
            SubscriptionFilter memory filter,
            SubscriptionOptions memory options
        )
            private
            pure
            returns (
                ISomniaReactivityPrecompile.SubscriptionData memory subscriptionData
            )
        {
            ISomniaReactivityPrecompile.SubscriptionData
                memory data = ISomniaReactivityPrecompile.SubscriptionData({
                    eventTopics: filter.eventTopics,
                    origin: filter.origin,
                    caller: address(0),
                    emitter: filter.emitter,
                    handlerContractAddress: handler,
                    handlerFunctionSelector: ISomniaEventHandler.onEvent.selector,
                    priorityFeePerGas: options.priorityFeePerGas,
                    maxFeePerGas: options.maxFeePerGas,
                    gasLimit: options.gasLimit,
                    isGuaranteed: false,
                    isCoalesced: false
                });
    
            return data;
        }
    }
    
ABI · 51 entries
json
[
  {
    "type": "constructor",
    "inputs": [
      {
        "name": "_owner",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "_agentPlatform",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "_fastModeEnabled",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "stateMutability": "payable"
  },
  {
    "name": "BlockInPast",
    "type": "error",
    "inputs": []
  },
  {
    "name": "CallbackGasTooHigh",
    "type": "error",
    "inputs": [
      {
        "name": "provided",
        "type": "uint32",
        "internalType": "uint32"
      },
      {
        "name": "maximum",
        "type": "uint32",
        "internalType": "uint32"
      }
    ]
  },
  {
    "name": "CommitDelayTooLong",
    "type": "error",
    "inputs": [
      {
        "name": "provided",
        "type": "uint16",
        "internalType": "uint16"
      },
      {
        "name": "maximum",
        "type": "uint16",
        "internalType": "uint16"
      }
    ]
  },
  {
    "name": "CommitDelayTooShort",
    "type": "error",
    "inputs": [
      {
        "name": "provided",
        "type": "uint16",
        "internalType": "uint16"
      },
      {
        "name": "minimum",
        "type": "uint16",
        "internalType": "uint16"
      }
    ]
  },
  {
    "name": "DrandAlreadyResolved",
    "type": "error",
    "inputs": [
      {
        "name": "requestId",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "name": "DrandResponseFailed",
    "type": "error",
    "inputs": [
      {
        "name": "status",
        "type": "uint8",
        "internalType": "uint8"
      }
    ]
  },
  {
    "name": "EmptyFilter",
    "type": "error",
    "inputs": []
  },
  {
    "name": "FastModeDisabled",
    "type": "error",
    "inputs": []
  },
  {
    "name": "GasLimitExceeded",
    "type": "error",
    "inputs": []
  },
  {
    "name": "GasLimitZero",
    "type": "error",
    "inputs": []
  },
  {
    "name": "HandlerZeroAddress",
    "type": "error",
    "inputs": []
  },
  {
    "name": "InsufficientBalance",
    "type": "error",
    "inputs": []
  },
  {
    "name": "InvalidMaxFeePerGas",
    "type": "error",
    "inputs": []
  },
  {
    "name": "NotAgentPlatform",
    "type": "error",
    "inputs": [
      {
        "name": "caller",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "name": "NotOwner",
    "type": "error",
    "inputs": []
  },
  {
    "name": "NotPrecompile",
    "type": "error",
    "inputs": [
      {
        "name": "caller",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "name": "NumWordsOutOfRange",
    "type": "error",
    "inputs": [
      {
        "name": "provided",
        "type": "uint32",
        "internalType": "uint32"
      },
      {
        "name": "minimum",
        "type": "uint32",
        "internalType": "uint32"
      },
      {
        "name": "maximum",
        "type": "uint32",
        "internalType": "uint32"
      }
    ]
  },
  {
    "name": "TransferFailed",
    "type": "error",
    "inputs": []
  },
  {
    "name": "UnknownAgentRequest",
    "type": "error",
    "inputs": [
      {
        "name": "agentRequestId",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "name": "UnknownRequest",
    "type": "error",
    "inputs": [
      {
        "name": "requestId",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "name": "BlockhashCaptured",
    "type": "event",
    "inputs": [
      {
        "name": "requestId",
        "type": "uint256",
        "indexed": true,
        "internalType": "uint256"
      },
      {
        "name": "revealBlock",
        "type": "uint64",
        "indexed": false,
        "internalType": "uint64"
      },
      {
        "name": "blockhashValue",
        "type": "bytes32",
        "indexed": false,
        "internalType": "bytes32"
      }
    ],
    "anonymous": false
  },
  {
    "name": "CoordinatorFunded",
    "type": "event",
    "inputs": [
      {
        "name": "from",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "amount",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "newBalance",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "name": "CoordinatorWithdrawn",
    "type": "event",
    "inputs": [
      {
        "name": "to",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "amount",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "name": "DrandRequested",
    "type": "event",
    "inputs": [
      {
        "name": "requestId",
        "type": "uint256",
        "indexed": true,
        "internalType": "uint256"
      },
      {
        "name": "agentRequestId",
        "type": "uint256",
        "indexed": true,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "name": "OwnershipTransferred",
    "type": "event",
    "inputs": [
      {
        "name": "previous",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "next",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      }
    ],
    "anonymous": false
  },
  {
    "name": "RandomWordsFulfilled",
    "type": "event",
    "inputs": [
      {
        "name": "requestId",
        "type": "uint256",
        "indexed": true,
        "internalType": "uint256"
      },
      {
        "name": "consumer",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "revealBlock",
        "type": "uint64",
        "indexed": false,
        "internalType": "uint64"
      },
      {
        "name": "callbackSuccess",
        "type": "bool",
        "indexed": false,
        "internalType": "bool"
      },
      {
        "name": "usedVerifiableEntropy",
        "type": "bool",
        "indexed": false,
        "internalType": "bool"
      }
    ],
    "anonymous": false
  },
  {
    "name": "RandomWordsRequested",
    "type": "event",
    "inputs": [
      {
        "name": "requestId",
        "type": "uint256",
        "indexed": true,
        "internalType": "uint256"
      },
      {
        "name": "consumer",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "revealBlock",
        "type": "uint64",
        "indexed": false,
        "internalType": "uint64"
      },
      {
        "name": "numWords",
        "type": "uint32",
        "indexed": false,
        "internalType": "uint32"
      },
      {
        "name": "callbackGasLimit",
        "type": "uint32",
        "indexed": false,
        "internalType": "uint32"
      },
      {
        "name": "useVerifiableEntropy",
        "type": "bool",
        "indexed": false,
        "internalType": "bool"
      }
    ],
    "anonymous": false
  },
  {
    "name": "AGENT_PLATFORM",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "AGENT_SUBCOMMITTEE_SIZE",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "DRAND_SELECTOR",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "string",
        "internalType": "string"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "DRAND_URL",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "string",
        "internalType": "string"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "FAST_MODE_ENABLED",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "JSON_API_AGENT_ID",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "JSON_API_PRICE_PER_AGENT",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "MAX_CALLBACK_GAS_LIMIT",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint32",
        "internalType": "uint32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "MAX_COMMIT_DELAY_BLOCKS",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint16",
        "internalType": "uint16"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "MAX_NUM_WORDS",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint32",
        "internalType": "uint32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "MIN_COMMIT_DELAY_BLOCKS",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint16",
        "internalType": "uint16"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "SOMNIA_REACTIVITY_PRECOMPILE",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "getRequest",
    "type": "function",
    "inputs": [
      {
        "name": "requestId",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "tuple",
        "components": [
          {
            "name": "consumer",
            "type": "address",
            "internalType": "address"
          },
          {
            "name": "callbackGasLimit",
            "type": "uint32",
            "internalType": "uint32"
          },
          {
            "name": "numWords",
            "type": "uint32",
            "internalType": "uint32"
          },
          {
            "name": "revealBlock",
            "type": "uint64",
            "internalType": "uint64"
          },
          {
            "name": "useVerifiableEntropy",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "fulfilled",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "capturedBlockhash",
            "type": "bytes32",
            "internalType": "bytes32"
          }
        ],
        "internalType": "struct ISomniaVRFCoordinator.Request"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "handleDrandResponse",
    "type": "function",
    "inputs": [
      {
        "name": "agentRequestId",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "responses",
        "type": "tuple[]",
        "components": [
          {
            "name": "validator",
            "type": "address",
            "internalType": "address"
          },
          {
            "name": "result",
            "type": "bytes",
            "internalType": "bytes"
          },
          {
            "name": "status",
            "type": "uint8",
            "internalType": "enum ResponseStatus"
          },
          {
            "name": "receipt",
            "type": "uint256",
            "internalType": "uint256"
          },
          {
            "name": "timestamp",
            "type": "uint256",
            "internalType": "uint256"
          },
          {
            "name": "executionCost",
            "type": "uint256",
            "internalType": "uint256"
          }
        ],
        "internalType": "struct Response[]"
      },
      {
        "name": "status",
        "type": "uint8",
        "internalType": "enum ResponseStatus"
      },
      {
        "name": "",
        "type": "tuple",
        "components": [
          {
            "name": "id",
            "type": "uint256",
            "internalType": "uint256"
          },
          {
            "name": "requester",
            "type": "address",
            "internalType": "address"
          },
          {
            "name": "callbackAddress",
            "type": "address",
            "internalType": "address"
          },
          {
            "name": "callbackSelector",
            "type": "bytes4",
            "internalType": "bytes4"
          },
          {
            "name": "subcommittee",
            "type": "address[]",
            "internalType": "address[]"
          },
          {
            "name": "responses",
            "type": "tuple[]",
            "components": [
              {
                "name": "validator",
                "type": "address",
                "internalType": "address"
              },
              {
                "name": "result",
                "type": "bytes",
                "internalType": "bytes"
              },
              {
                "name": "status",
                "type": "uint8",
                "internalType": "enum ResponseStatus"
              },
              {
                "name": "receipt",
                "type": "uint256",
                "internalType": "uint256"
              },
              {
                "name": "timestamp",
                "type": "uint256",
                "internalType": "uint256"
              },
              {
                "name": "executionCost",
                "type": "uint256",
                "internalType": "uint256"
              }
            ],
            "internalType": "struct Response[]"
          },
          {
            "name": "responseCount",
            "type": "uint256",
            "internalType": "uint256"
          },
          {
            "name": "failureCount",
            "type": "uint256",
            "internalType": "uint256"
          },
          {
            "name": "threshold",
            "type": "uint256",
            "internalType": "uint256"
          },
          {
            "name": "createdAt",
            "type": "uint256",
            "internalType": "uint256"
          },
          {
            "name": "deadline",
            "type": "uint256",
            "internalType": "uint256"
          },
          {
            "name": "status",
            "type": "uint8",
            "internalType": "enum ResponseStatus"
          },
          {
            "name": "consensusType",
            "type": "uint8",
            "internalType": "enum ConsensusType"
          },
          {
            "name": "remainingBudget",
            "type": "uint256",
            "internalType": "uint256"
          },
          {
            "name": "perAgentBudget",
            "type": "uint256",
            "internalType": "uint256"
          }
        ],
        "internalType": "struct Request"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "nextRequestId",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "onEvent",
    "type": "function",
    "inputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "eventTopics",
        "type": "bytes32[]",
        "internalType": "bytes32[]"
      },
      {
        "name": "",
        "type": "bytes",
        "internalType": "bytes"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "owner",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "pendingRequestsAt",
    "type": "function",
    "inputs": [
      {
        "name": "blockNumber",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "uint256[]",
        "internalType": "uint256[]"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "requestRandomWords",
    "type": "function",
    "inputs": [
      {
        "name": "req",
        "type": "tuple",
        "components": [
          {
            "name": "callbackGasLimit",
            "type": "uint32",
            "internalType": "uint32"
          },
          {
            "name": "commitDelayBlocks",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "numWords",
            "type": "uint32",
            "internalType": "uint32"
          },
          {
            "name": "useVerifiableEntropy",
            "type": "bool",
            "internalType": "bool"
          }
        ],
        "internalType": "struct ISomniaVRFCoordinator.RandomWordsRequest"
      }
    ],
    "outputs": [
      {
        "name": "requestId",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "nonpayable"
  },
  {
    "name": "transferOwnership",
    "type": "function",
    "inputs": [
      {
        "name": "next",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "vrfRequestOfDrand",
    "type": "function",
    "inputs": [
      {
        "name": "agentRequestId",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "withdraw",
    "type": "function",
    "inputs": [
      {
        "name": "to",
        "type": "address",
        "internalType": "address payable"
      },
      {
        "name": "amount",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "type": "receive",
    "stateMutability": "payable"
  }
]