mirror of
https://github.com/logos-blockchain/lez-programs.git
synced 2026-08-25 06:01:11 +00:00
refactor(amm): remove the dead Network/context machinery
The liquidity token surface moved app-side (resolveTokens + custom tokens), leaving the whole newPositionContext path dormant. Delete it end to end and point the swap methods at the same lean program-id helper everything else uses.
This commit is contained in:
+10
-11
@@ -26,17 +26,16 @@ methods (the module API is generated from the header) are:
|
||||
**Amount / id conventions** below.
|
||||
- `tokenList()` — reads the `TOKENS_CONFIG` JSON array and returns it with
|
||||
`definitionId`/`holding` normalized to hex.
|
||||
- `newPositionContext(request, walletOpen, refreshWalletAccounts)` — the
|
||||
add-liquidity view state (available tokens, fee tiers, warnings) as a
|
||||
context map.
|
||||
- `quoteNewPosition(request, walletOpen)` — prices an add-liquidity request
|
||||
against current on-chain state (read-only).
|
||||
- `submitNewPosition(request, quoteHash, walletOpen, freshLpId)` — submits an
|
||||
add-liquidity transaction. When the quote needs a fresh LP holding and
|
||||
`freshLpId` is empty, returns `{ status: "requires_fresh_lp" }` **without**
|
||||
submitting: the caller (the app backend, which owns the wallet keyset) creates
|
||||
the account and calls again with its id. Headless callers pre-create an LP
|
||||
holding and pass it.
|
||||
- `resolveTokens(request, walletOpen)` — resolves an app-provided set of token
|
||||
ids into selector rows (definition + wallet holding per id). The lean,
|
||||
stateless successor to the removed `newPositionContext` path: the app owns the
|
||||
id set, so there is no network envelope or process-cached wallet state here.
|
||||
- `feeTiers()` — the AMM's supported fee tiers as raw basis points.
|
||||
- `createPoolQuote(request)` / `createPool(request)` and
|
||||
`addLiquidityQuote(request)` / `addLiquidity(request)` — the add-liquidity
|
||||
preview (read-only) and submit paths. The submit forwards the app-supplied
|
||||
fresh LP holding id; the app backend, which owns the wallet keyset, creates
|
||||
that account.
|
||||
|
||||
## How it fits together
|
||||
|
||||
|
||||
@@ -16,12 +16,8 @@ extern "C" {
|
||||
|
||||
char *amm_config_id(const char *request_json);
|
||||
|
||||
char *amm_token_ids(const char *request_json);
|
||||
|
||||
char *amm_pair_ids(const char *request_json);
|
||||
|
||||
char *amm_context(const char *request_json);
|
||||
|
||||
char *amm_resolve_tokens(const char *request_json);
|
||||
|
||||
char *amm_swap_pair(const char *request_json);
|
||||
|
||||
@@ -60,10 +60,6 @@ pub(crate) fn program_id_hex(program_id: ProgramId) -> String {
|
||||
hex::encode(bytes)
|
||||
}
|
||||
|
||||
pub(crate) fn program_id_base58(program_id: ProgramId) -> String {
|
||||
AccountId::new(program_id_bytes(program_id)).to_string()
|
||||
}
|
||||
|
||||
pub(crate) fn program_id_bytes(program_id: ProgramId) -> [u8; 32] {
|
||||
let mut bytes = [0_u8; 32];
|
||||
for (chunk, word) in bytes.chunks_exact_mut(4).zip(program_id) {
|
||||
|
||||
@@ -1,143 +1,20 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use amm_core::{
|
||||
FEE_TIER_BPS_1, FEE_TIER_BPS_100, FEE_TIER_BPS_30, FEE_TIER_BPS_5, MINIMUM_LIQUIDITY,
|
||||
};
|
||||
use nssa_core::{account::AccountId, program::ProgramId};
|
||||
use serde_json::{json, Value};
|
||||
use token_core::TokenDefinition;
|
||||
|
||||
use super::{
|
||||
config::load_config,
|
||||
holding::{select_holding, wallet_holdings, SelectedHolding},
|
||||
quote_error::issue,
|
||||
ContextRequest, ResolveTokensRequest, TokenIdsRequest,
|
||||
};
|
||||
use crate::account::{
|
||||
account_id_from_hex, account_id_hex, decode_account, parse_base58_id, parse_program_id,
|
||||
program_id_base58, AccountRead,
|
||||
holding::{select_holding, wallet_holdings},
|
||||
ResolveTokensRequest,
|
||||
};
|
||||
use crate::account::{account_id_from_hex, decode_account, parse_program_id, AccountRead};
|
||||
|
||||
pub(super) fn token_ids(request: TokenIdsRequest) -> Result<Value, String> {
|
||||
let amm_program = parse_program_id(&request.amm_program_id)?;
|
||||
let Ok(config) = load_config(amm_program, &request.config) else {
|
||||
return Ok(manifest_error("config_unavailable"));
|
||||
};
|
||||
|
||||
let holdings = wallet_holdings(&request.wallet_accounts, config.token_program_id);
|
||||
let mut token_ids = BTreeSet::new();
|
||||
for id in &request.configured_token_ids {
|
||||
if let Ok(id) = account_id_from_hex(id, "configured token id") {
|
||||
token_ids.insert(id);
|
||||
}
|
||||
}
|
||||
for id in request
|
||||
.recent_token_ids
|
||||
.iter()
|
||||
.chain(&request.resolved_token_ids)
|
||||
{
|
||||
if let Ok(id) = parse_base58_id(id, "token id") {
|
||||
token_ids.insert(id);
|
||||
}
|
||||
}
|
||||
token_ids.extend(holdings.into_iter().map(|holding| holding.definition_id));
|
||||
|
||||
Ok(json!({
|
||||
"status": "ok",
|
||||
"tokenIds": token_ids.into_iter().map(account_id_hex).collect::<Vec<_>>(),
|
||||
}))
|
||||
}
|
||||
|
||||
fn manifest_error(code: &str) -> Value {
|
||||
json!({ "status": "error", "code": code, "tokenIds": [] })
|
||||
}
|
||||
|
||||
pub(super) fn context(request: ContextRequest) -> Result<Value, String> {
|
||||
let amm_program = parse_program_id(&request.amm_program_id)?;
|
||||
let Ok(config) = load_config(amm_program, &request.config) else {
|
||||
return Ok(context_error(&request, "config_unavailable"));
|
||||
};
|
||||
|
||||
let holdings = wallet_holdings(&request.wallet_accounts, config.token_program_id);
|
||||
let source_map = token_sources(&request, &holdings);
|
||||
let mut rows = Vec::new();
|
||||
let mut warnings = Vec::new();
|
||||
|
||||
for (token_id, sources) in source_map {
|
||||
let read = request
|
||||
.token_definitions
|
||||
.iter()
|
||||
.find(|read| account_id_from_hex(&read.id, "token definition id") == Ok(token_id));
|
||||
let (name, total_supply, metadata_id) =
|
||||
match fungible_definition(read, token_id, config.token_program_id) {
|
||||
Ok(definition) => definition,
|
||||
Err(error) => {
|
||||
rows.push(unavailable_token_row(token_id, sources, error.code));
|
||||
if error.warn {
|
||||
warnings.push(issue(
|
||||
error.code,
|
||||
"Token definition could not be read.",
|
||||
&[],
|
||||
json!({ "tokenId": token_id.to_string() }),
|
||||
));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let selected = select_holding(&holdings, token_id);
|
||||
let mut row = json!({
|
||||
"definitionId": token_id.to_string(),
|
||||
"name": name,
|
||||
"metadataId": metadata_id.map(|id| id.to_string()),
|
||||
"totalSupplyRaw": total_supply.to_string(),
|
||||
"ownerProgramId": program_id_base58(config.token_program_id),
|
||||
"public": true,
|
||||
"fungible": true,
|
||||
"selectable": true,
|
||||
"status": "available",
|
||||
"code": "available",
|
||||
"sources": sources,
|
||||
});
|
||||
if let Some(selected) = selected {
|
||||
row["holdingId"] = json!(selected.id.to_string());
|
||||
row["balanceRaw"] = json!(selected.balance.to_string());
|
||||
}
|
||||
rows.push(row);
|
||||
}
|
||||
|
||||
rows.sort_by(|left, right| {
|
||||
let left_holding = left.get("holdingId").is_some();
|
||||
let right_holding = right.get("holdingId").is_some();
|
||||
right_holding.cmp(&left_holding).then_with(|| {
|
||||
left["definitionId"]
|
||||
.as_str()
|
||||
.cmp(&right["definitionId"].as_str())
|
||||
})
|
||||
});
|
||||
|
||||
Ok(json!({
|
||||
"status": if request.wallet_available { "ready" } else { "no_wallet" },
|
||||
"networkId": request.network_id,
|
||||
"networkFingerprint": request.network_fingerprint,
|
||||
"walletAvailable": request.wallet_available,
|
||||
"minimumLiquidityRaw": MINIMUM_LIQUIDITY.to_string(),
|
||||
"programIds": {
|
||||
"amm": program_id_base58(amm_program),
|
||||
"token": program_id_base58(config.token_program_id),
|
||||
"twapOracle": program_id_base58(config.twap_oracle_program_id),
|
||||
},
|
||||
"tokens": rows,
|
||||
"feeTiers": fee_tiers(),
|
||||
"warnings": warnings,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Resolves an explicit, app-provided set of token ids into selector rows — the lean,
|
||||
/// stateless successor to `context`. The app owns the id set (its configured tokens plus any
|
||||
/// custom/pasted ids it remembers), so there is no network envelope and no process-cached wallet
|
||||
/// state here: the module reads the definitions + wallet fresh and passes them in, exactly like
|
||||
/// `context` did per token.
|
||||
/// Resolves an explicit, app-provided set of token ids into selector rows. The app owns the
|
||||
/// id set (its configured tokens plus any custom/pasted ids it remembers), so there is no
|
||||
/// network envelope, no status, and no process-cached wallet state here: the module reads the
|
||||
/// definitions + wallet fresh and passes them in.
|
||||
///
|
||||
/// `token_ids` are hex (the module normalizes base58→hex at the boundary); `token_definitions`
|
||||
/// are the corresponding read accounts, keyed by hex id. Every returned row has the same shape —
|
||||
@@ -154,7 +31,7 @@ pub(super) fn resolve_tokens(request: ResolveTokensRequest) -> Result<Value, Str
|
||||
let holdings = wallet_holdings(&request.wallet_accounts, config.token_program_id);
|
||||
|
||||
// De-duplicate the requested ids, dropping any malformed ones. Order is irrelevant —
|
||||
// the rows are sorted below (held first, then by id) like `context`.
|
||||
// the rows are sorted below (held first, then by id).
|
||||
let mut token_ids = BTreeSet::new();
|
||||
for id in &request.token_ids {
|
||||
if let Ok(id) = account_id_from_hex(id, "token id") {
|
||||
@@ -169,7 +46,7 @@ pub(super) fn resolve_tokens(request: ResolveTokensRequest) -> Result<Value, Str
|
||||
.iter()
|
||||
.find(|read| account_id_from_hex(&read.id, "token definition id") == Ok(token_id));
|
||||
// Only readable, fungible definitions become rows; anything else is omitted.
|
||||
let Ok((name, total_supply, _metadata_id)) =
|
||||
let Some((name, total_supply)) =
|
||||
fungible_definition(read, token_id, config.token_program_id)
|
||||
else {
|
||||
continue;
|
||||
@@ -201,120 +78,21 @@ pub(super) fn resolve_tokens(request: ResolveTokensRequest) -> Result<Value, Str
|
||||
Ok(json!({ "status": "ok", "tokens": rows }))
|
||||
}
|
||||
|
||||
fn context_error(request: &ContextRequest, code: &str) -> Value {
|
||||
json!({
|
||||
"status": "error",
|
||||
"code": code,
|
||||
"networkId": request.network_id,
|
||||
"networkFingerprint": request.network_fingerprint,
|
||||
"walletAvailable": request.wallet_available,
|
||||
"tokens": [],
|
||||
"feeTiers": fee_tiers(),
|
||||
"warnings": [],
|
||||
})
|
||||
}
|
||||
|
||||
fn fee_tiers() -> Value {
|
||||
json!([
|
||||
{ "feeBps": FEE_TIER_BPS_1, "label": "0.01%", "enabled": true },
|
||||
{ "feeBps": FEE_TIER_BPS_5, "label": "0.05%", "enabled": true },
|
||||
{ "feeBps": FEE_TIER_BPS_30, "label": "0.30%", "enabled": true },
|
||||
{ "feeBps": FEE_TIER_BPS_100, "label": "1.00%", "enabled": true },
|
||||
])
|
||||
}
|
||||
|
||||
fn token_sources(
|
||||
request: &ContextRequest,
|
||||
holdings: &[SelectedHolding],
|
||||
) -> BTreeMap<AccountId, Vec<String>> {
|
||||
let mut sources: BTreeMap<AccountId, BTreeSet<String>> = BTreeMap::new();
|
||||
for id in &request.configured_token_ids {
|
||||
if let Ok(id) = account_id_from_hex(id, "configured token id") {
|
||||
sources
|
||||
.entry(id)
|
||||
.or_default()
|
||||
.insert(String::from("config"));
|
||||
}
|
||||
}
|
||||
for (ids, source) in [
|
||||
(&request.recent_token_ids, "recent"),
|
||||
(&request.resolved_token_ids, "resolved"),
|
||||
] {
|
||||
for id in ids {
|
||||
if let Ok(id) = parse_base58_id(id, "token id") {
|
||||
sources.entry(id).or_default().insert(String::from(source));
|
||||
}
|
||||
}
|
||||
}
|
||||
for holding in holdings {
|
||||
sources
|
||||
.entry(holding.definition_id)
|
||||
.or_default()
|
||||
.insert(String::from("holding"));
|
||||
}
|
||||
sources
|
||||
.into_iter()
|
||||
.map(|(id, values)| (id, values.into_iter().collect()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn unavailable_token_row(token_id: AccountId, sources: Vec<String>, code: &str) -> Value {
|
||||
json!({
|
||||
"definitionId": token_id.to_string(),
|
||||
"name": "",
|
||||
"metadataId": Value::Null,
|
||||
"totalSupplyRaw": "0",
|
||||
"selectable": false,
|
||||
"status": code,
|
||||
"code": code,
|
||||
"sources": sources,
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) struct DefinitionError {
|
||||
pub(super) code: &'static str,
|
||||
pub(super) warn: bool,
|
||||
}
|
||||
|
||||
pub(super) fn fungible_definition(
|
||||
/// Decodes a token definition read as a fungible `(name, total_supply)`, or `None` when it is
|
||||
/// unreadable, owned by a different program, its id mismatches, or it isn't fungible.
|
||||
fn fungible_definition(
|
||||
read: Option<&AccountRead>,
|
||||
token_id: AccountId,
|
||||
token_program: ProgramId,
|
||||
) -> Result<(String, u128, Option<AccountId>), DefinitionError> {
|
||||
let Some(read) = read else {
|
||||
return Err(DefinitionError {
|
||||
code: "token_definition_unreadable",
|
||||
warn: true,
|
||||
});
|
||||
};
|
||||
let Ok((id, account)) = decode_account(read) else {
|
||||
return Err(DefinitionError {
|
||||
code: "token_definition_unreadable",
|
||||
warn: true,
|
||||
});
|
||||
};
|
||||
if id != token_id {
|
||||
return Err(DefinitionError {
|
||||
code: "token_definition_unreadable",
|
||||
warn: false,
|
||||
});
|
||||
}
|
||||
if account.program_owner != token_program {
|
||||
return Err(DefinitionError {
|
||||
code: "token_program_mismatch",
|
||||
warn: false,
|
||||
});
|
||||
) -> Option<(String, u128)> {
|
||||
let (id, account) = decode_account(read?).ok()?;
|
||||
if id != token_id || account.program_owner != token_program {
|
||||
return None;
|
||||
}
|
||||
match TokenDefinition::try_from(&account.data) {
|
||||
Ok(TokenDefinition::Fungible {
|
||||
name,
|
||||
total_supply,
|
||||
metadata_id,
|
||||
..
|
||||
}) => Ok((name, total_supply, metadata_id)),
|
||||
_ => Err(DefinitionError {
|
||||
code: "token_not_fungible",
|
||||
warn: false,
|
||||
}),
|
||||
name, total_supply, ..
|
||||
}) => Some((name, total_supply)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ mod holding;
|
||||
mod liquidity;
|
||||
mod pair;
|
||||
mod quote;
|
||||
mod quote_error;
|
||||
mod request;
|
||||
mod swap;
|
||||
mod token_holdings;
|
||||
@@ -18,12 +17,11 @@ mod tests;
|
||||
use std::{error::Error, fmt};
|
||||
|
||||
pub use request::{
|
||||
AddLiquidityPlanRequest, AddLiquidityQuoteRequest, ConfigIdRequest, ContextRequest,
|
||||
CreatePoolPlanRequest, CreatePoolQuoteRequest, FeeTiersRequest, PairIdsRequest, PoolIdRequest,
|
||||
ProgramIdRequest, RemoveLiquidityPlanRequest, RemoveLiquidityQuoteRequest, ResolvePoolRequest,
|
||||
AddLiquidityPlanRequest, AddLiquidityQuoteRequest, ConfigIdRequest, CreatePoolPlanRequest,
|
||||
CreatePoolQuoteRequest, FeeTiersRequest, PairIdsRequest, PoolIdRequest, ProgramIdRequest,
|
||||
RemoveLiquidityPlanRequest, RemoveLiquidityQuoteRequest, ResolvePoolRequest,
|
||||
ResolveTokensRequest, SwapExactInPlanRequest, SwapExactInQuoteRequest, SwapExactOutPlanRequest,
|
||||
SwapExactOutQuoteRequest, SwapPairRequest, SyncReservesPlanRequest, TokenHoldingsRequest,
|
||||
TokenIdsRequest,
|
||||
};
|
||||
use serde_json::Value;
|
||||
|
||||
@@ -68,22 +66,12 @@ pub fn config_id(request: ConfigIdRequest) -> AmmResult {
|
||||
config::config_id(request).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Discovers token definition IDs available to the active wallet and app.
|
||||
pub fn token_ids(request: TokenIdsRequest) -> AmmResult {
|
||||
context::token_ids(request).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Derives canonical accounts for one token pair.
|
||||
pub fn pair_ids(request: PairIdsRequest) -> AmmResult {
|
||||
pair::pair_ids(request).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Builds network, token, holding, and fee-tier context.
|
||||
pub fn context(request: ContextRequest) -> AmmResult {
|
||||
context::context(request).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Resolves an app-provided set of token ids into selector rows (lean successor to `context`).
|
||||
/// Resolves an app-provided set of token ids into liquidity selector rows.
|
||||
pub fn resolve_tokens(request: ResolveTokensRequest) -> AmmResult {
|
||||
context::resolve_tokens(request).map_err(Into::into)
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
use serde_json::{json, Value};
|
||||
|
||||
pub(super) fn issue(code: &str, message: &str, fields: &[&str], details: Value) -> Value {
|
||||
json!({
|
||||
"code": code,
|
||||
"message": message,
|
||||
"details": details,
|
||||
"recoverable": true,
|
||||
"blockingFields": fields,
|
||||
})
|
||||
}
|
||||
@@ -8,45 +8,9 @@ pub struct ConfigIdRequest {
|
||||
pub amm_program_id: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TokenIdsRequest {
|
||||
pub amm_program_id: String,
|
||||
pub config: AccountRead,
|
||||
#[serde(default)]
|
||||
pub wallet_accounts: Vec<AccountRead>,
|
||||
#[serde(default)]
|
||||
pub configured_token_ids: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub recent_token_ids: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub resolved_token_ids: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ContextRequest {
|
||||
pub network_id: String,
|
||||
pub network_fingerprint: String,
|
||||
pub amm_program_id: String,
|
||||
pub wallet_available: bool,
|
||||
pub config: AccountRead,
|
||||
#[serde(default)]
|
||||
pub wallet_accounts: Vec<AccountRead>,
|
||||
#[serde(default)]
|
||||
pub token_definitions: Vec<AccountRead>,
|
||||
#[serde(default)]
|
||||
pub configured_token_ids: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub recent_token_ids: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub resolved_token_ids: Vec<String>,
|
||||
}
|
||||
|
||||
/// Resolves an app-provided set of token ids into selector rows (the lean successor to
|
||||
/// `ContextRequest`). `token_ids` are hex — the module normalizes base58→hex and reads each
|
||||
/// definition into `token_definitions` (keyed by hex id) plus the wallet accounts; the FFI is
|
||||
/// stateless and reads nothing itself.
|
||||
/// Resolves an app-provided set of token ids into selector rows. `token_ids` are hex — the
|
||||
/// module normalizes base58→hex and reads each definition into `token_definitions` (keyed by
|
||||
/// hex id) plus the wallet accounts; the FFI is stateless and reads nothing itself.
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ResolveTokensRequest {
|
||||
|
||||
@@ -14,13 +14,12 @@ use token_core::{TokenDefinition, TokenHolding};
|
||||
use twap_oracle_core::compute_current_tick_account_pda;
|
||||
|
||||
use super::{
|
||||
context::{context, resolve_tokens, token_ids},
|
||||
context::resolve_tokens,
|
||||
holding::{select_holding, SelectedHolding},
|
||||
pair::{is_canonical_pair, pair_ids, PairIds},
|
||||
quote::{div_ceil_u256, minimum_opening_pair, Q64},
|
||||
swap::{swap_exact_in_plan, swap_exact_out_plan},
|
||||
ContextRequest, PairIdsRequest, ResolveTokensRequest, SwapExactInPlanRequest,
|
||||
SwapExactOutPlanRequest, TokenIdsRequest,
|
||||
PairIdsRequest, ResolveTokensRequest, SwapExactInPlanRequest, SwapExactOutPlanRequest,
|
||||
};
|
||||
use crate::{
|
||||
account::{account_id_hex, account_read, decode_account, program_id_bytes},
|
||||
@@ -190,107 +189,6 @@ fn pair_manifest_reports_unavailable_config_as_domain_error() {
|
||||
assert_eq!(result["code"], "config_unavailable");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn token_manifest_includes_compatible_wallet_holdings() {
|
||||
let config_id = compute_config_pda(AMM_PROGRAM);
|
||||
let configured = AccountId::new([1; 32]);
|
||||
let held = AccountId::new([2; 32]);
|
||||
let recent = AccountId::new([3; 32]);
|
||||
let resolved = AccountId::new([4; 32]);
|
||||
|
||||
let value = token_ids(TokenIdsRequest {
|
||||
amm_program_id: amm_program_id(),
|
||||
config: account_read(config_id, &config_account()),
|
||||
wallet_accounts: vec![account_read(
|
||||
AccountId::new([5; 32]),
|
||||
&token_holding(held, 9),
|
||||
)],
|
||||
configured_token_ids: vec![account_id_hex(configured)],
|
||||
recent_token_ids: vec![recent.to_string()],
|
||||
resolved_token_ids: vec![resolved.to_string()],
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
value["tokenIds"],
|
||||
json!([
|
||||
account_id_hex(configured),
|
||||
account_id_hex(held),
|
||||
account_id_hex(recent),
|
||||
account_id_hex(resolved),
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrong_program_holdings_do_not_contribute_token_candidates() {
|
||||
let config_id = compute_config_pda(AMM_PROGRAM);
|
||||
let config = config_account();
|
||||
let definition = AccountId::new([2; 32]);
|
||||
let wrong_owner_holding = account(
|
||||
[99; 8],
|
||||
Data::from(&TokenHolding::Fungible {
|
||||
definition_id: definition,
|
||||
balance: 9,
|
||||
}),
|
||||
);
|
||||
let wallet_accounts = vec![account_read(AccountId::new([3; 32]), &wrong_owner_holding)];
|
||||
|
||||
let manifest = token_ids(TokenIdsRequest {
|
||||
amm_program_id: amm_program_id(),
|
||||
config: account_read(config_id, &config),
|
||||
wallet_accounts: wallet_accounts.clone(),
|
||||
configured_token_ids: Vec::new(),
|
||||
recent_token_ids: Vec::new(),
|
||||
resolved_token_ids: Vec::new(),
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(manifest["tokenIds"], json!([]));
|
||||
|
||||
let value = context(ContextRequest {
|
||||
network_id: String::from("testnet"),
|
||||
network_fingerprint: String::from("block10:abc"),
|
||||
amm_program_id: amm_program_id(),
|
||||
wallet_available: true,
|
||||
config: account_read(config_id, &config),
|
||||
wallet_accounts,
|
||||
token_definitions: vec![account_read(
|
||||
definition,
|
||||
&token_definition("Token", 1_000_000),
|
||||
)],
|
||||
configured_token_ids: Vec::new(),
|
||||
recent_token_ids: Vec::new(),
|
||||
resolved_token_ids: Vec::new(),
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(value["tokens"], json!([]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn context_selects_tokens_without_holdings() {
|
||||
let token_id = AccountId::new([3; 32]);
|
||||
let config_id = compute_config_pda(AMM_PROGRAM);
|
||||
let value = context(ContextRequest {
|
||||
network_id: String::from("testnet"),
|
||||
network_fingerprint: String::from("block10:abc"),
|
||||
amm_program_id: amm_program_id(),
|
||||
wallet_available: true,
|
||||
config: account_read(config_id, &config_account()),
|
||||
wallet_accounts: Vec::new(),
|
||||
token_definitions: vec![account_read(
|
||||
token_id,
|
||||
&token_definition("Token", 1_000_000),
|
||||
)],
|
||||
configured_token_ids: vec![account_id_hex(token_id)],
|
||||
recent_token_ids: Vec::new(),
|
||||
resolved_token_ids: Vec::new(),
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(value["tokens"][0]["selectable"], true);
|
||||
assert_eq!(value["tokens"][0]["sources"], json!(["config"]));
|
||||
assert!(value["tokens"][0].get("holdingId").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_tokens_returns_lean_rows_held_first_and_omits_unresolvable() {
|
||||
let held = AccountId::new([2; 32]);
|
||||
|
||||
@@ -7,11 +7,11 @@ use serde::{de::DeserializeOwned, Serialize};
|
||||
|
||||
use crate::api::{
|
||||
self, AddLiquidityPlanRequest, AddLiquidityQuoteRequest, AmmApiError, AmmResult,
|
||||
ConfigIdRequest, ContextRequest, CreatePoolPlanRequest, CreatePoolQuoteRequest,
|
||||
FeeTiersRequest, PairIdsRequest, PoolIdRequest, ProgramIdRequest, RemoveLiquidityPlanRequest,
|
||||
ConfigIdRequest, CreatePoolPlanRequest, CreatePoolQuoteRequest, FeeTiersRequest,
|
||||
PairIdsRequest, PoolIdRequest, ProgramIdRequest, RemoveLiquidityPlanRequest,
|
||||
RemoveLiquidityQuoteRequest, ResolvePoolRequest, ResolveTokensRequest, SwapExactInPlanRequest,
|
||||
SwapExactInQuoteRequest, SwapExactOutPlanRequest, SwapExactOutQuoteRequest, SwapPairRequest,
|
||||
SyncReservesPlanRequest, TokenHoldingsRequest, TokenIdsRequest,
|
||||
SyncReservesPlanRequest, TokenHoldingsRequest,
|
||||
};
|
||||
|
||||
#[derive(Serialize)]
|
||||
@@ -85,21 +85,11 @@ pub extern "C" fn amm_config_id(request_json: *const c_char) -> *mut c_char {
|
||||
call::<ConfigIdRequest>(request_json, api::config_id)
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn amm_token_ids(request_json: *const c_char) -> *mut c_char {
|
||||
call::<TokenIdsRequest>(request_json, api::token_ids)
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn amm_pair_ids(request_json: *const c_char) -> *mut c_char {
|
||||
call::<PairIdsRequest>(request_json, api::pair_ids)
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn amm_context(request_json: *const c_char) -> *mut c_char {
|
||||
call::<ContextRequest>(request_json, api::context)
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn amm_resolve_tokens(request_json: *const c_char) -> *mut c_char {
|
||||
call::<ResolveTokensRequest>(request_json, api::resolve_tokens)
|
||||
|
||||
@@ -6,12 +6,11 @@ mod ffi;
|
||||
pub mod api;
|
||||
|
||||
pub use api::{
|
||||
config_id, context, create_pool_plan, create_pool_quote, fee_tiers, pair_ids, pool_id,
|
||||
program_id, resolve_pool, resolve_tokens, swap_exact_in_plan, swap_exact_in_quote,
|
||||
swap_exact_out_plan, swap_exact_out_quote, swap_pair, token_ids, AccountRead, AmmApiError,
|
||||
AmmResponse, AmmResult, ConfigIdRequest, ContextRequest, CreatePoolPlanRequest,
|
||||
CreatePoolQuoteRequest, FeeTiersRequest, PairIdsRequest, PoolIdRequest, ProgramIdRequest,
|
||||
ResolvePoolRequest, ResolveTokensRequest, SwapExactInPlanRequest, SwapExactInQuoteRequest,
|
||||
SwapExactOutPlanRequest, SwapExactOutQuoteRequest, SwapPairRequest, TokenIdsRequest,
|
||||
WalletAccount,
|
||||
config_id, create_pool_plan, create_pool_quote, fee_tiers, pair_ids, pool_id, program_id,
|
||||
resolve_pool, resolve_tokens, swap_exact_in_plan, swap_exact_in_quote, swap_exact_out_plan,
|
||||
swap_exact_out_quote, swap_pair, AccountRead, AmmApiError, AmmResponse, AmmResult,
|
||||
ConfigIdRequest, CreatePoolPlanRequest, CreatePoolQuoteRequest, FeeTiersRequest,
|
||||
PairIdsRequest, PoolIdRequest, ProgramIdRequest, ResolvePoolRequest, ResolveTokensRequest,
|
||||
SwapExactInPlanRequest, SwapExactInQuoteRequest, SwapExactOutPlanRequest,
|
||||
SwapExactOutQuoteRequest, SwapPairRequest, WalletAccount,
|
||||
};
|
||||
|
||||
@@ -237,32 +237,6 @@ json publicError(const std::string& code,
|
||||
};
|
||||
}
|
||||
|
||||
json contextState(const std::string& status,
|
||||
const std::string& network_id,
|
||||
const std::string& network_fingerprint,
|
||||
const std::string& code = {}) {
|
||||
json state = {
|
||||
{"status", status},
|
||||
{"networkId", network_id},
|
||||
{"networkFingerprint", network_fingerprint},
|
||||
{"tokens", json::array()},
|
||||
{"feeTiers", json::array()},
|
||||
{"warnings", json::array()},
|
||||
};
|
||||
if (!code.empty()) state["code"] = code;
|
||||
return state;
|
||||
}
|
||||
|
||||
// A json array of the strings at `obj[key]` (empty array when absent/wrong type).
|
||||
json stringArray(const json& obj, const char* key) {
|
||||
const auto it = obj.find(key);
|
||||
if (it == obj.end() || !it->is_array()) return json::array();
|
||||
json out = json::array();
|
||||
for (const auto& v : *it)
|
||||
if (v.is_string()) out.push_back(v);
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::vector<uint8_t> AmmModuleImpl::loadAmmElf() {
|
||||
@@ -288,37 +262,6 @@ std::string AmmModuleImpl::ammProgramId() {
|
||||
return jStr(r.value, "programId");
|
||||
}
|
||||
|
||||
AmmModuleImpl::Network AmmModuleImpl::network() {
|
||||
// AMM_PROGRAM_BIN / TOKENS_CONFIG are fixed for the process lifetime and this
|
||||
// runs on the hot reply path, so resolve the program id + token ids once.
|
||||
if (!networkResolved) {
|
||||
const std::string id = ammProgramId();
|
||||
if (id.empty()) {
|
||||
// Not resolvable yet (AMM_PROGRAM_BIN unset/unreadable). Don't cache a
|
||||
// transient miss — a later call retries.
|
||||
Network net;
|
||||
net.status = "config_missing";
|
||||
return net;
|
||||
}
|
||||
programId = id;
|
||||
tokenIds.clear();
|
||||
for (const auto& token : tokenList()) {
|
||||
const std::string token_id = jStr(token, "definitionId");
|
||||
if (!token_id.empty()) tokenIds.push_back(token_id);
|
||||
}
|
||||
networkResolved = true;
|
||||
}
|
||||
|
||||
Network net;
|
||||
net.amm_program_id = programId;
|
||||
// The program id changes per deployment, so it doubles as the network
|
||||
// fingerprint (a quote can't be replayed against a different program).
|
||||
net.fingerprint = programId;
|
||||
net.token_ids = tokenIds;
|
||||
net.status = "ready";
|
||||
return net;
|
||||
}
|
||||
|
||||
std::string AmmModuleImpl::normalizeAccountId(const std::string& id) {
|
||||
size_t start = 0;
|
||||
size_t end = id.size();
|
||||
@@ -369,21 +312,15 @@ nlohmann::json AmmModuleImpl::readPublicAccount(const std::string& account_id) {
|
||||
return result;
|
||||
}
|
||||
|
||||
nlohmann::json AmmModuleImpl::walletAccountReads(bool wallet_open, bool refresh) {
|
||||
if (!wallet_open) {
|
||||
walletAccounts = json(); // invalidate — nothing to read while closed
|
||||
return json::array();
|
||||
}
|
||||
// Each readPublicAccount is a live sequencer round-trip, so serve the cached
|
||||
// set unless the caller forces a reload (submit / explicit UI refresh).
|
||||
if (!refresh && !walletAccounts.is_null())
|
||||
return walletAccounts;
|
||||
nlohmann::json AmmModuleImpl::walletAccountReads(bool wallet_open) {
|
||||
if (!wallet_open)
|
||||
return json::array(); // nothing to read while closed
|
||||
|
||||
// Normalize the wallet module's [any] return (a vector or a json array)
|
||||
// through json so we can iterate/type-check it uniformly.
|
||||
json accounts = modules().logos_execution_zone.list_accounts();
|
||||
if (!accounts.is_array())
|
||||
return json::array(); // transient — don't cache
|
||||
return json::array();
|
||||
|
||||
json out = json::array();
|
||||
for (const auto& entry : accounts) {
|
||||
@@ -394,13 +331,12 @@ nlohmann::json AmmModuleImpl::walletAccountReads(bool wallet_open, bool refresh)
|
||||
if (id.empty()) continue;
|
||||
out.push_back(readPublicAccount(id));
|
||||
}
|
||||
walletAccounts = out;
|
||||
return out;
|
||||
}
|
||||
|
||||
nlohmann::json AmmModuleImpl::readConfig(const Network& net) {
|
||||
nlohmann::json AmmModuleImpl::readConfig(const std::string& amm_program_id) {
|
||||
const FfiResult configResult =
|
||||
call(amm_config_id, json{{"ammProgramId", net.amm_program_id}});
|
||||
call(amm_config_id, json{{"ammProgramId", amm_program_id}});
|
||||
if (!configResult.ok) return json(); // null: config_id op failed
|
||||
return readPublicAccount(jStr(configResult.value, "configId"));
|
||||
}
|
||||
@@ -416,12 +352,12 @@ LogosMap AmmModuleImpl::resolvePool(const std::string& def_a_hex,
|
||||
return LogosMap{{"exists", false}, {"error", error}};
|
||||
};
|
||||
|
||||
const Network net = network();
|
||||
if (net.status != "ready")
|
||||
// config_missing == no program id from AMM_PROGRAM_BIN (unset/unreadable/bad).
|
||||
const std::string amm_program_id = ammProgramId();
|
||||
if (amm_program_id.empty())
|
||||
// no program id from AMM_PROGRAM_BIN (unset/unreadable/bad).
|
||||
return failed("no_program_bin");
|
||||
|
||||
const json config = readConfig(net);
|
||||
const json config = readConfig(amm_program_id);
|
||||
if (config.is_null())
|
||||
return failed("bad_config"); // amm_config_id op failed (malformed program id)
|
||||
|
||||
@@ -431,7 +367,7 @@ LogosMap AmmModuleImpl::resolvePool(const std::string& def_a_hex,
|
||||
const std::string token_b = normalizeAccountId(def_b_hex);
|
||||
|
||||
const FfiResult pairResult = call(amm_swap_pair, json{
|
||||
{"ammProgramId", net.amm_program_id},
|
||||
{"ammProgramId", amm_program_id},
|
||||
{"tokenInId", token_a},
|
||||
{"tokenOutId", token_b},
|
||||
{"config", config},
|
||||
@@ -584,13 +520,13 @@ std::string AmmModuleImpl::swapExactInput(const std::string& def_a_hex,
|
||||
return {};
|
||||
}
|
||||
|
||||
const Network net = network();
|
||||
if (net.status != "ready") {
|
||||
AMM_TRACE("swapExactInput: FAIL network not ready (" << net.status << ")");
|
||||
const std::string amm_program_id = ammProgramId();
|
||||
if (amm_program_id.empty()) {
|
||||
AMM_TRACE("swapExactInput: FAIL no program id (AMM_PROGRAM_BIN unset/unreadable)");
|
||||
return {};
|
||||
}
|
||||
|
||||
const json config = readConfig(net);
|
||||
const json config = readConfig(amm_program_id);
|
||||
if (config.is_null()) {
|
||||
AMM_TRACE("swapExactInput: FAIL config_id op failed");
|
||||
return {};
|
||||
@@ -599,7 +535,7 @@ std::string AmmModuleImpl::swapExactInput(const std::string& def_a_hex,
|
||||
// Read the pool so the plan can use its stored vault ids (the guest asserts
|
||||
// the vaults in the pool's creation order — see amm_swap_exact_in_plan).
|
||||
const FfiResult poolId = call(amm_pool_id, json{
|
||||
{"ammProgramId", net.amm_program_id},
|
||||
{"ammProgramId", amm_program_id},
|
||||
{"tokenInId", def_a_hex},
|
||||
{"tokenOutId", def_b_hex},
|
||||
});
|
||||
@@ -613,7 +549,7 @@ std::string AmmModuleImpl::swapExactInput(const std::string& def_a_hex,
|
||||
// amm_swap_exact_in_plan resolves the pool accounts, encodes SwapExactInput,
|
||||
// and returns a ready-to-submit plan.
|
||||
const FfiResult planResult = call(amm_swap_exact_in_plan, json{
|
||||
{"ammProgramId", net.amm_program_id},
|
||||
{"ammProgramId", amm_program_id},
|
||||
{"tokenInId", def_a_hex},
|
||||
{"tokenOutId", def_b_hex},
|
||||
{"config", config},
|
||||
@@ -667,13 +603,13 @@ std::string AmmModuleImpl::swapExactOutput(const std::string& def_a_hex,
|
||||
return {};
|
||||
}
|
||||
|
||||
const Network net = network();
|
||||
if (net.status != "ready") {
|
||||
AMM_TRACE("swapExactOutput: FAIL network not ready (" << net.status << ")");
|
||||
const std::string amm_program_id = ammProgramId();
|
||||
if (amm_program_id.empty()) {
|
||||
AMM_TRACE("swapExactOutput: FAIL no program id (AMM_PROGRAM_BIN unset/unreadable)");
|
||||
return {};
|
||||
}
|
||||
|
||||
const json config = readConfig(net);
|
||||
const json config = readConfig(amm_program_id);
|
||||
if (config.is_null()) {
|
||||
AMM_TRACE("swapExactOutput: FAIL config_id op failed");
|
||||
return {};
|
||||
@@ -682,7 +618,7 @@ std::string AmmModuleImpl::swapExactOutput(const std::string& def_a_hex,
|
||||
// Read the pool so the plan can use its stored vault ids (the guest asserts
|
||||
// the vaults in the pool's creation order — see amm_swap_exact_out_plan).
|
||||
const FfiResult poolId = call(amm_pool_id, json{
|
||||
{"ammProgramId", net.amm_program_id},
|
||||
{"ammProgramId", amm_program_id},
|
||||
{"tokenInId", def_a_hex},
|
||||
{"tokenOutId", def_b_hex},
|
||||
});
|
||||
@@ -696,7 +632,7 @@ std::string AmmModuleImpl::swapExactOutput(const std::string& def_a_hex,
|
||||
// amm_swap_exact_out_plan resolves the pool accounts, encodes SwapExactOutput,
|
||||
// and returns a ready-to-submit plan.
|
||||
const FfiResult planResult = call(amm_swap_exact_out_plan, json{
|
||||
{"ammProgramId", net.amm_program_id},
|
||||
{"ammProgramId", amm_program_id},
|
||||
{"tokenInId", def_a_hex},
|
||||
{"tokenOutId", def_b_hex},
|
||||
{"config", config},
|
||||
@@ -739,7 +675,7 @@ LogosMap AmmModuleImpl::createPoolQuote(const LogosMap& request) {
|
||||
};
|
||||
|
||||
// Pure preview — no program id / chain reads / fee. Normalize the pair to hex (the
|
||||
// liquidity UI still sources base58 ids from newPositionContext; transitional).
|
||||
// liquidity UI sources base58 ids from resolveTokens).
|
||||
const std::string token_a = normalizeAccountId(jStr(request, "tokenAId"));
|
||||
const std::string token_b = normalizeAccountId(jStr(request, "tokenBId"));
|
||||
if (token_a.empty() || token_b.empty())
|
||||
@@ -1297,7 +1233,7 @@ LogosList AmmModuleImpl::tokenHoldings(bool wallet_open) {
|
||||
const json config = readPublicAccount(jStr(configResult.value, "configId"));
|
||||
|
||||
// Fresh wallet read each call — the selector wants current holdings/balances.
|
||||
const json wallet_accounts = walletAccountReads(wallet_open, /*refresh=*/true);
|
||||
const json wallet_accounts = walletAccountReads(wallet_open);
|
||||
|
||||
const FfiResult result = call(amm_token_holdings, json{
|
||||
{"ammProgramId", amm_program_id},
|
||||
@@ -1365,7 +1301,7 @@ LogosList AmmModuleImpl::resolveTokens(const LogosMap& request, bool wallet_open
|
||||
}
|
||||
|
||||
// Fresh wallet read — the selector wants current holdings/balances.
|
||||
const json wallet_accounts = walletAccountReads(wallet_open, /*refresh=*/true);
|
||||
const json wallet_accounts = walletAccountReads(wallet_open);
|
||||
|
||||
const FfiResult result = call(amm_resolve_tokens, json{
|
||||
{"ammProgramId", amm_program_id},
|
||||
@@ -1384,60 +1320,3 @@ LogosList AmmModuleImpl::resolveTokens(const LogosMap& request, bool wallet_open
|
||||
return out;
|
||||
}
|
||||
|
||||
LogosMap AmmModuleImpl::newPositionContext(const LogosMap& request,
|
||||
bool wallet_open,
|
||||
bool refresh_wallet_accounts) {
|
||||
const Network net = network();
|
||||
if (net.status != "ready")
|
||||
return contextState(net.status, net.id, net.fingerprint);
|
||||
|
||||
const json walletAccounts = walletAccountReads(wallet_open, refresh_wallet_accounts);
|
||||
|
||||
const FfiResult configResult =
|
||||
call(amm_config_id, json{{"ammProgramId", net.amm_program_id}});
|
||||
if (!configResult.ok)
|
||||
return contextState("error", net.id, net.fingerprint, "backend_error");
|
||||
const json config = readPublicAccount(jStr(configResult.value, "configId"));
|
||||
|
||||
json configured = json::array();
|
||||
for (const auto& id : net.token_ids) configured.push_back(id);
|
||||
const json recent = stringArray(request, "recentTokenIds");
|
||||
const json resolved = stringArray(request, "resolvedTokenIds");
|
||||
|
||||
const FfiResult tokenResult = call(amm_token_ids, json{
|
||||
{"ammProgramId", net.amm_program_id},
|
||||
{"config", config},
|
||||
{"walletAccounts", walletAccounts},
|
||||
{"configuredTokenIds", configured},
|
||||
{"recentTokenIds", recent},
|
||||
{"resolvedTokenIds", resolved},
|
||||
});
|
||||
const json tokenManifest = tokenResult.value;
|
||||
if (!tokenResult.ok || jStr(tokenManifest, "status") != "ok") {
|
||||
const std::string code =
|
||||
tokenResult.ok ? jStr(tokenManifest, "code") : std::string("backend_error");
|
||||
return contextState("error", net.id, net.fingerprint,
|
||||
code.empty() ? "backend_error" : code);
|
||||
}
|
||||
|
||||
json definitions = json::array();
|
||||
for (const auto& id : tokenManifest.value("tokenIds", json::array()))
|
||||
if (id.is_string()) definitions.push_back(readPublicAccount(id.get<std::string>()));
|
||||
|
||||
const FfiResult contextResult = call(amm_context, json{
|
||||
{"networkId", net.id},
|
||||
{"networkFingerprint", net.fingerprint},
|
||||
{"ammProgramId", net.amm_program_id},
|
||||
{"walletAvailable", wallet_open},
|
||||
{"config", config},
|
||||
{"walletAccounts", walletAccounts},
|
||||
{"tokenDefinitions", definitions},
|
||||
{"configuredTokenIds", configured},
|
||||
{"recentTokenIds", recent},
|
||||
{"resolvedTokenIds", resolved},
|
||||
});
|
||||
return contextResult.ok
|
||||
? contextResult.value
|
||||
: contextState("error", net.id, net.fingerprint, "backend_error");
|
||||
}
|
||||
|
||||
|
||||
@@ -231,33 +231,7 @@ public:
|
||||
/// universal-module glue only supports map/scalar inputs.)
|
||||
LogosList resolveTokens(const LogosMap& request, bool wallet_open);
|
||||
|
||||
/// New-position (add-liquidity) view state: reads the AMM config + the
|
||||
/// user's wallet accounts and returns the new-position context map the
|
||||
/// UI renders (available tokens, fee tiers, warnings). `wallet_open` gates
|
||||
/// whether wallet accounts are included; `refresh_wallet_accounts` forces a
|
||||
/// fresh read rather than a cached one.
|
||||
LogosMap newPositionContext(const LogosMap& request,
|
||||
bool wallet_open,
|
||||
bool refresh_wallet_accounts);
|
||||
|
||||
private:
|
||||
// Off-chain "network" context, derived from the process env (the same
|
||||
// sources the app backend used): AMM deployment id from AMM_PROGRAM_BIN,
|
||||
// configured token set from TOKENS_CONFIG. `status` is "ready" once the
|
||||
// program id resolves, else "config_missing".
|
||||
struct Network {
|
||||
std::string id = "lez";
|
||||
std::string status;
|
||||
std::string fingerprint; // == amm_program_id (binds a quote to the deploy)
|
||||
std::string amm_program_id; // 64-char lowercase hex
|
||||
std::vector<std::string> token_ids;
|
||||
};
|
||||
// AMM_PROGRAM_BIN / TOKENS_CONFIG are fixed for the process lifetime, and
|
||||
// this runs on the hot reply path (every op), so it resolves the program id
|
||||
// + token ids ONCE and caches them (networkResolved). Cached only on
|
||||
// success, so a startup miss (bin not readable yet) retries.
|
||||
Network network();
|
||||
|
||||
// 64-char lowercase-hex AMM program id via the amm_ffi `program_id` op
|
||||
// over the AMM_PROGRAM_BIN bytes (empty if unset/unreadable/bad).
|
||||
std::string ammProgramId();
|
||||
@@ -272,7 +246,7 @@ private:
|
||||
// Derives the config account id (amm_config_id) and reads it, returning the
|
||||
// account-read shape the amm_ffi ops embed. Null json when the config_id
|
||||
// op itself fails (readPublicAccount always yields at least {id,status}).
|
||||
nlohmann::json readConfig(const Network& net);
|
||||
nlohmann::json readConfig(const std::string& amm_program_id);
|
||||
|
||||
// Reads a public account through the wallet module and returns the
|
||||
// { id, status, account:{ program_owner, balance, nonce, data } } shape the
|
||||
@@ -280,21 +254,7 @@ private:
|
||||
// omitted when the read has no data (uninitialized/nonexistent).
|
||||
nlohmann::json readPublicAccount(const std::string& account_id);
|
||||
|
||||
// The user's own public account reads (empty when the wallet is closed).
|
||||
// Cached across calls (walletAccounts); `refresh` reloads instead of serving
|
||||
// the cache — quote reuses it, submit forces fresh — since each read is a
|
||||
// live sequencer round-trip.
|
||||
nlohmann::json walletAccountReads(bool wallet_open, bool refresh);
|
||||
|
||||
// Process-lifetime network config, resolved once (see network()). Serialized
|
||||
// module dispatch means no locking is needed; there is no invalidation, as
|
||||
// runtime env reload is not supported.
|
||||
bool networkResolved = false;
|
||||
std::string programId;
|
||||
std::vector<std::string> tokenIds;
|
||||
|
||||
// Cache of the user's public account reads for the context/quote path (each
|
||||
// read is a live sequencer round-trip). Null until first read; `refresh`
|
||||
// reloads it, and it's dropped when the wallet closes. See walletAccountReads.
|
||||
nlohmann::json walletAccounts;
|
||||
// The user's own public account reads, fresh each call (empty when the wallet
|
||||
// is closed). Each read is a live sequencer round-trip.
|
||||
nlohmann::json walletAccountReads(bool wallet_open);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user