Skip to content
Open
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
123 changes: 90 additions & 33 deletions amm-ui/qml/SwapCard.qml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import "components"
import "state"

Rectangle {
id: root
Expand All @@ -9,9 +11,15 @@ Rectangle {
property var tokens: []
property var sellToken: null
property var buyToken: null
property string sellAmount: ""
property string sellInput: ""
property string buyInput: ""
property string editingSide: "sell"
property real slippageTolerancePercent: 0.5
readonly property real feePercent: 0.30

DummySwapState {
id: swapState
feeBps: 30
}

signal requestTokenSelect(string side)
signal submitRequested(var snapshot)
Expand All @@ -22,33 +30,44 @@ Rectangle {
}

function resetAmounts() {
root.sellAmount = ""
root.sellInput = ""
root.buyInput = ""
root.editingSide = "sell"
}

readonly property real parsedSellAmount: {
var amt = parseFloat(sellAmount)
readonly property real sellReserve: sellToken ? (sellToken.reserve || 0) : 0
readonly property real buyReserve: buyToken ? (buyToken.reserve || 0) : 0

readonly property real parsedSellInput: {
var amt = parseFloat(sellInput)
return isNaN(amt) || amt < 0 ? 0 : amt
}

readonly property real parsedBuyAmount: {
if (!sellToken || !buyToken || parsedSellAmount <= 0) return 0
return parsedSellAmount * sellToken.usdPrice / buyToken.usdPrice
readonly property real parsedBuyInput: {
var amt = parseFloat(buyInput)
return isNaN(amt) || amt < 0 ? 0 : amt
}

readonly property real minReceivedAmount: parsedBuyAmount * (1 - slippageTolerancePercent / 100)
readonly property real parsedSellAmount: editingSide === "sell"
? parsedSellInput
: swapState.amountInFor(parsedBuyInput, sellReserve, buyReserve)

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 real parsedBuyAmount: editingSide === "buy"
? parsedBuyInput
: swapState.amountOutFor(parsedSellInput, sellReserve, buyReserve)

readonly property bool hasAmount: parsedSellAmount > 0
readonly property real feeAmount: swapState.feeAmount(parsedSellAmount)
readonly property real minReceivedAmount: swapState.minReceived(parsedBuyAmount, slippageTolerancePercent)
readonly property real priceImpactPercent: swapState.priceImpactPercent(parsedSellAmount, parsedBuyAmount, sellReserve, buyReserve)

readonly property string swapMode: editingSide === "buy" ? "swap-exact-output" : "swap-exact-input"
readonly property string swapModeText: editingSide === "buy" ? qsTr("Exact output") : qsTr("Exact input")

readonly property bool hasAmount: editingSide === "sell" ? parsedSellInput > 0 : parsedBuyInput > 0
readonly property bool tokensSelected: sellToken !== null && buyToken !== null
readonly property bool insufficientBalance: hasAmount && sellToken !== null && parsedSellAmount > (sellToken.balance || 0)
readonly property bool insufficientLiquidity: hasAmount && buyToken !== null && parsedBuyAmount > (buyToken.reserve || 0)
readonly property bool canSubmit: tokensSelected && hasAmount && !insufficientBalance && !insufficientLiquidity
readonly property bool canSubmit: tokensSelected && hasAmount && parsedSellAmount > 0 && parsedBuyAmount > 0 && !insufficientBalance && !insufficientLiquidity

readonly property string submitButtonText: {
if (!hasAmount || !tokensSelected) return qsTr("Enter an amount")
Expand All @@ -63,21 +82,22 @@ Rectangle {
return val.toFixed(8)
}

readonly property string buyAmount: {
if (!sellToken || !buyToken || sellAmount === "") return ""
if (parsedSellAmount <= 0) return ""
return formatAmountValue(parsedBuyAmount)
}
readonly property string sellDisplay: editingSide === "sell"
? sellInput
: (parsedSellAmount > 0 ? formatAmountValue(parsedSellAmount) : "")

readonly property string buyDisplay: editingSide === "buy"
? buyInput
: (parsedBuyAmount > 0 ? formatAmountValue(parsedBuyAmount) : "")

readonly property string sellUsd: {
if (!sellToken || sellAmount === "") return ""
if (parsedSellAmount <= 0) return ""
if (!sellToken || parsedSellAmount <= 0) return ""
var val = parsedSellAmount * sellToken.usdPrice
return "~$" + val.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",")
}

readonly property string buyUsd: {
if (!buyToken || buyAmount === "") return ""
if (!buyToken || parsedBuyAmount <= 0) return ""
var val = parsedBuyAmount * buyToken.usdPrice
return "~$" + val.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",")
}
Expand All @@ -89,9 +109,12 @@ Rectangle {
"sellAmount": formatAmountValue(parsedSellAmount),
"buyAmount": formatAmountValue(parsedBuyAmount),
"minReceived": formatAmountValue(minReceivedAmount),
"feePercent": feePercent.toFixed(2) + "%",
"priceImpactPercent": priceImpactPercent < 0.01 ? "<0.01%" : priceImpactPercent.toFixed(2) + "%",
"slippageTolerance": slippageTolerancePercent.toFixed(2).replace(/0+$/, "").replace(/[.]$/, "") + "%"
"feeAmount": swapState.formatTokenAmount(feeAmount, sellToken ? sellToken.symbol : ""),
"priceImpactPercent": swapState.formatPercent(priceImpactPercent),
"priceImpactPercentValue": priceImpactPercent,
"slippageTolerance": swapState.formatSlippagePercent(slippageTolerancePercent),
"swapMode": swapMode,
"swapModeText": swapModeText
}
}

Expand All @@ -116,11 +139,14 @@ Rectangle {
Layout.fillWidth: true
theme: root.theme
label: "Sell"
amount: root.sellAmount
amount: root.sellDisplay
usdValue: root.sellUsd
token: root.sellToken
readOnly: false
onInputEdited: function(v) { root.sellAmount = v }
active: root.editingSide === "sell"
onInputEdited: function(v) {
root.sellInput = v
if (root.editingSide !== "sell") root.editingSide = "sell"
}
onTokenClicked: root.requestTokenSelect("sell")
}

Expand Down Expand Up @@ -169,13 +195,44 @@ Rectangle {
Layout.fillWidth: true
theme: root.theme
label: "Buy"
amount: root.buyAmount
amount: root.buyDisplay
usdValue: root.buyUsd
token: root.buyToken
readOnly: true
active: root.editingSide === "buy"
onInputEdited: function(v) {
root.buyInput = v
if (root.editingSide !== "buy") root.editingSide = "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
swapModeText: root.swapModeText
feeText: swapState.formatTokenAmount(root.feeAmount, root.sellToken ? root.sellToken.symbol : "")
priceImpactText: swapState.formatPercent(root.priceImpactPercent)
priceImpactPercent: root.priceImpactPercent
minReceivedText: swapState.formatTokenAmount(root.minReceivedAmount, root.buyToken ? root.buyToken.symbol : "")
}

SlippageToleranceControl {
Layout.fillWidth: true
Layout.topMargin: 12
Layout.leftMargin: 16
Layout.rightMargin: 16
tolerancePercent: root.slippageTolerancePercent
visible: root.tokensSelected && root.hasAmount

onToleranceChangeRequested: function(tolerancePercent) {
root.slippageTolerancePercent = swapState.clampSlippagePercent(tolerancePercent);
}
}

Rectangle {
id: ctaBox
Layout.fillWidth: true
Expand Down
10 changes: 4 additions & 6 deletions amm-ui/qml/TokenInput.qml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Rectangle {
property string amount: ""
property string usdValue: ""
property var token: null
property bool readOnly: false
property bool active: true

signal tokenClicked()
signal inputEdited(string newValue)
Expand All @@ -18,11 +18,10 @@ Rectangle {
target: tiInput
property: "text"
value: root.amount
when: root.readOnly
}

radius: 16
color: theme.colors.inputBg
color: root.active ? theme.colors.inputBg : theme.colors.panelBg
implicitHeight: 110

Behavior on color { ColorAnimation { duration: 300 } }
Expand Down Expand Up @@ -52,13 +51,12 @@ Rectangle {
TextInput {
id: tiInput
anchors.fill: parent
color: theme.colors.textPrimary
color: root.active ? theme.colors.textPrimary : theme.colors.textSecondary
font.pixelSize: 36
font.weight: Font.Bold
readOnly: root.readOnly
selectionColor: theme.colors.selection
clip: true
onTextChanged: { if (!root.readOnly) root.inputEdited(text) }
onTextEdited: root.inputEdited(text)
validator: RegularExpressionValidator {
regularExpression: /^[0-9]*\.?[0-9]*$/
}
Expand Down
67 changes: 10 additions & 57 deletions amm-ui/qml/components/SwapConfirmationDialog.qml
Original file line number Diff line number Diff line change
Expand Up @@ -141,64 +141,17 @@ FocusScope {
}
}

ColumnLayout {
spacing: 8
SwapSummary {
Layout.fillWidth: true

Item {
Layout.fillWidth: true
implicitHeight: 18
Text {
anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter
text: qsTr("Fee"); color: root.theme.colors.textSecondary; font.pixelSize: 12
}
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
}
}
theme: root.theme
swapModeText: root.snapshot.swapModeText || ""
feeText: root.snapshot.feeAmount || ""
priceImpactText: root.snapshot.priceImpactPercent || ""
priceImpactPercent: Number(root.snapshot.priceImpactPercentValue) || 0
slippageText: root.snapshot.slippageTolerance || ""
minReceivedText: qsTr("%1 %2")
.arg(root.snapshot.minReceived || "")
.arg(root.snapshot.buyToken || "")
}

RowLayout {
Expand Down
Loading
Loading