Problem
AMM economic calculations currently live inside assertion-driven instruction handlers. Host APIs,
wallets, and user interfaces cannot call those calculations without constructing runtime accounts
and handling panics, so consumers tend to reproduce formulas, account derivation, instruction
layout, and numeric conversion independently.
That creates drift risks across:
- initial LP supply and permanently locked liquidity;
- proportional add/remove-liquidity rounding;
- exact-input and exact-output swap rounding and fees;
- stored token order and swap direction;
- post-operation reserves, LP supply, and Q64.64 spot price;
- reserve synchronization and oracle-price initialization;
- PDA derivation, account order, signer/init/writable flags;
- instruction serialization;
- slippage rounding and conversion from quotes to chain guards; and
u128/u64 values crossing JSON, C, and JavaScript-capable UI boundaries.
Proposal
Deliver two layers in one change:
- Add a public
amm_program::quote module containing deterministic, fallible economic transition
functions. Refactor AMM handlers to consume those same results.
- Add a reusable
programs/amm/client crate that validates fetched account snapshots, exposes
deterministic protocol-account discovery, typed quote orchestration, task-level prepared
transactions, all canonical low-level transaction plans, exact instruction encoding, and
lossless JSON/C ABI adapters.
The client remains stateless. It must not perform RPC, own keys, sign, submit, or manage wallet
lifecycle. Rust, C++, QML, services, and other adapters can reuse it without copying AMM protocol
logic.
The task-level API must be sufficient to delete the AMM-specific Rust clients and AMM arithmetic,
PDA, Borsh, account-order, signer, and instruction copies in feat/amm-pool-creation and
feat/amm-swap-onchain. Thin host adapters may remain for RPC, wallet, and Qt representation
translation.
Program-owned economic API
Pair and direction resolution
pair_order(pool, first_token_id, second_token_id)
- returns stored or reversed order;
- maps raw amounts to and from the pool's stored A/B order.
swap_direction(pool, input_token_id)
- returns A-to-B or B-to-A;
- rejects definitions outside the pool.
Pool creation
create_pool(token_a_amount, token_b_amount, fee_bps)
- validates nonzero deposits and supported fee tier;
- returns total initial LP, locked LP, user LP, opening reserves, and opening Q64.64 price.
Add liquidity
preview_add_liquidity(pool, vault_a_balance, vault_b_balance, max_a, max_b)
- returns actual deposits, LP to mint, and post-operation pool values using the smallest
executable LP guard.
add_liquidity(pool, vault_a_balance, vault_b_balance, max_a, max_b, minimum_lp)
- applies the exact on-chain minimum-LP guard.
Remove liquidity
preview_remove_liquidity(pool, user_lp_balance, remove_lp)
- returns withdrawal amounts, LP to burn, and post-operation pool values using the smallest
executable withdrawal guards.
remove_liquidity(pool, user_lp_balance, remove_lp, minimum_a, minimum_b)
- applies the exact on-chain minimum-withdrawal guards.
Swaps
preview_swap_exact_input(pool, vault_a_balance, vault_b_balance, direction, amount_in)
swap_exact_input(pool, vault_a_balance, vault_b_balance, direction, amount_in, minimum_out)
preview_swap_exact_output(pool, vault_a_balance, vault_b_balance, direction, amount_out)
swap_exact_output(pool, vault_a_balance, vault_b_balance, direction, amount_out, maximum_in)
Swap results include gross input, fee-adjusted input, fee amount, output, direction,
post-operation reserves, and post-operation Q64.64 spot price.
Other reusable behavior
sync_reserves(pool, vault_a_balance, vault_b_balance) returns incorporated donations and
post-sync pool values.
create_oracle_price_account(pool, window_duration) returns program-derived base/quote assets,
initial spot price, and validated window.
Initialization, configuration updates, and price-observation creation contain no reusable AMM
economic calculation. They are covered by transaction planners instead of pass-through quote
functions.
Reusable Rust client
Add amm_client as both an rlib and cdylib workspace member.
Validated account boundary
Expose:
AccountSnapshot from canonical nssa_core::account::Account plus AccountId;
AmmContext, constructed from a validated config account;
PoolContext, which validates canonical pool, vault, and LP-definition PDAs;
ValidatedFungibleDefinition;
ValidatedFungibleHolding; and
ValidatedPoolSnapshot.
Snapshot validation must cover:
- AMM config ID, owner, and
AmmConfig decoding;
- pool owner,
PoolDefinition decoding, distinct tokens, and canonical pool PDA;
- stored vault/LP IDs and their canonical PDAs;
- configured Token Program ownership of definitions, vaults, and user holdings;
- fungible definition and holding decoding;
- vault holding definition IDs matching the stored pool tokens;
- LP definition supply matching pool LP supply and its self-authority invariant;
- remove-liquidity holdings using the pool LP definition;
- swap input/output holdings using opposite pool tokens; and
- swap input balance covering the quoted input.
After validation, high-level client quote functions delegate economic work directly to
amm_program::quote. They must not reproduce formulas. All raw amounts remain u128.
Deterministic discovery and pair inspection
Expose pure discovery before consumers fetch account state:
derive_config_id(amm_program_id) returns the singleton AMM config ID;
inspect_config(amm_program_id, config_snapshot) validates and returns the configured Token
Program, TWAP Oracle Program, and authority without exposing Borsh decoding to the host;
canonical_pair(first_token_id, second_token_id) returns the deterministic pre-pool token order
used by AMM PDA derivation and rejects identical definitions; and
derive_pair_read_manifest(context, first_token_id, second_token_id) returns the config, pool,
vault-by-token, LP definition, LP lock holding, TWAP current-tick, and clock IDs.
The canonical-pair comparison must be shared with amm_core::compute_pool_pda_seed; UI code must
not reproduce it through base58, hex, or JavaScript string comparison.
After an adapter fetches the manifest, expose inspect_pair over raw account snapshots. Its result
distinguishes a missing pool from an active pool and, for an active pool, returns the validated
stored token order, pool/vault/LP IDs, stored reserves, vault balances, LP supply, fee tier, and
Q64.64 spot price. It also returns caller-to-stored order mapping. Missing-pool validation must model
current guest and chained-program preconditions, not copy stricter assumptions from one UI branch.
Discovery and inspection are stateless. They derive a read set and decode supplied snapshots; they
do not perform network I/O.
User-intent preparation
Keep raw program quotes as the economic authority, then add client-owned intent translation needed
by API/UI consumers:
- derive the minimum executable opening pair for a requested Q64.64 price;
- derive the paired opening amount when the user edits one side, using checked integer arithmetic;
- when both opening amounts and a price intent are supplied, require canonical
spot_price_q64_64(amount_a, amount_b) to equal that intent;
- return final opening amounts only after
amm_program::quote::create_pool accepts them;
- accept caller token order for create/add/remove/swap operations and return both caller-order
display values and stored-order instruction values; and
- expose an exact, named swap
pool_spot_change_bps metric derived from pre/post quote state.
pool_spot_change_bps is pool spot-price movement, not execution-price impact. If execution-price
impact is added later, expose it as a separately named metric with separately tested semantics.
For directional reserves reserve_in/reserve_out, compute the nonnegative relative change from
reserve_in / reserve_out to
(reserve_in + amount_in) / (reserve_out - amount_out), multiply by 10,000, and floor once using
checked widened integer arithmetic. QML/JavaScript must not perform Q64.64, proportional-deposit,
fee, price-movement, or amount-guard arithmetic.
Active-pool preparation always uses the validated pool's stored fee. A UI display fallback or
default fee must never enter an active quote. If a caller supplies an expected fee, treat it only as
an equality precondition and return a stable mismatch error.
Integer-only slippage guards
Expose a validated SlippageTolerance using the canonical 10,000 basis-point denominator, plus
checked minimum-guard floor rounding and maximum-guard ceil rounding. Tolerances from zero through
10,000 basis points are accepted; larger values return a typed error. Positive minimum guards remain
at least one raw unit, and an upper guard beyond u128 returns an overflow error instead of
saturating.
Expose prepared high-level results for:
- pool creation, returning exact
NewDefinition amount fields;
- add liquidity, deriving
min_amount_liquidity, preserving caller-entered token caps as
instruction maxima, and returning the deterministic actual deposits separately;
- remove liquidity, deriving both minimum-withdrawal fields;
- exact-input swap, deriving
min_amount_out; and
- exact-output swap, deriving
max_amount_in.
Each prepared result contains the canonical program quote and fields that feed the matching planner
directly. UI and API adapters select a basis-point tolerance but never calculate chain guards.
Create- and add-liquidity preparation must accept the selected user token holdings and LP
destination. It validates configured Token Program ownership, held definition IDs, available token
balances against instruction spend caps, and the LP destination state required by the current
guest/callee path. Exact-output swap preparation likewise validates the maximum-input guard rather
than only the current quoted input. Holding selection and creation of a fresh wallet account remain
wallet policy.
Planner signer/init metadata always comes from the current guest/IDL contract; do not preserve
branch-local metadata when it differs.
Snapshot-to-transaction facade
Keep the low-level quote and planner functions, but expose these task-oriented operations for normal
API/UI use:
prepare_create_pool_transaction;
prepare_add_liquidity_transaction;
prepare_remove_liquidity_transaction;
prepare_swap_exact_input_transaction; and
prepare_swap_exact_output_transaction.
Each operation accepts raw validated-source snapshots, caller-ordered intent, selected holding IDs,
slippage, and an explicit deadline. It performs validation, caller/stored-order mapping, canonical
quoting, guard derivation, and planning in one call. It returns:
- the canonical quote and caller-order display values;
- the exact
TransactionPlan built from that quote;
- a stable quote commitment;
- writable/affected account IDs; and
- any wallet-owned prerequisite, such as provisioning a fresh LP destination.
The quote commitment binds its own domain-separation discriminator, supplied AMM program ID,
economic intent, selected holding IDs, generated guards, and source fields that affect economics or
account selection: config, pool, vault balances, token/LP definitions, user holdings, and pair
lifecycle state. It is not a deployment-version proof. Before submission, the host re-fetches
snapshots, calls the same preparation again, compares the commitment, and submits only the newly
returned plan. A changed commitment returns a stable quote_changed result and requires renewed
confirmation.
Use canonical nssa_core::Commitment values for bound account snapshots and a versioned,
typed deterministic serialization for the outer quote commitment. Do not hash presentation JSON.
The commitment excludes app-only network labels, request IDs, deadline-window policy, and ephemeral
clock/oracle values that do not affect the quote or account selection. Those snapshots are still
validated during preparation. Network/request generation IDs remain host correlation data rather
than AMM quote identity.
Canonical transaction planning
Expose one typed planner for every guest instruction:
| Guest instruction |
Client planner |
Initialize |
plan_initialize |
UpdateConfig |
plan_update_config |
CreatePriceObservations |
plan_create_price_observations |
CreateOraclePriceAccount |
plan_create_oracle_price_account |
NewDefinition |
plan_create_pool |
AddLiquidity |
plan_add_liquidity |
RemoveLiquidity |
plan_remove_liquidity |
SwapExactInput |
plan_swap_exact_input |
SwapExactOutput |
plan_swap_exact_output |
SyncReserves |
plan_sync_reserves |
TransactionPlan must contain:
- the target AMM program ID;
- the actual
amm_core::Instruction, not a client mirror;
- ordered
PlannedAccount rows with semantic role, ID, writable, signer, and init flags; and
- helpers for account IDs, signer flags/IDs, writable/affected IDs, instruction name, and encoded
instruction words.
Planners derive AMM, oracle, and clock accounts through canonical core helpers. Quote-derived
amounts and guards flow into instruction fields without client-side recalculation.
Exact guest codec
Encode only the canonical instruction enum with the codec consumed by the guest:
pub fn encode_instruction(
instruction: &amm_core::Instruction,
) -> risc0_zkvm::serde::Result<nssa_core::program::InstructionData> {
risc0_zkvm::serde::to_vec(instruction)
}
Do not introduce a generated/client-only instruction mirror or alternate payload codec.
Lossless JSON and C ABI
Expose tagged JSON operations through:
plan_json, covering all ten canonical low-level planners and five snapshot-to-transaction
preparation operations; and
quote_json, covering protocol-account discovery, pair read manifests, pair inspection,
pre-pool canonical order, opening-liquidity preparation, pair order, create pool, preview/exact add
and remove liquidity, preview/exact swaps, five economic preparations, reserve sync, oracle-price
initialization, swap pool-price movement, and canonical protocol constants.
Requests and responses carry a client wire-schema discriminator such as amm-client.v1. This
versions the JSON contract only. It must not be interpreted as, or coupled to, an on-chain program
version check.
All u128 and u64 request fields use non-empty unsigned decimal strings. Quote result amounts,
reserves, supply, Q64.64 prices, and window durations are also decimal strings. Account snapshot
balance and nonce use the same lossless representation. No floating-point or JSON-number conversion
is allowed for these fields.
Expose exactly three C symbols with a checked-in header:
char *amm_client_plan(const char *request_json);
char *amm_client_quote(const char *request_json);
void amm_client_free(char *value);
The two operations return an owned UTF-8 JSON envelope. amm_client_free releases it and accepts
NULL. Boundary failures such as null input, invalid UTF-8, malformed JSON, validation errors, and
caught panics return structured error codes.
The wire plan result includes ordered account rows, affectedAccountIds, and exact instruction
words. Base58 account IDs and ProgramId word arrays remain the canonical shared representation.
Wallet-specific hex/byte layouts belong in a tested host adapter.
Optimistic same-build guarantee
The shared guarantee is source/build parity: the AMM guest and client built from the corresponding
source execute the same economic functions.
The client accepts a supplied amm_program_id as the transaction target and PDA namespace. It uses
that value for ordinary protocol account/PDA validation. It does not decide whether the supplied ID
is the correct deployed release and must not add:
- a runtime ProgramId or ImageID allowlist;
- a compiled ImageID comparison;
- a compatibility manifest;
- version metadata checks; or
- version-specific formula branches.
Pairing a client build with its corresponding deployed program remains deployment/configuration
responsibility.
Shared core and error contracts
Expose from amm_core:
SUPPORTED_FEE_TIERS as a slice;
- a fallible
canonical_token_pair helper reused by compute_pool_pda_seed and the client;
checked_mul_div_floor; and
checked_mul_div_ceil.
Keep panic-based arithmetic helpers for compatibility, implemented through checked variants.
amm_program::quote returns QuoteError for expected invalid input and checked amount overflow.
Q64.64 spot-price conversion preserves spot_price_q64_64 saturation at u128::MAX.
Public errors must provide stable machine-readable codes:
- non-exhaustive
QuoteErrorCode and QuoteError::{kind, code, message};
- non-exhaustive
ClientError for snapshot, planning, balance, and wrapped quote failures;
- typed client errors for out-of-range slippage and upper-guard overflow; and
WireError::code for JSON/C transport mapping.
Quote result structs remain non-exhaustive. Consumers read returned values instead of constructing
results, allowing metadata to be added without breaking callers.
Handler integration
Refactor these handlers to consume shared quote outputs directly:
new_definition;
add_liquidity;
remove_liquidity;
swap_exact_input;
swap_exact_output;
sync_reserves; and
create_oracle_price_account.
Handlers retain runtime account decoding, signer/init rules, deadlines, post-state ordering, PDA
authorization, chained token/oracle calls, and clock validation. They must not recalculate amounts
or pool updates returned by the quote module.
Feature-branch consumer contract
The shared library must replace every AMM-owned concern consumed by the two audited feature
branches. Migration targets the current guest and checked-in IDL, not either branch's older copied
contract.
| Branch concern |
Shared owner |
| Config inspection plus pool, vault, LP, current-tick, and clock derivation |
Discovery/read-manifest API over canonical core helpers |
| Config, pool, token, holding, vault, LP, tick, and clock decoding/validation |
Typed snapshot and pair-inspection API |
| Missing versus active pool |
Pair lifecycle inspection |
| Pre-pool token ordering |
Shared canonical-pair helper used by pool PDA seed derivation |
| Desired opening price and minimum executable deposits |
Opening-liquidity preparation, verified through quote::create_pool |
| Caller-order proportional add amounts |
Pair-aware transaction preparation |
| User funding and LP destination readiness |
Create/add transaction preparation |
| Fee tiers, minimum liquidity, denominators |
Protocol constants response |
| Add/remove/swap amounts, fees, direction, slippage guards, pool updates |
amm_program::quote plus client preparation |
| Swap pool-price movement |
Exact integer pool_spot_change_bps result |
| Account order, writable/signer/init flags, affected accounts |
TransactionPlan, checked against current IDL |
| Instruction value and bytes |
Actual amm_core::Instruction plus RISC Zero Serde |
| Quote freshness between confirmation and submit |
Source-bound quote commitment and re-preparation |
The feat/amm-swap-onchain branch predates the current swap ABI. Its legacy
token_definition_id_in, two-signer layout, and
amm_client_program_id_from_elf/AMM_PROGRAM_BIN path are not compatibility requirements. Rebase
the consumer and use the configured ProgramId plus current one-input-signer plan returned by the
shared client.
The feat/amm-pool-creation branch contains account metadata that differs from the current IDL,
including init flags for new-pool vaults and signer/init treatment for a fresh add-liquidity LP
holding. Delete that metadata copy during migration; the shared IDL-tested plan is authoritative.
Application adapters may still own token-catalog merging, wallet holding selection, request
generation/cancellation, raw RPC normalization, network labels/fingerprints, account-ID and
instruction-byte representation translation, key/account creation, signing, submission, polling,
and human formatting. None may retain AMM formulas, Borsh AMM decoding, PDA recipes, instruction
arguments, account order, or signer/init rules.
Compatibility
This change must not alter:
amm_core::Instruction variants or field order;
- guest instruction signatures or account order;
- RISC Zero Serde instruction encoding;
- PDA seeds;
- stored
PoolDefinition encoding; or
- checked-in AMM IDL contents.
The guest source changes, so deployment must rebuild the binary, inspect its resulting ImageID, and
configure that program identity normally. That operational step does not become a runtime client
compatibility gate.
Out of scope
- network I/O, account-read batching, caching, retry, and endpoint orchestration;
- token-catalog and wallet-holding selection policy;
- generic sequencer-response normalization and wallet-specific representation translation;
- wallet/key lifecycle, signing, and submission;
- deadline-window policy, transaction polling, and post-submit refresh;
- UI-specific formatting or presentation state; and
- runtime release/version enforcement.
Deterministic AMM protocol read-set discovery is in scope even though executing those reads is not.
Tests
Add contract coverage for:
- every program-owned quote, preview/exact guard, direction, fee, rounding, sync, and oracle path;
- every typed quote-error code and stable string;
- checked amount overflow and canonical spot-price saturation;
- config, pool, vault, definition, LP, and user-holding validation;
- unrelated vault and unrelated swap-output rejection;
- LP supply/authority inconsistency and insufficient swap balance;
u128 and u64 values above 2^53 remaining exact;
- all ten instructions round-tripping through RISC Zero Serde;
- planner account role/order/writable/signer/init parity with the checked-in IDL;
- account IDs, signer flags, and signer IDs remaining positionally aligned;
- quote amounts and guards reaching instruction fields without recalculation;
- minimum/maximum slippage rounding, basis-point bounds, positive one-unit guards, and checked upper
overflow;
- all five prepared results feeding their corresponding planner instruction fields exactly;
- JSON/C success responses preserving decimal strings;
- prepared wire responses preserving instruction arguments above
2^53 as decimal strings;
- canonical fee tiers and minimum-liquidity metadata reaching C/JSON consumers without a local
copy;
- config and pair read manifests matching canonical core PDA helpers in both caller token orders;
- missing/active pair inspection, stored/caller order, reserves, fee, and dependency IDs;
- opening-price preparation at, below, and above Q64.64, including minimum-liquidity and overflow
boundaries;
- caller-order create/add preparation returning exact stored-order instruction fields;
- create/add holding ownership, token-definition, insufficient-balance, and LP-destination cases;
- all five snapshot-to-transaction operations returning a quote, source-bound commitment, complete
IDL-matching plan, and affected account IDs;
- commitment stability for identical inputs and change detection for every bound intent, holding,
source-account, and guard field;
- exact
pool_spot_change_bps semantics without floating point;
- versioned wire requests, branch fixture payloads, and values above
2^53; and
- null, invalid UTF-8, malformed JSON, and
NULL free behavior.
Keep existing handler and integration tests passing. Regenerate and diff the AMM IDL to prove no
wire-schema drift.
Acceptance criteria
- Program handlers and host consumers execute one economic implementation.
- The reusable client validates protocol snapshots before quoting.
- Every guest instruction has a canonical typed planner.
- Every plan stores the real
amm_core::Instruction and uses exact RISC Zero Serde.
- High-level create/add/remove/exact-in/exact-out calls return planner-ready instruction amounts;
their snapshot-to-transaction variants return the complete plan and quote commitment; UI/API code
performs no AMM, ordering, slippage, or Q64.64 arithmetic.
- Deterministic discovery returns every AMM-owned account ID needed to fetch and inspect either a
missing or active pair without consumer PDA/Borsh copies.
- Create/add preparation validates selected funding holdings and LP destination requirements.
- Both audited feature branches can delete their local AMM Rust/FFI implementations; remaining host
code is limited to RPC, wallet, representation, correlation, and presentation adapters.
- Planner account ABI mechanically matches the checked-in IDL.
- JSON/C boundaries preserve every
u128 and u64 through decimal strings.
- The JSON contract has a schema discriminator independent of program release identity.
- The three C symbols and ownership contract are documented and tested.
- Errors have typed/stable codes and public result types remain extensible.
- No runtime ProgramId/ImageID/version compatibility check is introduced.
- No instruction, IDL, PDA, or stored-state schema changes are introduced.
- Existing handler tests, client contract tests, workspace checks, and IDL checks pass.
Problem
AMM economic calculations currently live inside assertion-driven instruction handlers. Host APIs,
wallets, and user interfaces cannot call those calculations without constructing runtime accounts
and handling panics, so consumers tend to reproduce formulas, account derivation, instruction
layout, and numeric conversion independently.
That creates drift risks across:
u128/u64values crossing JSON, C, and JavaScript-capable UI boundaries.Proposal
Deliver two layers in one change:
amm_program::quotemodule containing deterministic, fallible economic transitionfunctions. Refactor AMM handlers to consume those same results.
programs/amm/clientcrate that validates fetched account snapshots, exposesdeterministic protocol-account discovery, typed quote orchestration, task-level prepared
transactions, all canonical low-level transaction plans, exact instruction encoding, and
lossless JSON/C ABI adapters.
The client remains stateless. It must not perform RPC, own keys, sign, submit, or manage wallet
lifecycle. Rust, C++, QML, services, and other adapters can reuse it without copying AMM protocol
logic.
The task-level API must be sufficient to delete the AMM-specific Rust clients and AMM arithmetic,
PDA, Borsh, account-order, signer, and instruction copies in
feat/amm-pool-creationandfeat/amm-swap-onchain. Thin host adapters may remain for RPC, wallet, and Qt representationtranslation.
Program-owned economic API
Pair and direction resolution
pair_order(pool, first_token_id, second_token_id)swap_direction(pool, input_token_id)Pool creation
create_pool(token_a_amount, token_b_amount, fee_bps)Add liquidity
preview_add_liquidity(pool, vault_a_balance, vault_b_balance, max_a, max_b)executable LP guard.
add_liquidity(pool, vault_a_balance, vault_b_balance, max_a, max_b, minimum_lp)Remove liquidity
preview_remove_liquidity(pool, user_lp_balance, remove_lp)executable withdrawal guards.
remove_liquidity(pool, user_lp_balance, remove_lp, minimum_a, minimum_b)Swaps
preview_swap_exact_input(pool, vault_a_balance, vault_b_balance, direction, amount_in)swap_exact_input(pool, vault_a_balance, vault_b_balance, direction, amount_in, minimum_out)preview_swap_exact_output(pool, vault_a_balance, vault_b_balance, direction, amount_out)swap_exact_output(pool, vault_a_balance, vault_b_balance, direction, amount_out, maximum_in)Swap results include gross input, fee-adjusted input, fee amount, output, direction,
post-operation reserves, and post-operation Q64.64 spot price.
Other reusable behavior
sync_reserves(pool, vault_a_balance, vault_b_balance)returns incorporated donations andpost-sync pool values.
create_oracle_price_account(pool, window_duration)returns program-derived base/quote assets,initial spot price, and validated window.
Initialization, configuration updates, and price-observation creation contain no reusable AMM
economic calculation. They are covered by transaction planners instead of pass-through quote
functions.
Reusable Rust client
Add
amm_clientas both anrlibandcdylibworkspace member.Validated account boundary
Expose:
AccountSnapshotfrom canonicalnssa_core::account::AccountplusAccountId;AmmContext, constructed from a validated config account;PoolContext, which validates canonical pool, vault, and LP-definition PDAs;ValidatedFungibleDefinition;ValidatedFungibleHolding; andValidatedPoolSnapshot.Snapshot validation must cover:
AmmConfigdecoding;PoolDefinitiondecoding, distinct tokens, and canonical pool PDA;After validation, high-level client quote functions delegate economic work directly to
amm_program::quote. They must not reproduce formulas. All raw amounts remainu128.Deterministic discovery and pair inspection
Expose pure discovery before consumers fetch account state:
derive_config_id(amm_program_id)returns the singleton AMM config ID;inspect_config(amm_program_id, config_snapshot)validates and returns the configured TokenProgram, TWAP Oracle Program, and authority without exposing Borsh decoding to the host;
canonical_pair(first_token_id, second_token_id)returns the deterministic pre-pool token orderused by AMM PDA derivation and rejects identical definitions; and
derive_pair_read_manifest(context, first_token_id, second_token_id)returns the config, pool,vault-by-token, LP definition, LP lock holding, TWAP current-tick, and clock IDs.
The canonical-pair comparison must be shared with
amm_core::compute_pool_pda_seed; UI code mustnot reproduce it through base58, hex, or JavaScript string comparison.
After an adapter fetches the manifest, expose
inspect_pairover raw account snapshots. Its resultdistinguishes a missing pool from an active pool and, for an active pool, returns the validated
stored token order, pool/vault/LP IDs, stored reserves, vault balances, LP supply, fee tier, and
Q64.64 spot price. It also returns caller-to-stored order mapping. Missing-pool validation must model
current guest and chained-program preconditions, not copy stricter assumptions from one UI branch.
Discovery and inspection are stateless. They derive a read set and decode supplied snapshots; they
do not perform network I/O.
User-intent preparation
Keep raw program quotes as the economic authority, then add client-owned intent translation needed
by API/UI consumers:
spot_price_q64_64(amount_a, amount_b)to equal that intent;amm_program::quote::create_poolaccepts them;display values and stored-order instruction values; and
pool_spot_change_bpsmetric derived from pre/post quote state.pool_spot_change_bpsis pool spot-price movement, not execution-price impact. If execution-priceimpact is added later, expose it as a separately named metric with separately tested semantics.
For directional reserves
reserve_in/reserve_out, compute the nonnegative relative change fromreserve_in / reserve_outto(reserve_in + amount_in) / (reserve_out - amount_out), multiply by 10,000, and floor once usingchecked widened integer arithmetic. QML/JavaScript must not perform Q64.64, proportional-deposit,
fee, price-movement, or amount-guard arithmetic.
Active-pool preparation always uses the validated pool's stored fee. A UI display fallback or
default fee must never enter an active quote. If a caller supplies an expected fee, treat it only as
an equality precondition and return a stable mismatch error.
Integer-only slippage guards
Expose a validated
SlippageToleranceusing the canonical 10,000 basis-point denominator, pluschecked minimum-guard floor rounding and maximum-guard ceil rounding. Tolerances from zero through
10,000 basis points are accepted; larger values return a typed error. Positive minimum guards remain
at least one raw unit, and an upper guard beyond
u128returns an overflow error instead ofsaturating.
Expose prepared high-level results for:
NewDefinitionamount fields;min_amount_liquidity, preserving caller-entered token caps asinstruction maxima, and returning the deterministic actual deposits separately;
min_amount_out; andmax_amount_in.Each prepared result contains the canonical program quote and fields that feed the matching planner
directly. UI and API adapters select a basis-point tolerance but never calculate chain guards.
Create- and add-liquidity preparation must accept the selected user token holdings and LP
destination. It validates configured Token Program ownership, held definition IDs, available token
balances against instruction spend caps, and the LP destination state required by the current
guest/callee path. Exact-output swap preparation likewise validates the maximum-input guard rather
than only the current quoted input. Holding selection and creation of a fresh wallet account remain
wallet policy.
Planner signer/init metadata always comes from the current guest/IDL contract; do not preserve
branch-local metadata when it differs.
Snapshot-to-transaction facade
Keep the low-level quote and planner functions, but expose these task-oriented operations for normal
API/UI use:
prepare_create_pool_transaction;prepare_add_liquidity_transaction;prepare_remove_liquidity_transaction;prepare_swap_exact_input_transaction; andprepare_swap_exact_output_transaction.Each operation accepts raw validated-source snapshots, caller-ordered intent, selected holding IDs,
slippage, and an explicit deadline. It performs validation, caller/stored-order mapping, canonical
quoting, guard derivation, and planning in one call. It returns:
TransactionPlanbuilt from that quote;The quote commitment binds its own domain-separation discriminator, supplied AMM program ID,
economic intent, selected holding IDs, generated guards, and source fields that affect economics or
account selection: config, pool, vault balances, token/LP definitions, user holdings, and pair
lifecycle state. It is not a deployment-version proof. Before submission, the host re-fetches
snapshots, calls the same preparation again, compares the commitment, and submits only the newly
returned plan. A changed commitment returns a stable
quote_changedresult and requires renewedconfirmation.
Use canonical
nssa_core::Commitmentvalues for bound account snapshots and a versioned,typed deterministic serialization for the outer quote commitment. Do not hash presentation JSON.
The commitment excludes app-only network labels, request IDs, deadline-window policy, and ephemeral
clock/oracle values that do not affect the quote or account selection. Those snapshots are still
validated during preparation. Network/request generation IDs remain host correlation data rather
than AMM quote identity.
Canonical transaction planning
Expose one typed planner for every guest instruction:
Initializeplan_initializeUpdateConfigplan_update_configCreatePriceObservationsplan_create_price_observationsCreateOraclePriceAccountplan_create_oracle_price_accountNewDefinitionplan_create_poolAddLiquidityplan_add_liquidityRemoveLiquidityplan_remove_liquiditySwapExactInputplan_swap_exact_inputSwapExactOutputplan_swap_exact_outputSyncReservesplan_sync_reservesTransactionPlanmust contain:amm_core::Instruction, not a client mirror;PlannedAccountrows with semantic role, ID, writable, signer, and init flags; andinstruction words.
Planners derive AMM, oracle, and clock accounts through canonical core helpers. Quote-derived
amounts and guards flow into instruction fields without client-side recalculation.
Exact guest codec
Encode only the canonical instruction enum with the codec consumed by the guest:
Do not introduce a generated/client-only instruction mirror or alternate payload codec.
Lossless JSON and C ABI
Expose tagged JSON operations through:
plan_json, covering all ten canonical low-level planners and five snapshot-to-transactionpreparation operations; and
quote_json, covering protocol-account discovery, pair read manifests, pair inspection,pre-pool canonical order, opening-liquidity preparation, pair order, create pool, preview/exact add
and remove liquidity, preview/exact swaps, five economic preparations, reserve sync, oracle-price
initialization, swap pool-price movement, and canonical protocol constants.
Requests and responses carry a client wire-schema discriminator such as
amm-client.v1. Thisversions the JSON contract only. It must not be interpreted as, or coupled to, an on-chain program
version check.
All
u128andu64request fields use non-empty unsigned decimal strings. Quote result amounts,reserves, supply, Q64.64 prices, and window durations are also decimal strings. Account snapshot
balance and nonce use the same lossless representation. No floating-point or JSON-number conversion
is allowed for these fields.
Expose exactly three C symbols with a checked-in header:
The two operations return an owned UTF-8 JSON envelope.
amm_client_freereleases it and acceptsNULL. Boundary failures such as null input, invalid UTF-8, malformed JSON, validation errors, andcaught panics return structured error codes.
The wire plan result includes ordered account rows,
affectedAccountIds, and exact instructionwords. Base58 account IDs and ProgramId word arrays remain the canonical shared representation.
Wallet-specific hex/byte layouts belong in a tested host adapter.
Optimistic same-build guarantee
The shared guarantee is source/build parity: the AMM guest and client built from the corresponding
source execute the same economic functions.
The client accepts a supplied
amm_program_idas the transaction target and PDA namespace. It usesthat value for ordinary protocol account/PDA validation. It does not decide whether the supplied ID
is the correct deployed release and must not add:
Pairing a client build with its corresponding deployed program remains deployment/configuration
responsibility.
Shared core and error contracts
Expose from
amm_core:SUPPORTED_FEE_TIERSas a slice;canonical_token_pairhelper reused bycompute_pool_pda_seedand the client;checked_mul_div_floor; andchecked_mul_div_ceil.Keep panic-based arithmetic helpers for compatibility, implemented through checked variants.
amm_program::quotereturnsQuoteErrorfor expected invalid input and checked amount overflow.Q64.64 spot-price conversion preserves
spot_price_q64_64saturation atu128::MAX.Public errors must provide stable machine-readable codes:
QuoteErrorCodeandQuoteError::{kind, code, message};ClientErrorfor snapshot, planning, balance, and wrapped quote failures;WireError::codefor JSON/C transport mapping.Quote result structs remain non-exhaustive. Consumers read returned values instead of constructing
results, allowing metadata to be added without breaking callers.
Handler integration
Refactor these handlers to consume shared quote outputs directly:
new_definition;add_liquidity;remove_liquidity;swap_exact_input;swap_exact_output;sync_reserves; andcreate_oracle_price_account.Handlers retain runtime account decoding, signer/init rules, deadlines, post-state ordering, PDA
authorization, chained token/oracle calls, and clock validation. They must not recalculate amounts
or pool updates returned by the quote module.
Feature-branch consumer contract
The shared library must replace every AMM-owned concern consumed by the two audited feature
branches. Migration targets the current guest and checked-in IDL, not either branch's older copied
contract.
quote::create_poolamm_program::quoteplus client preparationpool_spot_change_bpsresultTransactionPlan, checked against current IDLamm_core::Instructionplus RISC Zero SerdeThe
feat/amm-swap-onchainbranch predates the current swap ABI. Its legacytoken_definition_id_in, two-signer layout, andamm_client_program_id_from_elf/AMM_PROGRAM_BINpath are not compatibility requirements. Rebasethe consumer and use the configured ProgramId plus current one-input-signer plan returned by the
shared client.
The
feat/amm-pool-creationbranch contains account metadata that differs from the current IDL,including init flags for new-pool vaults and signer/init treatment for a fresh add-liquidity LP
holding. Delete that metadata copy during migration; the shared IDL-tested plan is authoritative.
Application adapters may still own token-catalog merging, wallet holding selection, request
generation/cancellation, raw RPC normalization, network labels/fingerprints, account-ID and
instruction-byte representation translation, key/account creation, signing, submission, polling,
and human formatting. None may retain AMM formulas, Borsh AMM decoding, PDA recipes, instruction
arguments, account order, or signer/init rules.
Compatibility
This change must not alter:
amm_core::Instructionvariants or field order;PoolDefinitionencoding; orThe guest source changes, so deployment must rebuild the binary, inspect its resulting ImageID, and
configure that program identity normally. That operational step does not become a runtime client
compatibility gate.
Out of scope
Deterministic AMM protocol read-set discovery is in scope even though executing those reads is not.
Tests
Add contract coverage for:
u128andu64values above2^53remaining exact;overflow;
2^53as decimal strings;copy;
boundaries;
IDL-matching plan, and affected account IDs;
source-account, and guard field;
pool_spot_change_bpssemantics without floating point;2^53; andNULLfree behavior.Keep existing handler and integration tests passing. Regenerate and diff the AMM IDL to prove no
wire-schema drift.
Acceptance criteria
amm_core::Instructionand uses exact RISC Zero Serde.their snapshot-to-transaction variants return the complete plan and quote commitment; UI/API code
performs no AMM, ordering, slippage, or Q64.64 arithmetic.
missing or active pair without consumer PDA/Borsh copies.
code is limited to RPC, wallet, representation, correlation, and presentation adapters.
u128andu64through decimal strings.