# `Onchain.Transfer`
[🔗](https://github.com/ZenHive/onchain/blob/v0.5.0/lib/onchain/transfer.ex#L1)

Transfer event parser for ERC-20, ERC-721, and ERC-1155 token standards.

Parses raw Ethereum logs into normalized `%Transfer{}` structs — the "follow
the money" primitive for wallet analytics. Every token movement on Ethereum
emits a Transfer event; this module decodes all three standards.

## Does

- Parse raw log maps into `%Transfer{}` structs (`parse_log/1`, `parse_logs/1`)
- Fetch and parse transfer logs in one call (`fetch/2`)
- Expose topic hashes for filter building (`transfer_topics/0`)

## Does Not

- Index or store transfers (see rexex for durable indexing)
- Track balances over time (compose with `Onchain.ERC20` for snapshots)
- Handle non-standard transfer events (custom names, non-indexed params)

## Token Standard Detection

ERC-20 and ERC-721 share the same `Transfer(address,address,uint256)` topic hash.
Distinguished by topic count:

- **3 topics** `[topic0, from, to]` + value in `data` → ERC-20
- **4 topics** `[topic0, from, to, tokenId]` + empty/no data → ERC-721

ERC-1155 uses separate `TransferSingle` and `TransferBatch` signatures.

## Error Format

- Non-transfer log: `{:error, {:unknown_event, :not_a_transfer}}`
- Decode errors: propagated from `Onchain.Log.decode_event/2`

## Functions

| Function | Purpose |
|----------|---------|
| `parse_log/1` | Single raw log → Transfer struct(s) |
| `parse_log!/1` | Same, raises on error |
| `parse_logs/1` | List of raw logs → Transfer structs (skips non-Transfer) |
| `parse_logs!/1` | Same, raises on error |
| `fetch/2` | `eth_get_logs` + `parse_logs` convenience |
| `fetch!/2` | Same, raises on error |
| `transfer_topics/0` | The 3 topic0 hashes for filter building |

## API Functions
| Function | Arity | Description | Param Kinds |
| --- | --- | --- | --- |
| `fetch!` | 2 | Fetch transfer logs from chain and parse into structs. Raises on error. | `filter: value`, `opts: value` |
| `fetch` | 2 | Fetch transfer logs from chain and parse into structs. | `filter: value`, `opts: value` |
| `parse_logs!` | 1 | Parse a list of raw logs, skipping non-Transfer events. Raises on error. | `logs: value` |
| `parse_logs` | 1 | Parse a list of raw logs, skipping non-Transfer events. | `logs: value` |
| `parse_log!` | 1 | Parse a single raw log map into Transfer struct(s). Raises on error. | `log: value` |
| `parse_log` | 1 | Parse a single raw log map into Transfer struct(s). | `log: value` |
| `transfer_topics` | 0 | Returns the 3 topic0 hashes for ERC-20/721/1155 Transfer events. | - |

# `t`

```elixir
@type t() :: %Onchain.Transfer{
  amount: non_neg_integer() | nil,
  block_number: non_neg_integer(),
  from: String.t(),
  log_index: non_neg_integer(),
  operator: String.t() | nil,
  to: String.t(),
  token: String.t(),
  token_id: non_neg_integer() | nil,
  token_standard: :erc20 | :erc721 | :erc1155,
  transaction_hash: String.t()
}
```

# `fetch`

```elixir
@spec fetch(
  map(),
  keyword()
) :: {:ok, [t()]} | {:error, term()}
```

Fetch transfer logs from chain and parse into structs.

## Parameters

  * `filter` - Filter map for eth_get_logs (e.g. %\{from_block: 18_000_000, to_block: 18_000_100, address: "0x..."\}) (value)
  * `opts` - Options: :rpc_url, :timeout (default: `[]`, value)

## Returns

Parsed transfer structs from matching logs (`{:ok, [t()]} | {:error, term}`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout",
      kind: :value
    },
    filter: %{
      description: "Filter map for eth_get_logs (e.g. %{from_block: 18_000_000, to_block: 18_000_100, address: \"0x...\"})",
      kind: :value
    }
  },
  returns: %{
    type: "{:ok, [t()]} | {:error, term}",
    description: "Parsed transfer structs from matching logs"
  }
}
```

# `fetch!`

```elixir
@spec fetch!(
  map(),
  keyword()
) :: [t()]
```

Fetch transfer logs from chain and parse into structs. Raises on error.

## Parameters

  * `filter` - Filter map (see fetch/2) (value)
  * `opts` - Options: :rpc_url, :timeout (default: `[]`, value)

## Returns

Parsed transfer structs (`[t()]`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout",
      kind: :value
    },
    filter: %{description: "Filter map (see fetch/2)", kind: :value}
  },
  returns: %{type: "[t()]", description: "Parsed transfer structs"}
}
```

# `parse_log`

```elixir
@spec parse_log(map()) :: {:ok, t()} | {:ok, [t()]} | {:error, term()}
```

Parse a single raw log map into Transfer struct(s).

## Parameters

  * `log` - Raw log map with :topics, :data, :address, :block_number, :transaction_hash, :log_index (value)

## Returns

Single struct for ERC-20/721/1155-Single, list for 1155-Batch (`{:ok, t()} | {:ok, [t()]} | {:error, term}`)

```elixir
# descripex:contract
%{
  params: %{
    log: %{
      description: "Raw log map with :topics, :data, :address, :block_number, :transaction_hash, :log_index",
      kind: :value
    }
  },
  returns: %{
    type: "{:ok, t()} | {:ok, [t()]} | {:error, term}",
    description: "Single struct for ERC-20/721/1155-Single, list for 1155-Batch"
  }
}
```

# `parse_log!`

```elixir
@spec parse_log!(map()) :: t() | [t()]
```

Parse a single raw log map into Transfer struct(s). Raises on error.

## Parameters

  * `log` - Raw log map (see parse_log/1) (value)

## Returns

Transfer struct(s) (`t() | [t()]`)

```elixir
# descripex:contract
%{
  params: %{log: %{description: "Raw log map (see parse_log/1)", kind: :value}},
  returns: %{type: "t() | [t()]", description: "Transfer struct(s)"}
}
```

# `parse_logs`

```elixir
@spec parse_logs([map()]) :: {:ok, [t()]}
```

Parse a list of raw logs, skipping non-Transfer events.

## Parameters

  * `logs` - List of raw log maps from eth_get_logs (value)

## Returns

Flat list of Transfer structs (batches expanded, non-transfers skipped) (`{:ok, [t()]}`)

```elixir
# descripex:contract
%{
  params: %{
    logs: %{description: "List of raw log maps from eth_get_logs", kind: :value}
  },
  returns: %{
    type: "{:ok, [t()]}",
    description: "Flat list of Transfer structs (batches expanded, non-transfers skipped)"
  }
}
```

# `parse_logs!`

```elixir
@spec parse_logs!([map()]) :: [t()]
```

Parse a list of raw logs, skipping non-Transfer events. Raises on error.

## Parameters

  * `logs` - List of raw log maps (value)

## Returns

Flat list of Transfer structs (`[t()]`)

```elixir
# descripex:contract
%{
  params: %{logs: %{description: "List of raw log maps", kind: :value}},
  returns: %{type: "[t()]", description: "Flat list of Transfer structs"}
}
```

# `transfer_topics`

```elixir
@spec transfer_topics() :: [String.t()]
```

Returns the 3 topic0 hashes for ERC-20/721/1155 Transfer events.

## Returns

List of 0x-prefixed keccak256 hashes: [Transfer, TransferSingle, TransferBatch] (`[String.t()]`)

```elixir
# descripex:contract
%{
  returns: %{
    type: "[String.t()]",
    description: "List of 0x-prefixed keccak256 hashes: [Transfer, TransferSingle, TransferBatch]"
  }
}
```

---

*Consult [api-reference.md](api-reference.md) for complete listing*
