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"));
}
}
Submitted on: 2025-10-05 14:39:51
Comments
Log in to comment.
No comments yet.