feat(amm-ui): add new position prototype #221

This commit is contained in:
Ricardo Guilherme Schmidt 2026-07-10 02:43:57 -03:00
parent e8c70fb2a5
commit 07533bc799
No known key found for this signature in database
GPG Key ID: 1396EA17DE132FFE
5 changed files with 1910 additions and 140 deletions

View File

@ -0,0 +1,230 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
FocusScope {
id: root
property var snapshot: ({})
property bool open: false
signal canceled
signal confirmed(var snapshot)
visible: root.open
z: 20
Keys.onEscapePressed: root.cancel()
function openWithSnapshot(nextSnapshot) {
root.snapshot = nextSnapshot;
root.open = true;
root.forceActiveFocus();
cancelButton.forceActiveFocus();
}
function cancel() {
root.open = false;
root.canceled();
}
function confirm() {
const confirmedSnapshot = root.snapshot;
root.open = false;
root.confirmed(confirmedSnapshot);
}
Rectangle {
anchors.fill: parent
color: "#99000000"
MouseArea {
anchors.fill: parent
}
}
Rectangle {
id: panel
anchors.centerIn: parent
color: "#1D1D1D"
implicitHeight: dialogContent.implicitHeight + 24
radius: 8
width: Math.max(0, Math.min(420, root.width - 32))
border.color: "#343434"
border.width: 1
ColumnLayout {
id: dialogContent
anchors.fill: parent
anchors.margins: 12
spacing: 12
Text {
color: "#E7E1D8"
font.bold: true
font.pixelSize: 16
text: qsTr("Confirm new position")
Layout.fillWidth: true
}
Rectangle {
color: "#151515"
radius: 8
border.color: "#343434"
border.width: 1
Layout.fillWidth: true
Layout.preferredHeight: summaryLayout.implicitHeight + 20
ColumnLayout {
id: summaryLayout
anchors.fill: parent
anchors.margins: 10
spacing: 8
SummaryRow {
label: qsTr("Pair")
value: root.snapshot.pairText || ""
Layout.fillWidth: true
}
SummaryRow {
label: qsTr("Instruction")
value: root.snapshot.instructionText || ""
Layout.fillWidth: true
}
SummaryRow {
label: qsTr("Fee tier")
value: root.snapshot.feeLabel || ""
Layout.fillWidth: true
}
SummaryRow {
label: qsTr("Deposit %1").arg(root.snapshot.tokenA || "")
value: root.snapshot.depositA || ""
Layout.fillWidth: true
}
SummaryRow {
label: qsTr("Deposit %1").arg(root.snapshot.tokenB || "")
value: root.snapshot.depositB || ""
Layout.fillWidth: true
}
SummaryRow {
label: qsTr("Expected LP")
value: root.snapshot.expectedLp || ""
Layout.fillWidth: true
}
SummaryRow {
label: qsTr("Minimum LP")
value: root.snapshot.minimumLp || ""
Layout.fillWidth: true
}
SummaryRow {
label: qsTr("Quote hash")
value: root.snapshot.shortQuoteHash || ""
Layout.fillWidth: true
}
}
}
Text {
color: "#A9A098"
font.pixelSize: 12
lineHeight: 1.25
text: qsTr("Submit will re-quote against current wallet and chain state before dispatch.")
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
RowLayout {
spacing: 8
Layout.fillWidth: true
Button {
id: cancelButton
activeFocusOnTab: true
focusPolicy: Qt.StrongFocus
hoverEnabled: true
text: qsTr("Cancel")
Accessible.name: cancelButton.text
Layout.fillWidth: true
Layout.minimumHeight: 44
onClicked: root.cancel()
contentItem: Text {
color: cancelButton.hovered || cancelButton.activeFocus ? "#151515" : "#E7E1D8"
elide: Text.ElideRight
font.bold: true
font.pixelSize: 13
horizontalAlignment: Text.AlignHCenter
text: cancelButton.text
verticalAlignment: Text.AlignVCenter
}
background: Rectangle {
border.color: cancelButton.activeFocus ? "#F26A21" : "#343434"
border.width: 1
color: cancelButton.pressed ? "#343434" : cancelButton.hovered || cancelButton.activeFocus ? "#E7E1D8" : "#151515"
radius: 6
}
}
Button {
id: confirmButton
activeFocusOnTab: true
focusPolicy: Qt.StrongFocus
hoverEnabled: true
text: qsTr("Submit")
Accessible.name: confirmButton.text
Layout.fillWidth: true
Layout.minimumHeight: 44
onClicked: root.confirm()
contentItem: Text {
color: "#151515"
elide: Text.ElideRight
font.bold: true
font.pixelSize: 13
horizontalAlignment: Text.AlignHCenter
text: confirmButton.text
verticalAlignment: Text.AlignVCenter
}
background: Rectangle {
border.color: "#F26A21"
border.width: 1
color: confirmButton.pressed ? "#D95C1E" : confirmButton.hovered || confirmButton.activeFocus ? "#FF8A3D" : "#F26A21"
radius: 6
}
}
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -10,6 +10,8 @@ Rectangle {
property string errorText: ""
property string helperText: ""
property string label: ""
property bool readOnly: false
property bool showMaxButton: true
property string token: ""
signal editingChanged(string value)
@ -71,6 +73,7 @@ Rectangle {
font.pixelSize: 18
inputMethodHints: Qt.ImhFormattedNumbersOnly
placeholderText: qsTr("0")
readOnly: root.readOnly
selectByMouse: true
selectedTextColor: "#151515"
selectionColor: "#F26A21"
@ -83,7 +86,10 @@ Rectangle {
Layout.fillWidth: true
Layout.minimumHeight: 44
onTextEdited: root.editingChanged(text)
onTextEdited: {
if (!root.readOnly)
root.editingChanged(text);
}
background: Rectangle {
border.color: amountField.activeFocus ? "#F26A21" : "#343434"
@ -97,14 +103,16 @@ Rectangle {
id: maxButton
activeFocusOnTab: true
enabled: root.showMaxButton && !root.readOnly
focusPolicy: Qt.StrongFocus
hoverEnabled: true
text: qsTr("MAX")
visible: root.showMaxButton
Accessible.name: qsTr("Use maximum %1 balance").arg(root.token)
Layout.minimumHeight: 44
Layout.preferredWidth: 58
Layout.preferredWidth: visible ? 58 : 0
onClicked: root.maxClicked()

View File

@ -1,5 +1,4 @@
import QtQuick 2.15
import QtQuick.Layouts 1.15
import "../components/shared"
import "../components/liquidity"
import "../state"
@ -7,19 +6,17 @@ import "../state"
Item {
id: root
property int activeLiquidityTab: 0
property real slippageTolerancePercent: 0.5
readonly property int pageMargin: 16
readonly property int preferredCardWidth: 492
readonly property int pageCardY: pageCard.implicitHeight + root.pageMargin * 2 <= scroll.height ? Math.round((scroll.height - pageCard.implicitHeight) / 2) : root.pageMargin
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
width: parent ? parent.width : implicitWidth
height: parent ? parent.height : implicitHeight
implicitWidth: root.preferredCardWidth + root.pageMargin * 2
implicitHeight: pageCard.implicitHeight + root.pageMargin * 2
implicitHeight: newPositionForm.implicitHeight + root.pageMargin * 2
DummyPoolState {
id: poolState
NewPositionPrototypeBackend {
id: newPositionBackend
}
Rectangle {
@ -32,158 +29,57 @@ Item {
anchors.fill: parent
clip: true
contentHeight: Math.max(height, pageCard.y + pageCard.implicitHeight + root.pageMargin)
contentHeight: Math.max(height, newPositionForm.y + newPositionForm.implicitHeight + root.pageMargin)
contentWidth: width
enabled: !confirmationDialog.visible
enabled: !confirmationDialog.open
flickableDirection: Flickable.VerticalFlick
Rectangle {
id: pageCard
NewPositionForm {
id: newPositionForm
color: "#1B1B1B"
implicitHeight: shellContent.implicitHeight + 24
radius: 16
border.color: "#303030"
border.width: 1
prototypeBackend: newPositionBackend
width: Math.max(0, Math.min(scroll.width - root.pageMargin * 2, root.preferredCardWidth))
x: Math.max(root.pageMargin, (scroll.width - width) / 2)
y: root.pageCardY
ColumnLayout {
id: shellContent
anchors.fill: parent
anchors.margins: 12
spacing: 10
RowLayout {
spacing: 10
Layout.fillWidth: true
Text {
color: "#E7E1D8"
font.bold: true
font.pixelSize: 18
text: qsTr("Liquidity")
Layout.fillWidth: true
}
Rectangle {
color: "#211914"
radius: 12
border.color: "#49301F"
border.width: 1
Layout.preferredHeight: 26
Layout.preferredWidth: pairText.implicitWidth + 20
Text {
id: pairText
anchors.centerIn: parent
color: "#F2D8C7"
font.bold: true
font.pixelSize: 12
text: qsTr("%1 / %2").arg(poolState.tokenA).arg(poolState.tokenB)
}
}
}
LiquidityActionTabs {
currentIndex: root.activeLiquidityTab
Layout.fillWidth: true
Layout.preferredHeight: implicitHeight
onTabRequested: function (index) {
root.activeLiquidityTab = index;
}
}
PoolPositionSummary {
poolState: poolState
Layout.fillWidth: true
Layout.preferredHeight: implicitHeight
}
AddLiquidityForm {
id: addLiquidityForm
poolState: poolState
slippageTolerancePercent: root.slippageTolerancePercent
visible: root.activeLiquidityTab === 0
Layout.fillWidth: true
Layout.preferredHeight: visible ? implicitHeight : 0
onSlippageToleranceChangeRequested: function (tolerancePercent) {
root.slippageTolerancePercent = poolState.clampSlippageTolerancePercent(tolerancePercent);
}
onAddLiquidityRequested: function (snapshot) {
confirmationDialog.openWithSnapshot(snapshot);
}
}
RemoveLiquidityForm {
id: removeLiquidityForm
poolState: poolState
slippageTolerancePercent: root.slippageTolerancePercent
visible: root.activeLiquidityTab === 1
Layout.fillWidth: true
Layout.preferredHeight: visible ? implicitHeight : 0
onSlippageToleranceChangeRequested: function (tolerancePercent) {
root.slippageTolerancePercent = poolState.clampSlippageTolerancePercent(tolerancePercent);
}
onRemoveLiquidityRequested: function (snapshot) {
confirmationDialog.openWithSnapshot(snapshot);
}
}
}
SuccessToast {
id: successToast
width: Math.max(0, Math.min(380, parent.width - 24))
anchors {
bottom: parent.bottom
bottomMargin: 14
horizontalCenter: parent.horizontalCenter
}
onConfirmationRequested: function (snapshot) {
confirmationDialog.openWithSnapshot(snapshot);
}
}
}
LiquidityConfirmationDialog {
SuccessToast {
id: successToast
width: Math.max(0, Math.min(380, root.width - 32))
z: 30
anchors {
bottom: parent.bottom
bottomMargin: 18
horizontalCenter: parent.horizontalCenter
}
}
NewPositionConfirmationDialog {
id: confirmationDialog
anchors.fill: parent
onConfirmed: function (snapshot) {
root.confirmLiquidityAction(snapshot);
root.confirmNewPosition(snapshot);
}
}
function confirmLiquidityAction(snapshot) {
if (snapshot.action === "add") {
poolState.applyAddLiquidity(snapshot.actualA, snapshot.actualB, snapshot.deltaLp);
addLiquidityForm.resetForm();
successToast.show(qsTr("Liquidity added"), qsTr("Position updated"));
function confirmNewPosition(snapshot) {
const result = newPositionBackend.submitNewPosition(snapshot.request, snapshot.quoteHash);
if (result.status !== "ok") {
newPositionForm.setSubmitError(result.error);
return;
}
if (snapshot.action === "remove") {
poolState.applyRemoveLiquidity(snapshot.withdrawA, snapshot.withdrawB, snapshot.burnAmount);
removeLiquidityForm.resetForm();
successToast.show(qsTr("Liquidity removed"), qsTr("Position updated"));
}
newPositionForm.resetAfterSubmit();
successToast.show(result.message, result.detail);
}
}

View File

@ -0,0 +1,468 @@
import QtQuick 2.15
QtObject {
id: root
readonly property string activeAccount: "lz1q9s...42fd"
readonly property var holdings: [
{
"symbol": "USDC",
"name": "USD Coin",
"definitionId": "token:usdc-testnet",
"holdingId": "holding:active:usdc",
"balance": 12450,
"balanceText": "12,450.00 USDC",
"accent": "#2E7CF6"
},
{
"symbol": "LOGOS",
"name": "Logos",
"definitionId": "token:logos-testnet",
"holdingId": "holding:active:logos",
"balance": 850000,
"balanceText": "850,000 LOGOS",
"accent": "#F26A21"
},
{
"symbol": "WETH",
"name": "Wrapped Ether",
"definitionId": "token:weth-testnet",
"holdingId": "holding:active:weth",
"balance": 3.25,
"balanceText": "3.25 WETH",
"accent": "#B7C2D8"
}
]
readonly property var feeTiers: [
{
"bps": 1,
"label": "0.01%",
"supported": true
},
{
"bps": 5,
"label": "0.05%",
"supported": true
},
{
"bps": 25,
"label": "0.25%",
"supported": false
},
{
"bps": 30,
"label": "0.30%",
"supported": true
},
{
"bps": 100,
"label": "1.00%",
"supported": true
}
]
readonly property real minimumLiquidity: 1000
function holdingBySymbol(symbol) {
for (let i = 0; i < root.holdings.length; ++i) {
if (root.holdings[i].symbol === symbol)
return root.holdings[i];
}
return null;
}
function feeTierByBps(bps) {
for (let i = 0; i < root.feeTiers.length; ++i) {
if (root.feeTiers[i].bps === bps)
return root.feeTiers[i];
}
return null;
}
function feeLabel(bps) {
const tier = feeTierByBps(bps);
return tier ? tier.label : qsTr("%1 bps").arg(bps);
}
function parseAmount(value) {
const parsed = Number(value);
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$/, "").replace(/\B(?=(\d{3})+(?!\d))/g, ",");
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(formatAmount(value)).arg(symbol);
}
function unorderedPairKey(symbolA, symbolB) {
return symbolA < symbolB ? symbolA + "/" + symbolB : symbolB + "/" + symbolA;
}
function poolContext(symbolA, symbolB) {
if (!symbolA || !symbolB || symbolA === symbolB) {
return {
"poolStatus": "unavailable_pool",
"statusLabel": qsTr("Unavailable"),
"detail": qsTr("Choose two different assets from the active account."),
"instruction": "",
"storedFeeBps": 0,
"poolId": "",
"priceText": "",
"reserveText": ""
};
}
const key = unorderedPairKey(symbolA, symbolB);
if (key === "LOGOS/USDC") {
return {
"poolStatus": "active_pool",
"statusLabel": qsTr("Active pool"),
"detail": qsTr("Deposits are quoted against the existing pool ratio. Nonmatching fee tiers are locked."),
"instruction": "add_liquidity",
"storedFeeBps": 30,
"poolId": "pool:usdc-logos",
"priceText": qsTr("1 USDC = 8 LOGOS"),
"reserveText": qsTr("1,250,000 USDC / 10,000,000 LOGOS")
};
}
if (key === "USDC/WETH") {
return {
"poolStatus": "missing_pool",
"statusLabel": qsTr("Missing pool"),
"detail": qsTr("Set the initial price first, then scale both deposits together."),
"instruction": "new_definition",
"storedFeeBps": 0,
"poolId": "pool:usdc-weth",
"priceText": qsTr("No reserves yet"),
"reserveText": qsTr("Pool account is empty")
};
}
return {
"poolStatus": "unavailable_pool",
"statusLabel": qsTr("Unavailable"),
"detail": qsTr("A pool account exists, but it cannot be quoted safely for this prototype state."),
"instruction": "",
"storedFeeBps": 0,
"poolId": "pool:logos-weth",
"priceText": qsTr("Quote disabled"),
"reserveText": qsTr("Unsupported stored pool state")
};
}
function activeQuote(request, context) {
const tokenA = holdingBySymbol(request.tokenA);
const tokenB = holdingBySymbol(request.tokenB);
const inputA = parseAmount(request.amountA);
const inputB = parseAmount(request.amountB);
const editA = request.editedSide !== "B";
const amountA = editA ? inputA : inputB / activeRatio(request.tokenA, request.tokenB);
const amountB = editA ? inputA * activeRatio(request.tokenA, request.tokenB) : inputB;
const expectedLp = Math.floor(Math.min(amountA * 5.5, amountB * 0.69));
const minLp = Math.floor(expectedLp * (10000 - request.slippageBps) / 10000);
if (inputA <= 0 && inputB <= 0)
return quoteError(context, request, qsTr("Enter a deposit amount to preview LP output."));
if (amountA > tokenA.balance)
return quoteError(context, request, qsTr("Insufficient %1 balance.").arg(tokenA.symbol));
if (amountB > tokenB.balance)
return quoteError(context, request, qsTr("Insufficient %1 balance.").arg(tokenB.symbol));
if (minLp <= 0)
return quoteError(context, request, qsTr("LP minimum rounds to zero. Increase deposit amount."));
return quoteOk(context, request, {
"maxA": amountA,
"maxB": amountB,
"actualA": amountA,
"actualB": amountB,
"expectedLp": expectedLp,
"minimumLp": minLp,
"lockedLp": 0,
"position": {
"userLp": "148,320 LP",
"share": "1.18%",
"ownedA": request.tokenA === "USDC" ? "14,750 USDC" : "118,000 LOGOS",
"ownedB": request.tokenB === "LOGOS" ? "118,000 LOGOS" : "14,750 USDC"
},
"accountChanges": [
{
"role": qsTr("Config"),
"id": "amm:config",
"action": qsTr("Read")
},
{
"role": qsTr("Pool"),
"id": context.poolId,
"action": qsTr("Update")
},
{
"role": qsTr("Vault A"),
"id": "vault:" + request.tokenA.toLowerCase(),
"action": qsTr("Update")
},
{
"role": qsTr("Vault B"),
"id": "vault:" + request.tokenB.toLowerCase(),
"action": qsTr("Update")
},
{
"role": qsTr("User LP holding"),
"id": "holding:active:lp",
"action": qsTr("Update or create")
},
{
"role": qsTr("Clock"),
"id": "clock:canonical",
"action": qsTr("Read")
}
]
});
}
function missingQuote(request, context) {
const tokenA = holdingBySymbol(request.tokenA);
const tokenB = holdingBySymbol(request.tokenB);
const price = parseAmount(request.initialPrice) || defaultInitialPrice(request.tokenA, request.tokenB);
const scale = Math.max(1, Number(request.depositScale) || 1);
const amountA = price >= 1 ? price * scale : scale;
const amountB = price >= 1 ? scale : scale / price;
const expectedLp = Math.floor(Math.sqrt(amountA * amountB) * 48);
const userLp = Math.max(0, expectedLp - root.minimumLiquidity);
const minLp = Math.floor(userLp * (10000 - request.slippageBps) / 10000);
if (amountA > tokenA.balance)
return quoteError(context, request, qsTr("Initial deposit exceeds %1 balance.").arg(tokenA.symbol));
if (amountB > tokenB.balance)
return quoteError(context, request, qsTr("Initial deposit exceeds %1 balance.").arg(tokenB.symbol));
if (userLp <= 0)
return quoteError(context, request, qsTr("Deposit must mint more than the locked minimum liquidity."));
return quoteOk(context, request, {
"maxA": amountA,
"maxB": amountB,
"actualA": amountA,
"actualB": amountB,
"expectedLp": userLp,
"minimumLp": minLp,
"lockedLp": root.minimumLiquidity,
"position": {
"userLp": "0 LP",
"share": "New pool",
"ownedA": "0 " + request.tokenA,
"ownedB": "0 " + request.tokenB
},
"accountChanges": [
{
"role": qsTr("Config"),
"id": "amm:config",
"action": qsTr("Read")
},
{
"role": qsTr("Pool"),
"id": context.poolId,
"action": qsTr("Create")
},
{
"role": qsTr("Vault A"),
"id": "vault:" + request.tokenA.toLowerCase(),
"action": qsTr("Update or create")
},
{
"role": qsTr("Vault B"),
"id": "vault:" + request.tokenB.toLowerCase(),
"action": qsTr("Update or create")
},
{
"role": qsTr("LP definition"),
"id": "lp:" + context.poolId,
"action": qsTr("Create")
},
{
"role": qsTr("LP lock holding"),
"id": "holding:lp-lock",
"action": qsTr("Create")
},
{
"role": qsTr("User LP holding"),
"id": "holding:active:lp",
"action": qsTr("Update or create")
},
{
"role": qsTr("Current tick"),
"id": "twap:" + context.poolId,
"action": qsTr("Create")
},
{
"role": qsTr("Clock"),
"id": "clock:canonical",
"action": qsTr("Read")
}
]
});
}
function quoteNewPosition(request) {
const context = poolContext(request.tokenA, request.tokenB);
const tier = feeTierByBps(request.feeBps);
if (!tier || !tier.supported)
return quoteError(context, request, qsTr("Fee tier is not supported by the AMM program."));
if (context.poolStatus === "active_pool" && request.feeBps !== context.storedFeeBps)
return quoteError(context, request, qsTr("Existing pool uses %1.").arg(feeLabel(context.storedFeeBps)));
if (context.poolStatus === "active_pool")
return activeQuote(request, context);
if (context.poolStatus === "missing_pool")
return missingQuote(request, context);
return quoteError(context, request, context.detail);
}
function submitNewPosition(request, quoteHash) {
const quote = quoteNewPosition(request);
if (quote.status !== "ok") {
return {
"status": "error",
"error": quote.error
};
}
if (quote.quoteHash !== quoteHash) {
return {
"status": "error",
"error": qsTr("Quote changed. Refresh preview before submitting.")
};
}
return {
"status": "ok",
"message": quote.instruction === "new_definition" ? qsTr("Pool creation submitted") : qsTr("Liquidity deposit submitted"),
"detail": qsTr("%1 / %2").arg(request.tokenA).arg(request.tokenB)
};
}
function quoteOk(context, request, amounts) {
return {
"status": "ok",
"error": "",
"poolStatus": context.poolStatus,
"statusLabel": context.statusLabel,
"statusDetail": context.detail,
"instruction": context.instruction,
"feeBps": request.feeBps,
"feeLabel": feeLabel(request.feeBps),
"quoteHash": quoteHash(request),
"pool": {
"id": context.poolId,
"priceText": context.poolStatus === "missing_pool" ? qsTr("1 %1 = %2 %3").arg(request.tokenB).arg(formatAmount(parseAmount(request.initialPrice) || defaultInitialPrice(request.tokenA, request.tokenB))).arg(request.tokenA) : context.priceText,
"reserveText": context.reserveText
},
"deposit": {
"maxA": amountValue(amounts.maxA, request.tokenA),
"maxB": amountValue(amounts.maxB, request.tokenB),
"actualA": amountValue(amounts.actualA, request.tokenA),
"actualB": amountValue(amounts.actualB, request.tokenB)
},
"lp": {
"expected": amountValue(amounts.expectedLp, "LP"),
"minimum": amountValue(amounts.minimumLp, "LP"),
"locked": amountValue(amounts.lockedLp, "LP")
},
"position": amounts.position,
"accountChanges": amounts.accountChanges
};
}
function quoteError(context, request, errorText) {
return {
"status": "error",
"error": errorText,
"poolStatus": context.poolStatus,
"statusLabel": context.statusLabel,
"statusDetail": context.detail,
"instruction": context.instruction,
"feeBps": request.feeBps,
"feeLabel": feeLabel(request.feeBps),
"quoteHash": quoteHash(request),
"pool": {
"id": context.poolId,
"priceText": context.priceText,
"reserveText": context.reserveText
},
"deposit": {
"maxA": amountValue(0, request.tokenA),
"maxB": amountValue(0, request.tokenB),
"actualA": amountValue(0, request.tokenA),
"actualB": amountValue(0, request.tokenB)
},
"lp": {
"expected": amountValue(0, "LP"),
"minimum": amountValue(0, "LP"),
"locked": amountValue(context.poolStatus === "missing_pool" ? root.minimumLiquidity : 0, "LP")
},
"position": {
"userLp": "0 LP",
"share": "-",
"ownedA": "0 " + request.tokenA,
"ownedB": "0 " + request.tokenB
},
"accountChanges": []
};
}
function amountValue(value, symbol) {
const amount = Math.max(0, Number(value) || 0);
return {
"value": amount,
"input": formatAmount(amount),
"display": formatTokenAmount(amount, symbol),
"symbol": symbol
};
}
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 quoteHash(request) {
return "demo-" + request.tokenA + "-" + request.tokenB + "-" + request.feeBps + "-" + request.editedSide + "-" + request.amountA + "-" + request.amountB + "-" + request.initialPrice + "-" + request.depositScale + "-" + request.slippageBps;
}
}