# `X402.Solana.Transaction`
[🔗](https://github.com/cardotrejos/x402/blob/v0.9.0/lib/x402/solana/transaction.ex#L1)

Solana v0 transaction message building, serialization, and decoding.

Implements exactly what the `exact` SVM scheme needs, with no RPC and no
new dependencies:

* Instruction constructors for the reference client's instruction set —
  Compute Budget `SetComputeUnitLimit`/`SetComputeUnitPrice`, SPL Token /
  Token-2022 `TransferChecked`, and SPL Memo
* `compile/3` — compiles instructions into a serialized **version 0**
  message (the version the reference TypeScript and Python clients
  produce), including compact-u16 (shortvec) encoding, account
  deduplication, and the account ordering used by `@solana/kit`, so the
  output is byte-identical to the reference client
* `serialize/2` — the wire transaction: compact-u16 signature count
  followed by 64-byte signature slots (missing signatures are all-zero
  placeholders, which is how a *partially signed* transaction represents
  the fee payer's pending signature) and the message bytes
* `decode/1` — parses a wire transaction (v0 or legacy) back into its
  parts for structural validation

## Account ordering

Static accounts are ordered: fee payer first, then writable signers,
read-only signers, writable non-signers, read-only non-signers. Within a
group, addresses sort with `@solana/kit`'s comparator (case-insensitive
primary pass, lowercase-first tiebreak) so compiled messages are
byte-identical to the reference client's.

## Signing

Ed25519 signatures cover the *entire* serialized message returned by
`compile/3`, including the leading `0x80` version byte.

# `account_meta`

```elixir
@type account_meta() :: %{
  address: X402.Solana.address(),
  signer?: boolean(),
  writable?: boolean()
}
```

An account referenced by an instruction.

# `compiled`

```elixir
@type compiled() :: %{bytes: binary(), signers: [X402.Solana.address()]}
```

A compiled v0 message ready to sign.

# `decoded`

```elixir
@type decoded() :: %{
  version: 0 | :legacy,
  num_required_signatures: non_neg_integer(),
  num_readonly_signed: non_neg_integer(),
  num_readonly_unsigned: non_neg_integer(),
  signatures: [binary()],
  static_accounts: [X402.Solana.pubkey()],
  recent_blockhash: X402.Solana.pubkey(),
  instructions: [
    %{program_index: byte(), account_indices: [byte()], data: binary()}
  ],
  address_table_lookups: non_neg_integer(),
  message_bytes: binary()
}
```

A decoded wire transaction.

# `instruction`

```elixir
@type instruction() :: %{
  program: X402.Solana.address(),
  accounts: [account_meta()],
  data: binary()
}
```

An instruction to compile into a message.

# `attach_signature`
*since 0.6.0* 

```elixir
@spec attach_signature(decoded(), non_neg_integer(), binary()) ::
  {:ok, binary()} | {:error, :invalid_signature | :invalid_slot}
```

Splices a 64-byte Ed25519 signature into a decoded transaction's slot.

Rebuilds the wire transaction from a `decode/1` result — compact-u16
signature count, the signature slots with `signature` at `index`, then the
message bytes — **preserving** every other existing signature. This is how
a facilitator fills the fee payer's empty slot 0 at settlement without
disturbing the payer's signature.

Unlike `serialize/2` (which zero-fills missing signatures by design, the
partially-signed representation), a malformed signature here returns
`{:error, :invalid_signature}`: silently broadcasting a zeroed fee-payer
slot would only fail later on chain. An out-of-range `index` returns
`{:error, :invalid_slot}`.

## Examples

    iex> decoded = %{
    ...>   num_required_signatures: 2,
    ...>   signatures: [<<0::512>>, <<1::512>>],
    ...>   message_bytes: <<0x80, 2, 1, 4>>
    ...> }
    iex> {:ok, wire} = X402.Solana.Transaction.attach_signature(decoded, 0, <<9::512>>)
    iex> wire == <<2>> <> <<9::512>> <> <<1::512>> <> <<0x80, 2, 1, 4>>
    true

    iex> X402.Solana.Transaction.attach_signature(
    ...>   %{num_required_signatures: 1, signatures: [<<0::512>>], message_bytes: <<0x80>>},
    ...>   0,
    ...>   <<1, 2, 3>>
    ...> )
    {:error, :invalid_signature}

# `compile`
*since 0.6.0* 

```elixir
@spec compile(X402.Solana.address(), [instruction()], X402.Solana.address()) ::
  {:ok, compiled()} | {:error, :invalid_address}
```

Compiles instructions into a serialized v0 message.

Returns the message bytes (starting with the `0x80` version prefix —
these are the bytes Ed25519 signatures cover) and the required signer
addresses in signature-slot order (the fee payer is always first).

Returns `{:error, :invalid_address}` when any address fails Base58
decoding.

# `decode`
*since 0.6.0* 

```elixir
@spec decode(binary()) :: {:ok, decoded()} | {:error, :invalid_transaction}
```

Decodes a wire transaction (v0 or legacy) into its parts.

Used by the server-side structural checks in `X402.Scheme.ExactSVM`.
Rejects trailing bytes and truncated sections with
`{:error, :invalid_transaction}`.

# `decode_compact_u16`
*since 0.6.0* 

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

Decodes a compact-u16 prefix, returning the value and the rest.

## Examples

    iex> X402.Solana.Transaction.decode_compact_u16(<<0x80, 0x01, "rest">>)
    {:ok, 128, "rest"}

    iex> X402.Solana.Transaction.decode_compact_u16(<<0xFF, 0x7F>>)
    {:ok, 16_383, ""}

    iex> X402.Solana.Transaction.decode_compact_u16(<<0x80>>)
    :error

# `encode_compact_u16`
*since 0.6.0* 

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

Encodes a non-negative integer as compact-u16 (shortvec).

Little-endian 7-bit groups with a continuation bit, as used for all
counts in Solana's wire format.

## Examples

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

    iex> X402.Solana.Transaction.encode_compact_u16(127)
    <<0x7F>>

    iex> X402.Solana.Transaction.encode_compact_u16(128)
    <<0x80, 0x01>>

    iex> X402.Solana.Transaction.encode_compact_u16(16_383)
    <<0xFF, 0x7F>>

    iex> X402.Solana.Transaction.encode_compact_u16(16_384)
    <<0x80, 0x80, 0x01>>

# `max_transaction_size`
*since 0.6.0* 

```elixir
@spec max_transaction_size() :: pos_integer()
```

The Solana network's maximum serialized transaction size in bytes.

## Examples

    iex> X402.Solana.Transaction.max_transaction_size()
    1232

# `memo`
*since 0.6.0* 

```elixir
@spec memo(binary()) :: instruction()
```

An SPL Memo instruction carrying UTF-8 `data` (no accounts).

## Examples

    iex> ix = X402.Solana.Transaction.memo("pi_3abc123def456")
    iex> {ix.program, ix.data}
    {"MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr", "pi_3abc123def456"}

# `serialize`
*since 0.6.0* 

```elixir
@spec serialize(compiled(), %{required(X402.Solana.address()) =&gt; binary()}) ::
  binary()
```

Serializes a wire transaction from message bytes and signatures.

`signatures` maps signer addresses to 64-byte Ed25519 signatures;
signers without an entry get a 64-byte zero placeholder — how a
partially signed transaction leaves the fee payer's slot empty for the
facilitator to fill at settlement.

# `set_compute_unit_limit`
*since 0.6.0* 

```elixir
@spec set_compute_unit_limit(non_neg_integer()) :: instruction()
```

The Compute Budget `SetComputeUnitLimit` instruction (discriminator 2,
`u32` little-endian units).

## Examples

    iex> ix = X402.Solana.Transaction.set_compute_unit_limit(20_000)
    iex> {ix.program, ix.accounts, ix.data}
    {"ComputeBudget111111111111111111111111111111", [], <<2, 32, 78, 0, 0>>}

# `set_compute_unit_price`
*since 0.6.0* 

```elixir
@spec set_compute_unit_price(non_neg_integer()) :: instruction()
```

The Compute Budget `SetComputeUnitPrice` instruction (discriminator 3,
`u64` little-endian microlamports).

## Examples

    iex> ix = X402.Solana.Transaction.set_compute_unit_price(1)
    iex> ix.data
    <<3, 1, 0, 0, 0, 0, 0, 0, 0>>

# `transfer_checked`
*since 0.6.0* 

```elixir
@spec transfer_checked(%{
  source: X402.Solana.address(),
  mint: X402.Solana.address(),
  destination: X402.Solana.address(),
  authority: X402.Solana.address(),
  amount: non_neg_integer(),
  decimals: byte(),
  token_program: X402.Solana.address()
}) :: instruction()
```

The SPL Token / Token-2022 `TransferChecked` instruction.

Discriminator 12, `u64` little-endian amount, `u8` decimals; accounts
`[source (writable), mint, destination (writable), authority (signer)]` —
the layout the facilitator's static verification path parses.

---

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