> For the complete documentation index, see [llms.txt](https://hinkal-team.gitbook.io/hinkal/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://hinkal-team.gitbook.io/hinkal/protocol/solana-program/typical-flow.md).

# Typical Flow

A private swap on Solana spans several Solana transactions because of message-size limits. The order below is what a relayer-driven flow looks like end-to-end.

#### 1. Off-chain (client)

* Build `CircomData` for the chosen dimensions tuple (e.g. `2×2×1` for `swap2`).
* Generate the Groth16 proof.
* Encrypt the new UTXO output(s).
* Build the CPI instruction list for the chosen router (Jupiter, Raydium, …) as a `Vec<SolanaInstructionData>`.
* Pick an `id: u32` to seed the staging PDAs, and a 32-byte `swapper_account_additional_seed` to derive a unique swapper PDA for this swap.

#### 2. Stage (relayer or signer, multiple transactions)

* `init_nullifier(nullifier)` for each spent nullifier whose PDA does not yet exist.
* `store_proof_part1(id, proof_a, proof_b, proof_c, public_signals_part1)`.
* `store_proof_part2(id, public_signals_part2)`.
* `store_encrypted_outputs(id, encrypted_outputs)`.
* `store_instructions(id, instructions)` (swap only).

The split is deterministic: the staging PDAs are derived from `(program, deployer, signer, id)` so the final instruction can find them by re-deriving the seeds.

#### 3. Submit (single transaction)

`swap2` (or `swap6`) is invoked with:

* `id` (matches staging PDAs).
* `swapper_account_additional_seed` (matches the proof's public signals).
* `in_nullifiers` (must equal the public signals).
* `relayer_fee`, `variable_rate`.
* `dimensions = 2×2×1` (or `2×6×1`).

`remaining_accounts` carries every program/account referenced by the staged `SolanaInstructionData` list.

#### 4. Verify (`process_proof_and_state`)

* Groth16 verification (`verify_proof`).
* `calldata_hash` recomputation across recipient, signer, encrypted outputs, fees, instructions, and remaining accounts.
* Merkle root membership check against the 100-root window.
* Nullifier validation + insertion.
* Commitment insertion (skipped for legs flagged `on_chain_creation`).
* Whitelisted-relayer check, stealth-address randomness check, mint binding check.

#### 5. Move funds (`swap_common` + `perform_swap`)

* Storage vault → swapper PDA: input liquidity transferred under vault seeds.
* `perform_swap` replays the staged CPI batch. Account index `255` is replaced with the swapper PDA and signed via `invoke_signed`.
* `swapped_amount = balance_after - balance_before` on the destination side.

#### 6. Settle

* `calculate_total_fee = relayer_fee + swapped_amount * variable_rate / 10_000`. Fee is paid to the signer.
* The remainder forms the output UTXO. `build_onchain_utxo`-style construction → `process_onchain_commitments_swap*` inserts the commitment and emits `NewCommitment` (with non-zero `on_chain_data`) via `emit_cpi!`.
* `validate_token_balance_diff` / `validate_sol_balance_diff` runs on both legs to enforce:

```
balance_after - balance_before == amount_change + utxo_amount
                                 (or just utxo_amount when on_chain_creation)
```

#### 7. Cleanup (optional)

* `close_nullifier(nullifier)` (relayer only) reclaims rent for any pre-initialized nullifier PDA whose proof never landed (so the staging cost is not wasted on aborted attempts).

***

`transact*` and `transfer*` follow the same shape minus the swapper PDA and CPI batch:

* `transact2` / `transact6` move funds between the signer's wallet and the storage vault.
* `transfer2` / `transfer6` move only the relayer fee out of the vault; the rest is a pure shielded-to-shielded rebalance.
