# `Cartouche.Solana.Transaction`
[🔗](https://github.com/zenhive/cartouche/blob/main/lib/cartouche/solana/transaction.ex#L1)

Build, serialize, sign, and deserialize Solana transactions (legacy format).

A Solana transaction consists of signatures and a message. The message
contains a header, ordered account keys, a recent blockhash, and compiled
instructions. Each signer signs the raw serialized message bytes.

## Example: Build and sign a SOL transfer

    fee_payer = <<...>>  # 32-byte pubkey
    recipient = <<...>>  # 32-byte pubkey
    blockhash = <<...>>  # 32 bytes from getLatestBlockhash

    instruction = Cartouche.Solana.SystemProgram.transfer(fee_payer, recipient, 1_000_000_000)

    message = Cartouche.Solana.Transaction.build_message(fee_payer, [instruction], blockhash)
    transaction = Cartouche.Solana.Transaction.sign(message, [fee_payer_seed])

    # Serialize for RPC submission
    bytes = Cartouche.Solana.Transaction.serialize(transaction)

# `t`

```elixir
@type t() :: %Cartouche.Solana.Transaction{
  message: Cartouche.Solana.Transaction.Message.t(),
  signatures: [&lt;&lt;_::512&gt;&gt;]
}
```

# `add_signature`

```elixir
@spec add_signature(t(), non_neg_integer(), &lt;&lt;_::512&gt;&gt;) :: t()
```

Add a signature to a transaction at a specific signer position.

Used to fill in a missing signature on a partially-signed transaction,
typically by a sponsor or co-signer who receives the transaction from
another party. See `sign_partial/2` for the full sponsored transaction flow.

The `index` is the position in the signatures array (matching the account
keys order in the message). The existing signature at that position is
replaced.

## Examples

    # Sponsor receives a partially-signed transaction and adds their signature
    {:ok, partial} = Transaction.deserialize(bytes_from_user)
    msg_bytes = Transaction.serialize_message(partial.message)
    sponsor_sig = :crypto.sign(:eddsa, :none, msg_bytes, [sponsor_seed, :ed25519])
    full_trx = Transaction.add_signature(partial, 0, sponsor_sig)

# `build_message`

```elixir
@spec build_message(
  &lt;&lt;_::256&gt;&gt;,
  [Cartouche.Solana.Transaction.Instruction.t()],
  &lt;&lt;_::256&gt;&gt;
) ::
  Cartouche.Solana.Transaction.Message.t()
```

Build a compiled message from high-level instructions.

Handles account deduplication, permission merging, ordering, and index
compilation. The fee payer is always placed first as a writable signer.

# `decode_compact_u16`

```elixir
@spec decode_compact_u16(binary()) :: {non_neg_integer(), binary()}
```

Decode a compact-u16 from the beginning of a binary.

Returns `{value, rest}`. Raises `FunctionClauseError` on empty or truncated
input — internal callers that need an error tuple use `safe_decode_compact_u16/1`.

## Examples

    iex> Cartouche.Solana.Transaction.decode_compact_u16(<<0, 99>>)
    {0, <<99>>}

    iex> Cartouche.Solana.Transaction.decode_compact_u16(<<128, 1, 99>>)
    {128, <<99>>}

# `deserialize`

```elixir
@spec deserialize(binary()) :: {:ok, t()} | {:error, term()}
```

Deserialize a legacy transaction from binary.

Returns `{:ok, t()}` on a complete, well-formed transaction; `{:error, atom()}`
on malformed input. Possible error atoms:

  * `:truncated_compact_u16` — compact-u16 prefix ends mid-byte
  * `:insufficient_signature_data` — signature-count exceeds remaining bytes
  * `:insufficient_pubkey_data` — pubkey-count exceeds remaining bytes
  * `:insufficient_instruction_data` — instruction-count, account-list, or data-payload exceeds remaining bytes
  * `:invalid_message_header` — fewer than 3 header bytes
  * `:invalid_message_body` — blockhash truncated or other structural mismatch the inner clauses didn't tag
  * `:invalid_transaction` — message parsed but trailing bytes remain

# `deserialize_message`

```elixir
@spec deserialize_message(binary()) ::
  {:ok, Cartouche.Solana.Transaction.Message.t(), binary()} | {:error, term()}
```

Deserialize a message from binary.

Returns `{:ok, Message.t(), rest :: binary()}` on success — `rest` is whatever
bytes follow the message (callers like `deserialize/1` enforce `rest == <<>>`).

Returns `{:error, :invalid_message_header}` when fewer than 3 header bytes are
present. Specific atoms surface from inner parse clauses
(`:truncated_compact_u16`, `:insufficient_pubkey_data`, `:insufficient_instruction_data`);
`{:error, :invalid_message_body}` is the catch-all for structural mismatches the
inner clauses didn't tag (notably a truncated blockhash).

# `encode_compact_u16`

```elixir
@spec encode_compact_u16(non_neg_integer()) :: binary()
```

Encode a non-negative integer as a compact-u16 (variable-length).

## Examples

    iex> Cartouche.Solana.Transaction.encode_compact_u16(0)
    <<0>>

    iex> Cartouche.Solana.Transaction.encode_compact_u16(127)
    <<127>>

    iex> Cartouche.Solana.Transaction.encode_compact_u16(128)
    <<128, 1>>

    iex> Cartouche.Solana.Transaction.encode_compact_u16(16384)
    <<128, 128, 1>>

# `serialize`

```elixir
@spec serialize(t()) :: binary()
```

Serialize a full transaction (signatures + message) for RPC submission.

# `serialize_message`

```elixir
@spec serialize_message(Cartouche.Solana.Transaction.Message.t()) :: binary()
```

Serialize a message to the bytes that get signed.

# `sign`

```elixir
@spec sign(Cartouche.Solana.Transaction.Message.t(), [&lt;&lt;_::256&gt;&gt;]) :: t()
```

Sign a message with one or more seeds and produce a full transaction.

Seeds must be ordered to match the signer positions in the message's
account keys (i.e., the first `num_required_signatures` accounts).

# `sign_partial`

```elixir
@spec sign_partial(Cartouche.Solana.Transaction.Message.t(), %{
  required(non_neg_integer()) =&gt; &lt;&lt;_::256&gt;&gt;
}) :: t()
```

Partially sign a message, filling only the specified signer positions.

This is the core primitive for **sponsored transactions** (where one party
pays fees on behalf of another). The typical flow is:

1. User builds a message with the **sponsor's pubkey** as the fee payer
2. User calls `sign_partial/2` with their own seed to sign their position
3. User serializes the partially-signed transaction and sends it to the sponsor
4. Sponsor deserializes and calls `add_signature/3` to fill in their position
5. Sponsor submits the fully-signed transaction via `Cartouche.Solana.RPC.send_transaction/2`

`signers` is a map of `%{account_index => seed}` where `account_index` is
the position of the signer in the message's account keys list (0-based).
Positions not present in the map get zero-filled placeholder signatures.

## Examples

    # User is account[1], sponsor is account[0] (fee payer)
    partial = Transaction.sign_partial(message, %{1 => user_seed})
    # => %Transaction{signatures: [<<0::512>>, <user_sig>], ...}

    # Serialize and send to sponsor
    bytes = Transaction.serialize(partial)

---

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