mirror of
https://github.com/logos-blockchain/lez-programs.git
synced 2026-07-19 05:19:33 +00:00
feat(amm-ui): add new position backend API #222
This commit is contained in:
parent
636c23114c
commit
6c13ac6300
@ -87,6 +87,7 @@ Item {
|
|||||||
|
|
||||||
LiquidityPage {
|
LiquidityPage {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
backend: root.ready ? root.backend : null
|
||||||
visible: navbar.currentIndex === 1
|
visible: navbar.currentIndex === 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,13 +2,46 @@ import QtQuick 2.15
|
|||||||
import QtQuick.Controls 2.15
|
import QtQuick.Controls 2.15
|
||||||
import QtQuick.Layouts 1.15
|
import QtQuick.Layouts 1.15
|
||||||
import "../shared"
|
import "../shared"
|
||||||
import "../../state"
|
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
required property NewPositionPrototypeBackend prototypeBackend
|
property var newPositionContext: ({})
|
||||||
|
property var quote: ({
|
||||||
|
"status": "error",
|
||||||
|
"error": "",
|
||||||
|
"poolStatus": "unavailable_pool",
|
||||||
|
"statusLabel": qsTr("Unavailable"),
|
||||||
|
"statusDetail": qsTr("Connect a wallet to preview this position."),
|
||||||
|
"instruction": "",
|
||||||
|
"storedFeeBps": 0,
|
||||||
|
"feeBps": root.selectedFeeBps,
|
||||||
|
"feeLabel": root.feeLabel(root.selectedFeeBps),
|
||||||
|
"quoteHash": "",
|
||||||
|
"pool": {
|
||||||
|
"id": "",
|
||||||
|
"priceText": "",
|
||||||
|
"reserveText": ""
|
||||||
|
},
|
||||||
|
"deposit": {
|
||||||
|
"maxA": root.amountValue(0, root.tokenA.symbol),
|
||||||
|
"maxB": root.amountValue(0, root.tokenB.symbol),
|
||||||
|
"actualA": root.amountValue(0, root.tokenA.symbol),
|
||||||
|
"actualB": root.amountValue(0, root.tokenB.symbol)
|
||||||
|
},
|
||||||
|
"lp": {
|
||||||
|
"expected": root.amountValue(0, "LP"),
|
||||||
|
"minimum": root.amountValue(0, "LP"),
|
||||||
|
"locked": root.amountValue(0, "LP")
|
||||||
|
},
|
||||||
|
"position": {
|
||||||
|
"userLp": "0 LP",
|
||||||
|
"share": "-",
|
||||||
|
"ownedA": root.formatTokenAmount(0, root.tokenA.symbol),
|
||||||
|
"ownedB": root.formatTokenAmount(0, root.tokenB.symbol)
|
||||||
|
},
|
||||||
|
"accountChanges": []
|
||||||
|
})
|
||||||
property int selectedTokenAIndex: 0
|
property int selectedTokenAIndex: 0
|
||||||
property int selectedTokenBIndex: 1
|
property int selectedTokenBIndex: 1
|
||||||
property int selectedFeeBps: 30
|
property int selectedFeeBps: 30
|
||||||
@ -19,6 +52,7 @@ Rectangle {
|
|||||||
property string initialPrice: "2500"
|
property string initialPrice: "2500"
|
||||||
property int depositScale: 1
|
property int depositScale: 1
|
||||||
property string submitError: ""
|
property string submitError: ""
|
||||||
|
property bool applyingQuote: false
|
||||||
|
|
||||||
readonly property var emptyToken: ({
|
readonly property var emptyToken: ({
|
||||||
"symbol": "",
|
"symbol": "",
|
||||||
@ -26,20 +60,21 @@ Rectangle {
|
|||||||
"balanceText": "",
|
"balanceText": "",
|
||||||
"accent": "#343434"
|
"accent": "#343434"
|
||||||
})
|
})
|
||||||
readonly property var holdings: root.prototypeBackend ? root.prototypeBackend.holdings : []
|
readonly property var holdings: root.newPositionContext && root.newPositionContext.holdings ? root.newPositionContext.holdings : []
|
||||||
|
readonly property var feeTiers: root.newPositionContext && root.newPositionContext.feeTiers ? root.newPositionContext.feeTiers : []
|
||||||
|
readonly property string activeAccount: root.newPositionContext && root.newPositionContext.activeAccountDisplay ? root.newPositionContext.activeAccountDisplay : qsTr("Not connected")
|
||||||
readonly property var tokenA: root.holdings[root.selectedTokenAIndex] || root.emptyToken
|
readonly property var tokenA: root.holdings[root.selectedTokenAIndex] || root.emptyToken
|
||||||
readonly property var tokenB: root.holdings[root.selectedTokenBIndex] || root.emptyToken
|
readonly property var tokenB: root.holdings[root.selectedTokenBIndex] || root.emptyToken
|
||||||
readonly property var poolContext: root.prototypeBackend.poolContext(root.tokenA.symbol, root.tokenB.symbol)
|
readonly property string poolStatus: root.quote.poolStatus || "unavailable_pool"
|
||||||
readonly property string poolStatus: root.poolContext.poolStatus
|
|
||||||
readonly property bool activePool: root.poolStatus === "active_pool"
|
readonly property bool activePool: root.poolStatus === "active_pool"
|
||||||
readonly property bool missingPool: root.poolStatus === "missing_pool"
|
readonly property bool missingPool: root.poolStatus === "missing_pool"
|
||||||
readonly property int slippageBps: Math.round(root.slippageTolerancePercent * 100)
|
readonly property int slippageBps: Math.round(root.slippageTolerancePercent * 100)
|
||||||
readonly property var quote: root.prototypeBackend.quoteNewPosition(root.buildRequest())
|
|
||||||
readonly property bool canConfirm: root.quote.status === "ok"
|
readonly property bool canConfirm: root.quote.status === "ok"
|
||||||
readonly property bool compact: root.width < 820
|
readonly property bool compact: root.width < 820
|
||||||
readonly property bool hasActiveInput: root.amountA.length > 0 || root.amountB.length > 0
|
readonly property bool hasActiveInput: root.amountA.length > 0 || root.amountB.length > 0
|
||||||
readonly property bool showQuoteError: root.quote.status === "error" && (root.poolStatus === "unavailable_pool" || root.missingPool || root.hasActiveInput)
|
readonly property bool showQuoteError: root.quote.status === "error" && (root.poolStatus === "unavailable_pool" || root.missingPool || root.hasActiveInput)
|
||||||
|
|
||||||
|
signal quoteRequested(var request)
|
||||||
signal confirmationRequested(var snapshot)
|
signal confirmationRequested(var snapshot)
|
||||||
|
|
||||||
color: "#1B1B1B"
|
color: "#1B1B1B"
|
||||||
@ -50,9 +85,15 @@ Rectangle {
|
|||||||
|
|
||||||
Component.onCompleted: root.afterPairChanged()
|
Component.onCompleted: root.afterPairChanged()
|
||||||
|
|
||||||
|
onNewPositionContextChanged: root.afterPairChanged()
|
||||||
onSelectedTokenAIndexChanged: root.afterPairChanged()
|
onSelectedTokenAIndexChanged: root.afterPairChanged()
|
||||||
onSelectedTokenBIndexChanged: root.afterPairChanged()
|
onSelectedTokenBIndexChanged: root.afterPairChanged()
|
||||||
onPoolStatusChanged: root.normalizeFeeSelection()
|
onPoolStatusChanged: root.normalizeFeeSelection()
|
||||||
|
onSelectedFeeBpsChanged: root.requestQuote()
|
||||||
|
onSlippageTolerancePercentChanged: root.requestQuote()
|
||||||
|
onInitialPriceChanged: root.requestQuote()
|
||||||
|
onDepositScaleChanged: root.requestQuote()
|
||||||
|
onQuoteChanged: root.applyQuoteSideEffects()
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: content
|
id: content
|
||||||
@ -84,7 +125,7 @@ Rectangle {
|
|||||||
color: "#A9A098"
|
color: "#A9A098"
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
font.pixelSize: 12
|
font.pixelSize: 12
|
||||||
text: qsTr("Active account %1").arg(root.prototypeBackend.activeAccount)
|
text: qsTr("Active account %1").arg(root.activeAccount)
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
@ -215,7 +256,7 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: root.prototypeBackend.holdings
|
model: root.holdings
|
||||||
|
|
||||||
delegate: Button {
|
delegate: Button {
|
||||||
id: tokenAButton
|
id: tokenAButton
|
||||||
@ -298,7 +339,7 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: root.prototypeBackend.holdings
|
model: root.holdings
|
||||||
|
|
||||||
delegate: Button {
|
delegate: Button {
|
||||||
id: tokenBButton
|
id: tokenBButton
|
||||||
@ -385,7 +426,7 @@ Rectangle {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: root.prototypeBackend.feeTiers
|
model: root.feeTiers
|
||||||
|
|
||||||
delegate: Rectangle {
|
delegate: Rectangle {
|
||||||
id: feeChip
|
id: feeChip
|
||||||
@ -830,7 +871,7 @@ Rectangle {
|
|||||||
label: qsTr("Expected LP")
|
label: qsTr("Expected LP")
|
||||||
value: root.quote.lp.expected.display
|
value: root.quote.lp.expected.display
|
||||||
estimated: true
|
estimated: true
|
||||||
estimateHelp: qsTr("Prototype quote mirrors the future backend response shape.")
|
estimateHelp: qsTr("Backend preview quote. Final submission rechecks the quote hash.")
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
@ -1000,30 +1041,111 @@ Rectangle {
|
|||||||
return root.holdings.length > 0 ? (index + 1) % root.holdings.length : 0;
|
return root.holdings.length > 0 ? (index + 1) % root.holdings.length : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function requestQuote() {
|
||||||
|
root.quoteRequested(root.buildRequest());
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseAmount(value) {
|
||||||
|
const parsed = Number(String(value).replace(/,/g, ""));
|
||||||
|
return isFinite(parsed) && parsed > 0 ? parsed : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatAmount(value) {
|
||||||
|
const amount = Math.max(0, Number(value) || 0);
|
||||||
|
|
||||||
|
if (amount >= 1000)
|
||||||
|
return amount.toFixed(2).replace(/\.00$/, "");
|
||||||
|
|
||||||
|
if (amount >= 1)
|
||||||
|
return amount.toFixed(4).replace(/0+$/, "").replace(/[.]$/, "");
|
||||||
|
|
||||||
|
return amount.toFixed(6).replace(/0+$/, "").replace(/[.]$/, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTokenAmount(value, symbol) {
|
||||||
|
return qsTr("%1 %2").arg(root.formatAmount(value)).arg(symbol);
|
||||||
|
}
|
||||||
|
|
||||||
|
function amountValue(value, symbol) {
|
||||||
|
const amount = Math.max(0, Number(value) || 0);
|
||||||
|
return {
|
||||||
|
"value": amount,
|
||||||
|
"input": root.formatAmount(amount),
|
||||||
|
"display": root.formatTokenAmount(amount, symbol),
|
||||||
|
"symbol": symbol
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function feeLabel(bps) {
|
||||||
|
if (bps === 1)
|
||||||
|
return "0.01%";
|
||||||
|
if (bps === 5)
|
||||||
|
return "0.05%";
|
||||||
|
if (bps === 30)
|
||||||
|
return "0.30%";
|
||||||
|
if (bps === 100)
|
||||||
|
return "1.00%";
|
||||||
|
return qsTr("%1 bps").arg(bps);
|
||||||
|
}
|
||||||
|
|
||||||
|
function activeRatio(symbolA, symbolB) {
|
||||||
|
if (symbolA === "USDC" && symbolB === "LOGOS")
|
||||||
|
return 8;
|
||||||
|
|
||||||
|
if (symbolA === "LOGOS" && symbolB === "USDC")
|
||||||
|
return 0.125;
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function defaultInitialPrice(symbolA, symbolB) {
|
||||||
|
if (symbolA === "USDC" && symbolB === "WETH")
|
||||||
|
return 2500;
|
||||||
|
|
||||||
|
if (symbolA === "WETH" && symbolB === "USDC")
|
||||||
|
return 0.0004;
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
function afterPairChanged() {
|
function afterPairChanged() {
|
||||||
|
if (root.holdings.length < 2) {
|
||||||
|
root.selectedTokenAIndex = 0;
|
||||||
|
root.selectedTokenBIndex = 0;
|
||||||
|
root.submitError = "";
|
||||||
|
root.requestQuote();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root.selectedTokenAIndex >= root.holdings.length)
|
||||||
|
root.selectedTokenAIndex = 0;
|
||||||
|
if (root.selectedTokenBIndex >= root.holdings.length)
|
||||||
|
root.selectedTokenBIndex = root.nextTokenIndex(root.selectedTokenAIndex);
|
||||||
|
|
||||||
if (root.selectedTokenAIndex === root.selectedTokenBIndex)
|
if (root.selectedTokenAIndex === root.selectedTokenBIndex)
|
||||||
root.selectedTokenBIndex = root.nextTokenIndex(root.selectedTokenAIndex);
|
root.selectedTokenBIndex = root.nextTokenIndex(root.selectedTokenAIndex);
|
||||||
|
|
||||||
if (root.tokenA.symbol.length === 0 || root.tokenB.symbol.length === 0)
|
if (root.tokenA.symbol.length === 0 || root.tokenB.symbol.length === 0) {
|
||||||
|
root.requestQuote();
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
root.submitError = "";
|
root.submitError = "";
|
||||||
root.amountA = "";
|
root.amountA = "";
|
||||||
root.amountB = "";
|
root.amountB = "";
|
||||||
root.editedSide = "A";
|
root.editedSide = "A";
|
||||||
root.initialPrice = root.prototypeBackend.formatAmount(root.prototypeBackend.defaultInitialPrice(root.tokenA.symbol, root.tokenB.symbol));
|
root.initialPrice = root.formatAmount(root.defaultInitialPrice(root.tokenA.symbol, root.tokenB.symbol));
|
||||||
root.depositScale = 1;
|
root.depositScale = 1;
|
||||||
root.normalizeFeeSelection();
|
root.normalizeFeeSelection();
|
||||||
|
root.requestQuote();
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeFeeSelection() {
|
function normalizeFeeSelection() {
|
||||||
if (root.tokenA.symbol.length === 0 || root.tokenB.symbol.length === 0)
|
if (root.tokenA.symbol.length === 0 || root.tokenB.symbol.length === 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const context = root.prototypeBackend.poolContext(root.tokenA.symbol, root.tokenB.symbol);
|
if (root.poolStatus === "active_pool" && root.quote.storedFeeBps > 0) {
|
||||||
|
root.selectedFeeBps = root.quote.storedFeeBps;
|
||||||
if (context.poolStatus === "active_pool") {
|
|
||||||
root.selectedFeeBps = context.storedFeeBps;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1040,8 +1162,8 @@ Rectangle {
|
|||||||
if (!tier.supported)
|
if (!tier.supported)
|
||||||
return qsTr("Unsupported by the AMM program. Valid fee tiers are 0.01%, 0.05%, 0.30%, and 1.00%.");
|
return qsTr("Unsupported by the AMM program. Valid fee tiers are 0.01%, 0.05%, 0.30%, and 1.00%.");
|
||||||
|
|
||||||
if (root.poolStatus === "active_pool" && tier.bps !== root.poolContext.storedFeeBps)
|
if (root.poolStatus === "active_pool" && tier.bps !== root.quote.storedFeeBps)
|
||||||
return qsTr("Existing pool uses %1. Fee tier is fixed by pool configuration.").arg(root.prototypeBackend.feeLabel(root.poolContext.storedFeeBps));
|
return qsTr("Existing pool uses %1. Fee tier is fixed by pool configuration.").arg(root.feeLabel(root.quote.storedFeeBps));
|
||||||
|
|
||||||
if (root.poolStatus === "unavailable_pool")
|
if (root.poolStatus === "unavailable_pool")
|
||||||
return qsTr("Fee selection is disabled until this pair can be quoted.");
|
return qsTr("Fee selection is disabled until this pair can be quoted.");
|
||||||
@ -1058,30 +1180,31 @@ Rectangle {
|
|||||||
else
|
else
|
||||||
root.amountB = value;
|
root.amountB = value;
|
||||||
|
|
||||||
root.syncActiveCounterpart();
|
root.requestQuote();
|
||||||
}
|
}
|
||||||
|
|
||||||
function syncActiveCounterpart() {
|
function applyQuoteSideEffects() {
|
||||||
const nextQuote = root.prototypeBackend.quoteNewPosition(root.buildRequest());
|
root.normalizeFeeSelection();
|
||||||
|
if (root.quote.status !== "ok" || root.quote.poolStatus !== "active_pool")
|
||||||
if (nextQuote.status !== "ok" || nextQuote.poolStatus !== "active_pool")
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
root.applyingQuote = true;
|
||||||
if (root.editedSide === "A")
|
if (root.editedSide === "A")
|
||||||
root.amountB = nextQuote.deposit.maxB.input;
|
root.amountB = root.quote.deposit.maxB.input;
|
||||||
else
|
else
|
||||||
root.amountA = nextQuote.deposit.maxA.input;
|
root.amountA = root.quote.deposit.maxA.input;
|
||||||
|
root.applyingQuote = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function useMaxActive(side) {
|
function useMaxActive(side) {
|
||||||
const ratio = root.prototypeBackend.activeRatio(root.tokenA.symbol, root.tokenB.symbol);
|
const ratio = root.activeRatio(root.tokenA.symbol, root.tokenB.symbol);
|
||||||
const maxA = Math.min(root.tokenA.balance, root.tokenB.balance / ratio);
|
const maxA = Math.min(root.tokenA.balance, root.tokenB.balance / ratio);
|
||||||
const maxB = Math.min(root.tokenB.balance, root.tokenA.balance * ratio);
|
const maxB = Math.min(root.tokenB.balance, root.tokenA.balance * ratio);
|
||||||
|
|
||||||
if (side === "A")
|
if (side === "A")
|
||||||
root.editActiveAmount("A", root.prototypeBackend.formatAmount(maxA));
|
root.editActiveAmount("A", root.formatAmount(maxA));
|
||||||
else
|
else
|
||||||
root.editActiveAmount("B", root.prototypeBackend.formatAmount(maxB));
|
root.editActiveAmount("B", root.formatAmount(maxB));
|
||||||
}
|
}
|
||||||
|
|
||||||
function setSubmitError(message) {
|
function setSubmitError(message) {
|
||||||
|
|||||||
@ -1,11 +1,17 @@
|
|||||||
import QtQuick 2.15
|
import QtQuick 2.15
|
||||||
|
import QtQml 2.15
|
||||||
import "../components/shared"
|
import "../components/shared"
|
||||||
import "../components/liquidity"
|
import "../components/liquidity"
|
||||||
import "../state"
|
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
property var backend: null
|
||||||
|
property var newPositionContext: ({})
|
||||||
|
property var newPositionQuote: ({})
|
||||||
|
property int quoteSerial: 0
|
||||||
|
property bool formReady: false
|
||||||
|
|
||||||
readonly property int pageMargin: 16
|
readonly property int pageMargin: 16
|
||||||
readonly property int preferredCardWidth: 960
|
readonly property int preferredCardWidth: 960
|
||||||
readonly property int pageCardY: newPositionForm.implicitHeight + root.pageMargin * 2 <= scroll.height ? Math.max(root.pageMargin, Math.round((scroll.height - newPositionForm.implicitHeight) / 4)) : root.pageMargin
|
readonly property int pageCardY: newPositionForm.implicitHeight + root.pageMargin * 2 <= scroll.height ? Math.max(root.pageMargin, Math.round((scroll.height - newPositionForm.implicitHeight) / 4)) : root.pageMargin
|
||||||
@ -15,8 +21,17 @@ Item {
|
|||||||
implicitWidth: root.preferredCardWidth + root.pageMargin * 2
|
implicitWidth: root.preferredCardWidth + root.pageMargin * 2
|
||||||
implicitHeight: newPositionForm.implicitHeight + root.pageMargin * 2
|
implicitHeight: newPositionForm.implicitHeight + root.pageMargin * 2
|
||||||
|
|
||||||
NewPositionPrototypeBackend {
|
Component.onCompleted: root.refreshNewPositionContext()
|
||||||
id: newPositionBackend
|
onBackendChanged: root.refreshNewPositionContext()
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: root.backend
|
||||||
|
ignoreUnknownSignals: true
|
||||||
|
|
||||||
|
function onNewPositionContextChanged(value) {
|
||||||
|
root.newPositionContext = value;
|
||||||
|
root.requestQuote(root.currentRequest());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@ -37,11 +52,21 @@ Item {
|
|||||||
NewPositionForm {
|
NewPositionForm {
|
||||||
id: newPositionForm
|
id: newPositionForm
|
||||||
|
|
||||||
prototypeBackend: newPositionBackend
|
newPositionContext: root.newPositionContext
|
||||||
|
quote: root.newPositionQuote
|
||||||
width: Math.max(0, Math.min(scroll.width - root.pageMargin * 2, root.preferredCardWidth))
|
width: Math.max(0, Math.min(scroll.width - root.pageMargin * 2, root.preferredCardWidth))
|
||||||
x: Math.max(root.pageMargin, (scroll.width - width) / 2)
|
x: Math.max(root.pageMargin, (scroll.width - width) / 2)
|
||||||
y: root.pageCardY
|
y: root.pageCardY
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
root.formReady = true;
|
||||||
|
root.refreshNewPositionContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
onQuoteRequested: function (request) {
|
||||||
|
root.requestQuote(request);
|
||||||
|
}
|
||||||
|
|
||||||
onConfirmationRequested: function (snapshot) {
|
onConfirmationRequested: function (snapshot) {
|
||||||
confirmationDialog.openWithSnapshot(snapshot);
|
confirmationDialog.openWithSnapshot(snapshot);
|
||||||
}
|
}
|
||||||
@ -72,14 +97,128 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function confirmNewPosition(snapshot) {
|
function confirmNewPosition(snapshot) {
|
||||||
const result = newPositionBackend.submitNewPosition(snapshot.request, snapshot.quoteHash);
|
if (!root.backend || typeof logos === "undefined") {
|
||||||
|
newPositionForm.setSubmitError(qsTr("Wallet backend is unavailable."));
|
||||||
if (result.status !== "ok") {
|
|
||||||
newPositionForm.setSubmitError(result.error);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
newPositionForm.resetAfterSubmit();
|
logos.watch(root.backend.submitNewPosition(snapshot.request, snapshot.quoteHash),
|
||||||
successToast.show(result.message, result.detail);
|
function(result) {
|
||||||
|
if (result.status !== "ok") {
|
||||||
|
newPositionForm.setSubmitError(result.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
newPositionForm.resetAfterSubmit();
|
||||||
|
successToast.show(result.message, result.detail);
|
||||||
|
},
|
||||||
|
function(error) {
|
||||||
|
newPositionForm.setSubmitError(qsTr("Error submitting position: %1").arg(error));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshNewPositionContext() {
|
||||||
|
if (!root.backend || typeof logos === "undefined") {
|
||||||
|
root.newPositionContext = {
|
||||||
|
"activeAccountDisplay": qsTr("Not connected"),
|
||||||
|
"holdings": [],
|
||||||
|
"feeTiers": []
|
||||||
|
};
|
||||||
|
root.newPositionQuote = root.errorQuote(qsTr("Wallet backend is unavailable."), root.currentRequest());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logos.watch(root.backend.refreshNewPositionContext(),
|
||||||
|
function(context) {
|
||||||
|
root.newPositionContext = context;
|
||||||
|
root.requestQuote(root.currentRequest());
|
||||||
|
},
|
||||||
|
function(error) {
|
||||||
|
root.newPositionQuote = root.errorQuote(qsTr("Error loading position context: %1").arg(error), root.currentRequest());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestQuote(request) {
|
||||||
|
if (!root.backend || typeof logos === "undefined") {
|
||||||
|
root.newPositionQuote = root.errorQuote(qsTr("Wallet backend is unavailable."), request);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const serial = ++root.quoteSerial;
|
||||||
|
logos.watch(root.backend.quoteNewPosition(request),
|
||||||
|
function(quote) {
|
||||||
|
if (serial === root.quoteSerial)
|
||||||
|
root.newPositionQuote = quote;
|
||||||
|
},
|
||||||
|
function(error) {
|
||||||
|
if (serial === root.quoteSerial)
|
||||||
|
root.newPositionQuote = root.errorQuote(qsTr("Error loading quote: %1").arg(error), request);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function amountValue(symbol) {
|
||||||
|
return {
|
||||||
|
"value": 0,
|
||||||
|
"input": "0",
|
||||||
|
"display": qsTr("0 %1").arg(symbol),
|
||||||
|
"symbol": symbol
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function currentRequest() {
|
||||||
|
if (root.formReady)
|
||||||
|
return newPositionForm.buildRequest();
|
||||||
|
|
||||||
|
return {
|
||||||
|
"amountA": "",
|
||||||
|
"amountB": "",
|
||||||
|
"depositScale": 1,
|
||||||
|
"editedSide": "A",
|
||||||
|
"feeBps": 30,
|
||||||
|
"initialPrice": "1",
|
||||||
|
"slippageBps": 50,
|
||||||
|
"tokenA": "",
|
||||||
|
"tokenB": ""
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function errorQuote(message, request) {
|
||||||
|
const tokenA = request ? request.tokenA : "";
|
||||||
|
const tokenB = request ? request.tokenB : "";
|
||||||
|
return {
|
||||||
|
"status": "error",
|
||||||
|
"error": message,
|
||||||
|
"poolStatus": "unavailable_pool",
|
||||||
|
"statusLabel": qsTr("Unavailable"),
|
||||||
|
"statusDetail": message,
|
||||||
|
"instruction": "",
|
||||||
|
"storedFeeBps": 0,
|
||||||
|
"feeBps": request ? request.feeBps : 0,
|
||||||
|
"feeLabel": "",
|
||||||
|
"quoteHash": "",
|
||||||
|
"pool": {
|
||||||
|
"id": "",
|
||||||
|
"priceText": "",
|
||||||
|
"reserveText": ""
|
||||||
|
},
|
||||||
|
"deposit": {
|
||||||
|
"maxA": root.amountValue(tokenA),
|
||||||
|
"maxB": root.amountValue(tokenB),
|
||||||
|
"actualA": root.amountValue(tokenA),
|
||||||
|
"actualB": root.amountValue(tokenB)
|
||||||
|
},
|
||||||
|
"lp": {
|
||||||
|
"expected": root.amountValue("LP"),
|
||||||
|
"minimum": root.amountValue("LP"),
|
||||||
|
"locked": root.amountValue("LP")
|
||||||
|
},
|
||||||
|
"position": {
|
||||||
|
"userLp": "0 LP",
|
||||||
|
"share": "-",
|
||||||
|
"ownedA": qsTr("0 %1").arg(tokenA),
|
||||||
|
"ownedB": qsTr("0 %1").arg(tokenB)
|
||||||
|
},
|
||||||
|
"accountChanges": []
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,45 +1,84 @@
|
|||||||
#ifndef AMM_UI_BACKEND_H
|
#ifndef AMM_UI_BACKEND_H
|
||||||
#define AMM_UI_BACKEND_H
|
#define AMM_UI_BACKEND_H
|
||||||
|
|
||||||
#include <memory>
|
#include <QObject>
|
||||||
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QVariant>
|
||||||
|
|
||||||
#include "rep_AmmUiBackend_source.h"
|
#include "rep_AmmUiBackend_source.h"
|
||||||
|
|
||||||
#include "WalletAccountModel.h"
|
#include "AccountModel.h"
|
||||||
|
|
||||||
class LogosAPI;
|
class LogosAPI;
|
||||||
class LogosWalletProvider;
|
struct LogosModules;
|
||||||
class WalletController;
|
class QNetworkAccessManager;
|
||||||
|
class QTimer;
|
||||||
|
|
||||||
|
// Source-side implementation of the AmmUiBackend .rep interface.
|
||||||
|
// Inheriting from AmmUiBackendSimpleSource gives us the generated PROPs and
|
||||||
|
// SLOTs from AmmUiBackend.rep — all the simple ones flow over QtRO. Talks to
|
||||||
|
// the core logos_execution_zone wallet module via LogosModules.
|
||||||
class AmmUiBackend : public AmmUiBackendSimpleSource {
|
class AmmUiBackend : public AmmUiBackendSimpleSource {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_PROPERTY(WalletAccountModel* accountModel READ accountModel CONSTANT)
|
Q_PROPERTY(AccountModel* accountModel READ accountModel CONSTANT)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit AmmUiBackend(LogosAPI* logosAPI = nullptr, QObject* parent = nullptr);
|
explicit AmmUiBackend(LogosAPI* logosAPI = nullptr, QObject* parent = nullptr);
|
||||||
~AmmUiBackend() override;
|
~AmmUiBackend() override;
|
||||||
|
|
||||||
WalletAccountModel* accountModel() const;
|
AccountModel* accountModel() const { return m_accountModel; }
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
// Overrides of the pure-virtual slots generated from the .rep.
|
||||||
QString createAccountPublic() override;
|
QString createAccountPublic() override;
|
||||||
QString createAccountPrivate() override;
|
QString createAccountPrivate() override;
|
||||||
void refreshAccounts() override;
|
void refreshAccounts() override;
|
||||||
void refreshBalances() override;
|
void refreshBalances() override;
|
||||||
QString getBalance(QString accountIdHex, bool isPublic) override;
|
QString getBalance(QString accountIdHex, bool isPublic) override;
|
||||||
|
QVariant refreshNewPositionContext() override;
|
||||||
|
QVariant quoteNewPosition(QVariant request) override;
|
||||||
|
QVariant submitNewPosition(QVariant request, QString quoteHash) override;
|
||||||
|
// Return the new wallet's BIP39 mnemonic (empty string on failure) so the
|
||||||
|
// UI can force a one-time seed-phrase backup step.
|
||||||
QString createNewDefault(QString password) override;
|
QString createNewDefault(QString password) override;
|
||||||
QString createNew(QString configPath, QString storagePath, QString password) override;
|
QString createNew(QString configPath, QString storagePath, QString password) override;
|
||||||
bool openExisting() override;
|
bool openExisting() override;
|
||||||
void disconnectWallet() override;
|
void disconnectWallet() override;
|
||||||
|
bool changeSequencerAddr(QString url) override;
|
||||||
|
void copyToClipboard(QString text) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void syncWalletState();
|
// Per-app wallet home (kept distinct from the wallet's canonical
|
||||||
|
// ~/.lee/wallet so standalone instances stay isolated; Basecamp sharing
|
||||||
|
// is handled by adopting an already-open shared wallet on startup).
|
||||||
|
static QString defaultWalletHome();
|
||||||
|
QString defaultConfigPath() const;
|
||||||
|
QString defaultStoragePath() const;
|
||||||
|
|
||||||
|
void persistConfigPath(const QString& path);
|
||||||
|
void persistStoragePath(const QString& path);
|
||||||
|
void openOrAdoptWallet();
|
||||||
|
// True when the shared core already has a wallet open — including a freshly
|
||||||
|
// created one with zero accounts. See the definition for why list_accounts()
|
||||||
|
// alone is insufficient.
|
||||||
|
bool sharedWalletIsOpen();
|
||||||
|
void refreshBlockHeights();
|
||||||
|
void refreshSequencerAddr();
|
||||||
|
void saveWallet();
|
||||||
|
QString activeAccountAddress() const;
|
||||||
|
QVariantMap buildNewPositionContext() const;
|
||||||
|
QVariantMap quoteNewPositionMap(const QVariantMap& request) const;
|
||||||
|
|
||||||
|
// Probe the configured sequencer over HTTP and update sequencerReachable.
|
||||||
|
void checkReachability();
|
||||||
|
|
||||||
|
AccountModel* m_accountModel;
|
||||||
|
|
||||||
LogosAPI* m_logosAPI;
|
LogosAPI* m_logosAPI;
|
||||||
std::unique_ptr<LogosWalletProvider> m_wallet;
|
LogosModules* m_logos;
|
||||||
std::unique_ptr<WalletController> m_walletController;
|
|
||||||
|
QNetworkAccessManager* m_net;
|
||||||
|
QTimer* m_reachabilityTimer;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // AMM_UI_BACKEND_H
|
#endif // AMM_UI_BACKEND_H
|
||||||
|
|||||||
@ -23,6 +23,14 @@ class AmmUiBackend
|
|||||||
SLOT(void refreshBalances())
|
SLOT(void refreshBalances())
|
||||||
SLOT(QString getBalance(QString accountIdHex, bool isPublic))
|
SLOT(QString getBalance(QString accountIdHex, bool isPublic))
|
||||||
|
|
||||||
|
// New Position backend surface. QML calls these through logos.watch(...).
|
||||||
|
// The QVariant payloads are stable maps/lists so the UI never assembles AMM
|
||||||
|
// transactions or duplicates quote state.
|
||||||
|
PROP(QVariant newPositionContext READONLY)
|
||||||
|
SLOT(QVariant refreshNewPositionContext())
|
||||||
|
SLOT(QVariant quoteNewPosition(QVariant request))
|
||||||
|
SLOT(QVariant submitNewPosition(QVariant request, QString quoteHash))
|
||||||
|
|
||||||
// Wallet lifecycle. createNewDefault() is the happy path: it creates a
|
// Wallet lifecycle. createNewDefault() is the happy path: it creates a
|
||||||
// fresh per-app wallet at walletHome with no path picking. createNew()
|
// fresh per-app wallet at walletHome with no path picking. createNew()
|
||||||
// keeps explicit paths for an "advanced" flow. Both return the new wallet's
|
// keeps explicit paths for an "advanced" flow. Both return the new wallet's
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user