BondNote

Description:

Non-Fungible Token (NFT) contract following ERC721 standard.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

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

/*
  BondNote (Escrowed pUSDC Note, ERC-721)

  - Each NFT represents a fixed claim on pUSDC (6dp) from your pUSDC wrapper.
  - On mint, this contract PULLS (escrows) pUSDC from the minter (owner) into itself.
  - Holder can UNWRAP at any time: burns NFT -> sends the escrowed pUSDC to holder.
  - Ideal for Sudoswap: list these NFTs at a discount (bond-style).
*/

/* ----------------------- Minimal Interfaces ----------------------- */
interface IERC20 {
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function transfer(address to, uint256 value) external returns (bool);
    function transferFrom(address from, address to, uint256 value) external returns (bool);
    function approve(address spender, uint256 value) external returns (bool);
    function allowance(address owner_, address spender) external view returns (uint256);
}

interface IPUSDC is IERC20 {}

/* ----------------------- SafeERC20 ----------------------- */
library Address {
    function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
        (bool success, bytes memory returndata) = target.call(data);
        require(success, errorMessage);
        return returndata;
    }
}

library SafeERC20 {
    using Address for address;
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        bytes memory data = abi.encodeWithSelector(token.transfer.selector, to, value);
        bytes memory ret = address(token).functionCall(data, "SafeERC20: transfer failed");
        if (ret.length > 0) require(abi.decode(ret, (bool)), "SafeERC20: transfer false");
    }
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        bytes memory data = abi.encodeWithSelector(token.transferFrom.selector, from, to, value);
        bytes memory ret = address(token).functionCall(data, "SafeERC20: transferFrom failed");
        if (ret.length > 0) require(abi.decode(ret, (bool)), "SafeERC20: transferFrom false");
    }
}

/* ----------------------- Utilities ----------------------- */
library Strings {
    bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";
    function toString(uint256 value) internal pure returns (string memory) {
        if (value == 0) return "0";
        uint256 temp = value;
        uint256 digits;
        while (temp != 0) { digits++; temp /= 10; }
        bytes memory buffer = new bytes(digits);
        while (value != 0) { digits -= 1; buffer[digits] = bytes1(uint8(48 + uint256(value % 10))); value /= 10; }
        return string(buffer);
    }
}

/* ----------------------- Ownable + Reentrancy ----------------------- */
abstract contract Context { function _msgSender() internal view virtual returns (address) { return msg.sender; } }
contract Ownable is Context {
    address private _owner;
    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
    constructor(address initialOwner) { _transferOwnership(initialOwner); }
    function owner() public view returns (address) { return _owner; }
    modifier onlyOwner() { require(owner() == _msgSender(), "Ownable: not owner"); _; }
    function transferOwnership(address newOwner) public onlyOwner { _transferOwnership(newOwner); }
    function _transferOwnership(address newOwner) internal { require(newOwner != address(0), "Ownable: zero address"); _owner = newOwner; emit OwnershipTransferred(address(0), newOwner); }
}
abstract contract ReentrancyGuard { uint256 private constant _NOT_ENTERED = 1; uint256 private constant _ENTERED = 2; uint256 private _status = _NOT_ENTERED; modifier nonReentrant() { require(_status != _ENTERED, "reentrancy"); _status = _ENTERED; _; _status = _NOT_ENTERED; } }

/* ----------------------- Minimal ERC721 ----------------------- */
interface IERC165 { function supportsInterface(bytes4 interfaceId) external view returns (bool); }
interface IERC721 is IERC165 {
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
    function balanceOf(address owner) external view returns (uint256 balance);
    function ownerOf(uint256 tokenId) external view returns (address owner);
    function safeTransferFrom(address from, address to, uint256 tokenId) external;
    function transferFrom(address from, address to, uint256 tokenId) external;
    function approve(address to, uint256 tokenId) external;
    function getApproved(uint256 tokenId) external view returns (address operator);
    function setApprovalForAll(address operator, bool _approved) external;
    function isApprovedForAll(address owner, address operator) external view returns (bool);
}

/* Simplified ERC721 implementation (no Enumerable) */
contract ERC721Simple is IERC721 {
    using Strings for uint256;
    string public name;
    string public symbol;
    mapping(uint256 => address) internal _owners;
    mapping(address => uint256) internal _balances;
    mapping(uint256 => address) internal _tokenApprovals;
    mapping(address => mapping(address => bool)) internal _operatorApprovals;

    constructor(string memory name_, string memory symbol_) { name = name_; symbol = symbol_; }

    function supportsInterface(bytes4 interfaceId) external pure override returns (bool) {
        return interfaceId == type(IERC721).interfaceId;
    }

    function balanceOf(address owner) public view override returns (uint256) { return _balances[owner]; }
    function ownerOf(uint256 tokenId) public view override returns (address) { return _owners[tokenId]; }

    function approve(address to, uint256 tokenId) public override {
        address owner = ownerOf(tokenId);
        require(msg.sender == owner || _operatorApprovals[owner][msg.sender], "not allowed");
        _tokenApprovals[tokenId] = to;
        emit Approval(owner, to, tokenId);
    }

    function getApproved(uint256 tokenId) public view override returns (address) { return _tokenApprovals[tokenId]; }

    function setApprovalForAll(address operator, bool approved) external override {
        _operatorApprovals[msg.sender][operator] = approved;
        emit ApprovalForAll(msg.sender, operator, approved);
    }

    function isApprovedForAll(address owner, address operator) public view override returns (bool) { return _operatorApprovals[owner][operator]; }

    function transferFrom(address from, address to, uint256 tokenId) public override {
        require(_isApprovedOrOwner(msg.sender, tokenId), "not approved");
        _transfer(from, to, tokenId);
    }

    function safeTransferFrom(address from, address to, uint256 tokenId) external override { transferFrom(from, to, tokenId); }

    function _isApprovedOrOwner(address spender, uint256 tokenId) internal view returns (bool) {
        address owner = _owners[tokenId];
        return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender));
    }

    function _transfer(address from, address to, uint256 tokenId) internal {
        require(ownerOf(tokenId) == from, "not owner");
        require(to != address(0), "to=0");
        delete _tokenApprovals[tokenId];
        _balances[from] -= 1;
        _balances[to] += 1;
        _owners[tokenId] = to;
        emit Transfer(from, to, tokenId);
    }

    function _mint(address to, uint256 tokenId) internal {
        require(to != address(0), "to=0");
        require(_owners[tokenId] == address(0), "exists");
        _balances[to] += 1;
        _owners[tokenId] = to;
        emit Transfer(address(0), to, tokenId);
    }

    function _burn(uint256 tokenId) internal {
        address owner = ownerOf(tokenId);
        _balances[owner] -= 1;
        delete _owners[tokenId];
        delete _tokenApprovals[tokenId];
        emit Transfer(owner, address(0), tokenId);
    }
}

/* ----------------------- BondNote ----------------------- */
contract BondNote is ERC721Simple, Ownable, ReentrancyGuard {
    using SafeERC20 for IPUSDC;

    IPUSDC public immutable pUSDC;          // pUSDC wrapper (6dp)
    string  public baseURI;
    uint256 public nextId;
    mapping(uint256 => uint256) public claimAmount;

    event BondMinted(uint256 indexed tokenId, address indexed to, uint256 amount);
    event Unwrapped(uint256 indexed tokenId, address indexed to, uint256 amount);

    constructor(address _pUSDC, string memory _name, string memory _symbol, string memory _baseURI)
        ERC721Simple(_name, _symbol)
        Ownable(msg.sender)
    {
        require(_pUSDC != address(0), "pUSDC=0");
        pUSDC = IPUSDC(_pUSDC);
        baseURI = _baseURI;
    }

    /* --------------------- Admin --------------------- */
    function mintBond(address to, uint256 amount) external onlyOwner nonReentrant {
        require(to != address(0), "to=0");
        require(amount > 0, "amount=0");
        // pull pUSDC into escrow
        pUSDC.safeTransferFrom(msg.sender, address(this), amount);
        nextId++;
        uint256 tokenId = nextId;
        claimAmount[tokenId] = amount;
        _mint(to, tokenId);
        emit BondMinted(tokenId, to, amount);
    }

    function setBaseURI(string calldata newBase) external onlyOwner { baseURI = newBase; }

    /* --------------------- Holder --------------------- */
    function unwrap(uint256 tokenId) external nonReentrant {
        require(_isApprovedOrOwner(msg.sender, tokenId), "not owner");
        uint256 amt = claimAmount[tokenId];
        require(amt > 0, "none");
        claimAmount[tokenId] = 0;
        _burn(tokenId);
        pUSDC.safeTransfer(msg.sender, amt);
        emit Unwrapped(tokenId, msg.sender, amt);
    }

    /* --------------------- Views --------------------- */
    function tokenURI(uint256 tokenId) external view returns (string memory) {
        if (bytes(baseURI).length == 0) return "";
        return string(abi.encodePacked(baseURI, Strings.toString(tokenId), ".json"));
    }
}

Tags:
ERC20, ERC721, ERC165, NFT, Non-Fungible|addr:0x81b5d8f2674e270b022ffd51dadcee16728b1465|verified:true|block:23511324|tx:0x6a44a01dd9d9e7303638b5f79dd874063bd71383cbe31e0ba813c10804e184aa|first_check:1759667990

Submitted on: 2025-10-05 14:39:51

Comments

Log in to comment.

No comments yet.