# `Onchain.Tempo.Transaction`
[🔗](https://github.com/ZenHive/onchain_tempo/blob/v0.7.0/lib/onchain/tempo/transaction.ex#L1)

Tempo Transaction (EIP-2718 type 0x76) — RLP deserialization, payment call
matching, and fee payer co-signing (0x78 domain).

A Tempo Transaction is an RLP-encoded envelope prefixed with `0x76`:

    0x76 || rlp([chain_id, max_priority_fee_per_gas, max_fee_per_gas, gas_limit,
                  calls, access_list, nonce_key, nonce, valid_before, valid_after,
                  fee_token, fee_payer_signature, aa_authorization_list,
                  key_authorization?, sender_signature])

Each `call` is `rlp([to, value, input])`.

This module extracts the fields needed for payment verification (`chain_id`,
`calls`) and preserves the full serialized hex as `raw` for broadcast
passthrough. When fee payer mode is enabled, it co-signs the transaction
with a server-side key using the 0x78 domain separator.

## Dependencies

Uses `ExRLP` (available transitively via `cartouche` → `onchain`) for RLP
decoding. Signing uses `Cartouche.Signer.Curvy` and `Cartouche.Recover`
directly because Tempo 0x76 is a non-standard transaction type.

# `call`

```elixir
@type call() :: %{to: binary(), value: non_neg_integer(), input: binary()}
```

A single call within the transaction's batch.

# `t`

```elixir
@type t() :: %Onchain.Tempo.Transaction{
  calls: [call()],
  chain_id: non_neg_integer(),
  fields: [term()],
  raw: String.t()
}
```

A parsed Tempo Transaction with verification-relevant fields.

`raw` is the full serialized transaction as a hex string ("0x76...") with
0x prefix, suitable for direct JSON-RPC broadcast.

# `cosign_fee_payer`

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

Co-sign a client's transaction as fee payer and return the updated hex.

Takes the client-signed 0x76 transaction, adds the server's fee payer
signature (domain 0x78), injects the fee token, and returns a new
0x76 hex string ready for broadcast.

## Parameters

  * `tx` — deserialized transaction with fee payer placeholder
  * `fee_payer_key` — 32-byte binary private key for fee sponsorship
  * `fee_token` — 20-byte binary TIP-20 token address for fee payment

## Returns

  * `{:ok, updated_tx}` — transaction with new `raw` hex for broadcast
  * `{:error, reason}` — on signing or recovery failure

# `deserialize`

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

Deserialize a hex-encoded Tempo Transaction (0x76 prefix).

Returns `{:ok, %Transaction{}}` with `chain_id`, parsed `calls`, and the
original hex string as `raw` (for broadcast). Returns `{:error, reason}`
on invalid input.

## Examples

    iex> Onchain.Tempo.Transaction.deserialize("0x76" <> valid_rlp_hex)
    {:ok, %Onchain.Tempo.Transaction{chain_id: 42431, calls: [...], raw: "0x76..."}}

    iex> Onchain.Tempo.Transaction.deserialize("0x02" <> rlp_hex)
    {:error, "Not a Tempo transaction: expected 0x76 type prefix"}

# `fee_token_empty?`

```elixir
@spec fee_token_empty?(t()) :: boolean()
```

Check if the transaction's `fee_token` field is empty (RLP null).

Clients leave `fee_token` empty when `feePayer: true`, allowing the
server to choose the fee payment token.

# `find_payment_call`

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

Find a matching payment call (transfer or transferWithMemo) in the transaction.

Searches `tx.calls` for one targeting `currency` with the correct selector,
then ABI-decodes and verifies recipient, amount, and optional memo.

## Options

  * `:amount` — (required) expected amount as string
  * `:recipient` — (required) expected recipient as hex address
  * `:memo` — (optional) bytes32 hex memo; when set, MUST match transferWithMemo

# `has_fee_payer_placeholder?`

```elixir
@spec has_fee_payer_placeholder?(t()) :: boolean()
```

Check if the transaction has a fee payer signature placeholder (`0x00`).

Per spec, clients set `fee_payer_signature` to `0x00` when requesting
server-side fee sponsorship.

# `sender`

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

Recover the sender's 20-byte address from a parsed transaction.

Handles both self-signed and fee-payer co-signed transactions. For a
co-signed transaction the sender signed over placeholder `fee_token` (`<<>>`)
and `fee_payer_signature` (`<<0x00>>`) — the fee payer fills those in
afterward — so they are reset before the signing payload is reconstructed.

Returns `{:ok, address_binary}` or `{:error, reason}`.

# `simulate_request`

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

Build an `eth_simulateV1` call request (a `TempoTransactionRequest`) from a
co-signed transaction.

Mirrors mpp-rs's `build_simulate_payload`: the recovered sender becomes `from`,
and the LAST sub-call is folded into `to`/`value`/`input` (so the node does not
read an empty `to` as a contract CREATE) while the remaining N-1 calls stay in
`calls`. All numeric fields are hex-quantity strings; `nonceKey` and
`validBefore` are omitted when zero.

The single-call wire format is integration-verified against Moderato; the
multi-call path mirrors the same per-call `{to, value, input}` shape.

Returns `{:ok, request_map}` or `{:error, reason}`.

# `validate_call_scope`

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

Validate that a transaction's calls match an allowed fee-payer pattern.

Fee-payer sponsored transactions are restricted to specific call sequences
to prevent clients from bundling rogue calls that the server would pay gas for.

Allowed patterns (matching mppx `callScopes`):
  * `[transfer]`
  * `[transferWithMemo]`
  * `[approve, swapExactAmountOut, transfer]`
  * `[approve, swapExactAmountOut, transferWithMemo]`

When `approve` is present, the spender must be the stablecoin DEX.
When `swapExactAmountOut` is present, the call target must be the stablecoin DEX.

Returns `:ok` or `{:error, reason}`.

---

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