mirror of
https://github.com/logos-blockchain/lez-programs.git
synced 2026-05-18 15:09:51 +00:00
feat(amm/ui): show swap summary under the swap card
This commit is contained in:
parent
c8a192e377
commit
3df3c3d7c4
@ -1,6 +1,8 @@
|
|||||||
import QtQuick 2.15
|
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 "components"
|
||||||
|
import "state"
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
@ -11,7 +13,11 @@ Rectangle {
|
|||||||
property var buyToken: null
|
property var buyToken: null
|
||||||
property string sellAmount: ""
|
property string sellAmount: ""
|
||||||
property real slippageTolerancePercent: 0.5
|
property real slippageTolerancePercent: 0.5
|
||||||
readonly property real feePercent: 0.30
|
|
||||||
|
DummySwapState {
|
||||||
|
id: swapState
|
||||||
|
feeBps: 30
|
||||||
|
}
|
||||||
|
|
||||||
signal requestTokenSelect(string side)
|
signal requestTokenSelect(string side)
|
||||||
signal submitRequested(var snapshot)
|
signal submitRequested(var snapshot)
|
||||||
@ -25,24 +31,18 @@ Rectangle {
|
|||||||
root.sellAmount = ""
|
root.sellAmount = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
readonly property real sellReserve: sellToken ? (sellToken.reserve || 0) : 0
|
||||||
|
readonly property real buyReserve: buyToken ? (buyToken.reserve || 0) : 0
|
||||||
|
|
||||||
readonly property real parsedSellAmount: {
|
readonly property real parsedSellAmount: {
|
||||||
var amt = parseFloat(sellAmount)
|
var amt = parseFloat(sellAmount)
|
||||||
return isNaN(amt) || amt < 0 ? 0 : amt
|
return isNaN(amt) || amt < 0 ? 0 : amt
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly property real parsedBuyAmount: {
|
readonly property real parsedBuyAmount: swapState.amountOutFor(parsedSellAmount, sellReserve, buyReserve)
|
||||||
if (!sellToken || !buyToken || parsedSellAmount <= 0) return 0
|
readonly property real feeAmount: swapState.feeAmount(parsedSellAmount)
|
||||||
return parsedSellAmount * sellToken.usdPrice / buyToken.usdPrice
|
readonly property real minReceivedAmount: swapState.minReceived(parsedBuyAmount, slippageTolerancePercent)
|
||||||
}
|
readonly property real priceImpactPercent: swapState.priceImpactPercent(parsedSellAmount, parsedBuyAmount, sellReserve, buyReserve)
|
||||||
|
|
||||||
readonly property real minReceivedAmount: parsedBuyAmount * (1 - slippageTolerancePercent / 100)
|
|
||||||
|
|
||||||
readonly property real priceImpactPercent: {
|
|
||||||
if (!sellToken || parsedSellAmount <= 0) return 0
|
|
||||||
var reserve = sellToken.reserve || 0
|
|
||||||
if (reserve <= 0) return 0
|
|
||||||
return parsedSellAmount / (reserve + parsedSellAmount) * 100
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly property bool hasAmount: parsedSellAmount > 0
|
readonly property bool hasAmount: parsedSellAmount > 0
|
||||||
readonly property bool tokensSelected: sellToken !== null && buyToken !== null
|
readonly property bool tokensSelected: sellToken !== null && buyToken !== null
|
||||||
@ -89,9 +89,10 @@ Rectangle {
|
|||||||
"sellAmount": formatAmountValue(parsedSellAmount),
|
"sellAmount": formatAmountValue(parsedSellAmount),
|
||||||
"buyAmount": formatAmountValue(parsedBuyAmount),
|
"buyAmount": formatAmountValue(parsedBuyAmount),
|
||||||
"minReceived": formatAmountValue(minReceivedAmount),
|
"minReceived": formatAmountValue(minReceivedAmount),
|
||||||
"feePercent": feePercent.toFixed(2) + "%",
|
"feeAmount": swapState.formatTokenAmount(feeAmount, sellToken ? sellToken.symbol : ""),
|
||||||
"priceImpactPercent": priceImpactPercent < 0.01 ? "<0.01%" : priceImpactPercent.toFixed(2) + "%",
|
"priceImpactPercent": swapState.formatPercent(priceImpactPercent),
|
||||||
"slippageTolerance": slippageTolerancePercent.toFixed(2).replace(/0+$/, "").replace(/[.]$/, "") + "%"
|
"priceImpactPercentValue": priceImpactPercent,
|
||||||
|
"slippageTolerance": swapState.formatSlippagePercent(slippageTolerancePercent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,6 +177,20 @@ Rectangle {
|
|||||||
onTokenClicked: root.requestTokenSelect("buy")
|
onTokenClicked: root.requestTokenSelect("buy")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SwapSummary {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 12
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
theme: root.theme
|
||||||
|
visible: root.tokensSelected && root.hasAmount
|
||||||
|
feeText: swapState.formatTokenAmount(root.feeAmount, root.sellToken ? root.sellToken.symbol : "")
|
||||||
|
priceImpactText: swapState.formatPercent(root.priceImpactPercent)
|
||||||
|
priceImpactPercent: root.priceImpactPercent
|
||||||
|
slippageText: swapState.formatSlippagePercent(root.slippageTolerancePercent)
|
||||||
|
minReceivedText: swapState.formatTokenAmount(root.minReceivedAmount, root.buyToken ? root.buyToken.symbol : "")
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: ctaBox
|
id: ctaBox
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|||||||
@ -141,64 +141,16 @@ FocusScope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
SwapSummary {
|
||||||
spacing: 8
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
theme: root.theme
|
||||||
Item {
|
feeText: root.snapshot.feeAmount || ""
|
||||||
Layout.fillWidth: true
|
priceImpactText: root.snapshot.priceImpactPercent || ""
|
||||||
implicitHeight: 18
|
priceImpactPercent: Number(root.snapshot.priceImpactPercentValue) || 0
|
||||||
Text {
|
slippageText: root.snapshot.slippageTolerance || ""
|
||||||
anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter
|
minReceivedText: qsTr("%1 %2")
|
||||||
text: qsTr("Fee"); color: root.theme.colors.textSecondary; font.pixelSize: 12
|
.arg(root.snapshot.minReceived || "")
|
||||||
}
|
.arg(root.snapshot.buyToken || "")
|
||||||
Text {
|
|
||||||
anchors.right: parent.right; anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: root.snapshot.feePercent || ""
|
|
||||||
color: root.theme.colors.textPrimary; font.pixelSize: 12; font.bold: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Item {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
implicitHeight: 18
|
|
||||||
Text {
|
|
||||||
anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: qsTr("Price impact"); color: root.theme.colors.textSecondary; font.pixelSize: 12
|
|
||||||
}
|
|
||||||
Text {
|
|
||||||
anchors.right: parent.right; anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: root.snapshot.priceImpactPercent || ""
|
|
||||||
color: root.theme.colors.textPrimary; font.pixelSize: 12; font.bold: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Item {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
implicitHeight: 18
|
|
||||||
Text {
|
|
||||||
anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: qsTr("Slippage tolerance"); color: root.theme.colors.textSecondary; font.pixelSize: 12
|
|
||||||
}
|
|
||||||
Text {
|
|
||||||
anchors.right: parent.right; anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: root.snapshot.slippageTolerance || ""
|
|
||||||
color: root.theme.colors.textPrimary; font.pixelSize: 12; font.bold: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Item {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
implicitHeight: 18
|
|
||||||
Text {
|
|
||||||
anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: qsTr("Min received"); color: root.theme.colors.textSecondary; font.pixelSize: 12
|
|
||||||
}
|
|
||||||
Text {
|
|
||||||
anchors.right: parent.right; anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: qsTr("%1 %2")
|
|
||||||
.arg(root.snapshot.minReceived || "")
|
|
||||||
.arg(root.snapshot.buyToken || "")
|
|
||||||
color: root.theme.colors.textPrimary; font.pixelSize: 12; font.bold: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
|
|||||||
120
amm-ui/qml/components/SwapSummary.qml
Normal file
120
amm-ui/qml/components/SwapSummary.qml
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property var theme
|
||||||
|
property string feeText: ""
|
||||||
|
property string priceImpactText: ""
|
||||||
|
property real priceImpactPercent: 0
|
||||||
|
property string slippageText: ""
|
||||||
|
property string minReceivedText: ""
|
||||||
|
|
||||||
|
readonly property color priceImpactColor: {
|
||||||
|
if (root.priceImpactPercent > 5) return "#F08A76";
|
||||||
|
if (root.priceImpactPercent > 1) return "#F2B366";
|
||||||
|
return root.theme.colors.textPrimary;
|
||||||
|
}
|
||||||
|
|
||||||
|
implicitHeight: column.implicitHeight
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: column
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
Item {
|
||||||
|
implicitHeight: 18
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
color: root.theme.colors.textSecondary
|
||||||
|
font.pixelSize: 12
|
||||||
|
text: qsTr("Fee")
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
color: root.theme.colors.textPrimary
|
||||||
|
font.bold: true
|
||||||
|
font.pixelSize: 12
|
||||||
|
text: root.feeText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
implicitHeight: 18
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
color: root.theme.colors.textSecondary
|
||||||
|
font.pixelSize: 12
|
||||||
|
text: qsTr("Price impact")
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
color: root.priceImpactColor
|
||||||
|
font.bold: true
|
||||||
|
font.pixelSize: 12
|
||||||
|
text: root.priceImpactText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
implicitHeight: 18
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
color: root.theme.colors.textSecondary
|
||||||
|
font.pixelSize: 12
|
||||||
|
text: qsTr("Slippage tolerance")
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
color: root.theme.colors.textPrimary
|
||||||
|
font.bold: true
|
||||||
|
font.pixelSize: 12
|
||||||
|
text: root.slippageText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
implicitHeight: 18
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
color: root.theme.colors.textSecondary
|
||||||
|
font.pixelSize: 12
|
||||||
|
text: qsTr("Min received")
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
color: root.theme.colors.textPrimary
|
||||||
|
font.bold: true
|
||||||
|
font.pixelSize: 12
|
||||||
|
text: root.minReceivedText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
88
amm-ui/qml/state/DummySwapState.qml
Normal file
88
amm-ui/qml/state/DummySwapState.qml
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import QtQuick 2.15
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property int feeBps: 30
|
||||||
|
|
||||||
|
function parseAmount(value) {
|
||||||
|
return Math.max(0, Number(value) || 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function clampSlippagePercent(value) {
|
||||||
|
return Math.max(0, Math.min(50, Number(value) || 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
function feeAmount(amountIn) {
|
||||||
|
return parseAmount(amountIn) * root.feeBps / 10000;
|
||||||
|
}
|
||||||
|
|
||||||
|
function amountOutFor(amountIn, reserveIn, reserveOut) {
|
||||||
|
const safeAmountIn = parseAmount(amountIn);
|
||||||
|
const safeReserveIn = parseAmount(reserveIn);
|
||||||
|
const safeReserveOut = parseAmount(reserveOut);
|
||||||
|
|
||||||
|
if (safeAmountIn <= 0 || safeReserveIn <= 0 || safeReserveOut <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const amountInAfterFee = safeAmountIn * (10000 - root.feeBps) / 10000;
|
||||||
|
return safeReserveOut * amountInAfterFee / (safeReserveIn + amountInAfterFee);
|
||||||
|
}
|
||||||
|
|
||||||
|
function priceImpactPercent(amountIn, amountOut, reserveIn, reserveOut) {
|
||||||
|
const safeAmountIn = parseAmount(amountIn);
|
||||||
|
const safeAmountOut = parseAmount(amountOut);
|
||||||
|
const safeReserveIn = parseAmount(reserveIn);
|
||||||
|
const safeReserveOut = parseAmount(reserveOut);
|
||||||
|
|
||||||
|
if (safeAmountIn <= 0 || safeAmountOut <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (safeReserveIn <= 0 || safeReserveOut <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (safeReserveOut - safeAmountOut <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const priceBefore = safeReserveIn / safeReserveOut;
|
||||||
|
const priceAfter = (safeReserveIn + safeAmountIn) / (safeReserveOut - safeAmountOut);
|
||||||
|
return (priceAfter - priceBefore) / priceBefore * 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
function minReceived(amountOut, slippagePercent) {
|
||||||
|
const safeAmount = parseAmount(amountOut);
|
||||||
|
const safeSlippage = clampSlippagePercent(slippagePercent);
|
||||||
|
return safeAmount * (1 - safeSlippage / 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
function maxSent(amountIn, slippagePercent) {
|
||||||
|
const safeAmount = parseAmount(amountIn);
|
||||||
|
const safeSlippage = clampSlippagePercent(slippagePercent);
|
||||||
|
return safeAmount * (1 + safeSlippage / 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatAmountValue(value) {
|
||||||
|
const amount = Math.max(0, Number(value) || 0);
|
||||||
|
if (amount >= 1) return amount.toFixed(2);
|
||||||
|
if (amount >= 0.0001) return amount.toFixed(6);
|
||||||
|
return amount.toFixed(8);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTokenAmount(value, symbol) {
|
||||||
|
const formatted = formatAmountValue(value);
|
||||||
|
return symbol ? formatted + " " + symbol : formatted;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatPercent(value) {
|
||||||
|
const amount = Number(value) || 0;
|
||||||
|
if (amount > 0 && amount < 0.01) return "<0.01%";
|
||||||
|
return amount.toFixed(2) + "%";
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatSlippagePercent(value) {
|
||||||
|
const amount = clampSlippagePercent(value);
|
||||||
|
return amount.toFixed(2).replace(/0+$/, "").replace(/[.]$/, "") + "%";
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user