This guide explains how to decode raw contract events on-the-fly using Mirror Decoding Functions within transforms in Mirror pipelines.

What you’ll need

  1. A Goldky account and the CLI installed
  1. A basic understanding of the Mirror product
  2. A destination sink to write your data to. In this example, we will use PostgresSQL Sink

Preface

To get decoded contract data on EVM chains in a Mirror pipeline, there are three options:

  1. Decode data with a subgraph, then use a subgraph entity source.
  2. Use the decoded_logs and decoded_traces direct indexing datasets. These are pre-decoded datasets, with coverage for common contracts, events, and functions.
  3. Use the raw_logs dataset and decode inside a pipeline transform.

In this guide we are going to focus on the third method. We will use as example the Friendtech contract deployed in Base but the same logic applies to any other contract and chain for which there’s an availabe Raw Log Direct Indexing dataset as per this list.

Pipeline definition

event-decoding-pipeline.yaml
name: decoding-contract-events
apiVersion: 3
sources:
  my_base_raw_logs:
    type: dataset
    dataset_name: base.raw_logs
    version: 1.0.0
transforms:
  friendtech_decoded:
    primary_key: id
    # Fetch the ABI from basescan, then use it to decode from the friendtech address.
    sql: >
      SELECT 
        `id`,
        _gs_log_decode(
            _gs_fetch_abi('https://api.basescan.org/api?module=contract&action=getabi&address=0xcf205808ed36593aa40a44f10c7f7c2f67d4a4d4', 'etherscan'), 
            `topics`, 
            `data`
        ) AS `decoded`, 
        block_number, 
        transaction_hash 
      FROM my_base_raw_logs
      WHERE address='0xcf205808ed36593aa40a44f10c7f7c2f67d4a4d4'
  friendtech_clean:
    primary_key: id
    # Clean up the previous transform, unnest the values from the `decoded` object.
    sql: >
      SELECT 
        `id`, 
        decoded.event_params AS `event_params`, 
        decoded.event_signature AS `event_signature`,
        block_number,
        transaction_hash
        FROM friendtech_decoded 
        WHERE decoded IS NOT NULL
sinks:
  friendtech_events:
    secret_name: EXAMPLE_SECRET
    type: postgres
    from: friendtech_clean
    schema: decoded_events
    table: friendtech

There are two important transforms in this pipeline definition which are responsible for decoding the contract; we’ll explain how they work in detail. If you copy and use this configuration file, make sure to update:

  1. Your secret_name (v2: secretName). If you already created a secret, you can find it via the CLI command goldsky secret list.
  2. The schema and table you want the data written to, by default it writes to decoded_events.friendtech.

Decoding transforms

Let’s start analyzing the first transform:

Transform: friendtech_decoded
SELECT 
    `id`,
    _gs_log_decode(
        _gs_fetch_abi('https://api.basescan.org/api?module=contract&action=getabi&address=0xcf205808ed36593aa40a44f10c7f7c2f67d4a4d4', 'etherscan'), 
        `topics`, 
        `data`
    ) AS `decoded`, 
    block_number, 
    transaction_hash 
    FROM base.raw_logs
    WHERE address='0xcf205808ed36593aa40a44f10c7f7c2f67d4a4d4'

Looking at the Raw Logs schema we see there are standard log columns such as id, block_number and transaction_hash. Since its columns topics and data are encoded we need to make use of the _gs_log_decode to decode the data. This function takes the following parameters:

  1. The contract ABI: rather than specifying ABI directly into the SQL query, which would made the code considerably less legible, we have decided to make use of the _gs_fetch_abi function to fetch the ABI from the BaseScan API but you could also fetch it from an external public repository like Github Gist if you preferred.
  2. topics: as a second argument to the decode function we pass in the name of the column in our dataset that contains the topics as comma-separated string.
  3. data: as a third argument to the decode function we pass in the name of the column in our dataset that contains the encoded data.
    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.

We are storing the decoding result in a new column called decoded which is a nested ROW with the properties event_param::TEXT[] and event_signature::TEXT. We create a second transform that reads from the resulting dataset of this first SELECT query to access the decoded data:

Transform: friendtech_clean
SELECT 
    `id`, 
    decoded.event_params AS `event_params`, 
    decoded.event_signature AS `event_signature`,
    block_number,
    transaction_hash
    FROM friendtech_decoded 
    WHERE decoded IS NOT NULL

Notice how we add a filter for decoded IS NOT NULL as a safety measure to discard processing potential issues in the decoding phase.

Deploying the pipeline

As a last step, to deploy this pipeline and start sinking decoded data into your database simply execute:

goldsky pipeline create <pipeline_name> --definition-path <yaml_file>

Conclusion

In this guide we have explored an example implementation of how we can use Mirror Decoding Functions to decode raw contract events and stream them into our PostgreSQL database. This same methodology can be applied to any contract of interest for any chain with raw_log and raw_traces Direct Indexing datasets available (see list).

Goldsky also provides alternative decoding methods:

Decoding contracts on the flight is a very powerful way of understanding onchain data and making it usable for your users.

Can't find what you're looking for? Reach out to us at support@goldsky.com for help.