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

<AgentInstructions>

## Submitting Feedback

If you encounter incorrect, outdated, or confusing documentation on this page, submit feedback:

POST https://docs.sqd.dev/feedback

```json
{
  "path": "/en/sdk/pipes-sdk/evm/guides/basic-development/anatomy",
  "feedback": "Description of the issue"
}
```

Only submit feedback when you have something specific and actionable to report.

</AgentInstructions>

# Pipe anatomy

> How a pipe is put together

<Frame>
  <img src="https://mintcdn.com/sqd-2119b3c3/YYfk3z0lFA57q8HW/en/sdk/pipes-sdk/evm/guides/basic-development/anatomy-basic.svg?fit=max&auto=format&n=YYfk3z0lFA57q8HW&q=85&s=c3decc7db6d3f7aee3c465e2269a1230" alt="Pipe anatomy" width="800" height="510" data-path="en/sdk/pipes-sdk/evm/guides/basic-development/anatomy-basic.svg" />
</Frame>

An EVM pipe made with SQD's Pipes SDK consists of:

* A **source** - typically made with `evmPortalSource()`. Can have one or more outputs.

* **Queries** - tell the source which data has to be retrieved to compute each output. A query is defined by a chain call terminated by `.build()`. Here's an example:

  ```ts theme={"system"}
  evmQuery()
    .addFields({
      block: { timestamp: true },
      log: { address: true, transactionHash: true },
    })
    .addLog({
      topic0: [ TRANSFER_TOPIC ]
    })
    .build()
  ```

* **Per-query transforms** (optional) - you can pass data from each query through a chain of simple transforms:
  ```ts theme={"system"}
  query
    .pipe(data => data.map(item => ({
      funkyNumber: item.header.timestamp + item.header.number,
      ...item
    })))
    .pipe(someOtherSimpleTransformCallback)
  ```
  Source object streams the data you get out of each chain of transforms as the value of the corresponding output field.

* Making utils that return reusable **query-transform combos** is a very useful pattern. In particular, on EVM it is often convenient to keep retrieval and decoding of event logs in a single module. You can easily make such combos with the `evmDecoder()` function - see the [Handling events](./handling-events) guide.

* **Whole pipe transformers** (optional) - use this if you need to compute something based on data originating from multiple queries, or if you need access to per-batch context (cursor, logger, profiler, fork callbacks). Use `createTransformer()` so the SDK can thread cursor and rollback information ([1](../architecture-deep-dives/cursor-management), [2](../architecture-deep-dives/fork-handling)) through your transform:
  ```ts theme={"system"}
  import { createTransformer } from '@subsquid/pipes'

  const enrichTransfers = createTransformer<
    { transfers: Transfer[]; approvals: Approval[] },
    { events: EnrichedEvent[] }
  >({
    transform: ({ transfers, approvals }, ctx) => {
      ctx.logger.info({ batch: ctx.stream.state.current?.number }, 'enriching')
      return {
        events: [
          ...transfers.map((t) => ({ kind: 'transfer' as const, ...t })),
          ...approvals.map((a) => ({ kind: 'approval' as const, ...a })),
        ],
      }
    },
  })

  evmPortalStream({ /* ... */ }).pipe(enrichTransfers)
  ```

* Pipe termination: a plain async iterator or a **target**.
  * If you use the pipe as an async iterator it will throw exceptions if the underlying chain is experiencing reorgs, see [Fork handling](../architecture-deep-dives/fork-handling).
  * We offer two targets out of the box:

    * Postgres via Drizzle
    * ClickHouse

    You can make your own using [`createTarget()`](../../reference/basic-components/target/create-target).
