All events

On-chain verification

SogliaTreasury

A smart contract records contributions. Soglia reads the receipt on Base, Ethereum or Arbitrum and confirms the pledge only when recipient, asset and amount match.

Any public hash. No keys, read-only RPC.

Source

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

interface IERC20 {
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
}

/// @title SogliaTreasury
/// @notice Permissionless event treasuries. First openEvent wins the slug hash.
///         ETH and ERC-20 land on the organiser payout address. Soglia verifies
///         ContributionRecorded (or a direct transfer to the same payout) on-chain.
contract SogliaTreasury {
    struct EventConfig {
        address payout;
        address opener;
        bool active;
    }

    mapping(bytes32 eventId => EventConfig) public events;

    event EventOpened(bytes32 indexed eventId, address payout, address opener);
    event ContributionRecorded(
        bytes32 indexed eventId,
        bytes32 indexed listId,
        address indexed donor,
        address token,
        uint256 amount,
        bytes32 receiptId
    );

    error EventExists();
    error UnknownEvent();
    error ZeroAddress();
    error ZeroAmount();
    error PayoutFailed();
    error TransferFailed();

    function openEvent(bytes32 eventId, address payout) external {
        if (payout == address(0)) revert ZeroAddress();
        if (events[eventId].active) revert EventExists();
        events[eventId] = EventConfig({ payout: payout, opener: msg.sender, active: true });
        emit EventOpened(eventId, payout, msg.sender);
    }

    function contribute(bytes32 eventId, bytes32 listId) external payable {
        EventConfig memory ev = events[eventId];
        if (!ev.active) revert UnknownEvent();
        if (msg.value == 0) revert ZeroAmount();
        bytes32 receiptId = keccak256(
            abi.encode(block.chainid, block.number, msg.sender, eventId, listId, msg.value, address(0))
        );
        emit ContributionRecorded(eventId, listId, msg.sender, address(0), msg.value, receiptId);
        (bool ok, ) = ev.payout.call{ value: msg.value }("");
        if (!ok) revert PayoutFailed();
    }

    function contributeToken(bytes32 eventId, bytes32 listId, address token, uint256 amount) external {
        EventConfig memory ev = events[eventId];
        if (!ev.active) revert UnknownEvent();
        if (token == address(0)) revert ZeroAddress();
        if (amount == 0) revert ZeroAmount();
        bool sent = IERC20(token).transferFrom(msg.sender, ev.payout, amount);
        if (!sent) revert TransferFailed();
        bytes32 receiptId = keccak256(
            abi.encode(block.chainid, block.number, msg.sender, eventId, listId, amount, token)
        );
        emit ContributionRecorded(eventId, listId, msg.sender, token, amount, receiptId);
    }
}