CosigoRedeemer

Description:

Smart contract deployed on Ethereum.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

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

/// @title Cosigo Redeemer (burn-by-sink)
/// @notice Pulls ERC20 tokens from user and forwards to a sink (accounting/burn vault).
///         Exposes both redeemPhysicalSilver overloads and a burn-only method.
interface IERC20 {
    function transferFrom(address from, address to, uint256 value) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
    function balanceOf(address who) external view returns (uint256);
}

interface IERC20Metadata is IERC20 {
    function decimals() external view returns (uint8);
}

contract CosigoRedeemer {
    // --- immutables / storage ---
    address public immutable token;       // ERC20 token (Cosigo)
    uint8   public immutable tokenDecimals;
    uint256 public immutable decMultiplier;

    address public owner;                  // simple ownable
    address public sink;                   // your burn/accounting vault (EOA you control)
    bool    public paused;                 // global pause
    uint256 public minRedemptionMg;        // minimum mg allowed

    // --- events ---
    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
    event Paused(bool ok);
    event MinRedemptionUpdated(uint256 mg);
    event SinkUpdated(address sink);
    event Redeemed(address indexed user, uint256 grossMg, bytes32 refHash, string refText);
    event Burned(address indexed user, uint256 mg);

    // --- errors ---
    error NotOwner();
    error PausedError();
    error BelowMinimum();
    error BadAddress();
    error TransferFailed();

    // --- modifiers ---
    modifier onlyOwner() {
        if (msg.sender != owner) revert NotOwner();
        _;
    }
    modifier notPaused() {
        if (paused) revert PausedError();
        _;
    }

    /// @param _token  ERC20 token address (Cosigo)
    /// @param _sink   sink/burn vault address (your accounting wallet, NOT zero)
    /// @param _minMg  minimum redemption (mg)
    constructor(address _token, address _sink, uint256 _minMg) {
        if (_token == address(0) || _sink == address(0)) revert BadAddress();
        token = _token;
        owner = msg.sender;
        emit OwnershipTransferred(address(0), msg.sender);

        uint8 dec;
        // Try to read decimals; default to 18 if token doesn't implement metadata
        try IERC20Metadata(_token).decimals() returns (uint8 d) { dec = d; } catch { dec = 18; }
        tokenDecimals = dec;
        decMultiplier = 10 ** uint256(dec);

        sink = _sink;
        minRedemptionMg = _minMg;
        emit SinkUpdated(_sink);
        emit MinRedemptionUpdated(_minMg);
    }

    // ---- owner admin ----
    function setPaused(bool _p) external onlyOwner {
        paused = _p;
        emit Paused(_p);
    }

    function setMinRedemptionMg(uint256 _mg) external onlyOwner {
        minRedemptionMg = _mg;
        emit MinRedemptionUpdated(_mg);
    }

    function setSink(address _sink) external onlyOwner {
        if (_sink == address(0)) revert BadAddress();
        sink = _sink;
        emit SinkUpdated(_sink);
    }

    function transferOwnership(address newOwner) external onlyOwner {
        if (newOwner == address(0)) revert BadAddress();
        emit OwnershipTransferred(owner, newOwner);
        owner = newOwner;
    }

    // ---- core ----

    /// @notice Redeem using a human-readable string reference (preferred for UIs)
    /// @param amountMg mg to redeem (NOT base units)
    /// @param shippingRef freeform text; hashed and emitted
    function redeemPhysicalSilver(uint256 amountMg, string calldata shippingRef)
        external
        notPaused
    {
        _ensureMin(amountMg);
        bytes32 h = keccak256(bytes(shippingRef));
        _consumeAndSink(msg.sender, amountMg);
        emit Redeemed(msg.sender, amountMg, h, shippingRef);
    }

    /// @notice Redeem using a bytes32 reference (compact / fixed size)
    /// @param amountMg mg to redeem (NOT base units)
    /// @param shippingRef32 pre-packed reference (bytes32)
    function redeemPhysicalSilver(uint256 amountMg, bytes32 shippingRef32)
        external
        notPaused
    {
        _ensureMin(amountMg);
        _consumeAndSink(msg.sender, amountMg);
        emit Redeemed(msg.sender, amountMg, shippingRef32, "");
    }

    /// @notice Burn-only path (no shipping reference)
    function burnTokens(uint256 amountMg) external notPaused {
        _ensureMin(amountMg);
        _consumeAndSink(msg.sender, amountMg);
        emit Burned(msg.sender, amountMg);
    }

    // ---- internal helpers ----
    function _ensureMin(uint256 mg) internal view {
        if (mg == 0 || (minRedemptionMg > 0 && mg < minRedemptionMg)) revert BelowMinimum();
    }

    function _consumeAndSink(address from, uint256 mg) internal {
        unchecked {
            if (mg > (type(uint256).max / decMultiplier)) revert();
        }
        uint256 units = mg * decMultiplier;

        // low-level call to accept tokens that (a) return bool, or (b) return nothing
        (bool ok1, bytes memory d1) = address(token).call(
            abi.encodeWithSelector(IERC20.transferFrom.selector, from, sink, units)
        );
        if (!ok1) revert TransferFailed();
        if (d1.length != 0) {
            bool ret = abi.decode(d1, (bool));
            if (!ret) revert TransferFailed();
        }
    }
}

Tags:
addr:0x9124918ee307d20e4d987b22df3bd3b78086320d|verified:true|block:23456953|tx:0x580c1ee83359a1e1c188b7bcfe105ae6da2398ce580fe364aec2ab57de84a0ec|first_check:1759047419

Submitted on: 2025-09-28 10:16:59

Comments

Log in to comment.

No comments yet.