refactor(amm): drop the new-position schema version tag

The add-liquidity flow stamped a `new-position.v1` schema tag on every
request and response and validated it across all three layers — the QML
plugin, the amm_module core module, and the amm_client Rust crate. It was a
cross-version compatibility guard, but these artifacts always ship together,
so the contract is honored implicitly, and the swap view already works fine
without one. Dropping it makes the liquidity view consistent with swap and
removes a layer of ceremony.

- amm_client: remove PositionRequest.schema and the unsupported_schema check
  in compute_quote; drop QuoteCommitment.schema (changes quoteHash, which is
  internal-only) and every "schema" response stamp; delete the SCHEMA /
  NEW_POSITION_SCHEMA constants and the public export.
- amm_module: remove the SCHEMA constant and its four response stamps.
- AmmUiBackend: remove its local NEW_POSITION_SCHEMA and the stamps in
  loadingContext() / newPositionError().
- QML: drop the "schema" fields from the request/envelope builders and relax
  the validity gates to check status / canSubmit instead (the sole check in
  NewPositionFlow now guards on a missing `status`); strip the now-dead
  schema fields from the liquidity QML test fixtures.
- Also removes the last stale comment references to the deleted *Runtime
  classes.
This commit is contained in:
r4bbit
2026-08-03 15:39:56 +02:00
parent 23963b22f0
commit 27da99aa57
19 changed files with 27 additions and 87 deletions
-1
View File
@@ -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],
+1 -3
View File
@@ -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,
@@ -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,
@@ -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,
-5
View File
@@ -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;
+1 -7
View File
@@ -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};
@@ -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,
@@ -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,
@@ -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,
@@ -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",
}));
@@ -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<_>>(),
@@ -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,
+1 -2
View File
@@ -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};
@@ -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,
+1 -12
View File
@@ -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,
@@ -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,
@@ -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),
@@ -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" },
@@ -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),
@@ -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" },
-1
View File
@@ -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,
+2 -2
View File
@@ -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" }));
}
+1 -2
View File
@@ -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},
@@ -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,
-1
View File
@@ -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,
};
+1 -2
View File
@@ -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() {
@@ -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");
}
@@ -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
@@ -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
@@ -1077,7 +1075,6 @@ AmmActionCard {
function pairRequest() {
return {
"schema": "new-position.v1",
"tokenAId": root.displayIsCanonical
? root.selectedTokenAId : root.selectedTokenBId,
"tokenBId": root.displayIsCanonical
+5 -9
View File
@@ -182,8 +182,8 @@ QtObject {
root.quoteLoading = false
root.quoteStale = false
root.quoteErrorCode = ""
if (!quote || quote.schema !== "new-position.v1")
root.newPositionQuote = root.quoteError("unsupported_schema")
if (!quote || !quote.status)
root.newPositionQuote = root.quoteError("backend_error")
else
root.newPositionQuote = quote
},
@@ -209,8 +209,7 @@ QtObject {
root.runtime.watch(root.backend.submitNewPosition(snapshot.request, snapshot.quoteHash),
function(result) {
if (result && result.schema === "new-position.v1"
&& result.status === "submitted"
if (result && result.status === "submitted"
&& /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(
String(result.transactionId || ""))) {
if (snapshot.request.initialPriceRealRaw !== undefined)
@@ -234,7 +233,7 @@ QtObject {
function finishSubmitFailure(result) {
root.submitting = false
const hasFreshQuote = result && result.quote
&& result.quote.schema === "new-position.v1"
&& result.quote.status
if (hasFreshQuote) {
root.newPositionQuote = result.quote
root.quoteLoading = false
@@ -283,8 +282,7 @@ QtObject {
function finishPoolProbe(pending, quote) {
root.poolProbeInFlight = false
if (quote && quote.schema === "new-position.v1"
&& quote.poolStatus === "active_pool") {
if (quote && quote.poolStatus === "active_pool") {
root.removePendingPool(pending.key)
if (root.matchesSelectedPair(pending.request)) {
root.poolActivated(quote)
@@ -349,7 +347,6 @@ QtObject {
function loadingContext() {
return {
"schema": "new-position.v1",
"status": "loading",
"tokens": [],
"feeTiers": []
@@ -358,7 +355,6 @@ QtObject {
function quoteError(code) {
return {
"schema": "new-position.v1",
"status": "error",
"canSubmit": false,
"code": code,
+2 -6
View File
@@ -9,14 +9,11 @@
#include "logos_sdk.h"
namespace {
const char NEW_POSITION_SCHEMA[] = "new-position.v1";
// The new-position context placeholder published before the module
// connection is up (matches the module's "loading" contextState).
QVariantMap loadingContext()
{
return QVariantMap {
{ QStringLiteral("schema"), QString::fromLatin1(NEW_POSITION_SCHEMA) },
{ QStringLiteral("status"), QStringLiteral("loading") },
{ QStringLiteral("networkId"), QStringLiteral("lez") },
{ QStringLiteral("networkFingerprint"), QString() },
@@ -26,12 +23,11 @@ namespace {
};
}
// A new-position.v1 error envelope (matches the module's publicError), for
// A new-position error envelope (matches the module's publicError), for
// the backend-side failure paths (e.g. LP-account creation failing).
QVariantMap newPositionError(const QString& code)
{
return QVariantMap {
{ QStringLiteral("schema"), QString::fromLatin1(NEW_POSITION_SCHEMA) },
{ QStringLiteral("status"), QStringLiteral("error") },
{ QStringLiteral("canSubmit"), false },
{ QStringLiteral("code"), code },
@@ -223,7 +219,7 @@ QString AmmUiBackend::swapExactInput(QString defAHex, QString defBHex, QString u
// only locks this UI and leaves the shared logos_execution_zone wallet open (another
// app may keep it open, or this app opened-then-disconnected), and the QML submit path
// doesn't check isWalletOpen — so without this a swap could sign/submit while the UI
// shows "Connect". Mirrors the guard the old SwapRuntime::swap() enforced.
// shows "Connect".
if (!isWalletOpen())
return {};
-9
View File
@@ -19,12 +19,10 @@ TestCase {
property bool walletStateReady: false
property var submitResult: ({})
property var quoteResult: ({
"schema": "new-position.v1",
"status": "ok",
"poolStatus": "missing_pool"
})
property var newPositionContext: ({
"schema": "new-position.v1",
"status": "ready",
"tokens": [],
"feeTiers": []
@@ -151,11 +149,9 @@ TestCase {
page.flow.quoteSerial = 7
page.flow.finishSubmitFailure({
"schema": "new-position.v1",
"status": "error",
"code": "quote_not_submittable",
"quote": {
"schema": "new-position.v1",
"status": "ok",
"canSubmit": false,
"quoteHash": "sha256:fresh"
@@ -171,7 +167,6 @@ TestCase {
var backend = createTemporaryObject(backendComponent, testCase, {
"walletStateReady": true,
"submitResult": {
"schema": "new-position.v1",
"status": "submitted",
"transactionId": submittedTransactionId,
"deadlineMs": String(Date.now() + 60000)
@@ -200,7 +195,6 @@ TestCase {
var backend = createTemporaryObject(backendComponent, testCase, {
"walletStateReady": true,
"submitResult": {
"schema": "new-position.v1",
"status": "submitted",
"transactionId": submittedTransactionId,
"deadlineMs": String(Date.now() + 60000)
@@ -239,7 +233,6 @@ TestCase {
var backend = createTemporaryObject(backendComponent, testCase, {
"walletStateReady": true,
"submitResult": {
"schema": "new-position.v1",
"status": "submitted",
"transactionId": "000102030405060708090a0b0c0d0e0f"
+ "101112131415161718191a1b1c1d1e1f"
@@ -282,7 +275,6 @@ TestCase {
page.flow.pendingQuoteRequest = { "ok": true, "request": request }
page.flow.pendingPoolProbes = [pending]
page.flow.newPositionQuote = {
"schema": "new-position.v1",
"status": "ok",
"poolStatus": "missing_pool",
"tokenAId": request.tokenAId,
@@ -291,7 +283,6 @@ TestCase {
page.flow.quoteStale = false
page.flow.finishPoolProbe(pending, {
"schema": "new-position.v1",
"status": "ok",
"poolStatus": "active_pool",
"tokenAId": request.tokenAId,
@@ -354,7 +354,6 @@ TestCase {
form.minimumAmountBRaw = "2000000"
verify(form.acceptPoolActivation({
"schema": "new-position.v1",
"status": "ok",
"tokenAId": tokenHigh,
"tokenBId": tokenLow,
@@ -378,7 +377,6 @@ TestCase {
function test_staleQuoteErrorsDoNotMarkCurrentDraft() {
var quote = {
"schema": "new-position.v1",
"status": "ok",
"tokenAId": tokenHigh,
"tokenBId": tokenLow,
+1 -1
View File
@@ -28,7 +28,7 @@ methods (the module API is generated from the header) are:
`definitionId`/`holding` normalized to hex.
- `newPositionContext(request, walletOpen, refreshWalletAccounts)` — the
add-liquidity view state (available tokens, fee tiers, warnings) as a
`new-position.v1` map.
context map.
- `quoteNewPosition(request, walletOpen)` — prices an add-liquidity request
against current on-chain state (read-only).
- `submitNewPosition(request, quoteHash, walletOpen, freshLpId)` — submits an
+1 -9
View File
@@ -47,10 +47,6 @@ constexpr char AMM_PROGRAM_BIN_ENV[] = "AMM_PROGRAM_BIN";
// Absolute path to the JSON token-list config consumed by tokenList().
constexpr char TOKENS_CONFIG_ENV[] = "TOKENS_CONFIG";
// new-position.v1 response schema tag (matches the app-side NewPositionRuntime
// and the Rust client's NEW_POSITION_SCHEMA).
constexpr char SCHEMA[] = "new-position.v1";
int hexVal(char c) {
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
@@ -233,7 +229,7 @@ FfiResult call(char* (*op)(const char*), const json& request) {
return {true, *it};
}
// new-position.v1 envelope builders (ported from NewPositionRuntime).
// new-position response envelope builders.
json issue(const std::string& code, const json& blockingFields = json::array()) {
return {
{"code", code},
@@ -249,7 +245,6 @@ json publicError(const std::string& code,
json error = issue(code, blockingFields);
error["details"] = details;
return {
{"schema", SCHEMA},
{"status", "error"},
{"canSubmit", false},
{"code", code},
@@ -264,7 +259,6 @@ json contextState(const std::string& status,
const std::string& network_fingerprint,
const std::string& code = {}) {
json state = {
{"schema", SCHEMA},
{"status", status},
{"networkId", network_id},
{"networkFingerprint", network_fingerprint},
@@ -750,7 +744,6 @@ LogosMap AmmModuleImpl::submitNewPosition(const LogosMap& request,
if (quote.value("requiresFreshLp", false)) {
if (fresh_lp_id.empty()) {
return json{
{"schema", SCHEMA},
{"status", "requires_fresh_lp"},
{"quote", quote},
};
@@ -794,7 +787,6 @@ LogosMap AmmModuleImpl::submitNewPosition(const LogosMap& request,
if (transaction_id.empty()) return publicError("wallet_submission_failed");
return {
{"schema", SCHEMA},
{"status", "submitted"},
{"transactionId", transaction_id},
{"deadlineMs", plan.value("deadlineMs", json())},
+8 -8
View File
@@ -17,10 +17,10 @@
// `logos_execution_zone` wallet module (reached via modules().logos_execution_zone).
//
// The same surface is consumed by the QML UI (via modules().amm_module) and
// headlessly (logoscore call amm_module ...). Ported from the app-side
// SwapRuntime / NewPositionRuntime orchestration (apps/amm/src) plus the
// backend's network-context derivation, made Qt-free (std::string / LogosMap /
// LogosList / nlohmann::json) as the universal authoring model requires.
// headlessly (logoscore call amm_module ...). The swap / add-liquidity
// orchestration and the backend's network-context derivation are made Qt-free
// (std::string / LogosMap / LogosList / nlohmann::json) as the universal
// authoring model requires.
//
// Public methods ARE the module's API; the Qt plugin glue is generated from
// this header because metadata.json sets "interface": "universal". Keep the
@@ -65,7 +65,7 @@ public:
LogosList tokenList();
/// New-position (add-liquidity) view state: reads the AMM config + the
/// user's wallet accounts and returns the `new-position.v1` context map 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.
@@ -74,7 +74,7 @@ public:
bool refresh_wallet_accounts);
/// Prices an add-liquidity request against current on-chain state and
/// returns the `new-position.v1` quote map (quoteHash, canSubmit,
/// returns the new-position quote map (quoteHash, canSubmit,
/// requiresFreshLp, amounts, warnings). Read-only — no submission.
LogosMap quoteNewPosition(const LogosMap& request, bool wallet_open);
@@ -84,7 +84,7 @@ public:
/// WITHOUT submitting — the caller (the app backend, which owns the wallet
/// keyset) creates the account and calls again with its id. Otherwise builds
/// the plan (injecting the fresh LP account when given) and submits, then
/// returns the `new-position.v1` submitted/error map.
/// returns the new-position submitted/error map.
LogosMap submitNewPosition(const LogosMap& request,
const std::string& quote_hash,
bool wallet_open,
@@ -138,7 +138,7 @@ private:
// Builds the { networkId, networkFingerprint, ammProgramId, request,
// snapshot } input shared by quoteNewPosition / submitNewPosition. On a
// recoverable precondition failure, sets *error to a new-position.v1 error
// recoverable precondition failure, sets *error to a new-position error
// map and returns a null json.
nlohmann::json buildQuoteInput(const LogosMap& request,
const Network& net,