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();
}
}
}
Submitted on: 2025-09-28 10:16:59
Comments
Log in to comment.
No comments yet.