Transaction Hash:
Block:
14416292 at Mar-19-2022 10:18:16 AM +UTC
Transaction Fee:
0.002812726 ETH
$7.30
Gas Used:
200,909 Gas / 14 Gwei
Emitted Events:
122 |
YAT.Transfer( from=0x00000000...000000000, to=[Sender] 0xf7dbfb01c39a6936ae050920531cfe75ae4e46ae, tokenId=10812 )
|
123 |
YAT.Transfer( from=0x00000000...000000000, to=[Sender] 0xf7dbfb01c39a6936ae050920531cfe75ae4e46ae, tokenHash=47BD2523288BBF7C7144F6EBEBD6FE8789BF7F762091632775DD03DFD417EEAC, token=🐴☁️🆚📱 )
|
124 |
YAT.Mint( tokenHash=47BD2523288BBF7C7144F6EBEBD6FE8789BF7F762091632775DD03DFD417EEAC, tokenId=10812, account=[Sender] 0xf7dbfb01c39a6936ae050920531cfe75ae4e46ae, token=🐴☁️🆚📱 )
|
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x7d256d82...E6e0dA9e2 | |||||
0x8F03f1a3...e06659Be7
Miner
| (Miner: 0x8f0...be7) | 2,810.792893255714714003 Eth | 2,810.793306516526187167 Eth | 0.000413260811473164 | |
0xF7dbfB01...5aE4e46Ae |
0.004429585128812067 Eth
Nonce: 12
|
0.001616859128812067 Eth
Nonce: 13
| 0.002812726 |
Execution Trace
YAT.mint( _token=🐴☁️🆚📱, _account=0xF7dbfB01C39a6936AE050920531cFe75aE4e46Ae, _expiry=1647686430, _signature=0x72F215D777B40B176FBCE65FE44F944DAA3ECB1CC44C0126506D6F881A849B292A36C862818D3F8B1978A2FB6964DCE9C7585EC38935F263385DFF5F6D2A95B01B )

-
Null: 0x000...001.a5b76ad2( )
mint[YAT (ln:129)]
_verifyMint[YAT (ln:131)]
_recoverSigner[YAT (ln:590)]
_splitSignature[YAT (ln:584)]
ecrecover[YAT (ln:585)]
_getEthSignedMessageHash[YAT (ln:590)]
signer[YAT (ln:590)]
_mint[YAT (ln:132)]
hashOf[YAT (ln:464)]
Transfer[YAT (ln:475)]
Transfer[YAT (ln:476)]
idOf[YAT (ln:478)]
hashOf[YAT (ln:263)]
_transfer[YAT (ln:480)]
ownerOf[YAT (ln:486)]
totalSupply[YAT (ln:274)]
getApproved[YAT (ln:487)]
totalSupply[YAT (ln:303)]
isApprovedForAll[YAT (ln:489)]
Approval[YAT (ln:496)]
_updateResolvesTo[YAT (ln:498)]
resolve[YAT (ln:519)]
tokenOf[YAT (ln:521)]
totalSupply[YAT (ln:269)]
_deleteAllRecords[YAT (ln:499)]
_deleteRecord[YAT (ln:565)]
RecordDeleted[YAT (ln:558)]
hashOf[YAT (ln:558)]
tokenOf[YAT (ln:558)]
totalSupply[YAT (ln:269)]
hashOf[YAT (ln:558)]
tokenOf[YAT (ln:558)]
totalSupply[YAT (ln:269)]
getKey[YAT (ln:565)]
Transfer[YAT (ln:510)]
Transfer[YAT (ln:511)]
hashOf[YAT (ln:511)]
tokenOf[YAT (ln:511)]
totalSupply[YAT (ln:269)]
tokenOf[YAT (ln:511)]
totalSupply[YAT (ln:269)]
Mint[YAT (ln:482)]
// SPDX-License-Identifier: MIT pragma solidity 0.8.6; interface Receiver { function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes calldata _data) external returns (bytes4); } contract Metadata { string public name = "Yat NFT"; string public symbol = "Yats"; function contractURI() external pure returns (string memory) { return "https://a.y.at/nft_transfers/contract/"; } function baseTokenURI() public pure returns (string memory) { return "https://a.y.at/nft_transfers/metadata/"; } function tokenURI(uint256 _tokenId) external pure returns (string memory) { bytes memory _base = bytes(baseTokenURI()); uint256 _digits = 1; uint256 _n = _tokenId; while (_n > 9) { _n /= 10; _digits++; } bytes memory _uri = new bytes(_base.length + _digits); for (uint256 i = 0; i < _uri.length; i++) { if (i < _base.length) { _uri[i] = _base[i]; } else { uint256 _dec = (_tokenId / (10**(_uri.length - i - 1))) % 10; _uri[i] = bytes1(uint8(_dec) + 48); } } return string(_uri); } } contract YAT { address constant private USE_GLOBAL_SIGNER = address(type(uint160).max); struct User { uint256 balance; mapping(uint256 => uint256) list; mapping(address => bool) approved; mapping(uint256 => uint256) indexOf; } struct Token { address owner; address cosigner; address approved; address pointsTo; address resolvesTo; string token; uint256 records; mapping(uint256 => bytes32) keys; mapping(bytes32 => string) values; mapping(bytes32 => uint256) indexOf; uint256 nonce; } struct Info { uint256 totalSupply; mapping(uint256 => Token) list; mapping(bytes32 => uint256) idOf; mapping(bytes32 => string) dictionary; mapping(address => string) resolve; mapping(address => User) users; Metadata metadata; address owner; address signer; } Info private info; mapping(bytes4 => bool) public supportsInterface; event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); event Transfer(address indexed from, address indexed to, bytes32 indexed tokenHash, string token); event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); event ApprovalForAll(address indexed owner, address indexed operator, bool approved); event Mint(bytes32 indexed tokenHash, uint256 indexed tokenId, address indexed account, string token); event Burn(bytes32 indexed tokenHash, uint256 indexed tokenId, address indexed account, string token); event RecordUpdated(bytes32 indexed tokenHash, address indexed manager, bytes32 indexed keyHash, string token, string key, string value); event RecordAdded(bytes32 indexed tokenHash, address indexed manager, bytes32 indexed keyHash, string token, string key); event RecordDeleted(bytes32 indexed tokenHash, address indexed manager, bytes32 indexed keyHash, string token, string key); modifier _onlyOwner() { require(msg.sender == owner()); _; } modifier _onlyTokenOwner(uint256 _tokenId) { require(msg.sender == ownerOf(_tokenId)); _; } modifier _onlyTokenOwnerOrCosigner(uint256 _tokenId) { require(msg.sender == ownerOf(_tokenId) || msg.sender == cosignerOf(_tokenId)); _; } constructor(address _signer) { info.metadata = new Metadata(); info.owner = msg.sender; info.signer = _signer; supportsInterface[0x01ffc9a7] = true; // ERC-165 supportsInterface[0x80ac58cd] = true; // ERC-721 supportsInterface[0x5b5e139f] = true; // Metadata supportsInterface[0x780e9d63] = true; // Enumerable } function setOwner(address _owner) external _onlyOwner { info.owner = _owner; } function setSigner(address _signer) external _onlyOwner { info.signer = _signer; } function setMetadata(Metadata _metadata) external _onlyOwner { info.metadata = _metadata; } function mint(string calldata _token, address _account, uint256 _expiry, bytes memory _signature) external { require(block.timestamp < _expiry); require(_verifyMint(_token, _account, _expiry, _signature)); _mint(_token, _account); } /** * "Soft-burns" the NFT by transferring the token to the contract address. **/ function burn(uint256 _tokenId) external _onlyTokenOwner(_tokenId) { _transfer(msg.sender, address(this), _tokenId); emit Burn(hashOf(tokenOf(_tokenId)), _tokenId, msg.sender, tokenOf(_tokenId)); } function setCosigner(address _cosigner, uint256 _tokenId) public _onlyTokenOwner(_tokenId) { info.list[_tokenId].cosigner = _cosigner; } function resetCosigner(uint256 _tokenId) external { setCosigner(USE_GLOBAL_SIGNER, _tokenId); } function revokeCosigner(uint256 _tokenId) external { setCosigner(address(0x0), _tokenId); } function setPointsTo(address _pointsTo, uint256 _tokenId) public _onlyTokenOwner(_tokenId) { info.list[_tokenId].pointsTo = _pointsTo; } function resolveTo(address _resolvesTo, uint256 _tokenId) public _onlyTokenOwner(_tokenId) { _updateResolvesTo(_resolvesTo, _tokenId); } function unresolve(uint256 _tokenId) external { resolveTo(address(0x0), _tokenId); } function updateRecord(uint256 _tokenId, string memory _key, string memory _value, bytes memory _signature) external { require(_verifyRecordUpdate(_tokenId, _key, _value, info.list[_tokenId].nonce++, _signature)); _updateRecord(_tokenId, _key, _value); } function updateRecord(uint256 _tokenId, string memory _key, string memory _value) public _onlyTokenOwnerOrCosigner(_tokenId) { _updateRecord(_tokenId, _key, _value); } function deleteRecord(uint256 _tokenId, string memory _key) external { updateRecord(_tokenId, _key, ""); } function deleteAllRecords(uint256 _tokenId) external _onlyTokenOwnerOrCosigner(_tokenId) { _deleteAllRecords(_tokenId); } function approve(address _approved, uint256 _tokenId) external _onlyTokenOwner(_tokenId) { info.list[_tokenId].approved = _approved; emit Approval(msg.sender, _approved, _tokenId); } function setApprovalForAll(address _operator, bool _approved) external { info.users[msg.sender].approved[_operator] = _approved; emit ApprovalForAll(msg.sender, _operator, _approved); } function transferFrom(address _from, address _to, uint256 _tokenId) external { _transfer(_from, _to, _tokenId); } function safeTransferFrom(address _from, address _to, uint256 _tokenId) external { safeTransferFrom(_from, _to, _tokenId, ""); } function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes memory _data) public { _transfer(_from, _to, _tokenId); uint32 _size; assembly { _size := extcodesize(_to) } if (_size > 0) { require(Receiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data) == 0x150b7a02); } } function name() external view returns (string memory) { return info.metadata.name(); } function symbol() external view returns (string memory) { return info.metadata.symbol(); } function contractURI() external view returns (string memory) { return info.metadata.contractURI(); } function baseTokenURI() external view returns (string memory) { return info.metadata.baseTokenURI(); } function tokenURI(uint256 _tokenId) external view returns (string memory) { return info.metadata.tokenURI(_tokenId); } function owner() public view returns (address) { return info.owner; } function signer() public view returns (address) { return info.signer; } function totalSupply() public view returns (uint256) { return info.totalSupply; } function balanceOf(address _owner) public view returns (uint256) { return info.users[_owner].balance; } function resolve(address _account) public view returns (string memory) { return info.resolve[_account]; } function reverseResolve(string memory _token) public view returns (address) { return info.list[idOf(_token)].resolvesTo; } function hashOf(string memory _token) public pure returns (bytes32) { return keccak256(abi.encodePacked(_token)); } function idOf(string memory _token) public view returns (uint256) { bytes32 _hash = hashOf(_token); require(info.idOf[_hash] != 0); return info.idOf[_hash] - 1; } function tokenOf(uint256 _tokenId) public view returns (string memory) { require(_tokenId < totalSupply()); return info.list[_tokenId].token; } function ownerOf(uint256 _tokenId) public view returns (address) { require(_tokenId < totalSupply()); return info.list[_tokenId].owner; } function cosignerOf(uint256 _tokenId) public view returns (address) { require(_tokenId < totalSupply()); address _cosigner = info.list[_tokenId].cosigner; if (_cosigner == USE_GLOBAL_SIGNER) { _cosigner = signer(); } return _cosigner; } function pointsTo(uint256 _tokenId) public view returns (address) { require(_tokenId < totalSupply()); return info.list[_tokenId].pointsTo; } function nonceOf(uint256 _tokenId) public view returns (uint256) { require(_tokenId < totalSupply()); return info.list[_tokenId].nonce; } function recordsOf(uint256 _tokenId) public view returns (uint256) { require(_tokenId < totalSupply()); return info.list[_tokenId].records; } function getApproved(uint256 _tokenId) public view returns (address) { require(_tokenId < totalSupply()); return info.list[_tokenId].approved; } function isApprovedForAll(address _owner, address _operator) public view returns (bool) { return info.users[_owner].approved[_operator]; } function tokenByIndex(uint256 _index) public view returns (uint256) { require(_index < totalSupply()); return _index; } function tokenOfOwnerByIndex(address _owner, uint256 _index) public view returns (uint256) { require(_index < balanceOf(_owner)); return info.users[_owner].list[_index]; } function getKey(bytes32 _hash) public view returns (string memory) { return info.dictionary[_hash]; } function getRecord(string memory _token, string memory _key) public view returns (string memory) { return getRecord(idOf(_token), _key); } function getRecord(uint256 _tokenId, string memory _key) public view returns (string memory) { bytes32 _hash = keccak256(abi.encodePacked(_key)); return getRecord(_tokenId, _hash); } function getRecord(uint256 _tokenId, bytes32 _hash) public view returns (string memory) { require(_tokenId < totalSupply()); return info.list[_tokenId].values[_hash]; } function getFullRecord(uint256 _tokenId, bytes32 _hash) public view returns (string memory, string memory) { return (getKey(_hash), getRecord(_tokenId, _hash)); } function getRecords(uint256 _tokenId, bytes32[] memory _hashes) public view returns (bytes32[] memory values, bool[] memory trimmed) { require(_tokenId < totalSupply()); uint256 _length = _hashes.length; values = new bytes32[](_length); trimmed = new bool[](_length); for (uint256 i = 0; i < _length; i++) { string memory _value = info.list[_tokenId].values[_hashes[i]]; values[i] = _stringToBytes32(_value); trimmed[i] = bytes(_value).length > 32; } } function getRecordsTable(uint256 _tokenId, uint256 _limit, uint256 _page, bool _isAsc) public view returns (bytes32[] memory hashes, bytes32[] memory keys, bool[] memory keysTrimmed, bytes32[] memory values, bool[] memory valuesTrimmed, uint256 totalRecords, uint256 totalPages) { require(_limit > 0); totalRecords = recordsOf(_tokenId); if (totalRecords > 0) { totalPages = (totalRecords / _limit) + (totalRecords % _limit == 0 ? 0 : 1); require(_page < totalPages); uint256 _offset = _limit * _page; if (_page == totalPages - 1 && totalRecords % _limit != 0) { _limit = totalRecords % _limit; } hashes = new bytes32[](_limit); keys = new bytes32[](_limit); keysTrimmed = new bool[](_limit); for (uint256 i = 0; i < _limit; i++) { hashes[i] = info.list[_tokenId].keys[_isAsc ? _offset + i : totalRecords - _offset - i - 1]; string memory _key = getKey(hashes[i]); keys[i] = _stringToBytes32(_key); keysTrimmed[i] = bytes(_key).length > 32; } } else { totalPages = 0; hashes = new bytes32[](0); keys = new bytes32[](0); keysTrimmed = new bool[](0); } (values, valuesTrimmed) = getRecords(_tokenId, hashes); } function getYAT(string memory _token) public view returns (uint256 tokenId, address tokenOwner, address tokenCosigner, address pointer, address approved, uint256 nonce, uint256 records) { tokenId = idOf(_token); ( , tokenOwner, tokenCosigner, pointer, approved, nonce, records) = getYAT(tokenId); } function getYAT(uint256 _tokenId) public view returns (string memory token, address tokenOwner, address tokenCosigner, address pointer, address approved, uint256 nonce, uint256 records) { return (tokenOf(_tokenId), ownerOf(_tokenId), cosignerOf(_tokenId), pointsTo(_tokenId), getApproved(_tokenId), nonceOf(_tokenId), recordsOf(_tokenId)); } function getYATs(uint256[] memory _tokenIds) public view returns (bytes32[] memory tokens, address[] memory owners, address[] memory cosigners, address[] memory pointers, address[] memory approveds) { uint256 _length = _tokenIds.length; tokens = new bytes32[](_length); owners = new address[](_length); cosigners = new address[](_length); pointers = new address[](_length); approveds = new address[](_length); for (uint256 i = 0; i < _length; i++) { string memory _token; (_token, owners[i], cosigners[i], pointers[i], approveds[i], , ) = getYAT(_tokenIds[i]); tokens[i] = _stringToBytes32(_token); } } function getYATsTable(uint256 _limit, uint256 _page, bool _isAsc) public view returns (uint256[] memory tokenIds, bytes32[] memory tokens, address[] memory owners, address[] memory cosigners, address[] memory pointers, address[] memory approveds, uint256 totalYATs, uint256 totalPages) { require(_limit > 0); totalYATs = totalSupply(); if (totalYATs > 0) { totalPages = (totalYATs / _limit) + (totalYATs % _limit == 0 ? 0 : 1); require(_page < totalPages); uint256 _offset = _limit * _page; if (_page == totalPages - 1 && totalYATs % _limit != 0) { _limit = totalYATs % _limit; } tokenIds = new uint256[](_limit); for (uint256 i = 0; i < _limit; i++) { tokenIds[i] = tokenByIndex(_isAsc ? _offset + i : totalYATs - _offset - i - 1); } } else { totalPages = 0; tokenIds = new uint256[](0); } (tokens, owners, cosigners, pointers, approveds) = getYATs(tokenIds); } function getOwnerYATsTable(address _owner, uint256 _limit, uint256 _page, bool _isAsc) public view returns (uint256[] memory tokenIds, bytes32[] memory tokens, address[] memory cosigners, address[] memory pointers, address[] memory approveds, uint256 totalYATs, uint256 totalPages) { require(_limit > 0); totalYATs = balanceOf(_owner); if (totalYATs > 0) { totalPages = (totalYATs / _limit) + (totalYATs % _limit == 0 ? 0 : 1); require(_page < totalPages); uint256 _offset = _limit * _page; if (_page == totalPages - 1 && totalYATs % _limit != 0) { _limit = totalYATs % _limit; } tokenIds = new uint256[](_limit); for (uint256 i = 0; i < _limit; i++) { tokenIds[i] = tokenOfOwnerByIndex(_owner, _isAsc ? _offset + i : totalYATs - _offset - i - 1); } } else { totalPages = 0; tokenIds = new uint256[](0); } (tokens, , cosigners, pointers, approveds) = getYATs(tokenIds); } function allInfoFor(address _owner) external view returns (uint256 supply, uint256 ownerBalance) { return (totalSupply(), balanceOf(_owner)); } function _mint(string memory _token, address _account) internal { uint256 _tokenId; bytes32 _hash = hashOf(_token); if (info.idOf[_hash] == 0) { _tokenId = info.totalSupply++; info.idOf[_hash] = _tokenId + 1; Token storage _newToken = info.list[_tokenId]; _newToken.owner = _account; _newToken.cosigner = USE_GLOBAL_SIGNER; _newToken.token = _token; uint256 _index = info.users[_account].balance++; info.users[_account].indexOf[_tokenId] = _index + 1; info.users[_account].list[_index] = _tokenId; emit Transfer(address(0x0), _account, _tokenId); emit Transfer(address(0x0), _account, _hash, _token); } else { _tokenId = idOf(_token); info.list[_tokenId].approved = msg.sender; _transfer(address(this), _account, _tokenId); } emit Mint(_hash, _tokenId, _account, _token); } function _transfer(address _from, address _to, uint256 _tokenId) internal { address _owner = ownerOf(_tokenId); address _approved = getApproved(_tokenId); require(_from == _owner); require(msg.sender == _owner || msg.sender == _approved || isApprovedForAll(_owner, msg.sender)); info.list[_tokenId].owner = _to; info.list[_tokenId].cosigner = USE_GLOBAL_SIGNER; info.list[_tokenId].pointsTo = _to; if (_approved != address(0x0)) { info.list[_tokenId].approved = address(0x0); emit Approval(_to, address(0x0), _tokenId); } _updateResolvesTo(address(0x0), _tokenId); _deleteAllRecords(_tokenId); uint256 _index = info.users[_from].indexOf[_tokenId] - 1; uint256 _moved = info.users[_from].list[info.users[_from].balance - 1]; info.users[_from].list[_index] = _moved; info.users[_from].indexOf[_moved] = _index + 1; info.users[_from].balance--; delete info.users[_from].indexOf[_tokenId]; uint256 _newIndex = info.users[_to].balance++; info.users[_to].indexOf[_tokenId] = _newIndex + 1; info.users[_to].list[_newIndex] = _tokenId; emit Transfer(_from, _to, _tokenId); emit Transfer(_from, _to, hashOf(tokenOf(_tokenId)), tokenOf(_tokenId)); } function _updateResolvesTo(address _resolvesTo, uint256 _tokenId) internal { if (_resolvesTo == address(0x0)) { delete info.resolve[info.list[_tokenId].resolvesTo]; info.list[_tokenId].resolvesTo = _resolvesTo; } else { require(bytes(resolve(_resolvesTo)).length == 0); require(info.list[_tokenId].resolvesTo == address(0x0)); info.resolve[_resolvesTo] = tokenOf(_tokenId); info.list[_tokenId].resolvesTo = _resolvesTo; } } function _updateRecord(uint256 _tokenId, string memory _key, string memory _value) internal { require(bytes(_key).length > 0); bytes32 _hash = keccak256(abi.encodePacked(_key)); if (bytes(getKey(_hash)).length == 0) { info.dictionary[_hash] = _key; } Token storage _token = info.list[_tokenId]; if (bytes(_value).length == 0) { _deleteRecord(_tokenId, _key, _hash); } else { if (_token.indexOf[_hash] == 0) { uint256 _index = _token.records++; _token.indexOf[_hash] = _index + 1; _token.keys[_index] = _hash; emit RecordAdded(hashOf(tokenOf(_tokenId)), msg.sender, hashOf(_key), tokenOf(_tokenId), _key); } _token.values[_hash] = _value; } emit RecordUpdated(hashOf(tokenOf(_tokenId)), msg.sender, hashOf(_key), tokenOf(_tokenId), _key, _value); } function _deleteRecord(uint256 _tokenId, string memory _key, bytes32 _hash) internal { Token storage _token = info.list[_tokenId]; require(_token.indexOf[_hash] != 0); uint256 _index = _token.indexOf[_hash] - 1; bytes32 _moved = _token.keys[_token.records - 1]; _token.keys[_index] = _moved; _token.indexOf[_moved] = _index + 1; _token.records--; delete _token.indexOf[_hash]; delete _token.values[_hash]; emit RecordDeleted(hashOf(tokenOf(_tokenId)), msg.sender, hashOf(_key), tokenOf(_tokenId), _key); } function _deleteAllRecords(uint256 _tokenId) internal { Token storage _token = info.list[_tokenId]; while (_token.records > 0) { bytes32 _hash = _token.keys[_token.records - 1]; _deleteRecord(_tokenId, getKey(_hash), _hash); } } function _getEthSignedMessageHash(bytes32 _messageHash) internal pure returns (bytes32) { return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", _messageHash)); } function _splitSignature(bytes memory _signature) internal pure returns (bytes32 r, bytes32 s, uint8 v) { require(_signature.length == 65); assembly { r := mload(add(_signature, 32)) s := mload(add(_signature, 64)) v := byte(0, mload(add(_signature, 96))) } } function _recoverSigner(bytes32 _ethSignedMessageHash, bytes memory _signature) internal pure returns (address) { (bytes32 r, bytes32 s, uint8 v) = _splitSignature(_signature); return ecrecover(_ethSignedMessageHash, v, r, s); } function _verifyMint(string calldata _token, address _account, uint256 _expiry, bytes memory _signature) internal view returns (bool) { bytes32 _hash = keccak256(abi.encodePacked("yatNFT", _token, _account, _expiry)); return _recoverSigner(_getEthSignedMessageHash(_hash), _signature) == signer(); } function _verifyRecordUpdate(uint256 _tokenId, string memory _key, string memory _value, uint256 _nonce, bytes memory _signature) internal view returns (bool) { bytes32 _hash = keccak256(abi.encodePacked(_tokenId, _key, _value, _nonce)); address _signer = _recoverSigner(_getEthSignedMessageHash(_hash), _signature); return _signer == ownerOf(_tokenId) || _signer == cosignerOf(_tokenId); } function _stringToBytes32(string memory _in) internal pure returns (bytes32 out) { if (bytes(_in).length == 0) { return 0x0; } assembly { out := mload(add(_in, 32)) } } }