Skip to main content

Contract Source

Table of contents

  1. Auction
  2. BaseAuction
  3. SaleAuction
  4. StokingAuction
  5. IAuction
  6. Pyro
  7. PyroAuction
  8. PyroBase
  9. PyroCore
  10. PyroGenesis
  11. Embers
  12. MRC20
  13. MRC20Burnable
  14. MRC721

Auction

// SPDX-License-Identifier: GPL-3.0

pragma solidity ^0.8.4;

struct Auction {
uint256 tokenId;
uint256 winningBid;
uint256 minimumBid;
uint256 biddingTime;
uint256 startTime;
address winningBidder;
address payable beneficiaryAddress;
bool ended;
}

BaseAuction

// SPDX-License-Identifier: GPL-3.0

pragma solidity ^0.8.4;

import '../PyroAuction.sol';
import '../interface/IAuction.sol';
import '@openzeppelin/contracts/utils/Address.sol';
import '@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol';

abstract contract BaseAuction is IAuction, ERC721Holder {
using Address for address;
event Canceled();

event HighestBidIncreased(uint256 tokenId, address bidder, uint256 amount);

event AuctionEnded(uint256 tokenId, address winner, uint256 amount);

event AuctionCreated(
uint256 tokenId,
uint256 minimumBid,
uint256 biddingTime,
address payable beneficiaryAddress
);

// Errors that describe failures.

// The triple-slash comments are so-called natspec
// comments. They will be shown when the user
// is asked to confirm a transaction or
// when an error is displayed.

/// The auction has already ended.
error AuctionAlreadyEnded();
/// There is already a higher or equal bid.
error BidNotHighEnough(uint256 highestBid);
/// The bid is below the minimum set at creation.
error BidBelowMinimum();
/// The auction has not ended yet.
error AuctionNotYetEnded();
/// The function auctionEnd has already been called.
error AuctionEndAlreadyCalled();
/// The function is not cancelable.
error AuctionNotCancelable();

PyroAuction public core;

mapping(uint256 => Auction) public auctions;

mapping(address => uint256) public pendingReturns;

function createAuction(
uint256 tokenId,
uint256 minimumBid,
uint256 biddingTime,
address payable beneficiaryAddress
) external virtual override returns (bool success) {
address owner = core.ownerOf(tokenId);
require(owner == msg.sender || core.isApprovedForAll(owner, msg.sender));

core.safeTransferFrom(owner, address(this), tokenId);

if (auctions[tokenId].startTime != 0) revert('Token already on auction');
Auction memory auction = Auction({
tokenId: tokenId,
winningBid: 0,
minimumBid: minimumBid,
biddingTime: biddingTime,
startTime: block.timestamp,
winningBidder: address(0x0),
beneficiaryAddress: beneficiaryAddress,
ended: false
});
auctions[tokenId] = auction;
emit AuctionCreated(tokenId, minimumBid, biddingTime, beneficiaryAddress);
return true;
}

/// Withdraw a bid that was overbid.
function withdraw() external override returns (bool success) {
uint256 amount = pendingReturns[msg.sender];
if (amount > 0) {
// It is important to set this to zero because the recipient
// can call this function again as part of the receiving call
// before `send` returns.
pendingReturns[msg.sender] = 0;

if (!payable(msg.sender).send(amount)) {
// No need to call throw here, just reset the amount owing
pendingReturns[msg.sender] = amount;
return false;
}
}
return true;
}

/// Cancel the auction only if it has not recieved any bids
function cancelAuction(uint256 tokenId)
external
override
returns (bool success)
{
Auction storage auction = auctions[tokenId];

if (auction.startTime == 0) revert('Auction does not exist');
if (auction.beneficiaryAddress != msg.sender) revert('Not beneficiary');
if (auction.winningBid != 0 || auction.winningBidder != address(0x0)) {
revert AuctionNotCancelable();
}

if (
block.timestamp > auction.startTime + auction.biddingTime || auction.ended
) revert AuctionAlreadyEnded();

auction.ended = true;
emit Canceled();
return true;
}

/// End the auction and send the highest bid
/// to the beneficiary.
function auctionEnd(uint256 tokenId) external override {
// It is a good guideline to structure functions that interact
// with other contracts (i.e. they call functions or send Ether)
// into three phases:
// 1. checking conditions
// 2. performing actions (potentially changing conditions)
// 3. interacting with other contracts
// If these phases are mixed up, the other contract could call
// back into the current contract and modify the state or cause
// effects (ether payout) to be performed multiple times.
// If functions called internally include interaction with external
// contracts, they also have to be considered interaction with
// external contracts.

Auction storage auction = auctions[tokenId];

// 1. Conditions
if (auction.startTime == 0) revert('Auction does not exist');
if (block.timestamp < auction.startTime + auction.biddingTime)
revert AuctionNotYetEnded();
if (auction.ended) revert AuctionEndAlreadyCalled();

// 2. Effects
auction.ended = true;
emit AuctionEnded(tokenId, auction.winningBidder, auction.winningBid);

// 3. Interaction
auction.beneficiaryAddress.transfer(auction.winningBid);
}

function getAuction(uint256 _tokenId)
external
view
override
returns (
uint256 tokenId,
uint256 winningBid,
uint256 minimumBid,
uint256 biddingTime,
uint256 startTime,
address winningBidder,
address payable beneficiaryAddress,
bool ended
)
{
Auction storage auction = auctions[_tokenId];
return (
auction.tokenId,
auction.winningBid,
auction.minimumBid,
auction.biddingTime,
auction.startTime,
auction.winningBidder,
auction.beneficiaryAddress,
auction.ended
);
}
}

SaleAuction

// SPDX-License-Identifier: GPL-3.0

pragma solidity ^0.8.4;

import '../PyroAuction.sol';
import './BaseAuction.sol';
import '@openzeppelin/contracts/utils/Address.sol';

contract SaleAuction is BaseAuction {
using Address for address;

constructor(address _core) {
require(_core != address(0x0));
core = PyroAuction(_core);
}

function bid(uint256 tokenId) external payable {
Auction storage auction = auctions[tokenId];

// Revert the call if the bidding
// period is over.
if (
block.timestamp > auction.startTime + auction.biddingTime || auction.ended
) revert AuctionAlreadyEnded();

// If the bid is not higher, send the
// money back (the revert statement
// will revert all changes in this
// function execution including
// it having received the money).
if (msg.value < auction.minimumBid || msg.value <= auction.winningBid)
revert BidNotHighEnough(auction.winningBid);

if (auction.winningBid != 0) {
// Sending back the money by simply using
// highestBidder.send(highestBid) is a security risk
// because it could execute an untrusted contract.
// It is always safer to let the recipients
// withdraw their money themselves.
pendingReturns[auction.winningBidder] += auction.winningBid;
}
auction.winningBidder = msg.sender;
auction.winningBid = msg.value;
emit HighestBidIncreased(tokenId, msg.sender, msg.value);
}

/// Claim the asset being auctioned
function claim(uint256 tokenId) external override returns (bool success) {
Auction storage auction = auctions[tokenId];
if (auction.startTime == 0) revert('Auction does not exist');
if (!auction.ended) revert AuctionNotYetEnded();
if (msg.sender == auction.winningBidder) {
core.safeTransferFrom(
address(this),
auction.winningBidder,
auction.tokenId
);
} else if (auction.winningBidder == address(0x0)) {
core.safeTransferFrom(
address(this),
auction.beneficiaryAddress,
auction.tokenId
);
} else {
revert('Not the winning bidder or the creator');
}
delete auctions[tokenId];
return true;
}
}

StokingAuction

// SPDX-License-Identifier: GPL-3.0

pragma solidity ^0.8.4;

import '../Pyro.sol';
import '../PyroAuction.sol';
import './BaseAuction.sol';
import '@openzeppelin/contracts/utils/Address.sol';

contract StokingAuction is BaseAuction {
using Address for address;

/// Invalid stoking donor
error InvalidStokingPair();

mapping(uint256 => uint256) public donors;

constructor(address _core) {
require(_core != address(0x0));
core = PyroAuction(_core);
}

function createAuction(
uint256 tokenId,
uint256 minimumBid,
uint256 biddingTime,
address payable beneficiaryAddress
) external override returns (bool success) {
if (!core.canStoke(tokenId)) revert('Pyro cannot stoke');
address owner = core.ownerOf(tokenId);
require(owner == msg.sender || core.isApprovedForAll(owner, msg.sender));
core.safeTransferFrom(owner, address(this), tokenId);

if (auctions[tokenId].startTime != 0) revert('Token already on auction');
Auction memory auction = Auction({
tokenId: tokenId,
winningBid: 0,
minimumBid: minimumBid,
biddingTime: biddingTime,
startTime: block.timestamp,
winningBidder: address(0x0),
beneficiaryAddress: beneficiaryAddress,
ended: false
});
auctions[tokenId] = auction;
emit AuctionCreated(tokenId, minimumBid, biddingTime, beneficiaryAddress);
return true;
}

function stokingCost(uint256 tokenId, uint256 donor)
public
view
returns (uint256 cost)
{
uint256 aGeneration = core.generationOfPyro(donor);

uint256 bGeneration = core.generationOfPyro(tokenId);

cost = aGeneration > bGeneration
? core.pyroGenesisCosts(aGeneration)
: core.pyroGenesisCosts(bGeneration);
return cost;
}

function bid(uint256 tokenId, uint256 donor) external payable {
Auction storage auction = auctions[tokenId];

// Revert the call if the bidding
// period is over.
if (
block.timestamp > auction.startTime + auction.biddingTime || auction.ended
) revert AuctionAlreadyEnded();

if (!core.isValidStokingPair(donor, tokenId)) revert InvalidStokingPair();

uint256 amount = msg.value - stokingCost(donor, tokenId);

// If the bid is not higher, send the
// money back (the revert statement
// will revert all changes in this
// function execution including
// it having received the money).
if (
amount < auction.minimumBid || amount <= auction.winningBid || amount < 1
) revert BidNotHighEnough(auction.winningBid);

if (auction.winningBid != 0) {
// Sending back the money by simply using
// highestBidder.send(highestBid) is a security risk
// because it could execute an untrusted contract.
// It is always safer to let the recipients
// withdraw their money themselves.
pendingReturns[auction.winningBidder] += (auction.winningBid +
stokingCost(donors[auction.tokenId], tokenId));
core.safeTransferFrom(
address(this),
auction.winningBidder,
donors[auction.tokenId]
);
}
core.safeTransferFrom(msg.sender, address(this), donor);
auction.winningBidder = msg.sender;
auction.winningBid = amount;
donors[auction.tokenId] = donor;
emit HighestBidIncreased(tokenId, msg.sender, amount);
}

/// Claim the asset being auctioned
function claim(uint256 tokenId) external override returns (bool success) {
Auction storage auction = auctions[tokenId];
if (auction.startTime == 0) revert('Auction does not exist');
if (!auction.ended) revert AuctionNotYetEnded();
uint256 donorB = auction.tokenId;
if (
msg.sender == auction.winningBidder ||
msg.sender == auction.beneficiaryAddress
) {
if (auction.winningBidder != address(0x0)) {
uint256 donorA = donors[donorB];
(bool stoked, ) = address(core).call{
value: stokingCost(donorA, donorB)
}(
abi.encodeWithSignature('stokeWith(uint256,uint256)', donorA, donorB)
);
require(stoked, 'Stoking failed');
core.safeTransferFrom(address(this), auction.winningBidder, donorA);
}

core.safeTransferFrom(address(this), auction.beneficiaryAddress, donorB);
} else if (auction.winningBidder == address(0x0)) {
core.safeTransferFrom(address(this), auction.beneficiaryAddress, donorB);
} else {
revert('Not the winning bidder or the creator');
}
delete auctions[tokenId];
delete donors[tokenId];
return true;
}
}

IAuction

// SPDX-License-Identifier: GPL-3.0

pragma solidity ^0.8.4;

import '../auction/Auction.sol';

interface IAuction {
function createAuction(
uint256 tokenId,
uint256 minimumBid,
uint256 biddingTime,
address payable beneficiaryAddress
) external returns (bool success);

function withdraw() external returns (bool success);

function claim(uint256 tokenId) external returns (bool success);

function cancelAuction(uint256 tokenId) external returns (bool success);

function auctionEnd(uint256 tokenId) external;

function getAuction(uint256 _tokenId)
external
view
returns (
uint256 tokenId,
uint256 winningBid,
uint256 minimumBid,
uint256 biddingTime,
uint256 startTime,
address winningBidder,
address payable beneficiaryAddress,
bool ended
);
}

Pyro

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.4;

struct Pyro {
uint256 donorA;
uint256 donorB;
uint256 generation;
string name;
uint256 ignitionTime;
uint256 nextPyroGenesis;
uint256 pyroGenesisCount;
uint256 stokingWith;
uint8 hunger;
uint8 eyes;
uint8 snout;
uint8 color;
}

PyroAuction

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.4;

import './PyroGenesis.sol';
import './auction/Auction.sol';
import './auction/SaleAuction.sol';
import './auction/StokingAuction.sol';

contract PyroAuction is PyroGenesis {
SaleAuction public immutable saleAuction;
StokingAuction public immutable stokingAuction;

constructor(
string memory _uri,
uint256 _generationCost,
uint256 _stokingBaseCost,
uint256 _timeUnit
) PyroGenesis(_uri, _generationCost, _stokingBaseCost, _timeUnit) {
saleAuction = new SaleAuction(address(this));
stokingAuction = new StokingAuction(address(this));
}

function getSaleAuction(uint256 _tokenId)
public
view
returns (
uint256 tokenId,
uint256 winningBid,
uint256 minimumBid,
uint256 biddingTime,
uint256 startTime,
address winningBidder,
address payable beneficiaryAddress,
bool ended
)
{
return saleAuction.getAuction(_tokenId);
}

function getStokingAuction(uint256 _tokenId)
public
view
returns (
uint256 tokenId,
uint256 winningBid,
uint256 minimumBid,
uint256 biddingTime,
uint256 startTime,
address winningBidder,
address payable beneficiaryAddress,
bool ended
)
{
return stokingAuction.getAuction(_tokenId);
}
}

PyroBase

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.4;
import './Pyro.sol';
import './token/Embers.sol';
import './token/MRC721.sol';

contract PyroBase is MRC721 {
Embers public immutable embers;
string public baseURI;

uint32 public constant gen0Cap = 2**14;
uint256 public generationCost;
uint256 public stokingBaseCost;

uint256[14] public pyroGenesisCosts;

uint32[14] public pyroGenesisCooldowns;

uint32 public gen0Count;

Pyro[] public pyros;

mapping(uint256 => address) public stokingAllowedToAddress;

mapping(uint256 => uint256) public lastPlayed;

mapping(uint256 => uint256) public lastAte;

mapping(uint256 => uint256) public pyroLevel;

event Ignition(
uint256 tokenId,
string name,
uint256 donorA,
uint256 donorB,
address indexed owner
);

uint8[14] public emberRates = [
uint8(70),
uint8(47),
uint8(35),
uint8(28),
uint8(23),
uint8(20),
uint8(18),
uint8(16),
uint8(14),
uint8(13),
uint8(12),
uint8(11),
uint8(10),
uint8(9)
];

constructor(
string memory _uri,
uint256 _generationCost,
uint256 _stokingBaseCost,
uint256 _timeUnit
) MRC721('PyroPets', 'PYRO') {
baseURI = _uri;
generationCost = _generationCost;
stokingBaseCost = _stokingBaseCost;
embers = new Embers(address(this));
pyroGenesisCosts = [
uint256(stokingBaseCost / 8),
uint256(stokingBaseCost / 7),
uint256(stokingBaseCost / 6),
uint256(stokingBaseCost / 5),
uint256(stokingBaseCost / 4),
uint256(stokingBaseCost / 3),
uint256(stokingBaseCost / 2),
uint256(stokingBaseCost),
uint256(stokingBaseCost * 2),
uint256(stokingBaseCost * 3),
uint256(stokingBaseCost * 4),
uint256(stokingBaseCost * 5),
uint256(stokingBaseCost * 6),
uint256(stokingBaseCost * 7)
];
pyroGenesisCooldowns = [
uint32(35 * _timeUnit),
uint32(28 * _timeUnit),
uint32(23 * _timeUnit),
uint32(20 * _timeUnit),
uint32(18 * _timeUnit),
uint32(16 * _timeUnit),
uint32(14 * _timeUnit),
uint32(13 * _timeUnit),
uint32(12 * _timeUnit),
uint32(11 * _timeUnit),
uint32(10 * _timeUnit),
uint32(9 * _timeUnit),
uint32(8 * _timeUnit),
uint32(7 * _timeUnit)
];
}

/**
* @dev Base URI for computing {tokenURI}. If set, the resulting URI for each
* token will be the concatenation of the `baseURI` and the `tokenId`. Empty
* by default, can be overriden in child contracts.
*/
function _baseURI() internal view virtual override returns (string memory) {
return baseURI;
}

function generationOfPyro(uint256 tokenId) public view returns (uint256) {
require(ownerOf(tokenId) != address(0x0));
return pyros[tokenId].generation;
}

function getPyro(uint256 id)
public
view
returns (
uint256 donorA,
uint256 donorB,
uint256 generation,
string memory name,
uint256 ignitionTime,
uint256 nextPyroGenesis,
uint256 pyroGenesisCount,
uint256 stokingWith,
uint8 hunger,
uint8 eyes,
uint8 snout,
uint8 color
)
{
Pyro memory pyro = pyros[id];
return (
pyro.donorA,
pyro.donorB,
pyro.generation,
pyro.name,
pyro.ignitionTime,
pyro.nextPyroGenesis,
pyro.pyroGenesisCount,
pyro.stokingWith,
pyro.hunger,
pyro.eyes,
pyro.snout,
pyro.color
);
}

function _createRootPyro(string memory _name, address _owner)
internal
returns (uint256)
{
uint8 eyes = 0;
uint8 snout = 0;
Pyro memory pyro = Pyro({
donorA: 0,
donorB: 0,
generation: 0,
name: _name,
ignitionTime: block.timestamp,
nextPyroGenesis: pyroGenesisCooldowns[0],
pyroGenesisCount: 0,
stokingWith: 0,
hunger: 255,
eyes: eyes,
snout: snout,
color: 0x00
});

pyros.push(pyro);
uint256 tokenId = pyros.length - 1;
emit Ignition(tokenId, _name, 0, 0, _owner);
_safeMint(_owner, tokenId);
gen0Count++;
return tokenId;
}

function _createPyro(
uint256 _donorA,
uint256 _donorB,
uint256 _generation,
string memory _name,
address _owner
) internal returns (uint256) {
Pyro storage donorA = pyros[_donorA];
Pyro storage donorB = pyros[_donorB];
uint8 eyes = donorA.generation <= donorB.generation
? donorA.eyes
: donorB.eyes;
uint8 snout = donorB.generation <= donorA.generation
? donorB.snout
: donorA.snout;
if (_generation == 0) {
require(gen0Count + 1 <= gen0Cap);
gen0Count++;
bytes32 genes = keccak256(
abi.encodePacked(
block.timestamp,
block.difficulty,
blockhash(block.number),
_name,
_owner,
pyros.length
)
);

eyes = uint8(genes[0]) % 32;
snout = uint8(genes[31]) % 32;
}

Pyro memory pyro = Pyro({
donorA: _donorA,
donorB: _donorB,
generation: _generation,
name: _name,
ignitionTime: block.timestamp,
nextPyroGenesis: block.timestamp,
pyroGenesisCount: 0,
stokingWith: 0,
hunger: 255,
eyes: eyes,
snout: snout,
color: 0x00
});

pyros.push(pyro);

uint256 tokenId = pyros.length - 1;
lastAte[tokenId] = block.timestamp;
emit Ignition(tokenId, _name, _donorA, _donorB, _owner);
_safeMint(_owner, tokenId);
return tokenId;
}

function burn(uint256 tokenId) public override {
_burnPyro(tokenId);
super.burn(tokenId);
}

function _burnPyro(uint256 tokenId) internal {
Pyro storage pyro = pyros[tokenId];
require(_isApprovedOrOwner(_msgSender(), tokenId));
if (pyro.generation == 0) {
gen0Count--;
}
uint8 emberRate = emberRates[pyro.generation > 13 ? 13 : pyro.generation];
uint256 amount = ((emberRate * (pyroLevel[tokenId] + 1)) /
(pyro.generation + 1)) * 10;
(bool success, ) = address(embers).call(
abi.encodeWithSignature(
'generateEmbers(uint256,uint256)',
tokenId,
amount
)
);
require(success);
delete pyros[tokenId];
delete lastAte[tokenId];
delete lastPlayed[tokenId];
delete pyroLevel[tokenId];
}

/**
* @dev See {MRC721-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId)
public
view
virtual
override
returns (bool)
{
return super.supportsInterface(interfaceId);
}

/**
* @dev See {MRC721-_beforeTokenTransfer}.
*/
function _beforeTokenTransfer(
address from,
address to,
uint256 tokenId
) internal virtual override(MRC721) {
super._beforeTokenTransfer(from, to, tokenId);
delete stokingAllowedToAddress[tokenId];
}

function play(uint256 tokenId) public {
Pyro storage pyro = pyros[tokenId];
require(_isApprovedOrOwner(_msgSender(), tokenId));
require(lastPlayed[tokenId] + 1 days <= block.timestamp);
require(pyro.hunger > 0);
(bool success, ) = address(embers).call{value: 0}(
abi.encodeWithSignature(
'generateEmbers(uint256,uint256)',
tokenId,
uint256(emberRates[pyro.generation > 13 ? 13 : pyro.generation])
)
);
require(success);
pyroLevel[tokenId] += 1;
pyro.hunger -= 1;
lastPlayed[tokenId] = block.timestamp;
}

function feed(uint256 tokenId, uint8 amount) public {
Pyro storage pyro = pyros[tokenId];
require(pyro.hunger < 255);
require(_isApprovedOrOwner(_msgSender(), tokenId));

uint256 allowance = embers.allowance(_msgSender(), address(this));
require(allowance >= uint256(amount));
uint256 balance = embers.balanceOf(_msgSender());
require(balance >= uint256(amount));
embers.burnFrom(_msgSender(), uint256(amount));
require(embers.balanceOf(_msgSender()) == balance - uint256(amount));
pyroLevel[tokenId] += 1;
pyro.hunger += uint8(amount > 0xff ? 0xff : amount);
}

function setColor(uint256 tokenId, uint8 color) public {
Pyro storage pyro = pyros[tokenId];
require(_isApprovedOrOwner(_msgSender(), tokenId));
require(color <= 7);
uint256 allowance = embers.allowance(_msgSender(), address(this));
require(allowance >= 100);
uint256 balance = embers.balanceOf(_msgSender());
require(balance >= 100);
embers.burnFrom(_msgSender(), 100);
require(embers.balanceOf(_msgSender()) == balance - 100);
pyro.color = color;
}

function setName(uint256 tokenId, string calldata name) public {
Pyro storage pyro = pyros[tokenId];
require(_isApprovedOrOwner(_msgSender(), tokenId));
uint256 allowance = embers.allowance(_msgSender(), address(this));
require(allowance >= 100);
uint256 balance = embers.balanceOf(_msgSender());
require(balance >= 100);
embers.burnFrom(_msgSender(), 100);
require(embers.balanceOf(_msgSender()) == balance - 100);
pyro.name = name;
}

function levelUp(uint256 tokenId, uint256 amount) public {
require(_isApprovedOrOwner(_msgSender(), tokenId));
uint256 allowance = embers.allowance(_msgSender(), address(this));
require(allowance >= amount);
uint256 balance = embers.balanceOf(_msgSender());
require(balance >= amount);
embers.burnFrom(_msgSender(), amount);
require(embers.balanceOf(_msgSender()) == balance - amount);
pyroLevel[tokenId] += amount;
}
}

PyroCore

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.4;

import './token/Embers.sol';
import './PyroAuction.sol';

contract PyroCore is PyroAuction {
constructor(
string memory _uri,
uint256 _generationCost,
uint256 _stokingBaseCost,
uint256 _timeUnit
) PyroAuction(_uri, _generationCost, _stokingBaseCost, _timeUnit) {
_createRootPyro('Agni', address(this));
}
}

PyroGenesis

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.4;

import './PyroBase.sol';

contract PyroGenesis is PyroBase {
event FlareUp(address owner, uint256 donorA, uint256 donorB);

mapping(address => uint256) public lastGen0Mints;

constructor(
string memory _uri,
uint256 _generationCost,
uint256 _stokingBaseCost,
uint256 _timeUnit
) PyroBase(_uri, _generationCost, _stokingBaseCost, _timeUnit) {}

function generationZero(string calldata name) public payable {
require(msg.value >= generationCost);
require(
block.timestamp >= lastGen0Mints[_msgSender()] + pyroGenesisCooldowns[0]
);
(bool burned, ) = payable(address(0x0)).call{value: generationCost}('');
require(burned);
if (msg.value - generationCost > 0) {
(bool refunded, ) = payable(address(_msgSender())).call{
value: msg.value - generationCost
}('');
require(refunded);
}
_createPyro(0, 0, 0, name, _msgSender());
lastGen0Mints[_msgSender()] = block.timestamp;
}

function generationZeroForAddress(string calldata name, address owner)
public
payable
{
require(msg.value >= generationCost);
require(
block.timestamp >= lastGen0Mints[_msgSender()] + pyroGenesisCooldowns[0]
);
payable(address(0x0)).transfer(generationCost);
if (msg.value - generationCost > 0) {
payable(address(_msgSender())).transfer(msg.value - generationCost);
}
_createPyro(0, 0, 0, name, owner);
lastGen0Mints[owner] = block.timestamp;
}

function _triggerCooldown(Pyro storage pyro) internal {
pyro.nextPyroGenesis = uint64(
block.timestamp +
pyroGenesisCooldowns[
pyro.pyroGenesisCount >= 13 ? 13 : pyro.pyroGenesisCount
]
);
pyro.pyroGenesisCount += 1;
pyro.hunger = 127;
}

function approveStoking(address addr, uint256 donor) public {
require(ownerOf(donor) == _msgSender());
stokingAllowedToAddress[donor] = addr;
}

function canStokeWith(uint256 _donorA, uint256 _donorB)
public
view
returns (bool)
{
require(_donorA > 0);
require(_donorB > 0);
Pyro storage donorA = pyros[_donorA];
Pyro storage donorB = pyros[_donorB];
return
_isValidStokingPair(donorA, _donorA, donorB, _donorB) &&
_isStokingPermitted(_donorB, _donorA);
}

function isValidStokingPair(uint256 _donorAId, uint256 _donorBId)
public
view
returns (bool)
{
Pyro storage _donorA = pyros[_donorAId];
Pyro storage _donorB = pyros[_donorBId];
return _isValidStokingPair(_donorA, _donorAId, _donorB, _donorBId);
}

function _isValidStokingPair(
Pyro storage _donorA,
uint256 _donorAId,
Pyro storage _donorB,
uint256 _donorBId
) private view returns (bool) {
// same pyro
if (_donorAId == _donorBId) {
return false;
}

// donorB parent of donorA
if (_donorA.donorA == _donorBId || _donorA.donorB == _donorBId) {
return false;
}
// donorA parent of donorB
if (_donorB.donorA == _donorAId || _donorB.donorB == _donorAId) {
return false;
}
// gen0 donors parent
if ((_donorA.donorB == 0 && _donorA.donorA == 0)) {
return true;
}

// siblings
if (_donorB.donorA == _donorA.donorA || _donorB.donorA == _donorA.donorB) {
return false;
}
if (_donorB.donorB == _donorA.donorA || _donorB.donorB == _donorA.donorB) {
return false;
}

return true;
}

function _isStokingPermitted(uint256 donorA, uint256 donorB)
internal
view
returns (bool)
{
address aOwner = ownerOf(donorA);
address bOwner = ownerOf(donorB);
return (aOwner == bOwner || stokingAllowedToAddress[donorB] == aOwner);
}

function _isReadyToStoke(Pyro storage _pyro) internal view returns (bool) {
return
(_pyro.hunger == 0xff) &&
(_pyro.stokingWith == 0) &&
(_pyro.nextPyroGenesis <= block.timestamp);
}

function _isReadyToIgnite(Pyro storage _pyro) private view returns (bool) {
return
(_pyro.hunger == 0xff) &&
(_pyro.stokingWith != 0) &&
(_pyro.nextPyroGenesis <= block.timestamp);
}

function _stokeWith(uint256 _donorA, uint256 _donorB) internal {
Pyro storage donorA = pyros[_donorA];
Pyro storage donorB = pyros[_donorB];

donorA.stokingWith = _donorB;

_triggerCooldown(donorA);
_triggerCooldown(donorB);

delete stokingAllowedToAddress[_donorA];

emit FlareUp(ownerOf(_donorA), _donorA, _donorB);
}

function stokeWith(uint256 _donorA, uint256 _donorB) public payable {
require(
(ownerOf(_donorA) == _msgSender() ||
stokingAllowedToAddress[_donorA] == msg.sender) &&
ownerOf(_donorB) == _msgSender()
);

require(_isStokingPermitted(_donorA, _donorB));

Pyro storage donorA = pyros[_donorA];

require(_isReadyToStoke(donorA));

Pyro storage donorB = pyros[_donorB];

require(_isReadyToStoke(donorB));

require(_isValidStokingPair(donorA, _donorA, donorB, _donorB));

uint256 generation = donorA.generation > donorB.generation
? donorA.generation
: donorB.generation;
uint256 cost = pyroGenesisCosts[generation > 13 ? 13 : generation];
require(msg.value >= cost);

(bool burned, ) = payable(address(0x0)).call{value: cost}('');
require(burned);
if (msg.value - cost > 0) {
(bool refunded, ) = payable(address(msg.sender)).call{
value: msg.value - cost
}('');
require(refunded);
}
_stokeWith(_donorA, _donorB);
}

function canStoke(uint256 tokenId) public view returns (bool) {
Pyro storage pyro = pyros[tokenId];
return _isReadyToStoke(pyro);
}

function ignite(uint256 _pyro, string calldata _name)
public
returns (uint256)
{
Pyro storage donorA = pyros[_pyro];
require(donorA.ignitionTime != 0);

require(_isReadyToIgnite(donorA));

uint256 _donorB = donorA.stokingWith;
Pyro storage donorB = pyros[_donorB];

uint256 gen = donorA.generation > donorB.generation
? donorA.generation + 1
: donorB.generation + 1;

address owner = ownerOf(_pyro);
uint256 pyroId = _createPyro(_pyro, _donorB, gen, _name, owner);

delete donorA.stokingWith;

return pyroId;
}
}

Embers

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.4;

import "../PyroBase.sol";
import "./MRC20.sol";
import "./MRC20Burnable.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";

contract Embers is MRC20, MRC20Burnable {
uint256 public constant minBurn = 1e11;

address public immutable base;

constructor(address _base) MRC20("Embers", "MBRS") {
base = _base;
}

function createEmbers() public payable {
require(msg.value >= minBurn);
uint256 amount = msg.value / 1e11;
require(amount > 0);
payable(address(0x0)).transfer(msg.value);
_mint(msg.sender, amount);
}

function generateEmbers(uint256 id, uint256 amount) external {
require(msg.sender == base);
PyroBase _base = PyroBase(base);
address owner = _base.ownerOf(id);
_mint(owner, amount);
}

receive() external payable {
createEmbers();
}

fallback() external payable {
createEmbers();
}

function mint() public payable {
createEmbers();
}

function decimals() public view virtual override returns (uint8) {
return 0;
}
}

MRC20

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.4;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract MRC20 is ERC20 {
constructor(string memory name_, string memory symbol_)
ERC20(name_, symbol_)
{}
}

MRC20Burnable

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./MRC20.sol";
import "@openzeppelin/contracts/utils/Context.sol";

/**
* @dev Extension of {MRC20} that allows token holders to destroy both their own
* tokens and those that they have an allowance for, in a way that can be
* recognized off-chain (via event analysis).
*/
abstract contract MRC20Burnable is Context, MRC20 {
/**
* @dev Destroys `amount` tokens from the caller.
*
* See {MRC20-_burn}.
*/
function burn(uint256 amount) public virtual {
_burn(_msgSender(), amount);
}

/**
* @dev Destroys `amount` tokens from `account`, deducting from the caller's
* allowance.
*
* See {MRC20-_burn} and {MRC20-allowance}.
*
* Requirements:
*
* - the caller must have allowance for ``accounts``'s tokens of at least
* `amount`.
*/
function burnFrom(address account, uint256 amount) public virtual {
uint256 currentAllowance = allowance(account, _msgSender());
require(
currentAllowance >= amount,
"MRC20: burn amount exceeds allowance"
);
unchecked {
_approve(account, _msgSender(), currentAllowance - amount);
}
_burn(account, amount);
}
}

MRC721

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.4;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";

contract MRC721 is ERC721Burnable, ERC721Enumerable {
constructor(string memory name_, string memory symbol_)
ERC721(name_, symbol_)
{}

/**
* @dev Hook that is called before any token transfer. This includes minting
* and burning.
*
* Calling conditions:
*
* - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be
* transferred to `to`.
* - When `from` is zero, `tokenId` will be minted for `to`.
* - When `to` is zero, ``from``'s `tokenId` will be burned.
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _beforeTokenTransfer(
address from,
address to,
uint256 tokenId
) internal virtual override(ERC721, ERC721Enumerable) {
super._beforeTokenTransfer(from, to, tokenId);
}

/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId)
public
view
virtual
override(ERC721, ERC721Enumerable)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
}