Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 3 additions & 27 deletions apps/amm/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@ project(AmmUiPlugin LANGUAGES CXX)
find_package(Qt6 6.8 REQUIRED COMPONENTS Core Gui Network Qml Quick QuickControls2)
qt_standard_project_setup(REQUIRES 6.8)

find_package(PkgConfig REQUIRED)
pkg_check_modules(BASE58 REQUIRED IMPORTED_TARGET libbase58)

include(CTest)

if(DEFINED ENV{LOGOS_MODULE_BUILDER_ROOT})
Expand All @@ -27,6 +24,9 @@ add_subdirectory("${LOGOS_WALLET_SOURCE_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/share

# ui_qml module with a hand-written C++ backend (QtRO .rep view contract +
# generated *SimpleSource/*ViewPluginBase). Mirrors the LEZ wallet UI module.
# The AMM business logic lives in the amm_module core module (declared as a
# dependency in metadata.json, reached via modules().amm_module in the backend),
# so the UI links no amm_client library of its own.
logos_module(
NAME amm_ui
REP_FILE src/AmmUiBackend.rep
Expand All @@ -36,34 +36,10 @@ logos_module(
src/AmmUiPlugin.cpp
src/AmmUiBackend.h
src/AmmUiBackend.cpp
src/ActiveNetwork.h
src/AmmClient.h
src/AmmClient.cpp
src/NewPositionRuntime.h
src/NewPositionRuntime.cpp
src/SwapRuntime.h
src/SwapRuntime.cpp
FIND_PACKAGES
Qt6Gui
LINK_LIBRARIES
Qt6::Gui
PkgConfig::BASE58
LINK_TARGETS
logos_wallet_access
EXTERNAL_LIBS
amm_client
)

if(BUILD_TESTING)
add_executable(amm_new_position_runtime_test
tests/cpp/NewPositionRuntimeTest.cpp
src/NewPositionRuntime.cpp
)
target_include_directories(amm_new_position_runtime_test PRIVATE src)
target_link_libraries(amm_new_position_runtime_test PRIVATE
Qt6::Core
PkgConfig::BASE58
logos_wallet_access
)
add_test(NAME amm_new_position_runtime COMMAND amm_new_position_runtime_test)
endif()
13 changes: 7 additions & 6 deletions apps/amm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,19 +52,20 @@ This makes `lgpm` available as a global command.

## Running the UI standalone

The app is built from the **repository-root** flake (which also provides the
`amm_client_ffi` library it links). From the repo root, launch it with its named
attribute:
The app is built from the **repository-root** flake (which also builds the
`amm_module` core module the UI delegates its AMM logic to). From the repo root,
launch it with its named attribute:

```bash
nix run .#amm-ui
```

This builds and runs the application in development mode. The Logos bridge is unavailable in standalone mode, but the UI layout and mock data are fully functional.

Build just the FFI crate with `nix build .#amm_client_ffi`. (Each UI is exposed
under its own name, so future apps are `nix run .#<name>` — there is no bare
`nix run .` default.)
Build just the AMM core module with `nix build .#amm-module`, or its underlying
client crate with `nix build .#amm_client`. (Each UI is exposed under its own
name, so future apps are `nix run .#<name>` — there is no bare `nix run .`
default.)

## Running inside Logos Basecamp

Expand Down
1 change: 0 additions & 1 deletion apps/amm/client/src/api/commitment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ pub(super) struct FundingCommitment {

#[derive(BorshSerialize)]
pub(super) struct QuoteCommitment {
pub(super) schema: String,
pub(super) network_id: String,
pub(super) network_fingerprint: String,
pub(super) amm_program_id: [u8; 32],
Expand Down
4 changes: 1 addition & 3 deletions apps/amm/client/src/api/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use super::{
config::load_config,
holding::{select_holding, wallet_holdings, SelectedHolding},
quote_error::issue,
ContextRequest, TokenIdsRequest, SCHEMA,
ContextRequest, TokenIdsRequest,
};
use crate::account::{
account_id_from_hex, account_id_hex, decode_account, parse_base58_id, parse_program_id,
Expand Down Expand Up @@ -117,7 +117,6 @@ pub(super) fn context(request: ContextRequest) -> Result<Value, String> {
});

Ok(json!({
"schema": SCHEMA,
"status": if request.wallet_available { "ready" } else { "no_wallet" },
"networkId": request.network_id,
"networkFingerprint": request.network_fingerprint,
Expand All @@ -136,7 +135,6 @@ pub(super) fn context(request: ContextRequest) -> Result<Value, String> {

fn context_error(request: &ContextRequest, code: &str) -> Value {
json!({
"schema": SCHEMA,
"status": "error",
"code": code,
"networkId": request.network_id,
Expand Down
5 changes: 0 additions & 5 deletions apps/amm/client/src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,6 @@ use serde_json::Value;

pub use crate::account::{AccountRead, WalletAccount};

/// Schema identifier expected by position quote and plan requests.
pub const NEW_POSITION_SCHEMA: &str = "new-position.v1";

pub(crate) const SCHEMA: &str = NEW_POSITION_SCHEMA;

/// JSON response shared by direct Rust callers and transport adapters.
pub type AmmResponse = Value;

Expand Down
8 changes: 1 addition & 7 deletions apps/amm/client/src/api/plan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use super::{
clock::decode_clock,
position::{NewPositionPlan, QuoteBranch, QuoteComputation},
quote::compute_quote,
PlanRequest, QuoteRequest, SCHEMA,
PlanRequest, QuoteRequest,
};
use crate::account::{account_id_hex, decode_account, AccountRead};

Expand All @@ -22,7 +22,6 @@ pub(super) fn plan(input: PlanRequest) -> Result<Value, String> {
let quote = compute_quote(&quote_input)?;
if quote.quote_hash() != Some(input.quote_hash.as_str()) {
return Ok(json!({
"schema": SCHEMA,
"status": "error",
"code": "quote_changed",
"recoverable": true,
Expand All @@ -33,7 +32,6 @@ pub(super) fn plan(input: PlanRequest) -> Result<Value, String> {
QuoteComputation::Evaluated(evaluated) => evaluated,
QuoteComputation::Failed(failure) => {
return Ok(json!({
"schema": SCHEMA,
"status": "error",
"code": "quote_not_submittable",
"recoverable": true,
Expand All @@ -43,7 +41,6 @@ pub(super) fn plan(input: PlanRequest) -> Result<Value, String> {
};
let Some(plan) = evaluated.plan else {
return Ok(json!({
"schema": SCHEMA,
"status": "error",
"code": "quote_not_submittable",
"recoverable": true,
Expand All @@ -53,7 +50,6 @@ pub(super) fn plan(input: PlanRequest) -> Result<Value, String> {
let fresh_lp = if plan.requires_fresh_lp() {
let Some(read) = input.fresh_lp.as_ref() else {
return Ok(json!({
"schema": SCHEMA,
"status": "needs_fresh_lp",
"code": "fresh_lp_required",
}));
Expand Down Expand Up @@ -112,7 +108,6 @@ pub(super) fn plan(input: PlanRequest) -> Result<Value, String> {
};

Ok(json!({
"schema": SCHEMA,
"status": "ready",
"programId": input.amm_program_id,
"accountIds": account_ids.into_iter().map(account_id_hex).collect::<Vec<_>>(),
Expand All @@ -124,7 +119,6 @@ pub(super) fn plan(input: PlanRequest) -> Result<Value, String> {

fn plan_error(code: &str) -> Value {
json!({
"schema": SCHEMA,
"status": "error",
"code": code,
"recoverable": true,
Expand Down
3 changes: 1 addition & 2 deletions apps/amm/client/src/api/position.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use serde_json::{json, Value};

use super::{
commitment::SourceCommitment, holding::SelectedHolding, pair::PairIds, quote_error::issue,
PairSnapshot, PositionRequest, SCHEMA,
PairSnapshot, PositionRequest,
};
use crate::account::{account_id_from_hex, program_id_base58};

Expand Down Expand Up @@ -82,7 +82,6 @@ impl QuoteComputation {
impl QuoteFailure {
pub(super) fn into_value(self, request: &PositionRequest) -> Value {
json!({
"schema": SCHEMA,
"status": "error",
"canSubmit": false,
"code": self.code,
Expand Down
13 changes: 1 addition & 12 deletions apps/amm/client/src/api/quote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use super::{
QuoteComputation,
},
quote_error::{fatal_quote, issue},
QuoteRequest, SCHEMA,
QuoteRequest,
};
use crate::account::{
decode_account, parse_base58_id, parse_program_id, program_id_bytes, AccountRead,
Expand All @@ -40,13 +40,6 @@ pub(super) fn quote(request: QuoteRequest) -> Result<Value, String> {
}

pub(super) fn compute_quote(input: &QuoteRequest) -> Result<QuoteComputation, String> {
if input.request.schema != SCHEMA {
return Ok(fatal_quote(
"unsupported_schema",
&["schema"],
json!({ "received": input.request.schema }),
));
}
let amm_program = parse_program_id(&input.amm_program_id)?;
let token_a = match parse_base58_id(&input.request.token_a_id, "token A id") {
Ok(id) => id,
Expand Down Expand Up @@ -238,7 +231,6 @@ fn compute_missing_quote(
let sources = account_plan.take_sources();
let funding_commitment = funding_commitments(pair, &holding_a, amount_a, &holding_b, amount_b);
let commitment = QuoteCommitment {
schema: String::from(SCHEMA),
network_id: input.network_id.clone(),
network_fingerprint: input.network_fingerprint.clone(),
amm_program_id: program_id_bytes(amm_program),
Expand All @@ -261,7 +253,6 @@ fn compute_missing_quote(
let quote_hash = hash_quote(&commitment)?;
let preview = account_plan.preview();
let value = json!({
"schema": SCHEMA,
"status": "ok",
"canSubmit": can_submit,
"code": if can_submit { "ready" } else { "funding_required" },
Expand Down Expand Up @@ -482,7 +473,6 @@ fn compute_active_quote(
)?;
let sources = account_plan.take_sources();
let commitment = QuoteCommitment {
schema: String::from(SCHEMA),
network_id: input.network_id.clone(),
network_fingerprint: input.network_fingerprint.clone(),
amm_program_id: program_id_bytes(amm_program),
Expand All @@ -509,7 +499,6 @@ fn compute_active_quote(
let quote_hash = hash_quote(&commitment)?;
let preview = account_plan.preview();
let value = json!({
"schema": SCHEMA,
"status": "ok",
"canSubmit": can_submit,
"code": if can_submit { "ready" } else { "funding_required" },
Expand Down
1 change: 0 additions & 1 deletion apps/amm/client/src/api/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ pub struct ProgramIdRequest {
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PositionRequest {
pub schema: String,
pub token_a_id: String,
pub token_b_id: String,
pub fee_bps: u32,
Expand Down
4 changes: 2 additions & 2 deletions apps/amm/client/src/api/swap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,8 @@ pub(super) fn swap_plan(request: SwapPlanRequest) -> Result<Value, String> {
let token_out = account_id_from_hex(&request.token_out_id, "token out id")?;
// Domain errors (a bad pair, an unavailable config) mirror `swap_pair`'s
// `{ status: "error", code }` shape rather than `Err`, which is reserved for
// malformed inputs. `SwapRuntime::swap` treats any non-"ready" status as a
// failed plan, so both map to the same UI outcome.
// malformed inputs. Callers treat any non-"ready" status as a failed plan,
// so both map to the same outcome.
if token_in == token_out {
return Ok(json!({ "status": "error", "code": "same_token_pair" }));
}
Expand Down
3 changes: 1 addition & 2 deletions apps/amm/client/src/api/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use super::{
position::AccountPlanHoldings,
quote::{div_ceil_u256, minimum_opening_pair, quote, Q64},
ContextRequest, PairIdsRequest, PairSnapshot, PlanRequest, PositionRequest, QuoteRequest,
TokenIdsRequest, SCHEMA,
TokenIdsRequest,
};
use crate::{
account::{account_id_hex, account_read, decode_account, parse_base58_id, program_id_bytes},
Expand Down Expand Up @@ -140,7 +140,6 @@ fn base_snapshot(pair: PairIds) -> PairSnapshot {
fn request(pair: PairIds) -> PositionRequest {
assert!(is_canonical_pair(pair.token_a, pair.token_b));
PositionRequest {
schema: String::from(SCHEMA),
token_a_id: pair.token_a.to_string(),
token_b_id: pair.token_b.to_string(),
fee_bps: 30,
Expand Down
1 change: 0 additions & 1 deletion apps/amm/client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,4 @@ pub use api::{
token_ids, AccountRead, AmmApiError, AmmResponse, AmmResult, ConfigIdRequest, ContextRequest,
PairIdsRequest, PairSnapshot, PlanRequest, PositionRequest, ProgramIdRequest, QuoteRequest,
ResolvePoolRequest, SwapPairRequest, SwapPlanRequest, TokenIdsRequest, WalletAccount,
NEW_POSITION_SCHEMA,
};
3 changes: 1 addition & 2 deletions apps/amm/client/tests/public_api.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use amm_client::{config_id, ConfigIdRequest, NEW_POSITION_SCHEMA};
use amm_client::{config_id, ConfigIdRequest};

#[test]
fn direct_rust_api_does_not_require_ffi() {
Expand All @@ -9,5 +9,4 @@ fn direct_rust_api_does_not_require_ffi() {

assert_eq!(response["status"], "ok");
assert!(response["configId"].is_string());
assert_eq!(NEW_POSITION_SCHEMA, "new-position.v1");
}
8 changes: 3 additions & 5 deletions apps/amm/metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,18 @@
"main": "amm_ui_plugin",
"view": "qml/Main.qml",
"icon": "icons/amm.png",
"dependencies": ["logos_execution_zone"],
"dependencies": ["logos_execution_zone", "amm_module"],

"nix": {
"packages": {
"build": ["pkg-config"],
"runtime": ["qt6.qtdeclarative", "zstd", "krb5", "abseil-cpp", "libbase58"]
},
"external_libraries": [
{ "name": "amm_client" }
],
"external_libraries": [],
"cmake": {
"find_packages": [],
"extra_sources": [],
"extra_include_dirs": ["lib"],
"extra_include_dirs": [],
"extra_link_libraries": []
}
}
Expand Down
7 changes: 2 additions & 5 deletions apps/amm/qml/components/liquidity/NewPositionForm.qml
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,7 @@ AmmActionCard {
&& root.selectedTokenBId.length > 0
&& root.selectedTokenAId !== root.selectedTokenBId
readonly property bool resolvingToken: root.resolvingTokenId.length > 0
readonly property bool canConfirm: root.quotePayload.schema === "new-position.v1"
&& root.quotePayload.status === "ok"
readonly property bool canConfirm: root.quotePayload.status === "ok"
&& root.quotePayload.canSubmit === true
&& root.quoteMatchesPair()
&& String(root.quotePayload.quoteHash || "").length > 0
Expand Down Expand Up @@ -858,8 +857,7 @@ AmmActionCard {
}

function acceptPoolActivation(quote) {
if (!quote || quote.schema !== "new-position.v1"
|| quote.status !== "ok"
if (!quote || quote.status !== "ok"
|| quote.poolStatus !== "active_pool"
|| !root.quoteMatchesSelectedPair(quote)) {
return false
Expand Down Expand Up @@ -1077,7 +1075,6 @@ AmmActionCard {

function pairRequest() {
return {
"schema": "new-position.v1",
"tokenAId": root.displayIsCanonical
? root.selectedTokenAId : root.selectedTokenBId,
"tokenBId": root.displayIsCanonical
Expand Down
7 changes: 7 additions & 0 deletions apps/amm/qml/components/swap/SwapCard.qml
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,11 @@ Rectangle {
readonly property bool hasAmount: editingSide === "sell" ? parsedSellInput > 0 : parsedBuyInput > 0
readonly property bool tokensSelected: sellToken !== null && buyToken !== null
readonly property bool insufficientLiquidity: hasAmount && root.poolExists && parsedBuyAmount > buyReserveNum
// True only when THIS app's wallet is connected. The backend also enforces
// this before submitting (AmmUiBackend::swapExactInput), but gate the UI too
// so a disconnected app never even initiates a swap against the shared wallet.
readonly property bool walletOpen: root.backend !== null && root.backend.isWalletOpen

// The backend only exposes swapExactInput, so only the "I know exactly
// how much I'm selling" direction can actually be submitted. Editing the
// buy field still previews an estimate (via amountInFor above) but can't
Expand All @@ -175,6 +180,7 @@ Rectangle {
&& parsedSellAmount > 0 && parsedBuyAmount > 0
&& root.poolResolved && root.poolExists
&& !insufficientLiquidity && !root.swapInProgress
&& root.walletOpen

readonly property string submitButtonText: {
if (!tokensSelected) return qsTr("Select tokens")
Expand All @@ -185,6 +191,7 @@ Rectangle {
if (!root.poolExists) return qsTr("No pool / no liquidity")
if (insufficientLiquidity) return qsTr("Insufficient liquidity")
if (parsedBuyAmount <= 0) return qsTr("Amount too small")
if (!root.walletOpen) return qsTr("Connect wallet to swap")
return qsTr("Swap")
}

Expand Down
Loading
Loading