mirror of
https://github.com/logos-blockchain/lez-programs.git
synced 2026-08-25 06:01:11 +00:00
feat(modules/amm): add swap_exact_out_plan op and module swapExactOutput
Server-side SwapExactOutput submission, mirroring the exact-in path. The
swap_exact_out_plan FFI op resolves the pool via the shared derive_pair,
encodes amm_core::Instruction::SwapExactOutput { exact_amount_out,
max_amount_in, deadline }, and returns the same four tx-submission fields as
swap_exact_in_plan (programId, accountIds, signingRequirements, instruction)
in the identical fixed 8-account IDL order — only the user's input holding
signs. Recoverable domain failures (same_token_pair, config_unavailable) go
through the FFI envelope as Err. C export amm_swap_exact_out_plan (cbindgen
header regenerated).
The module swapExactOutput(defA, defB, userIn, userOut, amountOut, maxIn,
deadline) method reads the AMM config, calls the op, and submits the plan via
send_generic_public_transaction, returning the tx hash (empty string on
failure). Same argument conventions and I/O-free split as swapExactInput.
Not yet wired into AmmUiBackend/QML — the SwapCard rewire is a follow-up. The
existing swap UI path is untouched, so nothing breaks.
This commit is contained in:
@@ -38,6 +38,8 @@ char *amm_swap_exact_out_quote(const char *request_json);
|
||||
|
||||
char *amm_swap_exact_in_plan(const char *request_json);
|
||||
|
||||
char *amm_swap_exact_out_plan(const char *request_json);
|
||||
|
||||
char *amm_program_id(const char *request_json);
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,7 +23,8 @@ use std::{error::Error, fmt};
|
||||
pub use request::{
|
||||
ConfigIdRequest, ContextRequest, PairIdsRequest, PairSnapshot, PlanRequest, PoolIdRequest,
|
||||
PositionRequest, ProgramIdRequest, QuoteRequest, ResolvePoolRequest, SwapExactInPlanRequest,
|
||||
SwapExactInQuoteRequest, SwapExactOutQuoteRequest, SwapPairRequest, TokenIdsRequest,
|
||||
SwapExactInQuoteRequest, SwapExactOutPlanRequest, SwapExactOutQuoteRequest, SwapPairRequest,
|
||||
TokenIdsRequest,
|
||||
};
|
||||
use serde_json::Value;
|
||||
|
||||
@@ -123,6 +124,11 @@ pub fn swap_exact_in_plan(request: SwapExactInPlanRequest) -> AmmResult {
|
||||
swap::swap_exact_in_plan(request).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Builds the `SwapExactOutput` wallet submission for a token pair.
|
||||
pub fn swap_exact_out_plan(request: SwapExactOutPlanRequest) -> AmmResult {
|
||||
swap::swap_exact_out_plan(request).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Derives the AMM `ProgramId` (Image ID) from a deployed program binary.
|
||||
pub fn program_id(request: ProgramIdRequest) -> AmmResult {
|
||||
swap::program_id(request).map_err(Into::into)
|
||||
|
||||
@@ -117,6 +117,24 @@ pub struct SwapExactInPlanRequest {
|
||||
pub pool_data: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SwapExactOutPlanRequest {
|
||||
pub amm_program_id: String,
|
||||
pub token_in_id: String,
|
||||
pub token_out_id: String,
|
||||
pub config: AccountRead,
|
||||
pub user_input_holding_id: String,
|
||||
pub user_output_holding_id: String,
|
||||
pub amount_out: String,
|
||||
pub max_in: String,
|
||||
pub deadline_ms: String,
|
||||
/// Pool account data (hex Borsh `PoolDefinition`) — its stored `vault_a_id` /
|
||||
/// `vault_b_id` are used verbatim (the guest asserts the vaults in the pool's
|
||||
/// creation order, which needn't match the canonical token order).
|
||||
pub pool_data: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProgramIdRequest {
|
||||
|
||||
@@ -14,7 +14,7 @@ use serde_json::{json, Value};
|
||||
use super::{
|
||||
pair::{derive_pair, is_canonical_pair},
|
||||
PoolIdRequest, ProgramIdRequest, ResolvePoolRequest, SwapExactInPlanRequest,
|
||||
SwapExactInQuoteRequest, SwapExactOutQuoteRequest, SwapPairRequest,
|
||||
SwapExactInQuoteRequest, SwapExactOutPlanRequest, SwapExactOutQuoteRequest, SwapPairRequest,
|
||||
};
|
||||
use crate::account::{
|
||||
account_id_from_hex, account_id_hex, decode_account, parse_program_id, program_id_bytes,
|
||||
@@ -268,13 +268,16 @@ pub(super) fn swap_exact_out_quote(request: SwapExactOutQuoteRequest) -> Result<
|
||||
}
|
||||
|
||||
/// Builds the `SwapExactInput` submission for a pair: the fixed 8-account IDL
|
||||
/// order (vaults canonical, only the user's input holding signs) and the
|
||||
/// instruction words (`risc0_zkvm::serde` — the same encoding the guest
|
||||
/// decodes). Returns exactly the tx-submission fields (`programId`,
|
||||
/// `accountIds`, `signingRequirements`, `instruction`; the deadline is already
|
||||
/// baked into the instruction bytes). Recoverable domain failures
|
||||
/// (`same_token_pair`, `config_unavailable`) surface through the FFI envelope
|
||||
/// as `{ ok: false, error }`.
|
||||
/// order (vaults from the pool's stored ids, only the user's input holding
|
||||
/// signs) and the instruction words (`risc0_zkvm::serde` — the same encoding
|
||||
/// the guest decodes). On success returns exactly the tx-submission fields
|
||||
/// (`programId`, `accountIds`, `signingRequirements`, `instruction`; the
|
||||
/// deadline is already baked into the instruction bytes). Every recoverable
|
||||
/// domain failure fails CLOSED as `Err` (surfaced through the FFI envelope as
|
||||
/// `{ ok: false, error }`): `same_token_pair`, `config_unavailable`, and a
|
||||
/// missing/undecodable pool as `Err("no_pool")` — so the caller stops on
|
||||
/// `planResult.ok` rather than submit a tx with empty account/instruction
|
||||
/// vectors.
|
||||
pub(super) fn swap_exact_in_plan(request: SwapExactInPlanRequest) -> Result<Value, String> {
|
||||
let amm_program = parse_program_id(&request.amm_program_id)?;
|
||||
let token_in = account_id_from_hex(&request.token_in_id, "token in id")?;
|
||||
@@ -296,7 +299,7 @@ pub(super) fn swap_exact_in_plan(request: SwapExactInPlanRequest) -> Result<Valu
|
||||
.ok()
|
||||
.and_then(|bytes| borsh::from_slice::<PoolDefinition>(&bytes).ok())
|
||||
else {
|
||||
return Ok(json!({ "status": "error", "code": "no_pool" }));
|
||||
return Err(String::from("no_pool"));
|
||||
};
|
||||
|
||||
let user_input_holding =
|
||||
@@ -336,6 +339,73 @@ pub(super) fn swap_exact_in_plan(request: SwapExactInPlanRequest) -> Result<Valu
|
||||
}))
|
||||
}
|
||||
|
||||
/// Builds the `SwapExactOutput` submission for a pair: the same fixed 8-account
|
||||
/// IDL order and error contract as `swap_exact_in_plan` — every recoverable
|
||||
/// failure fails closed as `Err` (`same_token_pair`, `config_unavailable`, and
|
||||
/// `Err("no_pool")` for a missing/undecodable pool). Only the encoded
|
||||
/// instruction differs, carrying `exact_amount_out` / `max_amount_in` in place
|
||||
/// of the exact-in bounds.
|
||||
pub(super) fn swap_exact_out_plan(request: SwapExactOutPlanRequest) -> Result<Value, String> {
|
||||
let amm_program = parse_program_id(&request.amm_program_id)?;
|
||||
let token_in = account_id_from_hex(&request.token_in_id, "token in id")?;
|
||||
let token_out = account_id_from_hex(&request.token_out_id, "token out id")?;
|
||||
if token_in == token_out {
|
||||
return Err(String::from("same_token_pair"));
|
||||
}
|
||||
let (token_a, token_b) = canonical_pair(token_in, token_out);
|
||||
let Ok(pair) = derive_pair(amm_program, token_a, token_b, &request.config) else {
|
||||
return Err(String::from("config_unavailable"));
|
||||
};
|
||||
|
||||
// Take the vaults from the pool's STORED ids, in its `def_a`/`def_b` (creation)
|
||||
// order — the guest asserts the provided vaults against `pool.vault_a_id` /
|
||||
// `vault_b_id`, which needn't match `derive_pair`'s canonical order for a
|
||||
// non-canonically created pool. (`pair.pool`/`current_tick`/`clock` are
|
||||
// order-independent, so they still come from `derive_pair`.)
|
||||
let Some(pool) = hex::decode(&request.pool_data)
|
||||
.ok()
|
||||
.and_then(|bytes| borsh::from_slice::<PoolDefinition>(&bytes).ok())
|
||||
else {
|
||||
return Err(String::from("no_pool"));
|
||||
};
|
||||
|
||||
let user_input_holding =
|
||||
account_id_from_hex(&request.user_input_holding_id, "user input holding id")?;
|
||||
let user_output_holding =
|
||||
account_id_from_hex(&request.user_output_holding_id, "user output holding id")?;
|
||||
|
||||
let exact_amount_out = parse_u128(&request.amount_out, "amountOut")?;
|
||||
let max_amount_in = parse_u128(&request.max_in, "maxIn")?;
|
||||
let deadline = parse_u64(&request.deadline_ms, "deadlineMs")?;
|
||||
|
||||
let instruction = risc0_zkvm::serde::to_vec(&amm_core::Instruction::SwapExactOutput {
|
||||
exact_amount_out,
|
||||
max_amount_in,
|
||||
deadline,
|
||||
})
|
||||
.map_err(|error| format!("instruction serialization failed: {error}"))?;
|
||||
|
||||
// Fixed IDL account order for SwapExactOutput; only user_input_holding signs.
|
||||
let account_ids = [
|
||||
pair.config,
|
||||
pair.pool,
|
||||
pool.vault_a_id,
|
||||
pool.vault_b_id,
|
||||
user_input_holding,
|
||||
user_output_holding,
|
||||
pair.current_tick,
|
||||
pair.clock,
|
||||
];
|
||||
let signing_requirements = [false, false, false, false, true, false, false, false];
|
||||
|
||||
Ok(json!({
|
||||
"programId": request.amm_program_id,
|
||||
"accountIds": account_ids.into_iter().map(account_id_hex).collect::<Vec<_>>(),
|
||||
"signingRequirements": signing_requirements,
|
||||
"instruction": instruction,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Computes the AMM `ProgramId` (RISC Zero Image ID) of a deployed program
|
||||
/// binary. `elf` is the hex-encoded `.bin` (a RISC Zero `ProgramBinary`, not a
|
||||
/// raw ELF) — decoded, image-id computed, returned as 64-char lowercase hex.
|
||||
@@ -478,6 +548,32 @@ mod tests {
|
||||
assert_eq!(plan_error, "config_unavailable");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn swap_exact_out_plan_routes_same_token_through_the_envelope() {
|
||||
let program = "00".repeat(32);
|
||||
let same = "aa".repeat(32);
|
||||
let dummy_config = AccountRead {
|
||||
id: String::new(),
|
||||
status: String::from("read_failed"),
|
||||
account: None,
|
||||
};
|
||||
|
||||
let plan_error = swap_exact_out_plan(SwapExactOutPlanRequest {
|
||||
amm_program_id: program,
|
||||
token_in_id: same.clone(),
|
||||
token_out_id: same,
|
||||
config: dummy_config,
|
||||
user_input_holding_id: String::new(),
|
||||
user_output_holding_id: String::new(),
|
||||
amount_out: String::new(),
|
||||
max_in: String::new(),
|
||||
deadline_ms: String::new(),
|
||||
pool_data: String::new(),
|
||||
})
|
||||
.unwrap_err();
|
||||
assert_eq!(plan_error, "same_token_pair");
|
||||
}
|
||||
|
||||
fn pool_data_hex(pool: &PoolDefinition) -> String {
|
||||
hex::encode(borsh::to_vec(pool).unwrap())
|
||||
}
|
||||
|
||||
@@ -22,9 +22,9 @@ use super::{
|
||||
plan::plan,
|
||||
position::AccountPlanHoldings,
|
||||
quote::{div_ceil_u256, minimum_opening_pair, quote, Q64},
|
||||
swap::swap_exact_in_plan,
|
||||
swap::{swap_exact_in_plan, swap_exact_out_plan},
|
||||
ContextRequest, PairIdsRequest, PairSnapshot, PlanRequest, PositionRequest, QuoteRequest,
|
||||
SwapExactInPlanRequest, TokenIdsRequest,
|
||||
SwapExactInPlanRequest, SwapExactOutPlanRequest, TokenIdsRequest,
|
||||
};
|
||||
use crate::{
|
||||
account::{account_id_hex, account_read, decode_account, parse_base58_id, program_id_bytes},
|
||||
@@ -752,3 +752,73 @@ fn swap_plan_uses_the_pool_stored_vaults_not_canonical_order() {
|
||||
compute_vault_pda(AMM_PROGRAM, pool_id, token_large)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn swap_exact_in_plan_missing_pool_fails_closed_with_err() {
|
||||
// A valid config (so derive_pair succeeds) but undecodable pool_data must
|
||||
// surface as Err("no_pool") — NOT an Ok envelope. The FFI wraps Ok as
|
||||
// { ok: true, .. }, so an Ok envelope would leave AmmModuleImpl's
|
||||
// `planResult.ok` true and let it submit a tx with empty account/instruction
|
||||
// vectors. Failing closed matches swap_exact_out_plan.
|
||||
let token_a = AccountId::new([1; 32]);
|
||||
let token_b = AccountId::new([2; 32]);
|
||||
let holding = AccountId::new([9; 32]);
|
||||
let err = swap_exact_in_plan(SwapExactInPlanRequest {
|
||||
amm_program_id: amm_program_id(),
|
||||
token_in_id: account_id_hex(token_a),
|
||||
token_out_id: account_id_hex(token_b),
|
||||
config: account_read(compute_config_pda(AMM_PROGRAM), &config_account()),
|
||||
user_input_holding_id: account_id_hex(holding),
|
||||
user_output_holding_id: account_id_hex(holding),
|
||||
amount_in: String::from("100"),
|
||||
min_out: String::from("0"),
|
||||
deadline_ms: String::from("0"),
|
||||
pool_data: String::new(), // absent/undecodable
|
||||
})
|
||||
.unwrap_err();
|
||||
assert_eq!(err, "no_pool");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn swap_exact_out_plan_uses_the_pool_stored_vaults_not_canonical_order() {
|
||||
// Same non-canonical pool as the exact-input case: the exact-output plan must
|
||||
// likewise emit the pool's stored vaults, not the canonical derivation.
|
||||
let token_small = AccountId::new([1; 32]);
|
||||
let token_large = AccountId::new([2; 32]);
|
||||
assert!(is_canonical_pair(token_large, token_small)); // large is canonical token_a
|
||||
|
||||
let pool_id = compute_pool_pda(AMM_PROGRAM, token_small, token_large);
|
||||
let pool = PoolDefinition {
|
||||
definition_token_a_id: token_small, // stored non-canonically (small first)
|
||||
definition_token_b_id: token_large,
|
||||
vault_a_id: compute_vault_pda(AMM_PROGRAM, pool_id, token_small),
|
||||
vault_b_id: compute_vault_pda(AMM_PROGRAM, pool_id, token_large),
|
||||
liquidity_pool_id: compute_liquidity_token_pda(AMM_PROGRAM, pool_id),
|
||||
liquidity_pool_supply: 1_000,
|
||||
reserve_a: 1_000,
|
||||
reserve_b: 1_000,
|
||||
fees: 30,
|
||||
};
|
||||
|
||||
let holding = AccountId::new([9; 32]);
|
||||
let plan = swap_exact_out_plan(SwapExactOutPlanRequest {
|
||||
amm_program_id: amm_program_id(),
|
||||
token_in_id: account_id_hex(token_small),
|
||||
token_out_id: account_id_hex(token_large),
|
||||
config: account_read(compute_config_pda(AMM_PROGRAM), &config_account()),
|
||||
user_input_holding_id: account_id_hex(holding),
|
||||
user_output_holding_id: account_id_hex(holding),
|
||||
amount_out: String::from("100"),
|
||||
max_in: String::from("1000"),
|
||||
deadline_ms: String::from("0"),
|
||||
pool_data: hex::encode(borsh::to_vec(&pool).unwrap()),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(plan["accountIds"][2], account_id_hex(pool.vault_a_id));
|
||||
assert_eq!(plan["accountIds"][3], account_id_hex(pool.vault_b_id));
|
||||
assert_ne!(
|
||||
pool.vault_a_id,
|
||||
compute_vault_pda(AMM_PROGRAM, pool_id, token_large)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,8 @@ use serde::{de::DeserializeOwned, Serialize};
|
||||
use crate::api::{
|
||||
self, AmmApiError, AmmResult, ConfigIdRequest, ContextRequest, PairIdsRequest, PlanRequest,
|
||||
PoolIdRequest, ProgramIdRequest, QuoteRequest, ResolvePoolRequest, SwapExactInPlanRequest,
|
||||
SwapExactInQuoteRequest, SwapExactOutQuoteRequest, SwapPairRequest, TokenIdsRequest,
|
||||
SwapExactInQuoteRequest, SwapExactOutPlanRequest, SwapExactOutQuoteRequest, SwapPairRequest,
|
||||
TokenIdsRequest,
|
||||
};
|
||||
|
||||
#[derive(Serialize)]
|
||||
@@ -137,6 +138,11 @@ pub extern "C" fn amm_swap_exact_in_plan(request_json: *const c_char) -> *mut c_
|
||||
call::<SwapExactInPlanRequest>(request_json, api::swap_exact_in_plan)
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn amm_swap_exact_out_plan(request_json: *const c_char) -> *mut c_char {
|
||||
call::<SwapExactOutPlanRequest>(request_json, api::swap_exact_out_plan)
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn amm_program_id(request_json: *const c_char) -> *mut c_char {
|
||||
call::<ProgramIdRequest>(request_json, api::program_id)
|
||||
|
||||
@@ -7,9 +7,10 @@ pub mod api;
|
||||
|
||||
pub use api::{
|
||||
config_id, context, pair_ids, plan, pool_id, program_id, quote, resolve_pool,
|
||||
swap_exact_in_plan, swap_exact_in_quote, swap_exact_out_quote, swap_pair, token_ids,
|
||||
AccountRead, AmmApiError, AmmResponse, AmmResult, ConfigIdRequest, ContextRequest,
|
||||
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,
|
||||
PairIdsRequest, PairSnapshot, PlanRequest, PoolIdRequest, PositionRequest, ProgramIdRequest,
|
||||
QuoteRequest, ResolvePoolRequest, SwapExactInPlanRequest, SwapExactInQuoteRequest,
|
||||
SwapExactOutQuoteRequest, SwapPairRequest, TokenIdsRequest, WalletAccount,
|
||||
SwapExactOutPlanRequest, SwapExactOutQuoteRequest, SwapPairRequest, TokenIdsRequest,
|
||||
WalletAccount,
|
||||
};
|
||||
|
||||
@@ -663,6 +663,89 @@ std::string AmmModuleImpl::swapExactInput(const std::string& def_a_hex,
|
||||
return jStr(obj, "tx_hash");
|
||||
}
|
||||
|
||||
std::string AmmModuleImpl::swapExactOutput(const std::string& def_a_hex,
|
||||
const std::string& def_b_hex,
|
||||
const std::string& user_input_holding_hex,
|
||||
const std::string& user_output_holding_hex,
|
||||
const nlohmann::json& amount_out,
|
||||
const nlohmann::json& max_in,
|
||||
const nlohmann::json& deadline) {
|
||||
std::string amount_out_decimal;
|
||||
std::string max_in_decimal;
|
||||
std::string deadline_decimal;
|
||||
if (!jsonAmountToDecimal(amount_out, amount_out_decimal)
|
||||
|| !jsonAmountToDecimal(max_in, max_in_decimal)
|
||||
|| !jsonAmountToDecimal(deadline, deadline_decimal)) {
|
||||
AMM_TRACE("swapExactOutput: FAIL amount/deadline not a number or decimal string");
|
||||
return {};
|
||||
}
|
||||
|
||||
const Network net = network();
|
||||
if (net.status != "ready") {
|
||||
AMM_TRACE("swapExactOutput: FAIL network not ready (" << net.status << ")");
|
||||
return {};
|
||||
}
|
||||
|
||||
const json config = readConfig(net);
|
||||
if (config.is_null()) {
|
||||
AMM_TRACE("swapExactOutput: FAIL config_id op failed");
|
||||
return {};
|
||||
}
|
||||
|
||||
// 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},
|
||||
{"tokenInId", def_a_hex},
|
||||
{"tokenOutId", def_b_hex},
|
||||
});
|
||||
if (!poolId.ok) {
|
||||
AMM_TRACE("swapExactOutput: FAIL amm_pool_id");
|
||||
return {};
|
||||
}
|
||||
const json pool = readPublicAccount(jStr(poolId.value, "poolId"));
|
||||
const std::string pool_data = jStr(pool.value("account", json::object()), "data");
|
||||
|
||||
// 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},
|
||||
{"tokenInId", def_a_hex},
|
||||
{"tokenOutId", def_b_hex},
|
||||
{"config", config},
|
||||
{"poolData", pool_data},
|
||||
{"userInputHoldingId", user_input_holding_hex},
|
||||
{"userOutputHoldingId", user_output_holding_hex},
|
||||
{"amountOut", amount_out_decimal},
|
||||
{"maxIn", max_in_decimal},
|
||||
{"deadlineMs", deadline_decimal},
|
||||
});
|
||||
if (!planResult.ok) {
|
||||
AMM_TRACE("swapExactOutput: FAIL amm_swap_exact_out_plan: " << planResult.error);
|
||||
return {};
|
||||
}
|
||||
const json plan = planResult.value;
|
||||
|
||||
const std::vector<std::string> accounts = jsonStrVec(plan.value("accountIds", json::array()));
|
||||
const std::vector<bool> signers = jsonBoolVec(plan.value("signingRequirements", json::array()));
|
||||
const std::vector<uint8_t> instruction = jsonWordsToLeBytes(plan.value("instruction", json::array()));
|
||||
const std::string program_id = jStr(plan, "programId");
|
||||
|
||||
AMM_TRACE("swapExactOutput: SUBMIT programId=" << program_id
|
||||
<< " instrBytes=" << instruction.size() << " accounts=" << accounts.size());
|
||||
|
||||
const std::string reply = modules().logos_execution_zone.send_generic_public_transaction(
|
||||
accounts, signers, instruction, program_id);
|
||||
AMM_TRACE("swapExactOutput: tx reply=" << reply);
|
||||
|
||||
const auto obj = json::parse(reply, nullptr, /*allow_exceptions=*/false);
|
||||
if (!obj.is_object() || !obj.value("success", false)) {
|
||||
AMM_TRACE("swapExactOutput: FAIL tx not successful");
|
||||
return {};
|
||||
}
|
||||
return jStr(obj, "tx_hash");
|
||||
}
|
||||
|
||||
LogosList AmmModuleImpl::tokenList() {
|
||||
LogosList out = LogosList::array();
|
||||
|
||||
|
||||
@@ -85,6 +85,20 @@ public:
|
||||
const nlohmann::json& min_out,
|
||||
const nlohmann::json& deadline);
|
||||
|
||||
/// Submits an on-chain SwapExactOutput transaction against the pool for
|
||||
/// (def_a_hex = token in, def_b_hex = token out). amount_out / max_in are
|
||||
/// u128 base-unit amounts; deadline is a u64 unix-ms timestamp. Same argument
|
||||
/// conventions as swapExactInput (JSON integer or decimal string; floats
|
||||
/// rejected). Returns the tx hash, or an empty string on failure (no pool,
|
||||
/// unreadable AMM_PROGRAM_BIN, bad inputs, failed tx).
|
||||
std::string swapExactOutput(const std::string& def_a_hex,
|
||||
const std::string& def_b_hex,
|
||||
const std::string& user_input_holding_hex,
|
||||
const std::string& user_output_holding_hex,
|
||||
const nlohmann::json& amount_out,
|
||||
const nlohmann::json& max_in,
|
||||
const nlohmann::json& deadline);
|
||||
|
||||
/// Reads the token list config at TOKENS_CONFIG (a JSON array of
|
||||
/// { symbol, name, definitionId, holding, decimals }) and returns it,
|
||||
/// normalizing definitionId/holding to lowercase hex. Empty list if
|
||||
|
||||
Reference in New Issue
Block a user