Skip to content

Instantly share code, notes, and snippets.

@andrewxhill
Created May 4, 2023 18:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save andrewxhill/30843d59680066f04f2576a4205a33bd to your computer and use it in GitHub Desktop.
Save andrewxhill/30843d59680066f04f2576a4205a33bd to your computer and use it in GitHub Desktop.
Generate queryable ownership records and transfer logs in any ERC721. This example is extending OpenZepplin functions. You can see the contract here https://mumbai.polygonscan.com/address/0xB837771546756D58d2EB79CDb0281Bc5F84bC704 and the owner logs here https://testnets.opensea.io/assets/mumbai/0x4b48841d4b32c4650e4abc117a03fe8b51f38f68/5969 an…
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.12;
import "@openzeppelin/contracts@4.8.3/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts@4.8.3/token/ERC721/extensions/ERC721Burnable.sol";
import "@openzeppelin/contracts@4.8.3/token/ERC721/utils/ERC721Holder.sol";
import "@openzeppelin/contracts@4.8.3/access/Ownable.sol";
import "@openzeppelin/contracts@4.8.3/utils/Counters.sol";
import "@tableland/evm/contracts/ITablelandTables.sol";
import "@tableland/evm/contracts/utils/TablelandDeployments.sol";
contract TXHistory is ERC721, ERC721Burnable, Ownable, ERC721Holder {
using Counters for Counters.Counter;
Counters.Counter private _tokenIdCounter;
uint256 private _current_owner_logs_id;
string private _current_owner_logs;
uint256 private _transfer_logs_id;
string private _transfer_logs;
ITablelandTables private _tableland;
constructor() ERC721("TXHistory", "TXH") {
_tableland = TablelandDeployments.get();
_initTransferLogs();
}
// Function must be called once and only once in the smart contract
function _initTransferLogs() private {
// Create the table for our owners
_current_owner_logs_id = _tableland.createTable(
address(this),
string.concat(
"CREATE TABLE current_owner_",
Strings.toString(block.chainid),
" (",
" tokenId INTEGER NOT NULL PRIMARY KEY,",
" addr TEXT",
");"
)
);
// Store the log table name
_current_owner_logs = string.concat(
"current_owner_",
Strings.toString(block.chainid),
"_",
Strings.toString(_current_owner_logs_id)
);
// Create the table for our logs
_transfer_logs_id = _tableland.createTable(
address(this),
string.concat(
"CREATE TABLE transfer_log_",
Strings.toString(block.chainid),
" (",
" tokenId INTEGER,",
" timestamp INTEGER,",
" block INTEGER,",
" fromAddr TEXT,",
" toAddr TEXT",
");"
)
);
// Store the log table name
_transfer_logs = string.concat(
"transfer_log_",
Strings.toString(block.chainid),
"_",
Strings.toString(_transfer_logs_id)
);
}
// Called to update the owner log and the transfer history logs any time a token changes hands.
function _storeTransferLog (address from, address to, uint256 tokenId) private {
_tableland.runSQL(
address(this),
_current_owner_logs_id,
string.concat(
"INSERT INTO ",
_current_owner_logs,
"(tokenId, addr) VALUES (",
Strings.toString(tokenId),
",'",
Strings.toHexString(to),
"') ON CONFLICT (tokenId) DO UPDATE SET addr=excluded.addr;"
)
);
// transfer_log: timestamp, block, tokenId, from, to
// // INSERT INTO transfer_log (timestamp, block, tokenId, from, to) VALUES(timestamp, block, 1, '0x123', '0x123');
_tableland.runSQL(
address(this),
_transfer_logs_id,
string.concat(
"INSERT INTO ",
_transfer_logs,
"(tokenId, timestamp, block, fromAddr, toAddr) VALUES (",
Strings.toString(tokenId),
",",
Strings.toString(block.timestamp),
",",
Strings.toString(block.number),
",'",
Strings.toHexString(from),
"','",
Strings.toHexString(to),
"');"
)
);
}
function safeMint(address to) public {
uint256 tokenId = _tokenIdCounter.current();
_tokenIdCounter.increment();
_safeMint(to, tokenId);
_storeTransferLog(address(this), to, tokenId);
}
/**
* @dev See {IERC721-transferFrom}.
*/
function transferFrom(address from, address to, uint256 tokenId) public virtual override {
//solhint-disable-next-line max-line-length
require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");
_transfer(from, to, tokenId);
_storeTransferLog(from, to, tokenId);
}
/**
* @dev See {IERC721-safeTransferFrom}.
*/
function safeTransferFrom(address from, address to, uint256 tokenId) public virtual override {
safeTransferFrom(from, to, tokenId, "");
}
/**
* @dev See {IERC721-safeTransferFrom}.
*/
function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual override {
require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");
_safeTransfer(from, to, tokenId, data);
_storeTransferLog(from, to, tokenId);
}
function tokenURI(
uint256 tokenId
) public view override returns (string memory) {
_requireMinted(tokenId);
return 'data:application/json,{"name":"Zaraelia","external_url":"https://tableland.xyz","image":"https://nftstorage.link/ipfs/bafybeicwk7yg5rg4t72ifolr5eihyvjr6zqwlvgcvooummnacdg4zutscq"}';
}
function getLogTables() public view returns (string memory) {
return string.concat(
'data:application/json,{"owners":"',
_current_owner_logs,
'","transfers":"',
_transfer_logs,
'"}'
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment