> ## Documentation Index
> Fetch the complete documentation index at: https://docs.goldsky.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Decode contract events

> Sync decoded contract events to a database with the contract ABI using Turbo pipelines.

This guide explains how to decode raw contract events on-the-fly using the [Turbo SQL decoding functions](/turbo-pipelines/reference/sql-functions#evm--ethereum-functions) within SQL transforms in Turbo pipelines.

## What you'll need

1. A Goldsky account and the CLI installed

<Accordion title="Install Goldsky's CLI and log in">
  1) Install the Goldsky CLI:

     **For macOS/Linux:**

     ```shell theme={"dark"}
     curl https://goldsky.com | sh
     ```

     **For Windows:**

     ```shell theme={"dark"}
     npm install -g @goldskycom/cli
     ```

     <Note>Windows users need to have Node.js and npm installed first. Download from [nodejs.org](https://nodejs.org) if not already installed.</Note>
  2) Log into your Project by running:
     ```shell theme={"dark"}
     goldsky login
     ```
     This opens your browser to sign in (Google, GitHub, SSO, or email). Once you authenticate, the CLI is logged in automatically — there's no API key to copy or paste.
     <Note>On a headless or remote machine (or in CI), create an API key on your [Project Settings](https://app.goldsky.com/dashboard/settings) page and pass it directly with `goldsky login --token <API_KEY>`. Use `goldsky login --no-browser` to print the login URL instead of opening a browser.</Note>
  3) Now that you are logged in, run `goldsky` to get started:
     ```shell theme={"dark"}
     goldsky
     ```
</Accordion>

2. A basic understanding of [Turbo pipelines](/turbo-pipelines/introduction)
3. A destination sink to write your data to. In this example, we will use the [PostgreSQL sink](/turbo-pipelines/sinks/postgres)

## Preface

To get decoded contract data on EVM chains in a Turbo pipeline, you use the `raw_logs` dataset and decode inside a [SQL transform](/turbo-pipelines/transforms/sql). For common token events you can skip decoding entirely and use the curated `erc20_transfers`, `erc721_transfers`, and `erc1155_transfers` datasets; see [EVM sources](/turbo-pipelines/sources/evm).

In this guide we will use as example the [Friendtech contract](https://basescan.org/address/0xcf205808ed36593aa40a44f10c7f7c2f67d4a4d4) deployed on Base, but the same logic applies to any other contract and chain for which a raw logs dataset is available (see [supported chains](/chains/supported-networks)).

## Pipeline definition

<Tip>
  In the `_gs_fetch_abi` function call below, we pull from a gist. You can also pull from Basescan directly with an API key: \
  \
  `_gs_fetch_abi('<basescan-link>', 'etherscan')`
</Tip>

```yaml event-decoding-pipeline.yaml expandable theme={"dark"}
name: decoding-contract-events
resource_size: s

sources:
  my_base_raw_logs:
    type: dataset
    dataset_name: base.raw_logs
    version: 1.0.0
    start_at: latest

transforms:
  # Fetch the ABI from a gist (raw), then use it to decode logs from the Friendtech address
  friendtech_decoded:
    type: sql
    primary_key: id
    sql: |
      SELECT
        id,
        _gs_log_decode(
          _gs_fetch_abi('https://gist.githubusercontent.com/jeffling/0320808b7f3cc0e8d9cc6c3b113e8156/raw/99bde70acecd4dc339b5a81aae39954973f5d178/gistfile1.txt', 'raw'),
          topics,
          data
        ) as decoded,
        block_number,
        transaction_hash
      FROM my_base_raw_logs
      WHERE address = '0xcf205808ed36593aa40a44f10c7f7c2f67d4a4d4'

  # Clean up the previous transform, unnest the values from the `decoded` struct
  friendtech_clean:
    type: sql
    primary_key: id
    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:
    type: postgres
    from: friendtech_clean
    schema: decoded_events
    table: friendtech
    secret_name: EXAMPLE_SECRET
    primary_key: id
```

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`. If you already [created a secret](/platform/secrets), 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`.

<Tip>
  This pipeline starts at the chain tip (`start_at: latest`). If you want to decode the contract's full history, use `start_at: earliest` and add a source-level `filter` on the contract address so [fast scan](/turbo-pipelines/sources/evm#fast-scan) can skip irrelevant blocks during the backfill.
</Tip>

### Decoding transforms

Let's start analyzing the first transform:

```sql Transform: friendtech_decoded theme={"dark"}
SELECT
  id,
  _gs_log_decode(
    _gs_fetch_abi('https://api.basescan.org/api?module=contract&action=getabi&address=0xcf205808ed36593aa40a44f10c7f7c2f67d4a4d4&apikey=YOUR_KEY', 'etherscan'),
    topics,
    data
  ) as decoded,
  block_number,
  transaction_hash
FROM my_base_raw_logs
WHERE address = '0xcf205808ed36593aa40a44f10c7f7c2f67d4a4d4'
```

Looking at the raw logs schema ([EVM schemas](/turbo-pipelines/reference/schema/EVM-schemas#logs)) we see there are standard log columns such as `id`, `block_number` and `transaction_hash`. Since the columns `topics` and `data` are encoded, we need to make use of [`_gs_log_decode`](/turbo-pipelines/reference/sql-functions#evm-log-decode) to decode the data. This function takes the following parameters:

1. The contract ABI: rather than pasting the ABI directly into the SQL query, which would make the code considerably less legible, we use the [`_gs_fetch_abi`](/turbo-pipelines/reference/sql-functions#gs-fetch-abi) function to fetch the ABI. You can fetch it from the Basescan API (`'etherscan'` type) or from an external public location like a GitHub gist (`'raw'` type).
2. `topics`: as a second argument we pass the name of the column in our dataset that contains the topics as a comma-separated string.
3. `data`: as a third argument we pass the name of the column in our dataset that contains the encoded event payload.

We store the decoding result in a new column called `decoded`, which is a struct with two fields:

* `event_signature` (string): the event name, for example `Trade`
* `event_params` (array of strings): the decoded parameter values in positional order

We create a second transform that reads from the result of this first SELECT query to access the decoded data:

```sql Transform: friendtech_clean theme={"dark"}
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 potential issues in the decoding phase.

<Note>
  Decoded parameters are returned by position, not by name. To access the third parameter of an event, use `decoded.event_params[3]`; the array is 1-indexed.
</Note>

### Decode once, filter per event

The ABI we fetch contains every event the contract emits, so `friendtech_decoded` decodes all of them in a single pass. A useful pattern is to keep that one decode transform and add a downstream transform per event type, each filtering on `decoded.event_signature` and extracting the parameters it cares about:

```yaml theme={"dark"}
transforms:
  # Extract only Trade events with named columns
  friendtech_trades:
    type: sql
    primary_key: id
    sql: |
      SELECT
        id,
        decoded.event_params[1] as trader,
        decoded.event_params[2] as subject,
        decoded.event_params[3] as is_buy,
        block_number,
        transaction_hash
      FROM friendtech_decoded
      WHERE decoded IS NOT NULL
        AND decoded.event_signature = 'Trade'
```

Each of these transforms can feed its own sink (or table), so one pipeline can fan a single decoded stream out into per-event tables. This is more efficient than running a separate decode for every event type.

## Deploying the pipeline

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

```bash theme={"dark"}
goldsky turbo apply event-decoding-pipeline.yaml
```

You can watch decoded events flow through each transform in real time with [live inspect](/turbo-pipelines/live-inspect):

```bash theme={"dark"}
goldsky turbo inspect decoding-contract-events -n friendtech_clean
```

## Conclusion

In this guide we have explored an example implementation of how we can use the [Turbo SQL decoding functions](/turbo-pipelines/reference/sql-functions#evm--ethereum-functions) to decode raw contract events and stream them into a PostgreSQL database. This same methodology can be applied to any contract of interest on any chain with `raw_logs` and `raw_traces` datasets available (see [supported chains](/chains/supported-networks)).

Can't find what you're looking for? Reach out to us at [support@goldsky.com](mailto:support@goldsky.com) for help.


## Related topics

- [Decode traces](/turbo-pipelines/guides/decoding-traces.md)
- [Export contract events to Postgres](/turbo-pipelines/guides/export-events-to-database.md)
- [Pipeline cookbook](/turbo-pipelines/reference/pipeline-cookbook.md)
- [Stream DEX trades](/turbo-pipelines/guides/stream-dex-trades.md)
- [EVM sources for Turbo pipelines](/turbo-pipelines/sources/evm.md)
