# `Onchain.Log`
[🔗](https://github.com/ZenHive/onchain-stack/blob/onchain-v0.14.0/packages/onchain/lib/onchain/log.ex#L1)

Event log parsing for Ethereum contract events.

Computes event topic hashes from signatures and decodes raw log data
against event signatures with indexed parameter markers.

## Limitations

- Tuple-typed params (e.g. `(uint256,address)`) are not supported in event signatures.
  The comma-based parser cannot distinguish tuple-internal commas from param separators.
- Indexed dynamic types (string, bytes, arrays) return the raw keccak256 topic hash
  since the original value is not recoverable from the log.

## Error Format

- Invalid signature: `{:error, {:invalid_signature, input}}`
- Topic mismatch: `{:error, {:decode_error, {:topic_mismatch, [expected: ..., got: ...]}}}`
- Decode errors: `{:error, {:decode_error, reason}}`
- Strict decode: `{:error, {:decode_error, {:strict_violation, detail}}}` when
  `strict: true` rejected a non-canonical payload. Bang variants raise.

## Security — signature is a trust boundary

Each parameter name in a `signature` is interned with `String.to_atom/1` so the
decoded map can use atom keys. **The `signature` argument MUST be developer-controlled**
(a compile-time constant or value you author), never attacker-influenced input
(request body, config supplied by a third party). Atoms are not garbage-collected, so
routing untrusted signatures here is an atom-table-exhaustion (DoS) vector.

As a defence-in-depth bound — not a substitute for the contract above — `decode_event/3`
rejects signatures with more than `32` params and rejects any segment that is not a
bounded identifier-shaped token before interning it (`{:error, {:invalid_signature, _}}`).
This caps the per-call surface; it does not stop an attacker looping distinct valid
names across many calls. The only structural fix is string-keyed output, a breaking
change deliberately not taken here.

## Functions

| Function | Purpose |
|----------|---------|
| `event_topic/1` | Event signature → keccak256 topic hash |
| `event_topic!/1` | Same, raises on error |
| `decode_event/3` | Raw log + signature → decoded param map (optional decode opts) |
| `decode_event!/3` | Same, raises on error |

## API Functions
| Function | Arity | Description | Param Kinds |
| --- | --- | --- | --- |
| `decode_event!` | 3 | Decode a raw log map. Raises on error. | `log: value`, `signature: value`, `opts: value` |
| `decode_event` | 3 | Decode a raw log map against an event signature with indexed markers. | `log: value`, `signature: value`, `opts: value` |
| `event_topic!` | 1 | Compute the keccak256 topic hash. Raises on error. | `signature: value` |
| `event_topic` | 1 | Compute the keccak256 topic hash for an event signature. | `signature: value` |

# `decode_event`

```elixir
@spec decode_event(map(), String.t(), keyword()) :: {:ok, map()} | {:error, term()}
```

Decode a raw log map against an event signature with indexed markers.

## Parameters

  * `log` - Raw log map with :topics (list of hex strings) and :data (hex string) keys (value)
  * `signature` - Event signature with indexed markers, e.g. "Transfer(address indexed from, address indexed to, uint256 value)". MUST be developer-controlled — param names are interned as atoms; untrusted signatures are an atom-table DoS vector (see module "Security"). (value)
  * `opts` - Forwarded to Onchain.ABI.decode_response/3 and hieroglyph's indexed-value decode. Pass `strict: true` to reject non-canonical payloads (`{:error, {:decode_error, {:strict_violation, detail}}}`). (default: `[]`, value)

## Returns

Map with atom keys for each decoded parameter (`{:ok, map} | {:error, term}`)

```elixir
# descripex:contract
%{
  params: %{
    log: %{
      description: "Raw log map with :topics (list of hex strings) and :data (hex string) keys",
      kind: :value
    },
    opts: %{
      default: [],
      description: "Forwarded to Onchain.ABI.decode_response/3 and hieroglyph's indexed-value decode. Pass `strict: true` to reject non-canonical payloads (`{:error, {:decode_error, {:strict_violation, detail}}}`).",
      kind: :value
    },
    signature: %{
      description: "Event signature with indexed markers, e.g. \"Transfer(address indexed from, address indexed to, uint256 value)\". MUST be developer-controlled — param names are interned as atoms; untrusted signatures are an atom-table DoS vector (see module \"Security\").",
      kind: :value
    }
  },
  returns: %{
    type: "{:ok, map} | {:error, term}",
    description: "Map with atom keys for each decoded parameter",
    example: "%{from: \"0xAb5...\", to: \"0xCd9...\", value: 1000000}"
  }
}
```

# `decode_event!`

```elixir
@spec decode_event!(map(), String.t(), keyword()) :: map()
```

Decode a raw log map. Raises on error.

## Parameters

  * `log` - Raw log map with :topics and :data keys (value)
  * `signature` - Event signature with indexed markers. MUST be developer-controlled — param names are interned as atoms; untrusted signatures are an atom-table DoS vector (see module "Security" and decode_event/3). (value)
  * `opts` - Forwarded to decode_event/3 (e.g. `strict: true`) (default: `[]`, value)

## Returns

Map with atom keys for each decoded parameter (`map`)

```elixir
# descripex:contract
%{
  params: %{
    log: %{description: "Raw log map with :topics and :data keys", kind: :value},
    opts: %{
      default: [],
      description: "Forwarded to decode_event/3 (e.g. `strict: true`)",
      kind: :value
    },
    signature: %{
      description: "Event signature with indexed markers. MUST be developer-controlled — param names are interned as atoms; untrusted signatures are an atom-table DoS vector (see module \"Security\" and decode_event/3).",
      kind: :value
    }
  },
  returns: %{
    type: :map,
    description: "Map with atom keys for each decoded parameter"
  }
}
```

# `event_topic`

```elixir
@spec event_topic(String.t()) :: {:ok, String.t()} | {:error, term()}
```

Compute the keccak256 topic hash for an event signature.

## Parameters

  * `signature` - Event signature without param names, e.g. "Transfer(address,address,uint256)" (value)

## Returns

0x-prefixed 32-byte keccak256 hash (`{:ok, hex_string} | {:error, term}`)

```elixir
# descripex:contract
%{
  params: %{
    signature: %{
      description: "Event signature without param names, e.g. \"Transfer(address,address,uint256)\"",
      kind: :value
    }
  },
  returns: %{
    type: "{:ok, hex_string} | {:error, term}",
    description: "0x-prefixed 32-byte keccak256 hash",
    example: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
  }
}
```

# `event_topic!`

```elixir
@spec event_topic!(String.t()) :: String.t()
```

Compute the keccak256 topic hash. Raises on error.

## Parameters

  * `signature` - Event signature string (value)

## Returns

0x-prefixed 32-byte keccak256 hash (`string`)

```elixir
# descripex:contract
%{
  params: %{signature: %{description: "Event signature string", kind: :value}},
  returns: %{type: :string, description: "0x-prefixed 32-byte keccak256 hash"}
}
```

---

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