Skip to content

Rust client

pina_amm_client is the off-chain Rust client, generated from the program’s IDL. It builds solana_instruction::Instruction values, decodes accounts and events, and exposes the program’s errors.

[dependencies]
pina_amm_client = "0.1"
solana-instruction = "3.4"
solana-pubkey = "4"

For on-chain callers use pina_amm_cpi instead; it is no_std and builds CPIs rather than transactions.

Layout

Module Contents
crate root PINA_AMM_ID
instructions One account struct and one *InstructionData per instruction
accounts AmmConfig and Pool decoders and PDA helpers
events PoolCreated, Swapped, LiquidityChanged, FeesCollected
errors PinaAmmError

Derive addresses

use pina_amm_client::PINA_AMM_ID;
use pina_amm_client::accounts::{AmmConfig, Pool};
use solana_pubkey::Pubkey;
let (config, _) = AmmConfig::find_pda(0);
let (mint_0, mint_1) = if mint_a < mint_b { (mint_a, mint_b) } else { (mint_b, mint_a) };
let (pool, _) = Pool::find_pda(&config, &mint_0, &mint_1);
let vault_0 = Pubkey::find_program_address(
&[b"pool_vault", pool.as_ref(), mint_0.as_ref()],
&PINA_AMM_ID,
)
.0;
let lp_mint = Pubkey::find_program_address(&[b"pool_lp_mint", pool.as_ref()], &PINA_AMM_ID).0;

Pubkey’s Ord compares bytes, which is exactly the program’s mint order.

Read a pool

use pina_amm_client::PINA_AMM_ID;
use pina_amm_client::accounts::Pool;
let account = rpc.get_account(&pool)?;
assert_eq!(account.owner, PINA_AMM_ID, "not a Pina AMM account");
let state = Pool::from_bytes(&account.data)?;
println!("lp supply {}", state.lp_supply.get());
println!("vaults {} {}", state.vault0, state.vault1);
println!("accrued protocol fees {} {}", state.protocol_fees0.get(), state.protocol_fees1.get());

from_bytes checks the discriminator, schema version, and length. It cannot know where the bytes came from, so check the owner first, as above. Numeric fields are little-endian wrappers; call .get() to read them.

Swap

use pina_amm_client::instructions::{SwapExactIn, SwapExactInInstructionData};
let data = SwapExactInInstructionData::new(|data| {
data.amount_in.set(1_000_000);
data.minimum_amount_out.set(minimum_out);
})?;
let instruction = SwapExactIn::new(
trader,
pool,
trader_token_0, // input: selling token 0
trader_token_1, // output
state.vault0,
state.vault1,
token_program_0,
token_program_1,
)
.instruction(data);

Every account struct is plain data, so you can also build it with a struct literal and change a field before calling .instruction(...).

Create a pool

use pina_amm_client::instructions::{CreatePool, CreatePoolInstructionData};
let data = CreatePoolInstructionData::new(|data| {
data.amount0.set(1_000_000_000);
data.amount1.set(2_000_000_000);
data.creator = creator;
data.creator_fee_mode = 0;
})?;
let instruction = CreatePool::new(
payer,
depositor,
payer, // pool-creator authority: any signer for an open tier
config,
mint_0,
mint_1,
lp_mint,
vault_0,
vault_1,
depositor_token_0,
depositor_token_1,
lp_owner,
lp_owner_token, // lp_owner's associated token account for lp_mint
token_program_0,
token_program_1,
)
.instruction(data);

CreatePool::new derives the pool PDA and fills in the SPL Token, associated token, and system program addresses.

Events

The Rust events module decodes one record at a time. Find the AMM’s Program data: lines in a transaction’s logs, base64-decode them, and match on the first byte:

First byte Event
1 PoolCreated
2 Swapped
3 LiquidityChanged
4 FeesCollected
use pina_amm_client::events::Swapped;
if bytes.first() == Some(&2) {
let swap = Swapped::from_bytes(&bytes)?;
println!("{} in, {} out", swap.amount_in.get(), swap.amount_out.get());
}

Only attribute a Program data: line to the AMM while the AMM is the innermost invoked program in the log frames; the CLI’s program_events function (crates/pina_amm_cli/src/context.rs) is a complete reference implementation.

A complete reference

The end-to-end suite in programs/pina_amm/tests/surfpool uses this client for every instruction against the real program, including fee tiers, pool creation, both swap modes, liquidity, and fee collection. It is the most complete example of the client in use.