Skip to content

refactor(amm): share quotes and add reusable client APIs - #234

Open
3esmit wants to merge 8 commits into
mainfrom
refactor/amm-domain-api
Open

refactor(amm): share quotes and add reusable client APIs#234
3esmit wants to merge 8 commits into
mainfrom
refactor/amm-domain-api

Conversation

@3esmit

@3esmit 3esmit commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Add a public, fallible amm_program::quote API and make AMM handlers consume its returned
    amounts and pool updates.
  • Add a stateless amm_client rlib/cdylib with validated account snapshots and high-level
    quote orchestration.
  • Add canonical planners for all ten AMM guest instructions using the real
    amm_core::Instruction and exact RISC Zero Serde.
  • Add integer-only slippage preparation for pool creation, liquidity changes, and both swap modes,
    returning fields that feed the canonical planners directly.
  • Add lossless decimal-string JSON adapters and three C ABI symbols for reusable API/UI/backend
    integration.
  • Complete deterministic pair discovery, missing/active inspection, opening-liquidity intent,
    caller-order preparation, create/add funding validation, and snapshot-to-complete-plan operations
    required by the pool-creation and swap consumers.
  • Preserve an optimistic same-build guarantee without runtime ProgramId, ImageID, or version
    compatibility enforcement.

Solves #233

Program-owned quote API

The new amm_program::quote module covers:

Use case Functions
Resolve displayed token order pair_order
Resolve swap input direction swap_direction
Create pool create_pool
Add liquidity preview_add_liquidity, add_liquidity
Remove liquidity preview_remove_liquidity, remove_liquidity
Exact-input swap preview_swap_exact_input, swap_exact_input
Exact-output swap preview_swap_exact_output, swap_exact_output
Sync donated balances sync_reserves
Seed oracle price account create_oracle_price_account

Quote results expose exact transfer amounts, LP changes, fee-adjusted swap input, fee amount,
post-operation reserves/supply, and Q64.64 spot price where applicable.

These handlers now call the shared functions and consume their results:

  • new_definition;
  • add_liquidity;
  • remove_liquidity;
  • both swap modes;
  • sync_reserves; and
  • create_oracle_price_account.

Runtime account validation, signer/init rules, deadlines, post-state ordering, PDA authorization,
and chained token/oracle calls remain in the handlers.

Reusable AMM client

programs/amm/client is a stateless workspace crate. It performs no RPC, key management, signing,
submission, or wallet lifecycle work.

Validated snapshots

The Rust API adds:

  • AccountSnapshot over canonical NSSA Account plus AccountId;
  • AmmContext::from_config_account;
  • PoolContext;
  • ValidatedFungibleDefinition;
  • ValidatedFungibleHolding; and
  • ValidatedPoolSnapshot.

Validation covers config and pool ownership/decoding, canonical pool/vault/LP PDAs, stored account
IDs, configured Token Program ownership, fungible token definitions and holdings, vault definition
IDs, LP supply/self-authority consistency, remove-liquidity LP holdings, opposite swap input/output
tokens, and sufficient swap input balance.

After validation, client quote functions delegate economic calculations directly to
amm_program::quote; the client contains no AMM formula copy.

Prepared chain guards

SlippageTolerance validates 0..=10_000 basis points. minimum_guard_amount uses widened integer
floor rounding and keeps positive minimums executable at one raw unit.
maximum_guard_amount uses widened integer ceil rounding and returns slippage_bound_overflow
rather than saturating beyond u128.

The client exposes prepare_create_pool, prepare_add_liquidity,
prepare_remove_liquidity, prepare_swap_exact_input, and prepare_swap_exact_output. Each result
contains the canonical quote plus the exact instruction amount fields. Adapters choose tolerance but
do not reproduce guard arithmetic. Prepared add-liquidity maxima preserve the caller's original
caps, while the canonical quote returns deterministic actual deposits. This avoids applying the
program's integer-ratio rounding twice for non-divisible reserve ratios. Funding prerequisites use
instruction spend caps for add-liquidity and exact-output swaps, not only current quoted transfers.

Feature-branch completion

Read-only audits of feat/amm-pool-creation and feat/amm-swap-onchain identified the shared
surface required to delete both local AMM clients. This change completes that surface:

  • Add derive_config_id, raw-snapshot inspect_config, shared pre-pool canonical_pair, and
    derive_pair_read_manifest so hosts can fetch config, pool, per-token vault, LP definition, LP
    lock, current-tick, and clock accounts without copying PDA recipes.
  • Add raw-snapshot inspect_pair with missing/active status, validated stored/caller order,
    dependency IDs, stored reserves, vault balances, LP supply, fee tier, and Q64.64 spot price.
  • Add opening-liquidity preparation from a requested Q64.64 price or one edited side. Validate
    an explicit pair against the requested canonical spot price and validate final deposits through
    amm_program::quote::create_pool.
  • Add caller-order create/add/remove/swap preparation. Return caller-order display values and
    stored-order instruction values without QML proportional math.
  • Validate selected create/add token holdings, available balances, and LP destination readiness.
  • Add prepare_*_transaction for create, add, remove, exact-input swap, and exact-output swap.
    Each raw-snapshot call returns the canonical quote, complete TransactionPlan, source-bound quote
    commitment, writable/affected account IDs, and wallet-owned prerequisites.
  • Add exact integer pool_spot_change_bps, explicitly named as pool spot-price movement rather
    than execution-price impact.
  • Add a JSON schema discriminator independent of deployed program identity.

The host confirmation path re-fetches account state, repeats the same transaction preparation,
compares the quote commitment, and submits only the newly returned plan. App request IDs handle late
async responses separately from the quote commitment.

These additions remain stateless and perform no RPC, wallet account creation, signing, submission,
or runtime release compatibility check.

Transaction planning

The client exposes all ten planners:

  • plan_initialize;
  • plan_update_config;
  • plan_create_price_observations;
  • plan_create_oracle_price_account;
  • plan_create_pool for NewDefinition;
  • plan_add_liquidity;
  • plan_remove_liquidity;
  • plan_swap_exact_input;
  • plan_swap_exact_output; and
  • plan_sync_reserves.

Each TransactionPlan owns the target program ID, actual amm_core::Instruction, and ordered
PlannedAccount rows containing account role, ID, writable, signer, and init flags. Canonical AMM,
oracle, and clock helpers derive protocol accounts. Account ID and signer helpers use the same
ordered rows; the completed API also derives writable/affected IDs from them.

Instruction encoding is a direct call over the canonical enum:

pub fn encode_instruction(
    instruction: &amm_core::Instruction,
) -> risc0_zkvm::serde::Result<nssa_core::program::InstructionData> {
    risc0_zkvm::serde::to_vec(instruction)
}

No client instruction mirror or alternate codec is introduced.

JSON and C ABI

The client exposes tagged JSON operations through:

  • plan_json for all ten planners; and
  • quote_json for protocol constants, pair order, pool creation, preview/exact liquidity changes,
    preview/exact swaps, five prepared high-level operations, reserve sync, and oracle-price
    initialization.

The completed API extends those same entry points, without adding C symbols, with discovery,
inspection, opening-liquidity, and five raw-snapshot transaction-preparation operations. Both plan
and quote entry points accept the task operations. Plan values include typed instructionArgs
derived exhaustively from the same amm_core::Instruction encoded into instructionWords.
Versioned requests and responses carry an amm-client.v1 schema discriminator. This versions only
the JSON contract.

All u128 and u64 request values use unsigned decimal strings. Quote amounts, reserves, supply,
Q64.64 prices, window durations, and raw account balance/nonce values return or travel as decimal
strings. Values above JavaScript's exact integer range never pass through floating point.

The checked-in C header exposes exactly:

char *amm_client_plan(const char *request_json);
char *amm_client_quote(const char *request_json);
void amm_client_free(char *value);

Plan and quote calls return owned UTF-8 JSON envelopes. amm_client_free releases them and accepts
NULL. Null pointers, invalid UTF-8, malformed JSON, validation failures, serialization failures,
and caught panics map to structured boundary errors.

Public errors and API evolution

  • QuoteErrorCode is non-exhaustive and owns stable program quote code strings.
  • QuoteError exposes typed kind(), stable code(), and diagnostic message().
  • ClientError is non-exhaustive and covers account identity/owner/data, fungible/token matching,
    insufficient balance, identical-token pools, slippage bounds, and wrapped quote errors.
  • IntentError covers opening-price input, checked intent arithmetic, exact price matching, and
    directional pool-price movement.
  • TransactionError wraps validation/intent failures and adds stable fee-mismatch, encoding,
    quote_changed, and duplicate-account outcomes.
  • WireError preserves stable codes for JSON/C mapping.
  • Quote result structs are non-exhaustive.
  • SUPPORTED_FEE_TIERS is exposed as a slice.
  • Checked widened multiply/divide helpers return Option; existing panic-based helpers remain for
    compatibility and call the checked implementations.

Checked amount overflows return structured quote errors. Q64.64 spot-price conversion retains the
canonical saturating behavior at u128::MAX.

Optimistic same-build guarantee

The program and client built from corresponding source share one economic implementation.

The client accepts the supplied amm_program_id as the target and PDA namespace. Normal protocol
validation checks fetched accounts and derived addresses relative to that value and configured
Token/TWAP program IDs. The client does not attempt to prove that the supplied program ID is the
correct deployed release.

This change adds no:

  • ProgramId/ImageID allowlist;
  • compiled ImageID comparison;
  • compatibility manifest;
  • runtime version metadata check; or
  • version-specific formula branch.

Deployment configuration remains responsible for pairing the client and corresponding AMM build.

Feature-branch migration

feat/amm-pool-creation

  • Remove apps/amm/client after the completion-gate operations are available.
  • Replace its config/pair derivation, AMM decoding, missing/active pool classification, opening-price
    math, proportional add math, quote commitment, and account-plan construction with shared calls.
  • Follow the current shared plan and checked-in IDL. Do not preserve the branch's conflicting init
    flags for new-pool vaults or signer/init treatment for an add-liquidity LP destination.
  • Retain token-catalog UX, wallet holding selection, fresh-account creation, RPC reads, submission,
    polling, and human formatting in the app.

feat/amm-swap-onchain

  • Rebase onto the current swap ABI before migration.
  • Remove the branch-local client FFI, QML fee/output/slippage/pool-price formulas, Borsh config/pool
    views, manual account ordering, and instruction serialization.
  • Do not preserve legacy token_definition_id_in, the old two-signer layout, or
    amm_client_program_id_from_elf/AMM_PROGRAM_BIN. Use the configured supplied ProgramId and
    current one-input-signer shared plan.
  • Retain wallet/RPC calls and a narrow converter between shared base58/ProgramId-word values and the
    wallet's hex/byte representation.

Both hosts need request-generation correlation so late pool/quote replies cannot replace state for a
newer token pair. Signing/submission is single-flight and is not automatically retried after an
ambiguous result; use the returned transaction hash to query status first.

Compatibility

  • No amm_core::Instruction variant or field-order changes.
  • No guest signature or account-order changes.
  • No RISC Zero Serde format changes.
  • No PDA seed changes.
  • No PoolDefinition storage encoding changes.
  • No checked-in AMM IDL changes.
  • Existing panic-oriented arithmetic helpers preserve their API.
  • The new client crate and C ABI are additive.

The guest implementation changed. Rebuild the release guest, inspect the resulting ImageID, and
refresh deployment configuration before deployment. This operational identity refresh is not a
runtime client compatibility gate.

Tests

Completed validation:

  • cargo +nightly fmt --all -- --check
  • taplo fmt --check .
  • RISC0_SKIP_BUILD=1 cargo +1.94.0 clippy --workspace --all-targets -- -D warnings
  • make clippy-guest
  • RISC0_DEV_MODE=1 cargo +1.94.0 test -p amm_client
  • RISC0_DEV_MODE=1 cargo +1.94.0 test --workspace --exclude integration_tests
  • RISC0_DEV_MODE=1 cargo +1.94.0 test -p integration_tests
  • IDL regeneration/diff for all tracked guest sources
  • RISC0_SKIP_BUILD=1 cargo +1.94.0 doc -p amm_program -p amm_client --no-deps
  • make build-programs
  • spel inspect target/guest/amm.bin

All commands passed. The release guest build completed for every program, and AMM inspection
returned a valid ProgramId/ImageID. Deployment configuration still owns any identity refresh.

Required coverage includes:

  • quote success, preview/exact guards, fee/rounding behavior, overflow, and spot-price saturation;
  • the complete typed quote-error mapping;
  • validated config/pool/vault/definition/holding snapshots;
  • unrelated vault/output rejection, LP inconsistency, and insufficient swap balance;
  • client/program quote parity for every reusable economic operation;
  • exact u128 values above 2^53;
  • all ten instructions round-tripping through the guest codec;
  • every planner's account role/order/writable/signer/init fields matching the checked-in IDL;
  • signer/account positional alignment and canonical PDA derivation;
  • quote amounts and guards reaching instruction fields without recalculation;
  • floor/ceil slippage rounding, 0..=10_000 basis-point bounds, one-unit minimums, and checked upper
    overflow;
  • every prepared result feeding the corresponding canonical instruction fields exactly;
  • decimal-string u128/u64 JSON/C success paths;
  • all five prepared wire operations, including instruction arguments above 2^53;
  • config/pair discovery and missing/active inspection in either caller order;
  • opening-price and caller-order preparation across rounding and overflow boundaries;
  • create/add funding and LP-destination validation;
  • all five raw-snapshot transaction preparations returning a quote, commitment, complete plan, and
    affected IDs;
  • quote-commitment stability/change detection, versioned wire fixtures, and exact pool spot-change
    semantics;
  • fixture parity for both audited feature branches; and
  • null, invalid UTF-8, malformed JSON, and NULL free behavior.

3esmit added 7 commits July 22, 2026 12:12
Add validated shared quote orchestration, canonical planners for every guest instruction, and exact RISC Zero serialization. Expose integer-only slippage preparation and lossless JSON/C adapters without runtime deployment identity checks.
Use canonical ProgramId word arrays, include pool spot movement in standalone swap quotes, and keep quote commitments stable across deadline refreshes.

BREAKING CHANGE: JSON ProgramId values now use eight u32 words. Convert hex or byte representations in host adapters.
Copilot AI review requested due to automatic review settings July 23, 2026 02:58

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Remove the generic sequencer-response adapter and reuse the transaction order mapper.

BREAKING CHANGE: account_snapshot_from_sequencer_response and its quote JSON operation are removed. Hosts must normalize RPC responses into canonical account snapshots before calling amm_client.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants