> ## 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 traces

> Sync decoded traces to a database with Turbo pipelines.

This guide explains how to decode contract call traces on-the-fly inside a Turbo pipeline. We filter the `raw_traces` dataset down to a specific function using its 4-byte selector, then decode the call's inputs and outputs with a [TypeScript transform](/turbo-pipelines/transforms/typescript).

<Note>
  Turbo's SQL functions include [`_gs_log_decode`](/turbo-pipelines/reference/sql-functions#evm-log-decode) for decoding event logs, but there is no built-in SQL function for decoding trace calldata. The pattern in this guide (selector filtering in SQL plus a small TypeScript decoder) is the recommended way to decode traces in Turbo. To decode events instead, see [Decode contract events](/turbo-pipelines/guides/decoding-contract-events).
</Note>

## 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

This guide shows how to decode traces of a contract with Turbo. The example uses the [Friendtech contract](https://basescan.org/address/0xcf205808ed36593aa40a44f10c7f7c2f67d4a4d4) deployed on Base (specifically calls to its `getBuyPriceAfterFee(address,uint256)` function), but the same logic applies to any other contract and chain for which a `raw_traces` dataset is available (see [supported chains](/chains/supported-networks)).

Two columns of the raw traces schema ([EVM schemas](/turbo-pipelines/reference/schema/EVM-schemas#raw-traces)) hold the encoded call data:

* `input`: the data sent along with the message call: `0x`, followed by the 4-byte function selector (8 hex characters), followed by the ABI-encoded arguments as 32-byte words (64 hex characters each).
* `output`: the data returned by the message call: `0x` followed by the ABI-encoded return values as 32-byte words.

Knowing this layout, decoding a call with statically-sized arguments (addresses, integers, booleans) is just slicing hex strings.

## Pipeline definition

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

sources:
  my_base_raw_traces:
    type: dataset
    dataset_name: base.raw_traces
    version: 1.0.0
    start_at: latest

transforms:
  # Filter to getBuyPriceAfterFee(address,uint256) calls on the Friendtech contract.
  # The first 4 bytes of `input` are the function selector: the first 4 bytes of
  # the keccak256 hash of the function signature.
  buy_price_calls:
    type: sql
    primary_key: id
    sql: |
      SELECT
        id,
        input,
        output,
        block_number,
        transaction_hash
      FROM my_base_raw_traces
      WHERE to_address = '0xcf205808ed36593aa40a44f10c7f7c2f67d4a4d4'
        AND substr(input, 1, 10) = substr(_gs_keccak256('getBuyPriceAfterFee(address,uint256)'), 1, 10)

  # Decode the ABI-encoded input and output words with TypeScript
  buy_price_decoded:
    type: script
    primary_key: id
    language: typescript
    from: buy_price_calls
    schema:
      id: string
      shares_subject: string
      amount: string
      buy_price_after_fee: string
      block_number: uint64
      transaction_hash: string
    script: |
      // Argument words start after '0x' + the 8-character selector.
      // Each ABI-encoded argument is one 32-byte word (64 hex characters).
      function inputWord(input: string, index: number): string {
        return input.slice(10 + index * 64, 10 + (index + 1) * 64);
      }

      function invoke(data: any) {
        // Drop calls with no return data (e.g. reverted calls)
        if (!data.input || !data.output || data.output === '0x') return null;

        return {
          id: data.id,
          // address = the last 20 bytes (40 hex characters) of the padded word
          shares_subject: '0x' + inputWord(data.input, 0).slice(24),
          // uint256 values are decoded with native BigInt to avoid precision loss
          amount: BigInt('0x' + inputWord(data.input, 1)).toString(),
          // return data has no selector: the first word starts right after '0x'
          buy_price_after_fee: BigInt('0x' + data.output.slice(2, 66)).toString(),
          block_number: data.block_number,
          transaction_hash: data.transaction_hash,
        };
      }

sinks:
  friendtech_traces:
    type: postgres
    from: buy_price_decoded
    schema: decoded_traces
    table: friendtech
    secret_name: EXAMPLE_SECRET
    primary_key: id
```

There are two transforms in this pipeline definition which are responsible for decoding the contract calls; 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_traces.friendtech`.

### Filtering by function selector

Let's start analyzing the first transform:

```sql Transform: buy_price_calls theme={"dark"}
SELECT
  id,
  input,
  output,
  block_number,
  transaction_hash
FROM my_base_raw_traces
WHERE to_address = '0xcf205808ed36593aa40a44f10c7f7c2f67d4a4d4'
  AND substr(input, 1, 10) = substr(_gs_keccak256('getBuyPriceAfterFee(address,uint256)'), 1, 10)
```

The `WHERE` clause does two things:

1. `to_address = ...` keeps only traces where the Friendtech contract is the call target.
2. The `substr` comparison keeps only calls to the function we care about. [`_gs_keccak256`](/turbo-pipelines/reference/sql-functions#gs-keccak256) computes the keccak256 hash of the canonical function signature, and the first 10 characters (`0x` plus 8 hex characters) are the 4-byte selector that prefixes every call's `input`.

If you already know the selector, you can paste the literal hex value instead of computing it, but deriving it from the signature keeps the query self-documenting.

### Decoding transform

The second transform is a [TypeScript transform](/turbo-pipelines/transforms/typescript) that slices the ABI-encoded hex into named, typed columns:

* **Inputs**: after skipping `0x` and the 8-character selector, each argument occupies one 32-byte word. The `address` argument (`sharesSubject`) is the last 40 hex characters of its word; the `uint256` argument (`amount`) is the whole word, converted to a decimal string with JavaScript's native `BigInt` so large values don't lose precision.
* **Outputs**: return data is encoded the same way but has no selector, so the function's single `uint256` return value is the first 64 hex characters after `0x`.
* **Filtering**: returning `null` from `invoke` drops the record, which we use to discard calls with no return data (for example, reverted calls).

The `schema` field declares the transform's output columns, which is what the Postgres sink will create as the table structure.

<Note>
  This slicing approach works for statically-sized argument types: `address`, `uintN` / `intN`, `bool`, and `bytesN`. Dynamically-sized types (`string`, `bytes`, arrays) are encoded as offset pointers into the calldata and need extra logic to follow. For complex signatures, consider decoding in your own service via an [HTTP handler transform](/turbo-pipelines/transforms/http-handler) instead.
</Note>

## 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 traces-decoding-pipeline.yaml
```

You can verify the decoded output in real time with [live inspect](/turbo-pipelines/live-inspect):

```bash theme={"dark"}
goldsky turbo inspect decoding-traces -n buy_price_decoded
```

## Conclusion

In this guide we have explored an example implementation of how to decode raw traces and stream them into a PostgreSQL database using a selector filter in SQL and a TypeScript decoding transform. 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 contract events](/turbo-pipelines/guides/decoding-contract-events.md)
- [Turbo SQL Functions Reference](/turbo-pipelines/reference/sql-functions.md)
- [trace_get](/edge-rpc/evm/methods/trace_get.md)
- [trace_block](/edge-rpc/evm/methods/trace_block.md)
- [trace_call](/edge-rpc/evm/methods/trace_call.md)
