What you’ll need
- A basic understanding of the Mirror product’s more basic ETL use case.
- A basic understanding of SQL, though we use the syntax and functionality of Flink v1.17.
- A destination sink to write your data to
Preface
In this example we’ll provide a YAML file that includes the transforms needed to sync token transfers to your database. Several steps are needed to accomplish this and we’ll go into detail about the following subjects:- We need to differentate ERC-721 (NFT) transfers from ERC-20 token transfers since they have the same event signature:
Transfer(address,address,uint256) - We need to extract and deduplicate ERC-1155 batch transfers.
id, address (if you are syncing multiple contract addresses), sender, recipient, token_id, and value.
Pipeline YAML
There are two main transforms in this configuration and we’ll go through each one to explain how they work. If you copy and use this configuration file, make sure to update:- Your
secretName. If you already created a secret, you can find it via the CLI commandgoldsky secret list. - The schema and table you want the data written to, by default it writes to
mirror.transfers. - The contract address or addresses you want to sync.
Advanced NFT transfers pipeline
For a complete ERC-1155 implementation including batch transfers, see the ERC-1155 Transfers guide which provides the full decoding logic.
ERC-721 (NFT) Transfers
Let’s first look at NFT transfers. This is the simpler of the two transformations. The main thing we need to do here is to make sure we’re pulling thesender, recipient, and token_id from the raw topics, and only getting NFT transfers rather than other transfers that may share the same event signature. This isn’t usually a problem when filtering for a specific contract address, but can become one when looking at all contract addresses, or contracts that may implement multiple types of tokens.
We’ll start at the top.
Contract Address
lower function here to lower-case the address to make using this data simpler downstream, we also rename the column to contract_address to make it more explicit.
Sender
0x.
Recipient
Token ID
SPLIT_INDEX(topics, ',', 3)extracts the fourth topic which contains the token ID for ERC-721 transfers.SUBSTRING(..., 3)removes the0xprefix from the hex string.CONV(..., 16, 10)converts the hexadecimal value to decimal.TRY_CAST(... AS NUMERIC)casts the result to NUMERIC - token IDs can be as large as an unsigned 256 bit integer, so make sure your database can handle that. We useTRY_CASTbecause it will prevent the pipeline from failing in case the cast fails returning aNULLvalue instead.COALESCE(..., -999)returns-999if the cast fails. This isn’t strictly necessary but is useful for finding offending values that were unable to be cast.
Token Value
1.
Some columns are surrounded by backticks, this is because they are reserved words in Flink SQL. Common columns that need backticks are: data, output, value, and a full list can be found here.
Block Metadata
ID Primary Key
id, it is a string composed of the dataset name, block hash, and log index, which is unique per event, here’s an example: log_0x60eaf5a2ab37c73cf1f3bbd32fc17f2709953192b530d75aadc521111f476d6c_18.
You may can save some space when storing the ID by using
md5(id) as id in your transform. One reason you may want to keep the existing id format is that it makes it easier to order events in the same block without also syncing block hash and log index.Address Filter
Topic Filter and Length Check
event_signature as ERC-20 transfers. What differentiates ERC-721 transfers from ERC-20 transfers are the number of topics associated with the event. ERC-721 transfers have four topics, and ERC-20 transfers have three. If you want to get into the nitty gritty you may enjoy the Solidity developer documentation for events, but for now know that in Mirror, topics is a comma separated string. Each value in the string is a hash. The first is the hash of the event_signature, in our case Transfer(address,address,uint256) for ERC-721, which is hashed to 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef as seen in our WHERE clause.
We use LIKE to only consider the first signature, with a % at the end, which acts as a wildcard.
SPLIT_INDEX splits the first argument by the second argument, and then extracts the 0-indexed argument, in this case 3 which would be the fourth element. Here’s an example topic string to consider:
NOT NULL to make sure this is an NFT transfer. An ERC-20 transfer would only have three elements when the topics are split, so SPLIT_INDEX would return NULL.
ERC-1155 Transfers
ERC-1155 combines the features of ERC-20 and ERC-721 contracts and adds a few features. Each transfer has both a token ID and a value representing the quantity being transfered for funglible tokens, the number1 for tokens intended to represent NFTs, but how these work depends on how the contract is implemented. ERC-1155 also introduces new event signatures for transfers: TransferSingle(address,address,address,uint256,uint256) and TransferBatch(address,address,address,uint256[],uint256[]) which lets the contract transfer multiple tokens at once to a single recipient.
Extracting Data from Raw Logs
For ERC-1155 SingleTransfer events, we extract the sender and recipient from topics, and the token_id and value from the data field:data field - the first 64 hex characters (after the 0x prefix) contain the token_id, and the next 64 contain the value.
Topic Filter for Single Transfers
TransferSingle(address,address,address,uint256,uint256) event signature hash.
For batch transfers which require more complex array handling, see the complete ERC-1155 Transfers guide.
Using the Data
With this table in place, you can create views that show you a number of useful pieces of information:- All mints. For ERC-721 and ERC-1155 a mint is identified by being from the sender
0x0000000000000000000000000000000000000000 - All current holders of a token, or balances for ERC-1155 holders.