Chirps
0x84a508e988e7e4897dfdaf68efdaedeb9c5e5545
Verification
Verified
v0.8.28+commit.7893614a
Type
Contract
8,629 bytes
ABI entries
64
26 read · 16 write
License
none
Contract information
- Address
- 0x84a508e988e7e4897dfdaf68efdaedeb9c5e5545
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.28+commit.7893614a
- Optimization
- Enabled
- Creator
- 0x8319cc4c99…aBD47e0cD9
- Creation tx
- 0x13d35679f6…fb9abebafb
Token
- Name
- Chirps
- Symbol
- CHIRPS
- Decimals
- 18
- Holders
- 322
- Market
- View token page →
Read contract (26)
ChirpsTotalCount() → uint256
Holder(uint256) → address
HolderNumber(address) → uint256
HoldersCount() → uint256
INITIAL_SUPPLY() → uint256
IsHolder(address) → bool
OwnerChirp(address, uint256) → tuple
OwnerChirpIndex(address, uint256) → uint256
OwnerChirpsCount(address) → uint256
OwnerChirpsPage(address, uint256, uint256) → tuple[]
OwnerOwns(address, uint256) → bool
UNIT_PER_CHIRP() → uint256
allowance(address, address) → uint256
balanceOf(address) → uint256
decimals() → uint8
hook() → address
imageParams() → address
isStarted() → bool
maxBuy() → uint256
name() → string
owner() → address
ownershipHandoverExpiresAt(address) → uint256
pool() → address
randomSeedProvider() → address
symbol() → string
totalSupply() → uint256
Events (8)
ApprovalOnChirpBurnedOnChirpMintedOnChirpTransferOwnershipHandoverCanceledOwnershipHandoverRequestedOwnershipTransferredTransfer
ABI
[
{
"inputs": [
{
"internalType": "address",
"name": "owner_",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "AddressCanNotReceiveChirp",
"type": "error"
},
{
"inputs": [],
"name": "AlreadyInitialized",
"type": "error"
},
{
"inputs": [],
"name": "ChirpIndexOutOfRange",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "allowance",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "needed",
"type": "uint256"
}
],
"name": "ERC20InsufficientAllowance",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"internalType": "uint256",
"name": "balance",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "needed",
"type": "uint256"
}
],
"name": "ERC20InsufficientBalance",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "approver",
"type": "address"
}
],
"name": "ERC20InvalidApprover",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "receiver",
"type": "address"
}
],
"name": "ERC20InvalidReceiver",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "sender",
"type": "address"
}
],
"name": "ERC20InvalidSender",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "spender",
"type": "address"
}
],
"name": "ERC20InvalidSpender",
"type": "error"
},
{
"inputs": [],
"name": "NewOwnerIsZeroAddress",
"type": "error"
},
{
"inputs": [],
"name": "NoHandoverRequest",
"type": "error"
},
{
"inputs": [],
"name": "NotChirpOwner",
"type": "error"
},
{
"inputs": [],
"name": "Unauthorized",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "chirpId",
"type": "uint256"
}
],
"name": "OnChirpBurned",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "chirpId",
"type": "uint256"
}
],
"name": "OnChirpMinted",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "chirpId",
"type": "uint256"
}
],
"name": "OnChirpTransfer",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "pendingOwner",
"type": "address"
}
],
"name": "OwnershipHandoverCanceled",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "pendingOwner",
"type": "address"
}
],
"name": "OwnershipHandoverRequested",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "oldOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferred",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"inputs": [],
"name": "ChirpsTotalCount",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "index",
"type": "uint256"
}
],
"name": "Holder",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
}
],
"name": "HolderNumber",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "HoldersCount",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "INITIAL_SUPPLY",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
}
],
"name": "IsHolder",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"internalType": "uint256",
"name": "index",
"type": "uint256"
}
],
"name": "OwnerChirp",
"outputs": [
{
"components": [
{
"internalType": "uint256",
"name": "id",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "seed",
"type": "uint256"
}
],
"internalType": "struct ChirpSeedData",
"name": "",
"type": "tuple"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"internalType": "uint256",
"name": "chirpId",
"type": "uint256"
}
],
"name": "OwnerChirpIndex",
"outputs": [
{
"internalType": "uint256",
"name": "index",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
}
],
"name": "OwnerChirpsCount",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"internalType": "uint256",
"name": "page",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "pageSize",
"type": "uint256"
}
],
"name": "OwnerChirpsPage",
"outputs": [
{
"components": [
{
"internalType": "uint256",
"name": "id",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "seed",
"type": "uint256"
}
],
"internalType": "struct ChirpSeedData[]",
"name": "chirps",
"type": "tuple[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"internalType": "uint256",
"name": "chirpId",
"type": "uint256"
}
],
"name": "OwnerOwns",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "chirpId",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "newIndex",
"type": "uint256"
}
],
"name": "ReorderChirp",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "chirpId",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "newIndex",
"type": "uint256"
}
],
"name": "ReorderChirpPair",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "UNIT_PER_CHIRP",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"internalType": "address",
"name": "spender",
"type": "address"
}
],
"name": "allowance",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "approve",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "cancelOwnershipHandover",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "pendingOwner",
"type": "address"
}
],
"name": "completeOwnershipHandover",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "decimals",
"outputs": [
{
"internalType": "uint8",
"name": "",
"type": "uint8"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "hook",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "imageParams",
"outputs": [
{
"internalType": "contract IImageParams",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "isStarted",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "maxBuy",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "name",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "result",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "pendingOwner",
"type": "address"
}
],
"name": "ownershipHandoverExpiresAt",
"outputs": [
{
"internalType": "uint256",
"name": "result",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "pool",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "randomSeedProvider",
"outputs": [
{
"internalType": "contract IRandomSeedProvider",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "requestOwnershipHandover",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newHook",
"type": "address"
}
],
"name": "setHook",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "imageParamsProvider",
"type": "address"
}
],
"name": "setImageParamsProvider",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_randomSeedProvider",
"type": "address"
}
],
"name": "setRandomSeedProvider",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newPool",
"type": "address"
}
],
"name": "start",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "symbol",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "transfer",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "chirpId",
"type": "uint256"
}
],
"name": "transferChirp",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256[]",
"name": "chirpIds",
"type": "uint256[]"
}
],
"name": "transferChirpsList",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "payable",
"type": "function"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "./chirps/Chirp.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {Ownable} from "solady/src/auth/Ownable.sol";
import {IStartableToken} from "./chirps/IStartableToken.sol";
/// @title Chirps token
/// @notice ERC20 token that syncs Chirps (pixel robin NFTs) with balances.
/// OZ ERC20 (scanner-safe) + solady Ownable (renounce pattern).
/// - Mints Chirps on transfers from the pool (with generated art traits)
/// - Burns Chirps when sending to the pool or zero address
/// - Transfers Chirps 1:1 on user-to-user transfers
contract Chirps is Chirp, ERC20, Ownable, IStartableToken {
error AddressCanNotReceiveChirp();
uint256 public constant UNIT_PER_CHIRP = 1e18;
uint256 public constant INITIAL_SUPPLY = 10_000e18;
uint256 constant MAX_BUY_PRECISION = 100000;
uint256 constant _startMaxBuyCount = (INITIAL_SUPPLY * 500) / MAX_BUY_PRECISION;
uint256 constant _addMaxBuyPercentPerSec = 3;
address immutable _notMintableAccount;
uint256 _startTime;
address public hook;
address public pool;
constructor(address owner_) ERC20("Chirps", "CHIRPS") {
_initializeOwner(owner_);
_mint(owner_, INITIAL_SUPPLY);
_notMintableAccount = owner_;
}
modifier canReceiveChirps(address addr) {
if (!_canReceiveChirps(addr)) revert AddressCanNotReceiveChirp();
_;
}
modifier onlyHook() {
require(msg.sender == hook, "only for hook");
_;
}
function setHook(address newHook) external onlyOwner {
hook = newHook;
}
function setImageParamsProvider(address imageParamsProvider) public onlyOwner {
_setImageParamsProvider(imageParamsProvider);
}
function setRandomSeedProvider(address _randomSeedProvider) public onlyOwner {
_setRandomSeedProvider(_randomSeedProvider);
}
function start(address newPool) external onlyHook {
pool = newPool;
_startTime = block.timestamp;
}
function isStarted() public view returns (bool) {
return pool != address(0);
}
function maxBuy() public view returns (uint256) {
if (!isStarted()) return 0;
uint256 count = _startMaxBuyCount +
(INITIAL_SUPPLY * (block.timestamp - _startTime) * _addMaxBuyPercentPerSec) /
MAX_BUY_PRECISION;
if (count > INITIAL_SUPPLY) count = INITIAL_SUPPLY;
return count;
}
// ---- ERC20 hooks (OZ uses _update) ----
function _update(address from, address to, uint256 amount) internal override {
super._update(from, to, amount);
if (pool == address(0)) return;
_onTokenTransfer(from, to, amount, createRandom());
}
function _onTokenTransfer(
address from, address to, uint256 amount, Random memory random
) internal {
if (pool == address(0)) return;
// pool -> user: mint chirps with generated traits
if (from == pool && to != address(0)) {
if (_notMintableAccount == to) return;
require(isStarted(), "not started");
require(amount <= maxBuy(), "buy limit");
_mintChirps(to, amount / UNIT_PER_CHIRP, random);
return;
}
uint256 from_max_allowed = balanceOf(from) / UNIT_PER_CHIRP;
uint256 from_count = this.OwnerChirpsCount(from);
uint256 from_remove_count = from_count > from_max_allowed
? from_count - from_max_allowed
: 0;
uint256 to_max_allowed = balanceOf(to) / UNIT_PER_CHIRP;
uint256 to_count = this.OwnerChirpsCount(to);
uint256 to_receive_allowed = to_count < to_max_allowed
? to_max_allowed - to_count
: 0;
if (from_remove_count == 0) return;
if (!_canReceiveChirps(to)) {
to_receive_allowed = 0;
}
uint256 move_qty = from_remove_count < to_receive_allowed
? from_remove_count
: to_receive_allowed;
if (move_qty > 0) _moveChirps(from, to, move_qty);
_burnChirps(from, from_remove_count - move_qty);
}
function _mintChirps(address user, uint256 qty, Random memory random) internal {
for (uint256 i = 0; i < qty; i++) {
_mintChirp(user, random);
}
}
function _burnChirps(address user, uint256 qty) internal {
for (uint256 i = 0; i < qty; i++) {
uint count = this.OwnerChirpsCount(user);
if (count == 0) break;
uint lastIdx = count - 1;
uint chirpId = this.OwnerChirp(user, lastIdx).id;
_burnChirp(user, chirpId);
}
}
function _moveChirps(address from, address to, uint256 qty) internal canReceiveChirps(to) {
for (uint256 i = 0; i < qty; i++) {
uint count = this.OwnerChirpsCount(from);
if (count == 0) break;
uint lastIdx = count - 1;
uint chirpId = this.OwnerChirp(from, lastIdx).id;
_transferChirp(from, to, chirpId);
}
}
function _canReceiveChirps(address addr) internal view returns (bool) {
return addr != pool && addr != address(0);
}
function transferChirp(address to, uint chirpId) external override {
require(_owns[msg.sender][chirpId], "not owner");
_transferChirp(msg.sender, to, chirpId);
_transfer(msg.sender, to, UNIT_PER_CHIRP);
}
function transferChirpsList(address to, uint[] calldata chirpIds) external override {
for (uint i = 0; i < chirpIds.length; i++) {
require(_owns[msg.sender][chirpIds[i]], "not owner");
_transferChirp(msg.sender, to, chirpIds[i]);
}
_transfer(msg.sender, to, UNIT_PER_CHIRP * chirpIds.length);
}
}
Chain explorer1096msChain node88ms