# `X402.Scheme.ExactSVM`
[🔗](https://github.com/cardotrejos/x402/blob/v0.9.0/lib/x402/scheme/exact_svm.ex#L1)

Built-in `X402.Scheme` for `exact` payments on Solana (`solana:*`) networks.

Follows the x402 `exact` SVM scheme specification: the client builds a
**version 0** Solana transaction with the reference instruction layout —

1. Compute Budget `SetComputeUnitLimit`
2. Compute Budget `SetComputeUnitPrice`
3. SPL Token / Token-2022 `TransferChecked` to the Associated Token
   Account derived from `payTo` and `asset`
4. SPL Memo (the seller's `extra.memo`, or a random nonce for
   transaction uniqueness)

— signs it with the payer's Ed25519 key, and leaves the fee payer's
signature slot as a 64-byte zero placeholder (a *partially signed*
transaction). The wire payload is `%{"transaction" => base64}`, exactly
as the reference TypeScript and Python clients produce.

## Roles

* **Client** — `sign/3` builds and partially signs the transaction. The
  signer must implement the optional `c:X402.Signer.sign_ed25519/2`
  callback (`X402.Signer.SolanaKey` does). An entry is signable when
  `extra.feePayer` is present: the sponsor's public key is **required**
  (it becomes account 0 / the empty signature slot).
* **Server** — `validate_payload/3` runs structural checks that any
  valid `exact` SVM payment must satisfy (decodable Base64, the 1232-byte
  transaction size cap, a parseable v0/legacy transaction, fee payer
  match). `precheck/3` additionally enforces the facilitator's *static
  verification path* whitelist (spec §3.1): 3–7 instructions in the
  reference order, compute-budget bounds, fee payer isolation, transfer
  semantics against the requirements, and memo enforcement.

**On-chain verification and settlement remain facilitator-delegated**:
this module never talks to a Solana RPC node. The facilitator resolves
address lookup tables, simulates, signs as `feePayer`, and submits
(spec §2–3). Transactions using address lookup tables skip `precheck/3`
(the account set cannot be resolved locally) and are left to the
facilitator, and smart-wallet (CPI-wrapped) payments — the spec's opt-in
Path 2 — will fail the static-path pre-checks; gates fronting a
facilitator with `enableSmartWalletVerification` should disable
`:local_prechecks` for those routes.

## Client options

`sign/3` honors these `X402.Client.build_payment/3` options:

* `:svm_blockhash` — a Base58 recent blockhash for the transaction
  lifetime. Used when the requirements' `extra.recentBlockhash` hint is
  absent or malformed; keeps the module RPC-free.
* `:svm_blockhash_fetcher` — a 1-arity fun receiving the CAIP-2 network
  and returning `{:ok, blockhash}` (for example a wrapper around your
  RPC client's `getLatestBlockhash`). Consulted after `:svm_blockhash`.
* `:svm_decimals` / `:svm_token_program` — the mint's decimals and
  owning token program, needed by `TransferChecked`. Defaults come from
  the reference SDKs' known-asset table (USDC, USDT, USDG, PYUSD, CASH);
  for other mints pass both explicitly (production clients read them
  from the mint account via RPC).

Blockhash resolution order (per the spec): a valid
`extra.recentBlockhash` from the server wins, then `:svm_blockhash`,
then `:svm_blockhash_fetcher`; with none, `{:error, :missing_blockhash}`.

# `precheck_failure`

```elixir
@type precheck_failure() ::
  :invalid_transaction
  | :fee_payer_not_isolated
  | :instruction_count
  | :invalid_compute_limit_instruction
  | :invalid_compute_price_instruction
  | :compute_price_too_high
  | :missing_transfer_instruction
  | :amount_mismatch
  | :mint_mismatch
  | :recipient_mismatch
  | :unknown_optional_instruction
  | :memo_count
  | :memo_mismatch
```

Reasons `precheck/3` fails fast with `{:error, {:precheck_failed, reason}}`.

# `networks`
*since 0.6.0* 

```elixir
@spec networks() :: [String.t()]
```

Returns `["solana:*"]` — every Solana network.

## Examples

    iex> X402.Scheme.ExactSVM.networks()
    ["solana:*"]

# `precheck`
*since 0.6.0* 

```elixir
@spec precheck(map(), map(), keyword()) ::
  :ok | {:error, {:precheck_failed, precheck_failure()}}
```

Static-path pre-checks (spec §3.1) before the facilitator round-trip.

Enforces the facilitator's static verification whitelist as far as it is
verifiable without RPC: 3–7 top-level instructions in the reference
order (`SetComputeUnitLimit`, `SetComputeUnitPrice`, `TransferChecked`,
then only Lighthouse/Memo), the ≤ 5 lamports/CU priority-fee cap, fee
payer isolation (§2.1.1 — the fee payer referenced by no instruction),
transfer semantics against the requirements (amount equality, mint,
destination ATA), and memo enforcement when `extra.memo` is present.

Transactions using address lookup tables pass through with `:ok` — their
account set cannot be resolved without RPC, so the facilitator remains
the authority (§2.1.2). Failures return
`{:error, {:precheck_failed, reason}}` and the gate answers 402 without
a facilitator call.

# `scheme`
*since 0.6.0* 

```elixir
@spec scheme() :: String.t()
```

Returns `"exact"`.

## Examples

    iex> X402.Scheme.ExactSVM.scheme()
    "exact"

# `sign`
*since 0.6.0* 

```elixir
@spec sign(map(), X402.Signer.t(), keyword()) :: {:ok, map()} | {:error, term()}
```

Builds and partially signs the SVM `exact` transaction.

Returns `{:ok, %{"transaction" => base64}}` — the wire scheme payload —
or a structured error: `{:error, :missing_fee_payer}` when the
requirements lack `extra.feePayer`, `{:error, :missing_blockhash}` when
no blockhash source is available, `{:error, {:unknown_asset, mint}}`
for mints outside the known-asset table without explicit
`:svm_decimals`/`:svm_token_program`, and `{:error, :unsupported_signer}`
when the signer cannot sign Ed25519.

# `signable?`
*since 0.6.0* 

```elixir
@spec signable?(map()) :: boolean()
```

Whether the entry carries the required sponsor data.

`extra.feePayer` is required by the SVM `exact` scheme (the sponsor's
public key becomes the transaction's fee payer), and `asset`/`payTo`
must be valid Solana addresses.

## Examples

    iex> X402.Scheme.ExactSVM.signable?(%{
    ...>   "asset" => "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
    ...>   "payTo" => "GyGKxMyg1p9SsHfm15MkNUu1u9TN2JtTspcdmrtGUdse",
    ...>   "extra" => %{"feePayer" => "9hSR6S7WPtxmTojgo6GG3k4yDPecgJY292j7xrsUGWBu"}
    ...> })
    true

    iex> X402.Scheme.ExactSVM.signable?(%{"extra" => %{}})
    false

# `validate_payload`
*since 0.6.0* 

```elixir
@spec validate_payload(map(), map(), keyword()) ::
  :ok | {:error, {:invalid_scheme_payment, atom()}}
```

Structural validation of a decoded `PAYMENT-SIGNATURE` payload.

Checks what any valid `exact` SVM payment must satisfy without RPC:
`payload.transaction` present, Base64-decodable, within the network's
1232-byte transaction size cap, parseable as a v0 or legacy Solana
transaction, and — when the requirements advertise `extra.feePayer` —
carrying that fee payer as account 0. Failures return
`{:error, {:invalid_scheme_payment, reason}}`.

## Examples

    iex> X402.Scheme.ExactSVM.validate_payload(%{"payload" => %{}}, %{}, [])
    {:error, {:invalid_scheme_payment, :missing_transaction}}

    iex> X402.Scheme.ExactSVM.validate_payload(
    ...>   %{"payload" => %{"transaction" => "!!!"}},
    ...>   %{},
    ...>   []
    ...> )
    {:error, {:invalid_scheme_payment, :invalid_base64}}

---

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