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

Tempo transaction decoding, payment matching, and fee payer co-signing.

# `access`

```elixir
@type access() :: %{address: binary(), storage_keys: [binary()]}
```

# `authorization`

```elixir
@type authorization() :: %{
  chain_id: non_neg_integer(),
  address: binary(),
  nonce: non_neg_integer(),
  signature: signature()
}
```

# `call`

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

# `call_scope`

```elixir
@type call_scope() :: %{target: binary(), selector_rules: [selector_rule()]}
```

# `key_authorization`

```elixir
@type key_authorization() :: %{
  chain_id: non_neg_integer(),
  key_type: key_type(),
  key_id: binary(),
  expiry: pos_integer() | nil,
  limits: [token_limit()] | nil,
  allowed_calls: [call_scope()] | nil,
  witness: binary() | nil,
  is_admin: boolean(),
  account: binary() | nil,
  signature: primitive_signature() | nil
}
```

# `key_type`

```elixir
@type key_type() :: :secp256k1 | :p256 | :webauthn
```

# `p256_signature`

```elixir
@type p256_signature() :: %{
  r: binary(),
  s: binary(),
  pub_key_x: binary(),
  pub_key_y: binary(),
  pre_hash: boolean()
}
```

# `primitive_signature`

```elixir
@type primitive_signature() ::
  {:secp256k1, secp_signature()}
  | {:p256, p256_signature()}
  | {:webauthn, webauthn_signature()}
```

# `secp_signature`

```elixir
@type secp_signature() :: %{
  r: non_neg_integer(),
  s: non_neg_integer(),
  y_parity: 0 | 1
}
```

# `selector_rule`

```elixir
@type selector_rule() :: %{selector: binary(), recipients: [binary()]}
```

# `signature`

```elixir
@type signature() ::
  primitive_signature() | {:keychain, 1 | 2, binary(), primitive_signature()}
```

# `t`

```elixir
@type t() :: %Onchain.Tempo.Transaction{
  access_list: [access()],
  calls: [call()],
  chain_id: non_neg_integer(),
  fee_payer_signature: secp_signature() | :placeholder | nil,
  fee_token: binary() | nil,
  gas_limit: non_neg_integer(),
  key_authorization: key_authorization() | nil,
  max_fee_per_gas: non_neg_integer(),
  max_priority_fee_per_gas: non_neg_integer(),
  nonce: non_neg_integer(),
  nonce_key: non_neg_integer(),
  raw: String.t() | nil,
  signature: signature() | nil,
  tempo_authorization_list: [authorization()],
  valid_after: pos_integer() | nil,
  valid_before: pos_integer() | nil
}
```

Named transaction fields. Addresses and data are binaries; raw is broadcast-ready hex. Builder-assembled transactions carry nil signature and raw until signed.

# `token_limit`

```elixir
@type token_limit() :: %{
  token: binary(),
  limit: non_neg_integer(),
  period: non_neg_integer()
}
```

# `webauthn_signature`

```elixir
@type webauthn_signature() :: %{
  r: binary(),
  s: binary(),
  pub_key_x: binary(),
  pub_key_y: binary(),
  webauthn_data: binary()
}
```

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

# `hash`

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

Return the transaction hash computed by tempo-primitives.

# `key_authorization_hash`

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

Return the signing digest for a typed key authorization.

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

Keychain recovery verifies the inner signature and returns the root account.
It does not establish that the access key is authorized on-chain; callers must
check the AccountKeychain precompile for that authorization.

High-s Secp256k1 encodings are accepted: `s` and the recovery bit are flipped together
to BIP-62 low-s form before recovery, so a complement-s envelope returns the
same address. The original `raw` is not rewritten.

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

# `serialize`

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

Serialize the named fields through tempo-primitives.

# `signing_hash`

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

Return the native signing digest as 32 bytes.

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