mirror of
https://github.com/logos-blockchain/lez-programs.git
synced 2026-05-18 23:19:30 +00:00
parent
f5a01063ed
commit
67b1e501e8
@ -180,15 +180,32 @@ Item {
|
||||
color: "#151515"
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 12
|
||||
spacing: 10
|
||||
Flickable {
|
||||
id: scroll
|
||||
|
||||
PoolPositionSummary {
|
||||
poolState: poolState
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: implicitHeight
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
contentHeight: content.implicitHeight + 24
|
||||
contentWidth: width
|
||||
|
||||
ColumnLayout {
|
||||
id: content
|
||||
spacing: 10
|
||||
width: scroll.width - 24
|
||||
x: 12
|
||||
y: 12
|
||||
|
||||
PoolPositionSummary {
|
||||
poolState: poolState
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: implicitHeight
|
||||
}
|
||||
|
||||
AddLiquidityForm {
|
||||
poolState: poolState
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: implicitHeight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
142
amm-ui/qml/components/AddLiquidityForm.qml
Normal file
142
amm-ui/qml/components/AddLiquidityForm.qml
Normal file
@ -0,0 +1,142 @@
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import "../state"
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
required property DummyPoolState poolState
|
||||
|
||||
property string amountA: ""
|
||||
property string amountB: ""
|
||||
property string lastEditedToken: "A"
|
||||
readonly property real parsedA: root.poolState.parseAmount(root.amountA)
|
||||
readonly property real parsedB: root.poolState.parseAmount(root.amountB)
|
||||
readonly property var preview: root.poolState.addLiquidityPreview(root.parsedA, root.parsedB)
|
||||
readonly property bool hasAnyAmount: root.parsedA > 0 || root.parsedB > 0
|
||||
readonly property bool amountAOverBalance: root.parsedA > root.poolState.walletBalanceA
|
||||
readonly property bool amountBOverBalance: root.parsedB > root.poolState.walletBalanceB
|
||||
readonly property bool zeroTokenDeposit: root.hasAnyAmount && (root.preview.actualA === 0 || root.preview.actualB === 0)
|
||||
readonly property bool zeroLpDeposit: root.preview.actualA > 0 && root.preview.actualB > 0 && root.preview.deltaLp === 0
|
||||
readonly property string warningText: root.zeroTokenDeposit ? qsTr("Deposit would be rejected because one token amount rounds to zero") : root.zeroLpDeposit ? qsTr("Deposit would mint 0 LP tokens") : ""
|
||||
|
||||
color: "#1D1D1D"
|
||||
implicitHeight: content.implicitHeight + 20
|
||||
radius: 8
|
||||
border.color: "#343434"
|
||||
border.width: 1
|
||||
|
||||
function setAmounts(nextA, nextB, intentToken, showZero) {
|
||||
root.lastEditedToken = intentToken;
|
||||
root.amountA = nextA > 0 || showZero ? root.poolState.formatInputAmount(nextA) : "";
|
||||
root.amountB = nextB > 0 || showZero ? root.poolState.formatInputAmount(nextB) : "";
|
||||
}
|
||||
|
||||
function updateFromTokenA(value) {
|
||||
if (value.length === 0) {
|
||||
setAmounts(0, 0, "A", false);
|
||||
return;
|
||||
}
|
||||
|
||||
const nextA = root.poolState.parseAmount(value);
|
||||
setAmounts(nextA, root.poolState.amountBForA(nextA), "A", true);
|
||||
}
|
||||
|
||||
function updateFromTokenB(value) {
|
||||
if (value.length === 0) {
|
||||
setAmounts(0, 0, "B", false);
|
||||
return;
|
||||
}
|
||||
|
||||
const nextB = root.poolState.parseAmount(value);
|
||||
setAmounts(root.poolState.amountAForB(nextB), nextB, "B", true);
|
||||
}
|
||||
|
||||
function useMax(intentToken) {
|
||||
const capped = root.poolState.maxAddLiquidityForBalances();
|
||||
setAmounts(capped.actualA, capped.actualB, intentToken, false);
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: content
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: 10
|
||||
spacing: 10
|
||||
|
||||
Text {
|
||||
color: "#E7E1D8"
|
||||
font.bold: true
|
||||
font.pixelSize: 16
|
||||
text: qsTr("Add liquidity")
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
SummaryRow {
|
||||
label: qsTr("Current ratio")
|
||||
value: qsTr("1 %1 = %2 %3").arg(root.poolState.tokenB).arg(root.poolState.formatInteger(root.poolState.tokenAPerTokenB)).arg(root.poolState.tokenA)
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
TokenAmountInput {
|
||||
balance: root.poolState.formatTokenAmount(root.poolState.walletBalanceA, root.poolState.tokenA)
|
||||
errorText: root.amountAOverBalance ? qsTr("Insufficient %1 balance").arg(root.poolState.tokenA) : ""
|
||||
helperText: root.lastEditedToken === "B" && root.amountA.length > 0 ? qsTr("Calculated from current pool ratio") : ""
|
||||
label: qsTr("Token A amount")
|
||||
token: root.poolState.tokenA
|
||||
text: root.amountA
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
onEditingChanged: function (value) {
|
||||
root.updateFromTokenA(value);
|
||||
}
|
||||
onMaxClicked: root.useMax("A")
|
||||
}
|
||||
|
||||
TokenAmountInput {
|
||||
balance: root.poolState.formatTokenAmount(root.poolState.walletBalanceB, root.poolState.tokenB)
|
||||
errorText: root.amountBOverBalance ? qsTr("Insufficient %1 balance").arg(root.poolState.tokenB) : ""
|
||||
helperText: root.lastEditedToken === "A" && root.amountB.length > 0 ? qsTr("Calculated from current pool ratio") : ""
|
||||
label: qsTr("Token B amount")
|
||||
token: root.poolState.tokenB
|
||||
text: root.amountB
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
onEditingChanged: function (value) {
|
||||
root.updateFromTokenB(value);
|
||||
}
|
||||
onMaxClicked: root.useMax("B")
|
||||
}
|
||||
|
||||
SummaryRow {
|
||||
label: qsTr("Required ratio")
|
||||
value: qsTr("%1 %2 / 1 %3").arg(root.poolState.formatInteger(root.poolState.tokenAPerTokenB)).arg(root.poolState.tokenA).arg(root.poolState.tokenB)
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
SummaryRow {
|
||||
estimated: true
|
||||
estimateHelp: qsTr("Estimated with the same integer floor math used by the add-liquidity contract path.")
|
||||
label: qsTr("Estimated LP tokens")
|
||||
value: root.poolState.formatLpAmount(root.preview.deltaLp)
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Text {
|
||||
color: "#F08A76"
|
||||
font.pixelSize: 12
|
||||
lineHeight: 1.25
|
||||
text: root.warningText
|
||||
visible: root.warningText.length > 0
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
156
amm-ui/qml/components/TokenAmountInput.qml
Normal file
156
amm-ui/qml/components/TokenAmountInput.qml
Normal file
@ -0,0 +1,156 @@
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property alias text: amountField.text
|
||||
property string balance: ""
|
||||
property string errorText: ""
|
||||
property string helperText: ""
|
||||
property string label: ""
|
||||
property string token: ""
|
||||
|
||||
signal editingChanged(string value)
|
||||
signal maxClicked
|
||||
|
||||
color: "#151515"
|
||||
implicitHeight: content.implicitHeight + 20
|
||||
radius: 8
|
||||
border.color: root.errorText.length > 0 ? "#D85F4B" : amountField.activeFocus ? "#F26A21" : "#343434"
|
||||
border.width: 1
|
||||
|
||||
Accessible.name: root.label
|
||||
Accessible.role: Accessible.EditableText
|
||||
|
||||
ColumnLayout {
|
||||
id: content
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: 10
|
||||
spacing: 8
|
||||
|
||||
RowLayout {
|
||||
spacing: 8
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
Text {
|
||||
color: "#A9A098"
|
||||
elide: Text.ElideRight
|
||||
font.pixelSize: 12
|
||||
text: root.label
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Text {
|
||||
color: "#E7E1D8"
|
||||
elide: Text.ElideRight
|
||||
font.bold: true
|
||||
font.pixelSize: 12
|
||||
horizontalAlignment: Text.AlignRight
|
||||
text: root.token
|
||||
|
||||
Layout.maximumWidth: 76
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: 8
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
TextField {
|
||||
id: amountField
|
||||
|
||||
activeFocusOnTab: true
|
||||
color: "#E7E1D8"
|
||||
font.bold: true
|
||||
font.pixelSize: 18
|
||||
inputMethodHints: Qt.ImhFormattedNumbersOnly
|
||||
placeholderText: qsTr("0")
|
||||
selectByMouse: true
|
||||
selectedTextColor: "#151515"
|
||||
selectionColor: "#F26A21"
|
||||
validator: RegularExpressionValidator {
|
||||
regularExpression: /[0-9]*([.][0-9]*)?/
|
||||
}
|
||||
|
||||
Accessible.name: root.label
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: 44
|
||||
|
||||
onTextEdited: root.editingChanged(text)
|
||||
|
||||
background: Rectangle {
|
||||
border.color: amountField.activeFocus ? "#F26A21" : "#343434"
|
||||
border.width: 1
|
||||
color: amountField.activeFocus ? "#1F1B18" : "#101010"
|
||||
radius: 6
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
id: maxButton
|
||||
|
||||
activeFocusOnTab: true
|
||||
focusPolicy: Qt.StrongFocus
|
||||
hoverEnabled: true
|
||||
text: qsTr("MAX")
|
||||
|
||||
Accessible.name: qsTr("Use maximum %1 balance").arg(root.token)
|
||||
|
||||
Layout.minimumHeight: 44
|
||||
Layout.preferredWidth: 58
|
||||
|
||||
onClicked: root.maxClicked()
|
||||
|
||||
contentItem: Text {
|
||||
color: maxButton.activeFocus || maxButton.hovered ? "#151515" : "#F26A21"
|
||||
font.bold: true
|
||||
font.pixelSize: 11
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: maxButton.text
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
border.color: "#F26A21"
|
||||
border.width: 1
|
||||
color: maxButton.pressed ? "#D95C1E" : maxButton.hovered || maxButton.activeFocus ? "#F26A21" : "#201712"
|
||||
radius: 6
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: 8
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
Text {
|
||||
color: root.errorText.length > 0 ? "#F08A76" : root.helperText.length > 0 ? "#F26A21" : "#A9A098"
|
||||
elide: Text.ElideRight
|
||||
font.pixelSize: 11
|
||||
text: root.errorText.length > 0 ? root.errorText : root.helperText
|
||||
visible: text.length > 0
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Text {
|
||||
color: "#A9A098"
|
||||
elide: Text.ElideRight
|
||||
font.pixelSize: 11
|
||||
horizontalAlignment: Text.AlignRight
|
||||
text: qsTr("Balance %1").arg(root.balance)
|
||||
|
||||
Layout.alignment: Qt.AlignRight
|
||||
Layout.maximumWidth: 150
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -10,10 +10,13 @@ QtObject {
|
||||
property real reserveA: 1000000
|
||||
property real reserveB: 500
|
||||
property real totalLpSupply: 22360679
|
||||
property real walletBalanceA: 60000
|
||||
property real walletBalanceB: 20
|
||||
|
||||
readonly property real poolShare: totalLpSupply > 0 ? userLpBalance / totalLpSupply : 0
|
||||
readonly property real userOwnedA: reserveA * poolShare
|
||||
readonly property real userOwnedB: reserveB * poolShare
|
||||
readonly property real tokenAPerTokenB: reserveB > 0 ? Math.floor(reserveA / reserveB) : 0
|
||||
|
||||
function applyAddLiquidity(actualA, actualB, mintedLp) {
|
||||
const safeA = Math.max(0, Number(actualA) || 0);
|
||||
@ -45,6 +48,55 @@ QtObject {
|
||||
reserveA = 1000000;
|
||||
reserveB = 500;
|
||||
totalLpSupply = 22360679;
|
||||
walletBalanceA = 60000;
|
||||
walletBalanceB = 20;
|
||||
}
|
||||
|
||||
function parseAmount(value) {
|
||||
return Math.max(0, Number(value) || 0);
|
||||
}
|
||||
|
||||
function floorAmount(value) {
|
||||
return Math.floor(parseAmount(value));
|
||||
}
|
||||
|
||||
function amountBForA(amountA) {
|
||||
if (reserveA <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return reserveB * parseAmount(amountA) / reserveA;
|
||||
}
|
||||
|
||||
function amountAForB(amountB) {
|
||||
if (reserveB <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return reserveA * parseAmount(amountB) / reserveB;
|
||||
}
|
||||
|
||||
function addLiquidityPreview(maxA, maxB) {
|
||||
const safeMaxA = parseAmount(maxA);
|
||||
const safeMaxB = parseAmount(maxB);
|
||||
const idealA = reserveB > 0 ? reserveA * safeMaxB / reserveB : 0;
|
||||
const idealB = reserveA > 0 ? reserveB * safeMaxA / reserveA : 0;
|
||||
const actualA = Math.min(idealA, safeMaxA);
|
||||
const actualB = Math.min(idealB, safeMaxB);
|
||||
const lpFromA = reserveA > 0 ? Math.floor(totalLpSupply * actualA / reserveA) : 0;
|
||||
const lpFromB = reserveB > 0 ? Math.floor(totalLpSupply * actualB / reserveB) : 0;
|
||||
|
||||
return {
|
||||
"actualA": actualA,
|
||||
"actualB": actualB,
|
||||
"deltaLp": Math.min(lpFromA, lpFromB),
|
||||
"idealA": idealA,
|
||||
"idealB": idealB
|
||||
};
|
||||
}
|
||||
|
||||
function maxAddLiquidityForBalances() {
|
||||
return addLiquidityPreview(walletBalanceA, walletBalanceB);
|
||||
}
|
||||
|
||||
function formatInteger(value) {
|
||||
@ -52,8 +104,22 @@ QtObject {
|
||||
return rounded.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
||||
}
|
||||
|
||||
function formatDecimal(value) {
|
||||
const amount = Number(value) || 0;
|
||||
|
||||
if (Math.abs(amount - Math.round(amount)) < 0.000001) {
|
||||
return formatInteger(amount);
|
||||
}
|
||||
|
||||
return amount.toFixed(6).replace(/0+$/, "").replace(/[.]$/, "");
|
||||
}
|
||||
|
||||
function formatInputAmount(value) {
|
||||
return formatDecimal(value);
|
||||
}
|
||||
|
||||
function formatTokenAmount(value, token) {
|
||||
return formatInteger(value) + " " + token;
|
||||
return formatDecimal(value) + " " + token;
|
||||
}
|
||||
|
||||
function formatLpAmount(value) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user