// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v4.5.0) (utils/cryptography/ECDSA.sol)pragmasolidity ^0.8.0;import"../Strings.sol";
/**
* @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
*
* These functions can be used to verify that a message was signed by the holder
* of the private keys of a given address.
*/libraryECDSA{
enumRecoverError {
NoError,
InvalidSignature,
InvalidSignatureLength,
InvalidSignatureS,
InvalidSignatureV
}
function_throwError(RecoverError error) privatepure{
if (error == RecoverError.NoError) {
return; // no error: do nothing
} elseif (error == RecoverError.InvalidSignature) {
revert("ECDSA: invalid signature");
} elseif (error == RecoverError.InvalidSignatureLength) {
revert("ECDSA: invalid signature length");
} elseif (error == RecoverError.InvalidSignatureS) {
revert("ECDSA: invalid signature 's' value");
} elseif (error == RecoverError.InvalidSignatureV) {
revert("ECDSA: invalid signature 'v' value");
}
}
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature` or error string. This address can then be used for verification purposes.
*
* The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {toEthSignedMessageHash} on it.
*
* Documentation for signature generation:
* - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
* - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
*
* _Available since v4.3._
*/functiontryRecover(bytes32 hash, bytesmemory signature) internalpurereturns (address, RecoverError) {
// Check the signature length// - case 65: r,s,v signature (standard)// - case 64: r,vs signature (cf https://eips.ethereum.org/EIPS/eip-2098) _Available since v4.1._if (signature.length==65) {
bytes32 r;
bytes32 s;
uint8 v;
// ecrecover takes the signature parameters, and the only way to get them// currently is to use assembly.assembly {
r :=mload(add(signature, 0x20))
s :=mload(add(signature, 0x40))
v :=byte(0, mload(add(signature, 0x60)))
}
return tryRecover(hash, v, r, s);
} elseif (signature.length==64) {
bytes32 r;
bytes32 vs;
// ecrecover takes the signature parameters, and the only way to get them// currently is to use assembly.assembly {
r :=mload(add(signature, 0x20))
vs :=mload(add(signature, 0x40))
}
return tryRecover(hash, r, vs);
} else {
return (address(0), RecoverError.InvalidSignatureLength);
}
}
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature`. This address can then be used for verification purposes.
*
* The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {toEthSignedMessageHash} on it.
*/functionrecover(bytes32 hash, bytesmemory signature) internalpurereturns (address) {
(address recovered, RecoverError error) = tryRecover(hash, signature);
_throwError(error);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
*
* See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
*
* _Available since v4.3._
*/functiontryRecover(bytes32 hash,
bytes32 r,
bytes32 vs
) internalpurereturns (address, RecoverError) {
bytes32 s = vs &bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
uint8 v =uint8((uint256(vs) >>255) +27);
return tryRecover(hash, v, r, s);
}
/**
* @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
*
* _Available since v4.2._
*/functionrecover(bytes32 hash,
bytes32 r,
bytes32 vs
) internalpurereturns (address) {
(address recovered, RecoverError error) = tryRecover(hash, r, vs);
_throwError(error);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `v`,
* `r` and `s` signature fields separately.
*
* _Available since v4.3._
*/functiontryRecover(bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s
) internalpurereturns (address, RecoverError) {
// EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature// unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines// the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most// signatures from current libraries generate a unique signature with an s-value in the lower half order.//// If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value// with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or// vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept// these malleable signatures as well.if (uint256(s) >0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
return (address(0), RecoverError.InvalidSignatureS);
}
if (v !=27&& v !=28) {
return (address(0), RecoverError.InvalidSignatureV);
}
// If the signature is valid (and not malleable), return the signer addressaddress signer =ecrecover(hash, v, r, s);
if (signer ==address(0)) {
return (address(0), RecoverError.InvalidSignature);
}
return (signer, RecoverError.NoError);
}
/**
* @dev Overload of {ECDSA-recover} that receives the `v`,
* `r` and `s` signature fields separately.
*/functionrecover(bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s
) internalpurereturns (address) {
(address recovered, RecoverError error) = tryRecover(hash, v, r, s);
_throwError(error);
return recovered;
}
/**
* @dev Returns an Ethereum Signed Message, created from a `hash`. This
* produces hash corresponding to the one signed with the
* https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
* JSON-RPC method as part of EIP-191.
*
* See {recover}.
*/functiontoEthSignedMessageHash(bytes32 hash) internalpurereturns (bytes32) {
// 32 is the length in bytes of hash,// enforced by the type signature abovereturnkeccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
}
/**
* @dev Returns an Ethereum Signed Message, created from `s`. This
* produces hash corresponding to the one signed with the
* https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
* JSON-RPC method as part of EIP-191.
*
* See {recover}.
*/functiontoEthSignedMessageHash(bytesmemory s) internalpurereturns (bytes32) {
returnkeccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", Strings.toString(s.length), s));
}
/**
* @dev Returns an Ethereum Signed Typed Data, created from a
* `domainSeparator` and a `structHash`. This produces hash corresponding
* to the one signed with the
* https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
* JSON-RPC method as part of EIP-712.
*
* See {recover}.
*/functiontoTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internalpurereturns (bytes32) {
returnkeccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
}
}
Contract Source Code
File 2 of 10: ERC721.sol
// SPDX-License-Identifier: AGPL-3.0-onlypragmasolidity >=0.8.0;/// @notice Modern, minimalist, and gas efficient ERC-721 implementation./// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC721.sol)abstractcontractERC721{
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/eventTransfer(addressindexedfrom, addressindexed to, uint256indexed id);
eventApproval(addressindexed owner, addressindexed spender, uint256indexed id);
eventApprovalForAll(addressindexed owner, addressindexed operator, bool approved);
/*//////////////////////////////////////////////////////////////
METADATA STORAGE/LOGIC
//////////////////////////////////////////////////////////////*/stringpublic name;
stringpublic symbol;
functiontokenURI(uint256 id) publicviewvirtualreturns (stringmemory);
/*//////////////////////////////////////////////////////////////
ERC721 BALANCE/OWNER STORAGE
//////////////////////////////////////////////////////////////*/mapping(uint256=>address) internal _ownerOf;
mapping(address=>uint256) internal _balanceOf;
functionownerOf(uint256 id) publicviewvirtualreturns (address owner) {
require((owner = _ownerOf[id]) !=address(0), "NOT_MINTED");
}
functionbalanceOf(address owner) publicviewvirtualreturns (uint256) {
require(owner !=address(0), "ZERO_ADDRESS");
return _balanceOf[owner];
}
/*//////////////////////////////////////////////////////////////
ERC721 APPROVAL STORAGE
//////////////////////////////////////////////////////////////*/mapping(uint256=>address) public getApproved;
mapping(address=>mapping(address=>bool)) public isApprovedForAll;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/constructor(stringmemory _name, stringmemory _symbol) {
name = _name;
symbol = _symbol;
}
/*//////////////////////////////////////////////////////////////
ERC721 LOGIC
//////////////////////////////////////////////////////////////*/functionapprove(address spender, uint256 id) publicvirtual{
address owner = _ownerOf[id];
require(msg.sender== owner || isApprovedForAll[owner][msg.sender], "NOT_AUTHORIZED");
getApproved[id] = spender;
emit Approval(owner, spender, id);
}
functionsetApprovalForAll(address operator, bool approved) publicvirtual{
isApprovedForAll[msg.sender][operator] = approved;
emit ApprovalForAll(msg.sender, operator, approved);
}
functiontransferFrom(addressfrom,
address to,
uint256 id
) publicvirtual{
require(from== _ownerOf[id], "WRONG_FROM");
require(to !=address(0), "INVALID_RECIPIENT");
require(
msg.sender==from|| isApprovedForAll[from][msg.sender] ||msg.sender== getApproved[id],
"NOT_AUTHORIZED"
);
// Underflow of the sender's balance is impossible because we check for// ownership above and the recipient's balance can't realistically overflow.unchecked {
_balanceOf[from]--;
_balanceOf[to]++;
}
_ownerOf[id] = to;
delete getApproved[id];
emit Transfer(from, to, id);
}
functionsafeTransferFrom(addressfrom,
address to,
uint256 id
) publicvirtual{
transferFrom(from, to, id);
require(
to.code.length==0||
ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, "") ==
ERC721TokenReceiver.onERC721Received.selector,
"UNSAFE_RECIPIENT"
);
}
functionsafeTransferFrom(addressfrom,
address to,
uint256 id,
bytescalldata data
) publicvirtual{
transferFrom(from, to, id);
require(
to.code.length==0||
ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, data) ==
ERC721TokenReceiver.onERC721Received.selector,
"UNSAFE_RECIPIENT"
);
}
/*//////////////////////////////////////////////////////////////
ERC165 LOGIC
//////////////////////////////////////////////////////////////*/functionsupportsInterface(bytes4 interfaceId) publicviewvirtualreturns (bool) {
return
interfaceId ==0x01ffc9a7||// ERC165 Interface ID for ERC165
interfaceId ==0x80ac58cd||// ERC165 Interface ID for ERC721
interfaceId ==0x5b5e139f; // ERC165 Interface ID for ERC721Metadata
}
/*//////////////////////////////////////////////////////////////
INTERNAL MINT/BURN LOGIC
//////////////////////////////////////////////////////////////*/function_mint(address to, uint256 id) internalvirtual{
require(to !=address(0), "INVALID_RECIPIENT");
require(_ownerOf[id] ==address(0), "ALREADY_MINTED");
// Counter overflow is incredibly unrealistic.unchecked {
_balanceOf[to]++;
}
_ownerOf[id] = to;
emit Transfer(address(0), to, id);
}
function_burn(uint256 id) internalvirtual{
address owner = _ownerOf[id];
require(owner !=address(0), "NOT_MINTED");
// Ownership check above ensures no underflow.unchecked {
_balanceOf[owner]--;
}
delete _ownerOf[id];
delete getApproved[id];
emit Transfer(owner, address(0), id);
}
/*//////////////////////////////////////////////////////////////
INTERNAL SAFE MINT LOGIC
//////////////////////////////////////////////////////////////*/function_safeMint(address to, uint256 id) internalvirtual{
_mint(to, id);
require(
to.code.length==0||
ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, "") ==
ERC721TokenReceiver.onERC721Received.selector,
"UNSAFE_RECIPIENT"
);
}
function_safeMint(address to,
uint256 id,
bytesmemory data
) internalvirtual{
_mint(to, id);
require(
to.code.length==0||
ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, data) ==
ERC721TokenReceiver.onERC721Received.selector,
"UNSAFE_RECIPIENT"
);
}
}
/// @notice A generic interface for a contract which properly accepts ERC721 tokens./// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC721.sol)abstractcontractERC721TokenReceiver{
functiononERC721Received(address,
address,
uint256,
bytescalldata) externalvirtualreturns (bytes4) {
return ERC721TokenReceiver.onERC721Received.selector;
}
}
// SPDX-License-Identifier: MITpragmasolidity >=0.8.4;interfaceIProxyTracking{
/**
* @dev Called by original contract on _afterTokenTransfer ERC721 event.
*
* WARNING: Good practice will be to check that msg.sender is original contract, for example: require(msg.sender == _originalContract, "Only original contract can call this");
*
*/functionafterTokenTransfer(addressfrom,
address to,
uint256 tokenId
) external;
}
Contract Source Code
File 6 of 10: LedgerNFT.sol
// SPDX-License-Identifier: MITpragmasolidity >=0.8.4;import {ERC721} from"@rari-capital/solmate/src/tokens/ERC721.sol";
import"@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import"./ReentrancyGuard.sol";
import"./Signable.sol";
import"./IProxyTracking.sol";
import"./Helpers.sol";
import"./Errors.sol";
contractLedgerMarketPassisERC721, ReentrancyGuard, Signable{
// Phase States: None - can't mint, Pre Sale - only mint with sign, Main Sale - only regular mintenumPhase {
NONE,
PRE_SALE,
MAIN_SALE
}
// Current phase of the contract
Phase private _phase;
// Constants// Maximum number of NFTs can be allocateduint256publicimmutable maxSupply;
// ETH value should be sent with mint (owner mint is free)uint256public mintPrice =0.3ether;
// Address where all money from the contract will go if the owner of the contract will call withdraw functionaddressprivateconstant _withdrawalAddress =0xC55dA65c626Bad25532bE0d4f6B44aBFD733A152;
// Counter used for token number in mintinguint256private _nextTokenCount =1;
// Base token and contract URIstringprivate baseTokenURI;
stringprivate baseContractURI;
// Proxy contract for tracking afterTokenTransfer call
IProxyTracking public proxyTrackingContract;
// Has the account used minting alreadymapping(address=>bool) public minted;
// Modifier is used to check if the phase rule is metmodifierphaseRequired(Phase phase_) {
if (phase_ != _phase) revert Errors.MintNotAvailable();
_;
}
// Modifier is used to check if at least a minimal amount of money was sentmodifiercosts() {
if (msg.value< mintPrice) revert Errors.InsufficientFunds();
_;
}
constructor(uint256 _maxSupply,
stringmemory _baseTokenURI,
stringmemory _baseContractURI,
stringmemory _name,
stringmemory _symbol
) ERC721(_name, _symbol) {
maxSupply = _maxSupply;
baseTokenURI = _baseTokenURI;
baseContractURI = _baseContractURI;
}
// Contract owner can call this function to mint `amount` of tokens into account with the address `to`functionownerMint(address to, uint256 amount) externalonlyOwnerlock{
if (_nextTokenCount + amount -1> maxSupply)
revert Errors.SupplyLimitReached();
for (uint256 i; i < amount; ) {
_safeMint(to, _nextTokenCount);
unchecked {
++_nextTokenCount;
++i;
}
}
}
// Function used to do minting on pre-sale phase (with signature)functionpreSaleMint(bytescalldata signature)
externalpayablecostsphaseRequired(Phase.PRE_SALE)
{
if (!_verify(signer(), _hash(msg.sender), signature))
revert Errors.InvalidSignature();
_mintLogic();
}
// Function used to do minting on main-sale phasefunctionmint() externalpayablecostsphaseRequired(Phase.MAIN_SALE) {
_mintLogic();
}
// Contract owner can call this function to withdraw all money from the contract into a defined walletfunctionwithdrawAll() externalonlyOwner{
uint256 balance =address(this).balance;
if (balance ==0) revert Errors.NothingToWithdraw();
(bool success, ) = _withdrawalAddress.call{value: balance}("");
if (!success) revert Errors.WithdrawFailed();
}
// Contract owner can call this function to set minting price on pre-sale and main-salefunctionsetMintPrice(uint256 mintPrice_) externalonlyOwner{
if (mintPrice_ ==0) revert Errors.InvalidMintPrice();
// only allow to change price onceif (mintPrice !=0.3ether) revert Errors.MintPriceAlreadyUpdated();
mintPrice = mintPrice_;
}
// Contract owner can call this function to set the proxy tracking contract address (which gets a call of afterTokenTransfer function of the original contract)functionsetProxyTrackingContract(IProxyTracking proxyTrackingContract_)
externalonlyOwner{
proxyTrackingContract = proxyTrackingContract_;
}
functionsetContractURI(stringcalldata baseContractURI_)
externalonlyOwner{
if (bytes(baseContractURI_).length==0)
revert Errors.InvalidBaseContractURL();
baseContractURI = baseContractURI_;
}
functionsetBaseURI(stringcalldata baseURI_) externalonlyOwner{
if (bytes(baseURI_).length==0) revert Errors.InvalidBaseURI();
baseTokenURI = baseURI_;
}
functionsetPhase(Phase phase_) externalonlyOwner{
_phase = phase_;
}
functiontotalSupply() externalviewreturns (uint256) {
return _nextTokenCount -1;
}
functioncontractURI() externalviewreturns (stringmemory) {
return baseContractURI;
}
functionphase() externalviewreturns (Phase) {
return _phase;
}
function_baseURI() internalviewvirtualreturns (stringmemory) {
return baseTokenURI;
}
function_mint(address to, uint256 id) internalvirtualoverride{
super._mint(to, id);
_afterTokenTransfer(address(0), to, id);
}
function_burn(uint256 id) internalvirtualoverride{
address owner = _ownerOf[id];
super._burn(id);
_afterTokenTransfer(owner, address(0), id);
}
functiontransferFrom(addressfrom,
address to,
uint256 id
) publicvirtualoverride{
super.transferFrom(from, to, id);
_afterTokenTransfer(from, to, id);
}
// Function is overridden to do a proxy call into the proxy tracking contract if it is not zerofunction_afterTokenTransfer(addressfrom,
address to,
uint256 tokenId
) internalvirtual{
if (address(proxyTrackingContract) !=address(0)) {
proxyTrackingContract.afterTokenTransfer(from, to, tokenId);
}
}
function_mintLogic() private{
if (msg.sender.code.length > 0) revert Errors.ContractCantMint();
if (_nextTokenCount > maxSupply) revert Errors.SupplyLimitReached();
if (minted[msg.sender]) revert Errors.AccountAlreadyMintedMax();
minted[msg.sender] =true;
// smart-contracts are not allowed to call the method -- that means safeMint is useless
_mint(msg.sender, _nextTokenCount);
unchecked {
++_nextTokenCount;
}
}
function_verify(address signer,
bytes32 hash,
bytescalldata signature
) privatepurereturns (bool) {
return signer == ECDSA.recover(hash, signature);
}
function_hash(address account) privatepurereturns (bytes32) {
return
ECDSA.toEthSignedMessageHash(keccak256(abi.encodePacked(account)));
}
functiontokenURI(uint256 tokenId)
publicviewoverridereturns (stringmemory)
{
if (ownerOf(tokenId) ==address(0)) revert Errors.TokenDoesNotExist();
stringmemory baseURI = _baseURI();
returnbytes(baseURI).length>0
? string(
abi.encodePacked(baseURI, Helpers.uint2string(tokenId))
)
: "";
}
functionburn(uint256 id) external{
if (msg.sender!= ownerOf(id)) revert Errors.NotOwner();
_burn(id);
}
}
// SPDX-License-Identifier: MITpragmasolidity ^0.8.4;import"@rari-capital/solmate/src/auth/Owned.sol";
import"./Errors.sol";
/// @title Contract that manages the signer/owner rolesabstractcontractSignableisOwned{
addressprivate _signer;
constructor() Owned(msg.sender) {
_signer =msg.sender;
}
functionsigner() publicviewreturns (address) {
return _signer;
}
/// @notice This method allow the owner change the signer role/// @dev At first, the signer role and the owner role is associated to the same address/// @param newSigner The address of the new signerfunctiontransferSigner(address newSigner) externalonlyOwner{
if (newSigner ==address(0)) revert Errors.NewSignerCantBeZero();
_signer = newSigner;
}
}
Contract Source Code
File 10 of 10: Strings.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts v4.4.1 (utils/Strings.sol)pragmasolidity ^0.8.0;/**
* @dev String operations.
*/libraryStrings{
bytes16privateconstant _HEX_SYMBOLS ="0123456789abcdef";
/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/functiontoString(uint256 value) internalpurereturns (stringmemory) {
// Inspired by OraclizeAPI's implementation - MIT licence// https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.solif (value ==0) {
return"0";
}
uint256 temp = value;
uint256 digits;
while (temp !=0) {
digits++;
temp /=10;
}
bytesmemory buffer =newbytes(digits);
while (value !=0) {
digits -=1;
buffer[digits] =bytes1(uint8(48+uint256(value %10)));
value /=10;
}
returnstring(buffer);
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/functiontoHexString(uint256 value) internalpurereturns (stringmemory) {
if (value ==0) {
return"0x00";
}
uint256 temp = value;
uint256 length =0;
while (temp !=0) {
length++;
temp >>=8;
}
return toHexString(value, length);
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
*/functiontoHexString(uint256 value, uint256 length) internalpurereturns (stringmemory) {
bytesmemory buffer =newbytes(2* length +2);
buffer[0] ="0";
buffer[1] ="x";
for (uint256 i =2* length +1; i >1; --i) {
buffer[i] = _HEX_SYMBOLS[value &0xf];
value >>=4;
}
require(value ==0, "Strings: hex length insufficient");
returnstring(buffer);
}
}