mirror of
https://github.com/logos-blockchain/lez-programs.git
synced 2026-08-26 14:41:12 +00:00
After the amm_module refactor, this crate is linked only by the module (the
UI delegates to modules().amm_module and links nothing), so its home under
apps/amm/ and the name "client" were both misnomers: it's the AMM business
logic the module wraps, reached across an FFI boundary — the same relationship
logos_execution_zone has with wallet_ffi. Co-locate it with the module that
owns it and name it for that role.
The FFI surface is unchanged — the exported functions are already amm_* (not
amm_client_*) — so only the crate, directory, generated header, dylib, and
package names move. The module's call sites are untouched; it just includes the
renamed header.
- apps/amm/client → modules/amm/ffi (git-tracked rename; history preserved)
- crate amm_client → amm_ffi: package name, include/amm_ffi.h, libamm_ffi.dylib,
AMM_FFI_H guard, build.rs output path, tests/public_api.rs import
- workspace member path + Cargo.lock
- flake.nix: pname, -p, header-copy path, dylib install_name, packages.amm_ffi,
ammModuleOutputs externalLibInputs, DYLD wrapper
- modules/amm: metadata external_libraries, CMakeLists EXTERNAL_LIBS, impl
#include + comments, README, flake note
- apps/amm/flake.nix: drop the now-dead amm_client external-lib input (the UI
links no external lib of its own)
Resulting layout:
modules/amm/
src/ # C++ module (amm_module_impl.{h,cpp})
ffi/ # Rust crate amm_ffi (Cargo.toml, src/, include/amm_ffi.h)
131 lines
4.4 KiB
Rust
131 lines
4.4 KiB
Rust
use nssa_core::account::Account;
|
|
use serde_json::{json, Value};
|
|
|
|
use super::{
|
|
clock::decode_clock,
|
|
position::{NewPositionPlan, QuoteBranch, QuoteComputation},
|
|
quote::compute_quote,
|
|
PlanRequest, QuoteRequest,
|
|
};
|
|
use crate::account::{account_id_hex, decode_account, AccountRead};
|
|
|
|
const DEADLINE_WINDOW_MS: u64 = 1_200_000;
|
|
|
|
pub(super) fn plan(input: PlanRequest) -> Result<Value, String> {
|
|
let quote_input = QuoteRequest {
|
|
network_id: input.network_id,
|
|
network_fingerprint: input.network_fingerprint,
|
|
amm_program_id: input.amm_program_id.clone(),
|
|
request: input.request,
|
|
snapshot: input.snapshot,
|
|
};
|
|
let quote = compute_quote("e_input)?;
|
|
if quote.quote_hash() != Some(input.quote_hash.as_str()) {
|
|
return Ok(json!({
|
|
"status": "error",
|
|
"code": "quote_changed",
|
|
"recoverable": true,
|
|
"quote": quote.into_value("e_input.request),
|
|
}));
|
|
}
|
|
let evaluated = match quote {
|
|
QuoteComputation::Evaluated(evaluated) => evaluated,
|
|
QuoteComputation::Failed(failure) => {
|
|
return Ok(json!({
|
|
"status": "error",
|
|
"code": "quote_not_submittable",
|
|
"recoverable": true,
|
|
"quote": failure.into_value("e_input.request),
|
|
}))
|
|
}
|
|
};
|
|
let Some(plan) = evaluated.plan else {
|
|
return Ok(json!({
|
|
"status": "error",
|
|
"code": "quote_not_submittable",
|
|
"recoverable": true,
|
|
"quote": evaluated.value,
|
|
}));
|
|
};
|
|
let fresh_lp = if plan.requires_fresh_lp() {
|
|
let Some(read) = input.fresh_lp.as_ref() else {
|
|
return Ok(json!({
|
|
"status": "needs_fresh_lp",
|
|
"code": "fresh_lp_required",
|
|
}));
|
|
};
|
|
let Ok((id, account)) = decode_account(read) else {
|
|
return Ok(plan_error("wallet_submission_failed"));
|
|
};
|
|
if account != Account::default() || plan.accounts.contains(id) {
|
|
return Ok(plan_error("wallet_submission_failed"));
|
|
}
|
|
Some(id)
|
|
} else {
|
|
None
|
|
};
|
|
let deadline = input
|
|
.now_ms
|
|
.checked_add(DEADLINE_WINDOW_MS)
|
|
.ok_or_else(|| String::from("transaction deadline overflow"))?;
|
|
let clock_timestamp = clock_timestamp("e_input.snapshot.clock)?;
|
|
if clock_timestamp >= deadline {
|
|
return Ok(plan_error("transaction_deadline_expired"));
|
|
}
|
|
let NewPositionPlan { accounts, branch } = plan;
|
|
let (account_ids, signing_requirements) = accounts.wallet_args(fresh_lp)?;
|
|
let instruction = match branch {
|
|
QuoteBranch::Missing { amount_a, amount_b } => {
|
|
let instruction = amm_core::Instruction::NewDefinition {
|
|
token_a_amount: amount_a,
|
|
token_b_amount: amount_b,
|
|
fees: u128::from(quote_input.request.fee_bps),
|
|
deadline,
|
|
};
|
|
risc0_zkvm::serde::to_vec(&instruction)
|
|
.map_err(|error| format!("instruction serialization failed: {error}"))?
|
|
}
|
|
QuoteBranch::Active {
|
|
max_a,
|
|
max_b,
|
|
minimum_lp,
|
|
stored_reversed,
|
|
} => {
|
|
let (stored_max_a, stored_max_b) = if stored_reversed {
|
|
(max_b, max_a)
|
|
} else {
|
|
(max_a, max_b)
|
|
};
|
|
let instruction = amm_core::Instruction::AddLiquidity {
|
|
min_amount_liquidity: minimum_lp,
|
|
max_amount_to_add_token_a: stored_max_a,
|
|
max_amount_to_add_token_b: stored_max_b,
|
|
deadline,
|
|
};
|
|
risc0_zkvm::serde::to_vec(&instruction)
|
|
.map_err(|error| format!("instruction serialization failed: {error}"))?
|
|
}
|
|
};
|
|
|
|
Ok(json!({
|
|
"status": "ready",
|
|
"programId": input.amm_program_id,
|
|
"accountIds": account_ids.into_iter().map(account_id_hex).collect::<Vec<_>>(),
|
|
"signingRequirements": signing_requirements,
|
|
"instruction": instruction,
|
|
"deadlineMs": deadline.to_string(),
|
|
}))
|
|
}
|
|
|
|
fn plan_error(code: &str) -> Value {
|
|
json!({
|
|
"status": "error",
|
|
"code": code,
|
|
"recoverable": true,
|
|
})
|
|
}
|
|
|
|
fn clock_timestamp(read: &AccountRead) -> Result<u64, String> {
|
|
decode_clock(read).map(|(_, clock)| clock.timestamp)
|
|
}
|