mirror of
https://github.com/logos-blockchain/lez-programs.git
synced 2026-07-03 05:29:50 +00:00
feat(amm): complete wallet-backed AMM flow
This commit is contained in:
parent
35c2dd707e
commit
295c4efece
@ -1,6 +1,25 @@
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
project(AmmUiPlugin LANGUAGES CXX)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
|
||||
add_compile_definitions(AMM_UI_ASSET_DIR="${CMAKE_INSTALL_FULL_LIBDIR}")
|
||||
|
||||
set(AMM_UI_PROGRAM_DIR "" CACHE PATH "Directory containing amm.bin and token.bin")
|
||||
if(NOT AMM_UI_PROGRAM_DIR AND DEFINED ENV{AMM_UI_PROGRAM_DIR})
|
||||
set(AMM_UI_PROGRAM_DIR "$ENV{AMM_UI_PROGRAM_DIR}" CACHE PATH "Directory containing amm.bin and token.bin" FORCE)
|
||||
endif()
|
||||
|
||||
if(AMM_UI_PROGRAM_DIR)
|
||||
foreach(_program_binary IN ITEMS amm.bin token.bin)
|
||||
if(NOT EXISTS "${AMM_UI_PROGRAM_DIR}/${_program_binary}")
|
||||
message(FATAL_ERROR "Required AMM UI program binary not found: ${AMM_UI_PROGRAM_DIR}/${_program_binary}")
|
||||
endif()
|
||||
endforeach()
|
||||
else()
|
||||
message(WARNING "AMM_UI_PROGRAM_DIR not set; amm.bin and token.bin will not be installed")
|
||||
endif()
|
||||
|
||||
if(DEFINED ENV{LOGOS_MODULE_BUILDER_ROOT})
|
||||
include($ENV{LOGOS_MODULE_BUILDER_ROOT}/cmake/LogosModule.cmake)
|
||||
else()
|
||||
@ -26,4 +45,17 @@ logos_module(
|
||||
LINK_LIBRARIES
|
||||
Qt6::Gui
|
||||
Qt6::Network
|
||||
${CMAKE_DL_LIBS}
|
||||
)
|
||||
|
||||
install(DIRECTORY config
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
)
|
||||
|
||||
if(AMM_UI_PROGRAM_DIR)
|
||||
install(FILES
|
||||
${AMM_UI_PROGRAM_DIR}/amm.bin
|
||||
${AMM_UI_PROGRAM_DIR}/token.bin
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/programs
|
||||
)
|
||||
endif()
|
||||
|
||||
@ -27,6 +27,16 @@ Account/keystore sharing follows the runtime:
|
||||
startup the backend **adopts** the already-open wallet (see
|
||||
`openOrAdoptWallet()`), surfacing **shared** accounts across apps.
|
||||
|
||||
Deployment config is chain-aware. `config/supported-chains.json` stores each
|
||||
supported chain identity, including the deterministic block-1 fingerprint
|
||||
(`genesisBlockHash` + `genesisBlockSignature`). Each `config/*-programs.json`
|
||||
deployment uses a short `chainRef` plus program-specific IDs and transaction
|
||||
hashes. When a wallet connects, the backend reads the wallet's current
|
||||
`sequencer_addr`, probes block 1, selects the matching chain deployment, and
|
||||
verifies configured deployment transactions. If no matching AMM deployment is
|
||||
configured or deployed on that chain, the UI shows **Unsupported chain** instead
|
||||
of submitting transactions.
|
||||
|
||||
> Follow-up: the wallet FFI requires explicit `config_path`/`storage_path` even
|
||||
> though the wallet crate already defines defaults (`~/.lee/wallet`,
|
||||
> `from_path_or_initialize_default`). A `wallet_ffi_create_new_default()` /
|
||||
|
||||
31
apps/amm/config/amm-programs.json
Normal file
31
apps/amm/config/amm-programs.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"chains": [
|
||||
{
|
||||
"chainRef": "local.v0.2.0-rc5",
|
||||
"programs": [
|
||||
{
|
||||
"name": "AMM Program",
|
||||
"id": "EEkCMCyv7at1incttPxmc6Y1cgwmnzNPKhMwB5ypnKZv",
|
||||
"imageIdHex": "c4ad5271f1d1a076b6c9bcb5ca4e62812c88728b6c3cd00f6634f7305ca5ddf5",
|
||||
"deploymentTransaction": "35c8b75d26e2d1e84f81a77f69e1d17fe31b1e57f824a43599fa5f84926385cf",
|
||||
"tokenProgram": "2NPgqw4JxSrkV4MiMAQ4MgW6vcbxXrWkGoWmMMxSaYRV",
|
||||
"twapOracleProgram": "AjAJzR2rrEyCvwpemcjC9tbzre38WnXTX14YuT6cWugF",
|
||||
"abi": "legacy-v0.2.0-rc3",
|
||||
"configs": [],
|
||||
"pools": [
|
||||
{
|
||||
"name": "AMM197A/AMM197B",
|
||||
"account": "FPCZi416PrSbWBo8gMAuDqXk3DH8nmXw5PFn3NbCsod6",
|
||||
"tokenA": "AMM197A",
|
||||
"tokenB": "AMM197B",
|
||||
"vaultA": "DmoYscM1fKv5fur9kw6wtmVGkxLvjsBvD7L3Tdjiaxoe",
|
||||
"vaultB": "EkVPMUmGQbfo7zZ7dWnwnjMZCQqySEZ16vjN99PEC3dN",
|
||||
"lpDefinitionAccount": "2P5kBN1SfVcjJYCGFggzCDUHKZvVaMsbM9tFG4xj4NVy",
|
||||
"creationTransaction": "4ff22fcc9b70ae0b748b1cb186b547a1b35f76fd55eb96ff21a780b660322ac6"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
16
apps/amm/config/ata-programs.json
Normal file
16
apps/amm/config/ata-programs.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"chains": [
|
||||
{
|
||||
"chainRef": "local.v0.2.0-rc5",
|
||||
"programs": [
|
||||
{
|
||||
"name": "Associated Token Account Program",
|
||||
"id": "D7TdWT1wVpDiH9wfLBdLfE2R1Kgy8FNW9PWP62uRqu9x",
|
||||
"imageIdHex": "b3f3d685c4bc2ed261503262fd650be78d104c1179ce30ef35738b36d7e11d97",
|
||||
"deploymentTransaction": "988f6f08ac72df65c479e06d475ec08d0dc42c4dc060c4b179f2754094821449",
|
||||
"tokenProgram": "2NPgqw4JxSrkV4MiMAQ4MgW6vcbxXrWkGoWmMMxSaYRV"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
17
apps/amm/config/stablecoin-programs.json
Normal file
17
apps/amm/config/stablecoin-programs.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"chains": [
|
||||
{
|
||||
"chainRef": "local.v0.2.0-rc5",
|
||||
"programs": [
|
||||
{
|
||||
"name": "Stablecoin Program",
|
||||
"id": "2hMHPg6CRwyNqcou3sP6RTnNpJXnVPBWE8Za8aUFAMQu",
|
||||
"imageIdHex": "1931da57b56bc15a1c26a7965e1632f59dd2895affbf1258ce49e0268265a322",
|
||||
"deploymentTransaction": "85ee89661f478cdad87b4516b381b800f9185b55276c21d37666b30682e171be",
|
||||
"tokenProgram": "2NPgqw4JxSrkV4MiMAQ4MgW6vcbxXrWkGoWmMMxSaYRV",
|
||||
"twapOracleProgram": "AjAJzR2rrEyCvwpemcjC9tbzre38WnXTX14YuT6cWugF"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
14
apps/amm/config/supported-chains.json
Normal file
14
apps/amm/config/supported-chains.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"chains": [
|
||||
{
|
||||
"alias": "local.v0.2.0-rc5",
|
||||
"name": "LEZ local",
|
||||
"network": "http://127.0.0.1:3040",
|
||||
"chainId": "lez:block-1:e437c44cac2511b603b96195e6b490217da6ba47a326a1fa6c939167ec3e8656:032f82b1ba318b0028f853d25bbc2da49ace15c100b7337fc06eddcf4bfa777906409810c8e09e57831000462a857c26631bb1b8087391e34a00ab3d2ebf8739",
|
||||
"genesisBlockId": 1,
|
||||
"genesisBlockHash": "e437c44cac2511b603b96195e6b490217da6ba47a326a1fa6c939167ec3e8656",
|
||||
"genesisBlockSignature": "032f82b1ba318b0028f853d25bbc2da49ace15c100b7337fc06eddcf4bfa777906409810c8e09e57831000462a857c26631bb1b8087391e34a00ab3d2ebf8739",
|
||||
"default": true
|
||||
}
|
||||
]
|
||||
}
|
||||
35
apps/amm/config/token-programs.json
Normal file
35
apps/amm/config/token-programs.json
Normal file
@ -0,0 +1,35 @@
|
||||
{
|
||||
"chains": [
|
||||
{
|
||||
"chainRef": "local.v0.2.0-rc5",
|
||||
"programs": [
|
||||
{
|
||||
"name": "Token Program",
|
||||
"id": "2NPgqw4JxSrkV4MiMAQ4MgW6vcbxXrWkGoWmMMxSaYRV",
|
||||
"imageIdHex": "14568940ba11698d23703dff8d9df6703c32e2412f89ff32efc46a5cad70f500",
|
||||
"deploymentTransaction": "27401f74353c9863fecb27889d2e0fdaccb7e0a652a284929c67bf0fdc6c5470",
|
||||
"definitions": [
|
||||
{
|
||||
"symbol": "AMM197A",
|
||||
"name": "AMM197A",
|
||||
"color": "#2775ca",
|
||||
"letter": "A",
|
||||
"usdPrice": 1,
|
||||
"definitionAccount": "HjkLvjKqUt7PhUCs6CUEbqmrjAAbUJmeQg6TGkTAzLHS",
|
||||
"mintTransaction": "ec430fa48a6990dd31fa3f075eb16f842392bf62d0d6701b46574a3fd4549187"
|
||||
},
|
||||
{
|
||||
"symbol": "AMM197B",
|
||||
"name": "AMM197B",
|
||||
"color": "#26a17b",
|
||||
"letter": "B",
|
||||
"usdPrice": 1,
|
||||
"definitionAccount": "2b4UuHsax4pELYzQBjQe5DieHWN3zn9BrrtTGhpbYX71",
|
||||
"mintTransaction": "2edf40aefe53a8dcb2e3df8e22426841c4c9503d42179665cb8ab94434365e2a"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
15
apps/amm/config/twap-oracle-programs.json
Normal file
15
apps/amm/config/twap-oracle-programs.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"chains": [
|
||||
{
|
||||
"chainRef": "local.v0.2.0-rc5",
|
||||
"programs": [
|
||||
{
|
||||
"name": "TWAP Oracle Program",
|
||||
"id": "AjAJzR2rrEyCvwpemcjC9tbzre38WnXTX14YuT6cWugF",
|
||||
"imageIdHex": "90861a75ed743edaf5d2ab7dcc7563157435072b6c10f3db5cf50aa93322018c",
|
||||
"deploymentTransaction": "54bc196c66beefe4b8ea5ab8564743332cc6b690c0b1908c55aa1407347af488"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
10
apps/amm/flake.lock
generated
10
apps/amm/flake.lock
generated
@ -26763,7 +26763,15 @@
|
||||
"root": {
|
||||
"inputs": {
|
||||
"logos-module-builder": "logos-module-builder",
|
||||
"logos_execution_zone": "logos_execution_zone"
|
||||
"logos-standalone-app": [
|
||||
"logos-module-builder",
|
||||
"logos-standalone-app"
|
||||
],
|
||||
"logos_execution_zone": "logos_execution_zone",
|
||||
"nixpkgs": [
|
||||
"logos-module-builder",
|
||||
"nixpkgs"
|
||||
]
|
||||
}
|
||||
},
|
||||
"rust-overlay": {
|
||||
|
||||
@ -3,18 +3,79 @@
|
||||
|
||||
inputs = {
|
||||
logos-module-builder.url = "github:logos-co/logos-module-builder";
|
||||
nixpkgs.follows = "logos-module-builder/nixpkgs";
|
||||
logos-standalone-app.follows = "logos-module-builder/logos-standalone-app";
|
||||
|
||||
# Core wallet module (the LEZ wallet FFI Qt plugin). The input name must
|
||||
# match the metadata.json `dependencies` entry so the builder can resolve
|
||||
# it as a module dependency. This rev pins LEZ (lssa) at fb8cbac4, which
|
||||
# it as a module dependency. This rev pins LEZ (lssa) at d2e9400, which
|
||||
# includes the macOS Metal-build fix, so no `--override-input` is needed.
|
||||
logos_execution_zone.url = "github:logos-blockchain/logos-execution-zone-module?rev=d2e9400ac06c3cdbfc2405b4f153fff9841a453c";
|
||||
};
|
||||
|
||||
outputs = inputs@{ logos-module-builder, ... }:
|
||||
outputs = inputs@{ logos-module-builder, logos-standalone-app, nixpkgs, ... }:
|
||||
let
|
||||
systems = [ "aarch64-darwin" "x86_64-darwin" "aarch64-linux" "x86_64-linux" ];
|
||||
forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f (import nixpkgs { inherit system; }));
|
||||
|
||||
fixedStandalonePackages = forAllSystems (pkgs:
|
||||
let
|
||||
base = logos-standalone-app.packages.${pkgs.system};
|
||||
qtLibPath = pkgs.lib.makeLibraryPath ([
|
||||
pkgs.qt6.qtbase
|
||||
pkgs.qt6.qtremoteobjects
|
||||
pkgs.qt6.qtdeclarative
|
||||
pkgs.qt6.qtwebview
|
||||
pkgs.zstd
|
||||
pkgs.krb5
|
||||
pkgs.zlib
|
||||
pkgs.glib
|
||||
pkgs.stdenv.cc.cc
|
||||
pkgs.freetype
|
||||
pkgs.fontconfig
|
||||
pkgs.boost
|
||||
pkgs.openssl
|
||||
] ++ pkgs.lib.optionals pkgs.stdenv.isLinux [
|
||||
pkgs.libglvnd
|
||||
pkgs.mesa
|
||||
pkgs.xorg.libX11
|
||||
pkgs.xorg.libXext
|
||||
pkgs.xorg.libXrender
|
||||
pkgs.xorg.libXrandr
|
||||
pkgs.xorg.libXcursor
|
||||
pkgs.xorg.libXi
|
||||
pkgs.xorg.libXfixes
|
||||
pkgs.xorg.libxcb
|
||||
]);
|
||||
fixedApp = base.default.overrideAttrs (old: {
|
||||
inherit qtLibPath;
|
||||
qtWrapperArgs = (old.qtWrapperArgs or [ ]) ++ [
|
||||
"--prefix" "LD_LIBRARY_PATH" ":" qtLibPath
|
||||
"--prefix" "QT_PLUGIN_PATH" ":" old.qtPluginPath
|
||||
"--prefix" "QML_IMPORT_PATH" ":" old.qmlImportPath
|
||||
"--prefix" "QML2_IMPORT_PATH" ":" old.qmlImportPath
|
||||
];
|
||||
});
|
||||
in base // {
|
||||
app = fixedApp;
|
||||
default = fixedApp;
|
||||
});
|
||||
|
||||
fixedStandalone = logos-standalone-app // {
|
||||
packages = fixedStandalonePackages;
|
||||
};
|
||||
in
|
||||
logos-module-builder.lib.mkLogosQmlModule {
|
||||
src = ./.;
|
||||
configFile = ./metadata.json;
|
||||
flakeInputs = inputs;
|
||||
logosStandalone = fixedStandalone;
|
||||
preConfigure = ''
|
||||
export AMM_UI_PROGRAM_DIR=${inputs.logos_execution_zone.inputs."logos-execution-zone"}/artifacts/program_methods
|
||||
'';
|
||||
postInstall = ''
|
||||
mkdir -p "$out/lib/config"
|
||||
cp -r ${./config}/. "$out/lib/config/"
|
||||
'';
|
||||
};
|
||||
}
|
||||
|
||||
@ -14,6 +14,18 @@ Item {
|
||||
readonly property var accountModel: logos.model("amm_ui", "accountModel")
|
||||
|
||||
property bool ready: false
|
||||
readonly property bool deploymentNetworkMatched: !root.ready || !root.backend || root.backend.deploymentNetworkMatched
|
||||
readonly property bool deploymentIdentityPending: root.ready
|
||||
&& root.backend
|
||||
&& root.backend.isWalletOpen
|
||||
&& root.backend.deploymentIdentityPending
|
||||
readonly property bool unsupportedChain: root.ready
|
||||
&& root.backend
|
||||
&& root.backend.isWalletOpen
|
||||
&& !root.backend.deploymentIdentityPending
|
||||
&& !root.backend.deploymentNetworkMatched
|
||||
readonly property var deploymentTokens: root.deploymentNetworkMatched && root.backend ? root.backend.deploymentTokens : []
|
||||
readonly property var deploymentPoolConfig: root.deploymentNetworkMatched && root.backend ? root.backend.deploymentPool : ({})
|
||||
|
||||
Connections {
|
||||
target: logos
|
||||
@ -36,20 +48,25 @@ Item {
|
||||
anchors.right: parent.right
|
||||
z: 101
|
||||
|
||||
readonly property bool show: root.ready
|
||||
&& root.backend
|
||||
&& root.backend.isWalletOpen
|
||||
&& root.backend.sequencerAddr.length > 0
|
||||
&& !root.backend.sequencerReachable
|
||||
readonly property bool show: root.deploymentIdentityPending
|
||||
|| root.unsupportedChain
|
||||
|| (root.ready
|
||||
&& root.backend
|
||||
&& root.backend.isWalletOpen
|
||||
&& root.backend.sequencerAddr.length > 0
|
||||
&& !root.backend.sequencerReachable)
|
||||
|
||||
height: show ? 32 : 0
|
||||
visible: height > 0
|
||||
clip: true
|
||||
color: Theme.palette.warning
|
||||
Accessible.role: Accessible.AlertMessage
|
||||
Accessible.name: bannerText.text
|
||||
|
||||
Behavior on height { NumberAnimation { duration: 150; easing.type: Easing.OutCubic } }
|
||||
|
||||
Text {
|
||||
id: bannerText
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - 40
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
@ -57,7 +74,11 @@ Item {
|
||||
font.pixelSize: 12
|
||||
font.weight: Font.Medium
|
||||
color: Theme.palette.background
|
||||
text: qsTr("Unable to connect to network")
|
||||
text: root.deploymentIdentityPending
|
||||
? qsTr("Checking chain")
|
||||
: root.unsupportedChain
|
||||
? qsTr("Unsupported chain")
|
||||
: qsTr("Unable to connect to network")
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,11 +103,20 @@ Item {
|
||||
|
||||
SwapPage {
|
||||
anchors.fill: parent
|
||||
backend: root.ready ? root.backend : null
|
||||
tokens: root.deploymentTokens
|
||||
poolConfig: root.deploymentPoolConfig
|
||||
unsupportedChain: root.unsupportedChain
|
||||
selectedWalletAccount: navbar.selectedAddress
|
||||
visible: navbar.currentIndex === 0
|
||||
}
|
||||
|
||||
LiquidityPage {
|
||||
anchors.fill: parent
|
||||
backend: root.ready ? root.backend : null
|
||||
poolConfig: root.deploymentPoolConfig
|
||||
unsupportedChain: root.unsupportedChain
|
||||
selectedWalletAccount: navbar.selectedAddress
|
||||
visible: navbar.currentIndex === 1
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,6 +24,11 @@ Item {
|
||||
|
||||
implicitHeight: 56
|
||||
|
||||
function selectTab(index) {
|
||||
root.currentIndex = index
|
||||
root.tabChanged(index)
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Theme.palette.background
|
||||
@ -63,12 +68,20 @@ Item {
|
||||
delegate: Rectangle {
|
||||
readonly property bool active: root.currentIndex === index
|
||||
|
||||
activeFocusOnTab: true
|
||||
height: 36
|
||||
width: tabLabel.implicitWidth + 28
|
||||
radius: 18
|
||||
color: active ? Theme.palette.backgroundSecondary : "transparent"
|
||||
border.width: activeFocus ? 1 : 0
|
||||
border.color: Theme.palette.overlayOrange
|
||||
Accessible.role: Accessible.PageTab
|
||||
Accessible.name: modelData
|
||||
Accessible.checked: active
|
||||
|
||||
Behavior on color { ColorAnimation { duration: 150 } }
|
||||
Keys.onReturnPressed: root.selectTab(index)
|
||||
Keys.onSpacePressed: root.selectTab(index)
|
||||
|
||||
Text {
|
||||
id: tabLabel
|
||||
@ -84,10 +97,7 @@ Item {
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
root.currentIndex = index
|
||||
root.tabChanged(index)
|
||||
}
|
||||
onClicked: root.selectTab(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@ import "../../state"
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
required property DummyPoolState poolState
|
||||
required property PoolState poolState
|
||||
|
||||
property real slippageTolerancePercent: 0.5
|
||||
property string amountA: ""
|
||||
@ -24,7 +24,7 @@ Rectangle {
|
||||
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 bool canSubmit: root.hasAnyAmount && !root.amountAOverBalance && !root.amountBOverBalance && !root.minReceivedIsZero && !root.zeroTokenDeposit && !root.zeroLpDeposit
|
||||
readonly property string submitButtonText: !root.hasAnyAmount ? qsTr("Enter an amount") : root.amountAOverBalance ? qsTr("Insufficient %1 balance").arg(root.poolState.tokenA) : root.amountBOverBalance ? qsTr("Insufficient %1 balance").arg(root.poolState.tokenB) : root.zeroTokenDeposit ? qsTr("Amount rounds to zero") : root.zeroLpDeposit ? qsTr("LP output is 0") : root.minReceivedIsZero ? qsTr("Minimum received is 0") : qsTr("Add Liquidity")
|
||||
readonly property string submitButtonText: !root.hasAnyAmount ? qsTr("Enter an amount") : root.amountAOverBalance ? qsTr("Insufficient %1 balance").arg(root.poolState.tokenA) : root.amountBOverBalance ? qsTr("Insufficient %1 balance").arg(root.poolState.tokenB) : root.zeroTokenDeposit ? qsTr("Amount rounds to zero") : root.zeroLpDeposit ? qsTr("LP output is 0") : root.minReceivedIsZero ? qsTr("Minimum received is 0") : qsTr("Add liquidity")
|
||||
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") : ""
|
||||
|
||||
signal slippageToleranceChangeRequested(real tolerancePercent)
|
||||
@ -207,13 +207,16 @@ Rectangle {
|
||||
return {
|
||||
"action": "add",
|
||||
"actualA": root.preview.actualA,
|
||||
"actualAValue": Math.floor(root.preview.actualA),
|
||||
"actualB": root.preview.actualB,
|
||||
"actualBValue": Math.floor(root.preview.actualB),
|
||||
"currentRatio": qsTr("1 %1 = %2 %3").arg(root.poolState.tokenB).arg(root.poolState.formatInteger(root.poolState.tokenAPerTokenB)).arg(root.poolState.tokenA),
|
||||
"deltaLp": root.preview.deltaLp,
|
||||
"depositA": root.poolState.formatTokenAmount(root.preview.actualA, root.poolState.tokenA),
|
||||
"depositB": root.poolState.formatTokenAmount(root.preview.actualB, root.poolState.tokenB),
|
||||
"feeTier": root.poolState.feeTier,
|
||||
"minLpReceived": root.poolState.formatLpAmount(root.minLpReceived),
|
||||
"minLpReceivedAmount": root.minLpReceived,
|
||||
"slippageTolerance": root.poolState.formatPercent(root.slippageTolerancePercent),
|
||||
"tokenA": root.poolState.tokenA,
|
||||
"tokenB": root.poolState.tokenB
|
||||
|
||||
@ -206,7 +206,7 @@ FocusScope {
|
||||
activeFocusOnTab: true
|
||||
focusPolicy: Qt.StrongFocus
|
||||
hoverEnabled: true
|
||||
text: qsTr("Confirm")
|
||||
text: qsTr("Submit")
|
||||
|
||||
Accessible.name: confirmButton.text
|
||||
|
||||
|
||||
@ -5,8 +5,8 @@ import "../../state"
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
required property DummyPoolState poolState
|
||||
readonly property string estimateHelp: qsTr("This value is an estimate from the current dummy reserves and your share of total LP supply.")
|
||||
required property PoolState poolState
|
||||
readonly property string estimateHelp: qsTr("This value is estimated from the current testnet reserves and your share of total LP supply.")
|
||||
|
||||
color: "#151515"
|
||||
implicitHeight: content.implicitHeight + 20
|
||||
|
||||
@ -7,7 +7,7 @@ import "../../state"
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
required property DummyPoolState poolState
|
||||
required property PoolState poolState
|
||||
|
||||
property real slippageTolerancePercent: 0.5
|
||||
property int burnAmount: 0
|
||||
@ -23,7 +23,7 @@ Rectangle {
|
||||
readonly property bool minReceivedIsZero: root.burnAmount > 0 && (root.minTokenAReceived === 0 || root.minTokenBReceived === 0)
|
||||
readonly property bool canSubmit: root.hasLpTokens && root.burnAmount > 0 && !root.minReceivedIsZero
|
||||
readonly property string estimateHelp: qsTr("Estimated with the same integer floor math used by the remove-liquidity contract path.")
|
||||
readonly property string submitButtonText: !root.hasLpTokens ? qsTr("No LP balance") : root.burnAmount === 0 ? qsTr("Enter an amount") : root.minReceivedIsZero ? qsTr("Minimum received is 0") : qsTr("Remove Liquidity")
|
||||
readonly property string submitButtonText: !root.hasLpTokens ? qsTr("No LP balance") : root.burnAmount === 0 ? qsTr("Enter an amount") : root.minReceivedIsZero ? qsTr("Minimum received is 0") : qsTr("Remove liquidity")
|
||||
|
||||
signal slippageToleranceChangeRequested(real tolerancePercent)
|
||||
signal removeLiquidityRequested(var snapshot)
|
||||
@ -478,7 +478,9 @@ Rectangle {
|
||||
"burnPercent": root.poolState.formatPercent(root.removePercent),
|
||||
"burnText": root.poolState.formatLpAmount(root.preview.burnedLp),
|
||||
"minTokenAReceived": root.poolState.formatTokenAmount(root.minTokenAReceived, root.poolState.tokenA),
|
||||
"minTokenAReceivedAmount": root.minTokenAReceived,
|
||||
"minTokenBReceived": root.poolState.formatTokenAmount(root.minTokenBReceived, root.poolState.tokenB),
|
||||
"minTokenBReceivedAmount": root.minTokenBReceived,
|
||||
"postRemovalShare": root.poolState.formatPoolShare(root.preview.newUserShare),
|
||||
"slippageTolerance": root.poolState.formatPercent(root.slippageTolerancePercent),
|
||||
"tokenA": root.poolState.tokenA,
|
||||
|
||||
@ -69,13 +69,13 @@ Rectangle {
|
||||
color: "#E7E1D8"
|
||||
font.bold: true
|
||||
font.pixelSize: 18
|
||||
inputMethodHints: Qt.ImhFormattedNumbersOnly
|
||||
inputMethodHints: Qt.ImhDigitsOnly
|
||||
placeholderText: qsTr("0")
|
||||
selectByMouse: true
|
||||
selectedTextColor: "#151515"
|
||||
selectionColor: "#F26A21"
|
||||
validator: RegularExpressionValidator {
|
||||
regularExpression: /[0-9]*([.][0-9]*)?/
|
||||
regularExpression: /[0-9]*/
|
||||
}
|
||||
|
||||
Accessible.name: root.label
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
Item {
|
||||
@ -6,8 +7,11 @@ Item {
|
||||
|
||||
property string message: ""
|
||||
property string detail: ""
|
||||
property string transactionId: ""
|
||||
property string status: "success"
|
||||
property bool copied: false
|
||||
property bool open: false
|
||||
property int duration: 3600
|
||||
property int duration: 7000
|
||||
|
||||
height: implicitHeight
|
||||
implicitHeight: toast.implicitHeight
|
||||
@ -15,9 +19,28 @@ Item {
|
||||
visible: root.open || fadeOut.running
|
||||
z: 30
|
||||
|
||||
function show(nextMessage, nextDetail) {
|
||||
TextEdit { id: clipboardProxy; visible: false }
|
||||
|
||||
function shortTransactionId(value) {
|
||||
return value.length > 18 ? value.substring(0, 10) + "..." + value.slice(-8) : value;
|
||||
}
|
||||
|
||||
function copyTransactionId() {
|
||||
if (root.transactionId.length === 0) return;
|
||||
clipboardProxy.text = root.transactionId;
|
||||
clipboardProxy.selectAll();
|
||||
clipboardProxy.copy();
|
||||
clipboardProxy.deselect();
|
||||
clipboardProxy.text = "";
|
||||
root.copied = true;
|
||||
}
|
||||
|
||||
function show(nextMessage, nextDetail, nextTransactionId, nextStatus) {
|
||||
root.message = nextMessage;
|
||||
root.detail = nextDetail || "";
|
||||
root.transactionId = nextTransactionId || "";
|
||||
root.status = nextStatus || "success";
|
||||
root.copied = false;
|
||||
root.open = true;
|
||||
dismissTimer.restart();
|
||||
}
|
||||
@ -28,7 +51,12 @@ Item {
|
||||
interval: root.duration
|
||||
repeat: false
|
||||
|
||||
onTriggered: root.open = false
|
||||
onTriggered: {
|
||||
if (copyButton.activeFocus || hoverGuard.containsMouse)
|
||||
restart()
|
||||
else
|
||||
root.open = false
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
@ -47,8 +75,19 @@ Item {
|
||||
color: "#20201F"
|
||||
implicitHeight: Math.max(50, toastContent.implicitHeight + 18)
|
||||
radius: 8
|
||||
border.color: "#4D3A2E"
|
||||
border.color: root.status === "error" ? "#6A2E2E" : "#4D3A2E"
|
||||
border.width: 1
|
||||
Accessible.role: Accessible.AlertMessage
|
||||
Accessible.name: root.detail.length > 0
|
||||
? qsTr("%1. %2").arg(root.message).arg(root.detail)
|
||||
: root.message
|
||||
|
||||
MouseArea {
|
||||
id: hoverGuard
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.NoButton
|
||||
hoverEnabled: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: toastContent
|
||||
@ -62,7 +101,7 @@ Item {
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
color: "#78C88D"
|
||||
color: root.status === "error" ? "#D75C5C" : "#78C88D"
|
||||
radius: 6
|
||||
|
||||
Layout.alignment: Qt.AlignTop
|
||||
@ -90,14 +129,59 @@ Item {
|
||||
|
||||
Text {
|
||||
color: "#B8ADA3"
|
||||
elide: Text.ElideRight
|
||||
font.pixelSize: 12
|
||||
maximumLineCount: 3
|
||||
text: root.detail
|
||||
visible: root.detail.length > 0
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Text {
|
||||
color: "#F2D8C7"
|
||||
elide: Text.ElideMiddle
|
||||
font.pixelSize: 12
|
||||
text: qsTr("Tx %1").arg(root.shortTransactionId(root.transactionId))
|
||||
visible: root.transactionId.length > 0
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
id: copyButton
|
||||
|
||||
Accessible.name: root.copied ? qsTr("Transaction id copied") : qsTr("Copy transaction id")
|
||||
Accessible.role: Accessible.Button
|
||||
activeFocusOnTab: true
|
||||
focusPolicy: Qt.StrongFocus
|
||||
text: root.copied ? qsTr("Copied") : qsTr("Copy tx")
|
||||
visible: root.transactionId.length > 0
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredHeight: 30
|
||||
Layout.preferredWidth: copyText.implicitWidth + 18
|
||||
|
||||
contentItem: Text {
|
||||
id: copyText
|
||||
|
||||
color: "#E7E1D8"
|
||||
font.bold: true
|
||||
font.pixelSize: 11
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: copyButton.text
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
border.color: copyButton.activeFocus ? "#F2D8C7" : "transparent"
|
||||
color: copyButton.hovered ? "#F26A21" : "#2B2724"
|
||||
radius: 6
|
||||
}
|
||||
|
||||
onClicked: root.copyTransactionId()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,10 +15,11 @@ Rectangle {
|
||||
property string buyInput: ""
|
||||
property string editingSide: "sell"
|
||||
property real slippageTolerancePercent: 0.5
|
||||
property int feeBps: 30
|
||||
|
||||
DummySwapState {
|
||||
SwapState {
|
||||
id: swapState
|
||||
feeBps: 30
|
||||
feeBps: root.feeBps
|
||||
}
|
||||
|
||||
signal requestTokenSelect(string side)
|
||||
@ -77,9 +78,11 @@ Rectangle {
|
||||
}
|
||||
|
||||
function formatAmountValue(val) {
|
||||
if (val >= 1) return val.toFixed(2)
|
||||
if (val >= 0.0001) return val.toFixed(6)
|
||||
return val.toFixed(8)
|
||||
return Math.floor(val).toString()
|
||||
}
|
||||
|
||||
function formatMaxSentValue(val) {
|
||||
return Math.ceil(val).toString()
|
||||
}
|
||||
|
||||
readonly property string sellDisplay: editingSide === "sell"
|
||||
@ -107,8 +110,12 @@ Rectangle {
|
||||
"sellToken": sellToken ? sellToken.symbol : "",
|
||||
"buyToken": buyToken ? buyToken.symbol : "",
|
||||
"sellAmount": formatAmountValue(parsedSellAmount),
|
||||
"sellAmountValue": formatAmountValue(parsedSellAmount),
|
||||
"buyAmount": formatAmountValue(parsedBuyAmount),
|
||||
"buyAmountValue": formatAmountValue(parsedBuyAmount),
|
||||
"minReceived": formatAmountValue(minReceivedAmount),
|
||||
"minReceivedAmountValue": formatAmountValue(minReceivedAmount),
|
||||
"maxSentAmountValue": formatMaxSentValue(swapState.maxSent(parsedSellAmount, slippageTolerancePercent)),
|
||||
"feeAmount": swapState.formatTokenAmount(feeAmount, sellToken ? sellToken.symbol : ""),
|
||||
"priceImpactPercent": swapState.formatPercent(priceImpactPercent),
|
||||
"priceImpactPercentValue": priceImpactPercent,
|
||||
|
||||
@ -195,7 +195,7 @@ FocusScope {
|
||||
activeFocusOnTab: true
|
||||
focusPolicy: Qt.StrongFocus
|
||||
hoverEnabled: true
|
||||
text: qsTr("Confirm Swap")
|
||||
text: qsTr("Submit")
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: 48
|
||||
onClicked: root.confirm()
|
||||
|
||||
@ -46,7 +46,7 @@ Rectangle {
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
height: 44
|
||||
Layout.preferredHeight: 44
|
||||
|
||||
TextInput {
|
||||
id: tiInput
|
||||
@ -58,7 +58,7 @@ Rectangle {
|
||||
clip: true
|
||||
onTextEdited: root.inputEdited(text)
|
||||
validator: RegularExpressionValidator {
|
||||
regularExpression: /^[0-9]*\.?[0-9]*$/
|
||||
regularExpression: /^[0-9]*$/
|
||||
}
|
||||
}
|
||||
|
||||
@ -81,10 +81,10 @@ Rectangle {
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 40
|
||||
Layout.preferredHeight: 40
|
||||
radius: 20
|
||||
color: tokenBtnHover.containsMouse ? theme.colors.panelHoverBg : theme.colors.panelBg
|
||||
implicitWidth: tokenBtnRow.implicitWidth + 24
|
||||
Layout.preferredWidth: tokenBtnRow.implicitWidth + 24
|
||||
Behavior on color { ColorAnimation { duration: 120 } }
|
||||
|
||||
RowLayout {
|
||||
@ -93,7 +93,9 @@ Rectangle {
|
||||
spacing: 6
|
||||
|
||||
Rectangle {
|
||||
width: 24; height: 24; radius: 12
|
||||
Layout.preferredWidth: 24
|
||||
Layout.preferredHeight: 24
|
||||
radius: 12
|
||||
color: root.token ? root.token.color : theme.colors.noTokenCircle
|
||||
visible: root.token !== null
|
||||
Text {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import QtQuick
|
||||
import QtQml
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QtQuick 2.15
|
||||
import QtQml 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import Logos.Theme
|
||||
import Logos.Controls
|
||||
@ -12,8 +12,8 @@ import Logos.Controls
|
||||
// clicking it opens a popup (top-right, just under the
|
||||
// button) holding the account selector, create-account and
|
||||
// disconnect actions.
|
||||
// The selected account address is exposed via selectedAddress for the
|
||||
// trade/liquidity flows to use as the "from" account.
|
||||
// selectedAddress stays in the wallet module's raw hex format for backend
|
||||
// calls; selectedDisplayAddress is the base58 format shown/copied in the UI.
|
||||
Item {
|
||||
id: root
|
||||
|
||||
@ -22,11 +22,13 @@ Item {
|
||||
property var accountModel: null
|
||||
|
||||
readonly property bool connected: backend !== null && backend.isWalletOpen
|
||||
readonly property real viewportMargin: Theme.spacing.medium
|
||||
|
||||
// Index of the active account. selectedAddress/selectedName are derived from
|
||||
// the model mirror below so they stay valid while the popup (and its list)
|
||||
// is closed.
|
||||
property int selectedIndex: 0
|
||||
property string selectedAccountId: ""
|
||||
|
||||
// Non-visual mirror of the account model: realizes every row regardless of
|
||||
// popup visibility, so the active account is addressable by index at all
|
||||
@ -36,10 +38,13 @@ Item {
|
||||
model: root.accountModel
|
||||
delegate: QtObject {
|
||||
readonly property string address: model.address ?? ""
|
||||
readonly property string displayAddress: model.displayAddress ?? ""
|
||||
readonly property string name: model.name ?? ""
|
||||
readonly property string balance: model.balance ?? ""
|
||||
readonly property bool isPublic: model.isPublic ?? false
|
||||
}
|
||||
onObjectAdded: root.clampSelection()
|
||||
onObjectRemoved: root.clampSelection()
|
||||
}
|
||||
|
||||
function entryAt(i) {
|
||||
@ -48,26 +53,75 @@ Item {
|
||||
|
||||
readonly property string selectedAddress: {
|
||||
const e = root.entryAt(root.selectedIndex)
|
||||
return e ? e.address : ""
|
||||
return e && e.isPublic ? e.address : ""
|
||||
}
|
||||
readonly property string selectedDisplayAddress: {
|
||||
const e = root.entryAt(root.selectedIndex)
|
||||
return e && e.isPublic ? e.displayAddress : ""
|
||||
}
|
||||
readonly property string selectedName: {
|
||||
const e = root.entryAt(root.selectedIndex)
|
||||
return e ? e.name : ""
|
||||
return e && e.isPublic ? e.name : qsTr("No public account")
|
||||
}
|
||||
readonly property string selectedBalance: {
|
||||
const e = root.entryAt(root.selectedIndex)
|
||||
return e ? e.balance : ""
|
||||
return e && e.isPublic ? e.balance : ""
|
||||
}
|
||||
readonly property bool selectedIsPublic: {
|
||||
const e = root.entryAt(root.selectedIndex)
|
||||
return e ? e.isPublic : false
|
||||
// Keep the selection within bounds as accounts are added/removed.
|
||||
function firstPublicIndex() {
|
||||
for (let i = 0; i < accounts.count; ++i) {
|
||||
const e = root.entryAt(i)
|
||||
if (e && e.isPublic)
|
||||
return i
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
function publicIndexForAddress(address) {
|
||||
if (!address)
|
||||
return -1
|
||||
for (let i = 0; i < accounts.count; ++i) {
|
||||
const e = root.entryAt(i)
|
||||
if (e && e.isPublic && e.address === address)
|
||||
return i
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
function selectIndex(index) {
|
||||
const e = root.entryAt(index)
|
||||
root.selectedIndex = index
|
||||
root.selectedAccountId = e.address
|
||||
}
|
||||
|
||||
// Keep the selection within bounds as accounts are added/removed.
|
||||
function clampSelection() {
|
||||
if (accounts.count === 0) { root.selectedIndex = 0; return }
|
||||
if (accounts.count === 0) {
|
||||
root.selectedIndex = 0
|
||||
root.selectedAccountId = ""
|
||||
return
|
||||
}
|
||||
|
||||
const rememberedIndex = root.publicIndexForAddress(root.selectedAccountId)
|
||||
if (rememberedIndex >= 0) {
|
||||
root.selectedIndex = rememberedIndex
|
||||
return
|
||||
}
|
||||
|
||||
if (root.selectedIndex < 0) root.selectedIndex = 0
|
||||
else if (root.selectedIndex >= accounts.count) root.selectedIndex = accounts.count - 1
|
||||
|
||||
const selected = root.entryAt(root.selectedIndex)
|
||||
if (selected.isPublic) {
|
||||
root.selectedAccountId = selected.address
|
||||
return
|
||||
}
|
||||
|
||||
const firstPublic = root.firstPublicIndex()
|
||||
if (firstPublic >= 0) {
|
||||
root.selectIndex(firstPublic)
|
||||
} else {
|
||||
root.selectedAccountId = ""
|
||||
}
|
||||
}
|
||||
Connections {
|
||||
target: root.accountModel
|
||||
@ -97,9 +151,25 @@ Item {
|
||||
clipboardProxy.text = ""
|
||||
}
|
||||
|
||||
function handleOpenExistingFailure(error) {
|
||||
connectErrorDialog.message = error || qsTr("Could not open the existing wallet at %1.")
|
||||
.arg(root.backend ? root.backend.walletHome : "")
|
||||
if (root.backend && root.backend.walletExists)
|
||||
connectErrorDialog.open()
|
||||
else
|
||||
createWalletDialog.open()
|
||||
}
|
||||
|
||||
implicitWidth: root.connected ? connectedButton.width : connectButton.width
|
||||
implicitHeight: 40
|
||||
|
||||
Component.onCompleted: root.clampSelection()
|
||||
|
||||
function clampedOverlayWidth(maxWidth) {
|
||||
const overlay = Overlay.overlay
|
||||
return Math.min(maxWidth, Math.max(0, overlay ? overlay.width - root.viewportMargin * 2 : maxWidth))
|
||||
}
|
||||
|
||||
// ── Disconnected: Connect ────────────────────────────────────────────
|
||||
LogosButton {
|
||||
id: connectButton
|
||||
@ -110,36 +180,37 @@ Item {
|
||||
enabled: root.backend !== null
|
||||
text: qsTr("Connect")
|
||||
onClicked: {
|
||||
// Re-open an existing wallet; only show the create modal on first run.
|
||||
if (root.backend && root.backend.walletExists)
|
||||
logos.watch(root.backend.openExisting(),
|
||||
function(ok) { if (!ok) console.warn("openExisting failed") },
|
||||
function(error) { console.warn("openExisting error:", error) })
|
||||
else
|
||||
createWalletDialog.open()
|
||||
if (!root.backend) return
|
||||
logos.watch(root.backend.openExisting(),
|
||||
function(ok) {
|
||||
if (!ok) root.handleOpenExistingFailure("")
|
||||
},
|
||||
function(error) {
|
||||
console.warn("openExisting error:", error)
|
||||
root.handleOpenExistingFailure(qsTr("Could not open the existing wallet: %1").arg(error))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ── Connected: address pill that toggles the wallet menu ─────────────
|
||||
Rectangle {
|
||||
Button {
|
||||
id: connectedButton
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.connected
|
||||
implicitHeight: 40
|
||||
implicitWidth: connectedRow.implicitWidth + Theme.spacing.medium * 2
|
||||
radius: height / 2
|
||||
// Keep an opaque dark fill in both states: the navbar is white, and the
|
||||
// active "muted" fill is translucent gray, which renders light over white
|
||||
// and makes the white label unreadable. Signal "open" with an accent
|
||||
// border instead.
|
||||
color: Theme.palette.backgroundSecondary
|
||||
border.width: 1
|
||||
border.color: walletMenu.opened ? Theme.palette.overlayOrange : "transparent"
|
||||
implicitWidth: connectedRow.implicitWidth + leftPadding + rightPadding
|
||||
leftPadding: Theme.spacing.medium
|
||||
rightPadding: Theme.spacing.medium
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
text: root.selectedDisplayAddress.length > 0
|
||||
? root.truncated(root.selectedDisplayAddress)
|
||||
: qsTr("No public account")
|
||||
Accessible.name: text
|
||||
|
||||
RowLayout {
|
||||
contentItem: RowLayout {
|
||||
id: connectedRow
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacing.small
|
||||
|
||||
Rectangle {
|
||||
@ -149,7 +220,7 @@ Item {
|
||||
color: "#39c06a"
|
||||
}
|
||||
LogosText {
|
||||
text: root.truncated(root.selectedAddress) || qsTr("Connected")
|
||||
text: connectedButton.text
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
color: Theme.palette.text
|
||||
}
|
||||
@ -160,19 +231,26 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
// CloseOnPressOutside already dismisses the popup on this same press
|
||||
// (the button is outside it), so `opened` is false by the time this
|
||||
// fires. Without the recency guard the dismissing click would just
|
||||
// reopen it. If it just closed, leave it closed.
|
||||
onClicked: {
|
||||
if (walletMenu.opened || (Date.now() - walletMenu.lastClosedMs) < 200)
|
||||
walletMenu.close()
|
||||
else
|
||||
walletMenu.open()
|
||||
}
|
||||
background: Rectangle {
|
||||
radius: height / 2
|
||||
// Keep an opaque dark fill in both states: the navbar is white, and the
|
||||
// active "muted" fill is translucent gray, which renders light over white
|
||||
// and makes the white label unreadable. Signal "open" with an accent
|
||||
// border instead.
|
||||
color: Theme.palette.backgroundSecondary
|
||||
border.width: 1
|
||||
border.color: walletMenu.opened || connectedButton.activeFocus ? Theme.palette.overlayOrange : "transparent"
|
||||
}
|
||||
|
||||
// CloseOnPressOutside already dismisses the popup on this same press
|
||||
// (the button is outside it), so `opened` is false by the time this
|
||||
// fires. Without the recency guard the dismissing click would just
|
||||
// reopen it. If it just closed, leave it closed.
|
||||
onClicked: {
|
||||
if (walletMenu.opened || (Date.now() - walletMenu.lastClosedMs) < 200)
|
||||
walletMenu.close()
|
||||
else
|
||||
walletMenu.open()
|
||||
}
|
||||
}
|
||||
|
||||
@ -181,8 +259,18 @@ Item {
|
||||
id: walletMenu
|
||||
parent: connectedButton
|
||||
y: connectedButton.height + Theme.spacing.small
|
||||
x: connectedButton.width - width // right-align under the button
|
||||
width: 360
|
||||
x: {
|
||||
const overlay = Overlay.overlay
|
||||
if (!overlay)
|
||||
return connectedButton.width - width
|
||||
const buttonLeft = connectedButton.mapToItem(overlay, 0, 0).x
|
||||
const buttonRight = buttonLeft + connectedButton.width
|
||||
const desiredOverlayX = buttonRight - width
|
||||
const minX = root.viewportMargin
|
||||
const maxX = Math.max(minX, overlay.width - width - root.viewportMargin)
|
||||
return Math.max(minX, Math.min(desiredOverlayX, maxX)) - buttonLeft
|
||||
}
|
||||
width: root.clampedOverlayWidth(360)
|
||||
padding: Theme.spacing.medium
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||
|
||||
@ -239,14 +327,17 @@ Item {
|
||||
|
||||
WalletIconButton {
|
||||
iconSource: Qt.resolvedUrl("icons/account.svg")
|
||||
accessibleName: qsTr("Show accounts")
|
||||
onClicked: viewStack.push(accountsView)
|
||||
}
|
||||
WalletIconButton {
|
||||
iconSource: Qt.resolvedUrl("icons/settings.svg")
|
||||
accessibleName: qsTr("Open wallet settings")
|
||||
onClicked: viewStack.push(settingsView)
|
||||
}
|
||||
WalletIconButton {
|
||||
iconSource: Qt.resolvedUrl("icons/power.svg")
|
||||
accessibleName: qsTr("Disconnect wallet")
|
||||
onClicked: {
|
||||
walletMenu.close()
|
||||
if (root.backend) root.backend.disconnectWallet()
|
||||
@ -279,12 +370,13 @@ Item {
|
||||
Rectangle {
|
||||
Layout.preferredWidth: tagLabel.implicitWidth + Theme.spacing.small * 2
|
||||
Layout.preferredHeight: tagLabel.implicitHeight + 4
|
||||
visible: root.selectedAddress.length > 0
|
||||
radius: 4
|
||||
color: Theme.palette.backgroundSecondary
|
||||
LogosText {
|
||||
id: tagLabel
|
||||
anchors.centerIn: parent
|
||||
text: root.selectedIsPublic ? qsTr("Public") : qsTr("Private")
|
||||
text: qsTr("Public")
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
color: Theme.palette.textSecondary
|
||||
}
|
||||
@ -302,7 +394,7 @@ Item {
|
||||
LogosText {
|
||||
Layout.fillWidth: true
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
text: root.selectedAddress
|
||||
text: root.selectedDisplayAddress
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
color: Theme.palette.textMuted
|
||||
elide: Text.ElideMiddle
|
||||
@ -310,8 +402,9 @@ Item {
|
||||
LogosCopyButton {
|
||||
Layout.preferredHeight: 40
|
||||
Layout.preferredWidth: 40
|
||||
visible: root.selectedAddress.length > 0
|
||||
onCopyText: root.copyToClipboard(root.selectedAddress)
|
||||
accessibleName: qsTr("Copy selected account address")
|
||||
visible: root.selectedDisplayAddress.length > 0
|
||||
onCopyText: root.copyToClipboard(root.selectedDisplayAddress)
|
||||
icon.color: Theme.palette.textMuted
|
||||
}
|
||||
}
|
||||
@ -334,6 +427,7 @@ Item {
|
||||
|
||||
WalletIconButton {
|
||||
iconSource: Qt.resolvedUrl("icons/back.svg")
|
||||
accessibleName: qsTr("Back")
|
||||
onClicked: viewStack.pop()
|
||||
}
|
||||
LogosText {
|
||||
@ -356,9 +450,12 @@ Item {
|
||||
|
||||
delegate: AccountDelegate {
|
||||
width: ListView.view.width
|
||||
highlighted: index === root.selectedIndex
|
||||
selectable: model.isPublic ?? false
|
||||
highlighted: selectable && index === root.selectedIndex
|
||||
onClicked: {
|
||||
root.selectedIndex = index
|
||||
if (!selectable)
|
||||
return
|
||||
root.selectIndex(index)
|
||||
viewStack.pop()
|
||||
}
|
||||
onCopyRequested: (text) => root.copyToClipboard(text)
|
||||
@ -367,7 +464,7 @@ Item {
|
||||
|
||||
LogosButton {
|
||||
Layout.fillWidth: true
|
||||
height: 40
|
||||
Layout.preferredHeight: 40
|
||||
text: qsTr("Add")
|
||||
// Leave the wallet menu open behind the (modal) dialog.
|
||||
onClicked: createAccountDialog.open()
|
||||
@ -389,6 +486,7 @@ Item {
|
||||
|
||||
WalletIconButton {
|
||||
iconSource: Qt.resolvedUrl("icons/back.svg")
|
||||
accessibleName: qsTr("Back")
|
||||
onClicked: viewStack.pop()
|
||||
}
|
||||
LogosText {
|
||||
@ -422,11 +520,13 @@ Item {
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
property bool ok: false
|
||||
color: ok ? Theme.palette.success : Theme.palette.error
|
||||
Accessible.role: Accessible.AlertMessage
|
||||
Accessible.name: text
|
||||
}
|
||||
|
||||
LogosButton {
|
||||
Layout.fillWidth: true
|
||||
height: 40
|
||||
Layout.preferredHeight: 40
|
||||
text: qsTr("Save")
|
||||
onClicked: {
|
||||
if (!root.backend) return
|
||||
@ -454,16 +554,95 @@ Item {
|
||||
onCreateWallet: function(password) {
|
||||
if (!root.backend) return
|
||||
logos.watch(root.backend.createNewDefault(password),
|
||||
function(ok) {
|
||||
if (ok) createWalletDialog.close()
|
||||
else createWalletDialog.createError = qsTr("Failed to create wallet. Please try again.")
|
||||
function(result) {
|
||||
const mnemonic = String(result || "")
|
||||
if (mnemonic.length > 0) {
|
||||
createWalletDialog.close()
|
||||
backupWalletDialog.mnemonic = mnemonic
|
||||
backupWalletDialog.open()
|
||||
} else {
|
||||
createWalletDialog.createError = qsTr("Failed to create wallet. Please try again.")
|
||||
}
|
||||
},
|
||||
function(error) {
|
||||
createWalletDialog.createError = qsTr("Error creating wallet: %1").arg(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Popup {
|
||||
id: connectErrorDialog
|
||||
|
||||
property string message: ""
|
||||
|
||||
modal: true
|
||||
dim: true
|
||||
padding: Theme.spacing.large
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||
parent: Overlay.overlay
|
||||
anchors.centerIn: parent
|
||||
width: root.clampedOverlayWidth(380)
|
||||
|
||||
background: Rectangle {
|
||||
color: Theme.palette.backgroundSecondary
|
||||
radius: Theme.spacing.radiusXlarge
|
||||
border.color: Theme.palette.backgroundElevated
|
||||
}
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
width: connectErrorDialog.availableWidth
|
||||
spacing: Theme.spacing.large
|
||||
|
||||
LogosText {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Wallet connection failed")
|
||||
font.pixelSize: Theme.typography.titleText
|
||||
font.weight: Theme.typography.weightBold
|
||||
color: Theme.palette.text
|
||||
}
|
||||
|
||||
LogosText {
|
||||
Layout.fillWidth: true
|
||||
text: connectErrorDialog.message
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
color: Theme.palette.textSecondary
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
LogosText {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Creating a new wallet will use the same wallet home. Continue only if you do not need the existing wallet files.")
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
color: Theme.palette.error
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Theme.spacing.medium
|
||||
|
||||
LogosButton {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Cancel")
|
||||
onClicked: connectErrorDialog.close()
|
||||
}
|
||||
|
||||
LogosButton {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Create New")
|
||||
onClicked: {
|
||||
connectErrorDialog.close()
|
||||
createWalletDialog.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BackupWalletDialog {
|
||||
id: backupWalletDialog
|
||||
}
|
||||
|
||||
CreateAccountDialog {
|
||||
id: createAccountDialog
|
||||
onCreatePublicRequested: {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import Logos.Theme
|
||||
import Logos.Controls
|
||||
@ -13,6 +13,18 @@ ItemDelegate {
|
||||
// to its QML-side clipboard helper (AccountControl.copyToClipboard).
|
||||
signal copyRequested(string text)
|
||||
|
||||
property bool selectable: model.isPublic ?? false
|
||||
readonly property string displayAddress: model.displayAddress ?? ""
|
||||
|
||||
focusPolicy: root.selectable ? Qt.StrongFocus : Qt.NoFocus
|
||||
activeFocusOnTab: root.selectable
|
||||
Accessible.role: root.selectable ? Accessible.RadioButton : Accessible.ListItem
|
||||
Accessible.name: root.selectable
|
||||
? qsTr("Select account %1").arg(root.displayAddress)
|
||||
: qsTr("Private account %1").arg(root.displayAddress)
|
||||
Accessible.description: root.selectable ? "" : qsTr("Private accounts cannot be used for AMM actions")
|
||||
Accessible.checked: root.highlighted
|
||||
|
||||
leftPadding: Theme.spacing.medium
|
||||
rightPadding: Theme.spacing.medium
|
||||
topPadding: Theme.spacing.medium
|
||||
@ -67,7 +79,7 @@ ItemDelegate {
|
||||
id: addressLabel
|
||||
Layout.fillWidth: true
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
text: model.address ?? ""
|
||||
text: root.displayAddress
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
color: Theme.palette.textMuted
|
||||
elide: Text.ElideMiddle
|
||||
@ -75,8 +87,9 @@ ItemDelegate {
|
||||
LogosCopyButton {
|
||||
Layout.preferredHeight: 40
|
||||
Layout.preferredWidth: 40
|
||||
onCopyText: root.copyRequested(model.address)
|
||||
visible: addressLabel.text
|
||||
accessibleName: qsTr("Copy account address")
|
||||
onCopyText: root.copyRequested(root.displayAddress)
|
||||
visible: root.displayAddress.length > 0
|
||||
icon.color: Theme.palette.textMuted
|
||||
}
|
||||
}
|
||||
|
||||
93
apps/amm/qml/components/wallet/BackupWalletDialog.qml
Normal file
93
apps/amm/qml/components/wallet/BackupWalletDialog.qml
Normal file
@ -0,0 +1,93 @@
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import Logos.Theme
|
||||
import Logos.Controls
|
||||
|
||||
Popup {
|
||||
id: root
|
||||
|
||||
property string mnemonic: ""
|
||||
readonly property real viewportMargin: Theme.spacing.large
|
||||
|
||||
modal: true
|
||||
dim: true
|
||||
padding: Theme.spacing.large
|
||||
closePolicy: Popup.NoAutoClose
|
||||
parent: Overlay.overlay
|
||||
anchors.centerIn: parent
|
||||
width: Math.min(460, Math.max(0, parent ? parent.width - root.viewportMargin * 2 : 460))
|
||||
|
||||
onOpened: savedCheck.checked = false
|
||||
onClosed: root.mnemonic = ""
|
||||
|
||||
background: Rectangle {
|
||||
color: Theme.palette.backgroundSecondary
|
||||
radius: Theme.spacing.radiusXlarge
|
||||
border.color: Theme.palette.backgroundElevated
|
||||
}
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
width: root.availableWidth
|
||||
spacing: Theme.spacing.large
|
||||
|
||||
LogosText {
|
||||
text: qsTr("Back up your wallet")
|
||||
font.pixelSize: Theme.typography.titleText
|
||||
font.weight: Theme.typography.weightBold
|
||||
color: Theme.palette.text
|
||||
}
|
||||
|
||||
LogosText {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Save this recovery phrase now. You need it to restore this wallet.")
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
color: Theme.palette.textSecondary
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: phraseText.implicitHeight + Theme.spacing.medium * 2
|
||||
radius: Theme.spacing.radiusLarge
|
||||
color: Theme.palette.backgroundMuted
|
||||
border.color: Theme.palette.backgroundElevated
|
||||
|
||||
TextEdit {
|
||||
id: phraseText
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacing.medium
|
||||
readOnly: true
|
||||
selectByMouse: true
|
||||
textFormat: TextEdit.PlainText
|
||||
wrapMode: Text.WordWrap
|
||||
text: root.mnemonic
|
||||
color: Theme.palette.text
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
background: null
|
||||
Accessible.name: qsTr("Recovery phrase")
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Theme.spacing.medium
|
||||
|
||||
CheckBox {
|
||||
id: savedCheck
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("I saved this recovery phrase")
|
||||
checked: false
|
||||
}
|
||||
}
|
||||
|
||||
LogosButton {
|
||||
Layout.fillWidth: true
|
||||
height: 40
|
||||
text: qsTr("Continue")
|
||||
enabled: savedCheck.checked
|
||||
onClicked: root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import Logos.Theme
|
||||
import Logos.Controls
|
||||
@ -9,6 +9,8 @@ import Logos.Controls
|
||||
Popup {
|
||||
id: root
|
||||
|
||||
readonly property real viewportMargin: Theme.spacing.large
|
||||
|
||||
signal createPublicRequested()
|
||||
signal createPrivateRequested()
|
||||
|
||||
@ -21,7 +23,7 @@ Popup {
|
||||
// this popup is declared inside.
|
||||
parent: Overlay.overlay
|
||||
anchors.centerIn: parent
|
||||
width: 360
|
||||
width: Math.min(360, Math.max(0, parent ? parent.width - root.viewportMargin * 2 : 360))
|
||||
|
||||
background: Rectangle {
|
||||
color: Theme.palette.backgroundSecondary
|
||||
@ -74,6 +76,8 @@ Popup {
|
||||
LogosSwitch {
|
||||
id: privateSwitch
|
||||
checked: false
|
||||
Accessible.name: qsTr("Create private account")
|
||||
Accessible.description: qsTr("Private balance and activity.")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,19 +1,20 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import Logos.Theme
|
||||
import Logos.Controls
|
||||
|
||||
// Password-only wallet creation modal. Storage/config live at the per-app
|
||||
// default (backend.walletHome) — no path picking. Opened from the navbar
|
||||
// "Connect" button.
|
||||
// Password-only wallet creation modal. Storage/config live at the canonical
|
||||
// LEZ wallet home (backend.walletHome), with no path picking. Opened from the
|
||||
// navbar "Connect" button.
|
||||
Popup {
|
||||
id: root
|
||||
|
||||
// Where the wallet will be stored, shown for transparency.
|
||||
property string walletHome: ""
|
||||
property string createError: ""
|
||||
readonly property real viewportMargin: Theme.spacing.large
|
||||
|
||||
signal createWallet(string password)
|
||||
|
||||
@ -25,7 +26,7 @@ Popup {
|
||||
// this popup is declared inside.
|
||||
parent: Overlay.overlay
|
||||
anchors.centerIn: parent
|
||||
width: 380
|
||||
width: Math.min(380, Math.max(0, parent ? parent.width - root.viewportMargin * 2 : 380))
|
||||
|
||||
onOpened: {
|
||||
passwordField.text = ""
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
|
||||
import Logos.Theme
|
||||
|
||||
@ -8,13 +8,17 @@ Button {
|
||||
|
||||
signal copyText()
|
||||
|
||||
property string accessibleName: ""
|
||||
property string iconSource: Qt.resolvedUrl("icons/copy.svg")
|
||||
property bool copied: false
|
||||
|
||||
implicitWidth: 24
|
||||
implicitHeight: 24
|
||||
text: root.copied ? qsTr("Copied") : root.accessibleName
|
||||
Accessible.name: text
|
||||
display: AbstractButton.IconOnly
|
||||
flat: true
|
||||
|
||||
property string iconSource: Qt.resolvedUrl("icons/copy.svg")
|
||||
|
||||
icon.source: root.iconSource
|
||||
icon.width: 24
|
||||
icon.height: 24
|
||||
@ -22,6 +26,7 @@ Button {
|
||||
|
||||
function reset() {
|
||||
iconSource = Qt.resolvedUrl("icons/copy.svg")
|
||||
copied = false
|
||||
}
|
||||
|
||||
Timer {
|
||||
@ -34,6 +39,7 @@ Button {
|
||||
onClicked: {
|
||||
root.copyText()
|
||||
root.iconSource = Qt.resolvedUrl("icons/checkmark.svg")
|
||||
root.copied = true
|
||||
resetTimer.restart()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
|
||||
import Logos.Theme
|
||||
|
||||
@ -12,9 +12,12 @@ Button {
|
||||
property url iconSource
|
||||
property color iconColor: Theme.palette.textSecondary
|
||||
property int iconSize: 18
|
||||
property string accessibleName: ""
|
||||
|
||||
implicitWidth: 32
|
||||
implicitHeight: 32
|
||||
text: root.accessibleName
|
||||
Accessible.name: root.accessibleName
|
||||
display: AbstractButton.IconOnly
|
||||
flat: true
|
||||
|
||||
|
||||
@ -7,19 +7,27 @@ import "../state"
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var poolConfig: ({})
|
||||
property var backend: null
|
||||
property bool unsupportedChain: false
|
||||
property string selectedWalletAccount: ""
|
||||
property int activeLiquidityTab: 0
|
||||
property real slippageTolerancePercent: 0.5
|
||||
readonly property bool hasPoolConfig: !root.unsupportedChain && !!root.poolConfig.account
|
||||
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
|
||||
|
||||
onPoolConfigChanged: poolState.loadConfig(root.poolConfig)
|
||||
|
||||
width: parent ? parent.width : implicitWidth
|
||||
height: parent ? parent.height : implicitHeight
|
||||
implicitWidth: root.preferredCardWidth + root.pageMargin * 2
|
||||
implicitHeight: pageCard.implicitHeight + root.pageMargin * 2
|
||||
|
||||
DummyPoolState {
|
||||
PoolState {
|
||||
id: poolState
|
||||
Component.onCompleted: loadConfig(root.poolConfig)
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@ -32,6 +40,7 @@ Item {
|
||||
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
visible: !root.unsupportedChain
|
||||
contentHeight: Math.max(height, pageCard.y + pageCard.implicitHeight + root.pageMargin)
|
||||
contentWidth: width
|
||||
enabled: !confirmationDialog.visible
|
||||
@ -54,6 +63,7 @@ Item {
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: 12
|
||||
enabled: root.hasPoolConfig
|
||||
spacing: 10
|
||||
|
||||
RowLayout {
|
||||
@ -162,6 +172,15 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
visible: root.unsupportedChain
|
||||
text: qsTr("Unsupported chain")
|
||||
color: "#E7E1D8"
|
||||
font.pixelSize: 18
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
LiquidityConfirmationDialog {
|
||||
id: confirmationDialog
|
||||
|
||||
@ -173,17 +192,69 @@ Item {
|
||||
}
|
||||
|
||||
function confirmLiquidityAction(snapshot) {
|
||||
if (!root.backend) {
|
||||
successToast.show(qsTr("Transaction failed"), qsTr("Backend is not ready"), "", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
snapshot.selectedWalletAccount = root.selectedWalletAccount;
|
||||
const expectedSubmissionToken = root.submissionToken();
|
||||
logos.watch(root.backend.submitLiquidity(snapshot),
|
||||
function (resultJson) {
|
||||
if (root.submissionToken() !== expectedSubmissionToken)
|
||||
return;
|
||||
const result = root.parseTransactionResult(resultJson);
|
||||
if (!result.success) {
|
||||
successToast.show(qsTr("Transaction failed"),
|
||||
result.error || qsTr("Transaction rejected"),
|
||||
"",
|
||||
"error");
|
||||
return;
|
||||
}
|
||||
|
||||
root.applyConfirmedLiquidityAction(snapshot, result.tx_hash || "");
|
||||
},
|
||||
function (error) {
|
||||
if (root.submissionToken() !== expectedSubmissionToken)
|
||||
return;
|
||||
successToast.show(qsTr("Transaction failed"), String(error), "", "error");
|
||||
});
|
||||
}
|
||||
|
||||
function submissionToken() {
|
||||
if (!root.backend)
|
||||
return "";
|
||||
return [
|
||||
root.backend.isWalletOpen ? "open" : "closed",
|
||||
root.backend.sequencerAddr || "",
|
||||
root.backend.deploymentNetworkMatched ? "matched" : "unmatched",
|
||||
root.selectedWalletAccount || "",
|
||||
root.activeLiquidityTab
|
||||
].join("|");
|
||||
}
|
||||
|
||||
function parseTransactionResult(resultJson) {
|
||||
try {
|
||||
return JSON.parse(resultJson);
|
||||
} catch (err) {
|
||||
return { "success": false, "tx_hash": "", "error": String(err) };
|
||||
}
|
||||
}
|
||||
|
||||
function applyConfirmedLiquidityAction(snapshot, transactionId) {
|
||||
if (snapshot.action === "add") {
|
||||
poolState.applyAddLiquidity(snapshot.actualA, snapshot.actualB, snapshot.deltaLp);
|
||||
addLiquidityForm.resetForm();
|
||||
successToast.show(qsTr("Liquidity added"), qsTr("Position updated"));
|
||||
successToast.show(qsTr("Add liquidity submitted"),
|
||||
qsTr("Position refreshed from chain"),
|
||||
transactionId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (snapshot.action === "remove") {
|
||||
poolState.applyRemoveLiquidity(snapshot.withdrawA, snapshot.withdrawB, snapshot.burnAmount);
|
||||
removeLiquidityForm.resetForm();
|
||||
successToast.show(qsTr("Liquidity removed"), qsTr("Position updated"));
|
||||
successToast.show(qsTr("Remove liquidity submitted"),
|
||||
qsTr("Position refreshed from chain"),
|
||||
transactionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,21 +1,61 @@
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import "../components/shared"
|
||||
import "../components/swap"
|
||||
import "../state"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var tokens: [
|
||||
{ symbol: "TOK1", name: "Token 1", color: "#627eea", letter: "E", address: "0x0000000000000000000000000000000000000000", usdPrice: 2392.70, balance: 4.25, reserve: 850 },
|
||||
{ symbol: "TOK2", name: "Token 2", color: "#2775ca", letter: "$", address: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", usdPrice: 1.00, balance: 12480, reserve: 2400000 },
|
||||
{ symbol: "TOK3", name: "Token 3", color: "#26a17b", letter: "T", address: "0xdac17f958d2ee523a2206206994597c13d831ec7", usdPrice: 1.00, balance: 320, reserve: 1800000 },
|
||||
{ symbol: "TOK4", name: "Token 4", color: "#f7931a", letter: "B", address: "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599", usdPrice: 63500, balance: 0.18, reserve: 42 },
|
||||
{ symbol: "TOK5", name: "Token 5", color: "#627eea", letter: "E", address: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", usdPrice: 2392.70, balance: 0, reserve: 600 },
|
||||
{ symbol: "TOK6", name: "Token 6", color: "#9b59b6", letter: "L", address: "0x1337000000000000000000000000000000000cafe", usdPrice: 0.42, balance: 5400, reserve: 950000 }
|
||||
]
|
||||
property var tokens: []
|
||||
property var poolConfig: ({})
|
||||
property var backend: null
|
||||
property bool unsupportedChain: false
|
||||
property string selectedWalletAccount: ""
|
||||
readonly property string poolAccount: poolConfig.account || ""
|
||||
readonly property string poolAccountShort: poolAccount.length > 14
|
||||
? poolAccount.substring(0, 8) + "..." + poolAccount.slice(-6)
|
||||
: poolAccount
|
||||
|
||||
onTokensChanged: Qt.callLater(selectDefaultTokens)
|
||||
|
||||
Component.onCompleted: Qt.callLater(selectDefaultTokens)
|
||||
|
||||
function selectDefaultTokens() {
|
||||
if (root.tokens.length < 2) {
|
||||
swapCard.setToken("sell", null);
|
||||
swapCard.setToken("buy", null);
|
||||
swapCard.resetAmounts();
|
||||
return;
|
||||
}
|
||||
if (!swapCard.sellToken || swapCard.sellToken.address !== root.tokens[0].address)
|
||||
swapCard.setToken("sell", root.tokens[0]);
|
||||
if (!swapCard.buyToken || swapCard.buyToken.address !== root.tokens[1].address)
|
||||
swapCard.setToken("buy", root.tokens[1]);
|
||||
}
|
||||
|
||||
function parseTransactionResult(resultJson) {
|
||||
try {
|
||||
return JSON.parse(resultJson);
|
||||
} catch (err) {
|
||||
return { "success": false, "tx_hash": "", "error": String(err) };
|
||||
}
|
||||
}
|
||||
|
||||
function withSelectedWalletAccount(snapshot) {
|
||||
snapshot.selectedWalletAccount = root.selectedWalletAccount
|
||||
return snapshot
|
||||
}
|
||||
|
||||
function submissionToken() {
|
||||
if (!root.backend)
|
||||
return ""
|
||||
return [
|
||||
root.backend.isWalletOpen ? "open" : "closed",
|
||||
root.backend.sequencerAddr || "",
|
||||
root.backend.deploymentNetworkMatched ? "matched" : "unmatched",
|
||||
root.selectedWalletAccount || ""
|
||||
].join("|")
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: theme
|
||||
@ -92,10 +132,12 @@ Item {
|
||||
|
||||
SwapCard {
|
||||
id: swapCard
|
||||
visible: !root.unsupportedChain
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
theme: theme
|
||||
tokens: root.tokens
|
||||
width: Math.min(480, root.width - 32)
|
||||
feeBps: Number(root.poolConfig.feeBps) || 0
|
||||
Layout.preferredWidth: Math.min(480, root.width - 32)
|
||||
|
||||
onRequestTokenSelect: function(side) {
|
||||
tokenModal.targetSide = side
|
||||
@ -108,13 +150,26 @@ Item {
|
||||
}
|
||||
|
||||
Text {
|
||||
visible: !root.unsupportedChain
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: "Buy and sell crypto on <font color='" + theme.colors.textPrimary + "'>LEZ</font>."
|
||||
text: "Pool <font color='" + theme.colors.textPrimary + "'>" +
|
||||
root.poolAccountShort +
|
||||
"</font>"
|
||||
textFormat: Text.RichText
|
||||
color: theme.colors.textSecondary
|
||||
font.pixelSize: 15
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
visible: root.unsupportedChain
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: qsTr("Unsupported chain")
|
||||
color: theme.colors.textPrimary
|
||||
font.pixelSize: 18
|
||||
font.bold: true
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
|
||||
TokenSelectorModal {
|
||||
@ -150,13 +205,39 @@ Item {
|
||||
theme: theme
|
||||
|
||||
onConfirmed: function(snapshot) {
|
||||
swapCard.resetAmounts()
|
||||
swapToast.show(qsTr("Swap submitted"),
|
||||
qsTr("%1 %2 → %3 %4")
|
||||
.arg(snapshot.sellAmount)
|
||||
.arg(snapshot.sellToken)
|
||||
.arg(snapshot.minReceived)
|
||||
.arg(snapshot.buyToken))
|
||||
if (!root.backend) {
|
||||
swapToast.show(qsTr("Swap failed"), qsTr("Backend is not ready"), "", "error")
|
||||
return
|
||||
}
|
||||
|
||||
const expectedSubmissionToken = root.submissionToken()
|
||||
logos.watch(root.backend.submitSwap(root.withSelectedWalletAccount(snapshot)),
|
||||
function(resultJson) {
|
||||
if (root.submissionToken() !== expectedSubmissionToken)
|
||||
return
|
||||
const result = root.parseTransactionResult(resultJson)
|
||||
if (!result.success) {
|
||||
swapToast.show(qsTr("Swap failed"),
|
||||
result.error || qsTr("Transaction rejected"),
|
||||
"",
|
||||
"error")
|
||||
return
|
||||
}
|
||||
|
||||
swapCard.resetAmounts()
|
||||
swapToast.show(qsTr("Swap submitted"),
|
||||
qsTr("%1 %2 → %3 %4")
|
||||
.arg(snapshot.sellAmount)
|
||||
.arg(snapshot.sellToken)
|
||||
.arg(snapshot.minReceived)
|
||||
.arg(snapshot.buyToken),
|
||||
result.tx_hash || "")
|
||||
},
|
||||
function(error) {
|
||||
if (root.submissionToken() !== expectedSubmissionToken)
|
||||
return
|
||||
swapToast.show(qsTr("Swap failed"), String(error), "", "error")
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,15 +3,15 @@ import QtQuick 2.15
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
property string tokenA: "USDC"
|
||||
property string tokenB: "ETH"
|
||||
property string feeTier: "0.30%"
|
||||
property real userLpBalance: 1118033
|
||||
property real reserveA: 1000000
|
||||
property real reserveB: 500
|
||||
property real totalLpSupply: 22360679
|
||||
property real walletBalanceA: 60000
|
||||
property real walletBalanceB: 20
|
||||
property string tokenA: ""
|
||||
property string tokenB: ""
|
||||
property string feeTier: "0%"
|
||||
property real userLpBalance: 0
|
||||
property real reserveA: 0
|
||||
property real reserveB: 0
|
||||
property real totalLpSupply: 0
|
||||
property real walletBalanceA: 0
|
||||
property real walletBalanceB: 0
|
||||
readonly property real minimumLiquidity: 1000
|
||||
|
||||
readonly property real poolShare: totalLpSupply > 0 ? userLpBalance / totalLpSupply : 0
|
||||
@ -19,38 +19,16 @@ QtObject {
|
||||
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);
|
||||
const safeB = Math.max(0, Number(actualB) || 0);
|
||||
const safeLp = Math.max(0, Number(mintedLp) || 0);
|
||||
|
||||
reserveA += safeA;
|
||||
reserveB += safeB;
|
||||
totalLpSupply += safeLp;
|
||||
userLpBalance += safeLp;
|
||||
}
|
||||
|
||||
function applyRemoveLiquidity(withdrawA, withdrawB, burnedLp) {
|
||||
const safeA = Math.max(0, Number(withdrawA) || 0);
|
||||
const safeB = Math.max(0, Number(withdrawB) || 0);
|
||||
const safeLp = Math.max(0, Number(burnedLp) || 0);
|
||||
|
||||
reserveA = Math.max(0, reserveA - safeA);
|
||||
reserveB = Math.max(0, reserveB - safeB);
|
||||
totalLpSupply = Math.max(0, totalLpSupply - safeLp);
|
||||
userLpBalance = Math.max(0, userLpBalance - safeLp);
|
||||
}
|
||||
|
||||
function resetDummyState() {
|
||||
tokenA = "USDC";
|
||||
tokenB = "ETH";
|
||||
feeTier = "0.30%";
|
||||
userLpBalance = 1118033;
|
||||
reserveA = 1000000;
|
||||
reserveB = 500;
|
||||
totalLpSupply = 22360679;
|
||||
walletBalanceA = 60000;
|
||||
walletBalanceB = 20;
|
||||
function loadConfig(config) {
|
||||
tokenA = config.tokenA || "";
|
||||
tokenB = config.tokenB || "";
|
||||
feeTier = config.feeTier || "0%";
|
||||
userLpBalance = Number(config.userLpBalance) || 0;
|
||||
reserveA = Number(config.reserveA) || 0;
|
||||
reserveB = Number(config.reserveB) || 0;
|
||||
totalLpSupply = Number(config.totalLpSupply) || 0;
|
||||
walletBalanceA = Number(config.walletBalanceA) || 0;
|
||||
walletBalanceB = Number(config.walletBalanceB) || 0;
|
||||
}
|
||||
|
||||
function parseAmount(value) {
|
||||
@ -66,7 +44,7 @@ QtObject {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return reserveB * parseAmount(amountA) / reserveA;
|
||||
return Math.floor(reserveB * parseAmount(amountA) / reserveA);
|
||||
}
|
||||
|
||||
function amountAForB(amountB) {
|
||||
@ -74,16 +52,16 @@ QtObject {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return reserveA * parseAmount(amountB) / reserveB;
|
||||
return Math.floor(reserveA * parseAmount(amountB) / reserveB);
|
||||
}
|
||||
|
||||
function addLiquidityPreview(maxA, maxB) {
|
||||
const safeMaxA = parseAmount(maxA);
|
||||
const safeMaxB = parseAmount(maxB);
|
||||
const safeMaxA = floorAmount(maxA);
|
||||
const safeMaxB = floorAmount(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 actualA = Math.floor(Math.min(idealA, safeMaxA));
|
||||
const actualB = Math.floor(Math.min(idealB, safeMaxB));
|
||||
const lpFromA = reserveA > 0 ? Math.floor(totalLpSupply * actualA / reserveA) : 0;
|
||||
const lpFromB = reserveB > 0 ? Math.floor(totalLpSupply * actualB / reserveB) : 0;
|
||||
|
||||
@ -101,7 +79,7 @@ QtObject {
|
||||
}
|
||||
|
||||
function clampBurnAmount(value) {
|
||||
return Math.min(floorAmount(value), Math.max(0, floorAmount(userLpBalance)));
|
||||
return Math.min(floorAmount(value), floorAmount(userLpBalance));
|
||||
}
|
||||
|
||||
function clampSlippageTolerancePercent(value) {
|
||||
@ -118,10 +96,6 @@ QtObject {
|
||||
function burnAmountForPercent(percent) {
|
||||
const safePercent = Math.max(0, Math.min(100, Number(percent) || 0));
|
||||
|
||||
if (safePercent === 100) {
|
||||
return clampBurnAmount(userLpBalance);
|
||||
}
|
||||
|
||||
return clampBurnAmount(Math.floor(userLpBalance * safePercent / 100));
|
||||
}
|
||||
|
||||
@ -1 +0,0 @@
|
||||
/nix/store/05xmkf4hdg4dpk4hjanq8ik8pl7r74ym-logos-amm_ui-module
|
||||
@ -16,26 +16,28 @@ int AccountModel::rowCount(const QModelIndex& parent) const
|
||||
|
||||
QVariant AccountModel::data(const QModelIndex& index, int role) const
|
||||
{
|
||||
if (!index.isValid() || index.row() < 0 || index.row() >= m_entries.size())
|
||||
if (!index.isValid() || index.row() >= m_entries.size())
|
||||
return QVariant();
|
||||
|
||||
const AccountEntry& e = m_entries.at(index.row());
|
||||
switch (role) {
|
||||
case NameRole: return e.name;
|
||||
case AddressRole: return e.address;
|
||||
case BalanceRole: return e.balance;
|
||||
case IsPublicRole: return e.isPublic;
|
||||
default: return QVariant();
|
||||
case NameRole: return e.name;
|
||||
case AddressRole: return e.address;
|
||||
case DisplayAddressRole: return e.displayAddress;
|
||||
case BalanceRole: return e.balance;
|
||||
case IsPublicRole: return e.isPublic;
|
||||
default: return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> AccountModel::roleNames() const
|
||||
{
|
||||
return {
|
||||
{ NameRole, "name" },
|
||||
{ AddressRole, "address" },
|
||||
{ BalanceRole, "balance" },
|
||||
{ IsPublicRole, "isPublic" }
|
||||
{ NameRole, "name" },
|
||||
{ AddressRole, "address" },
|
||||
{ DisplayAddressRole, "displayAddress" },
|
||||
{ BalanceRole, "balance" },
|
||||
{ IsPublicRole, "isPublic" }
|
||||
};
|
||||
}
|
||||
|
||||
@ -52,9 +54,13 @@ void AccountModel::replaceFromJsonArray(const QJsonArray& arr)
|
||||
if (v.isObject()) {
|
||||
const QJsonObject obj = v.toObject();
|
||||
e.address = obj.value(QStringLiteral("account_id")).toString();
|
||||
e.displayAddress = obj.value(QStringLiteral("display_account_id")).toString();
|
||||
if (e.displayAddress.isEmpty())
|
||||
e.displayAddress = e.address;
|
||||
e.isPublic = obj.value(QStringLiteral("is_public")).toBool(true);
|
||||
} else {
|
||||
e.address = v.toString();
|
||||
e.displayAddress = e.address;
|
||||
e.isPublic = true;
|
||||
}
|
||||
m_entries.append(e);
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
struct AccountEntry {
|
||||
QString name;
|
||||
QString address;
|
||||
QString displayAddress;
|
||||
QString balance;
|
||||
bool isPublic = true;
|
||||
};
|
||||
@ -24,6 +25,7 @@ public:
|
||||
enum Role {
|
||||
NameRole = Qt::UserRole + 1,
|
||||
AddressRole,
|
||||
DisplayAddressRole,
|
||||
BalanceRole,
|
||||
IsPublicRole
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -2,7 +2,11 @@
|
||||
#define AMM_UI_BACKEND_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
#include <QVariantList>
|
||||
#include <QVariantMap>
|
||||
|
||||
#include "rep_AmmUiBackend_source.h"
|
||||
|
||||
@ -34,35 +38,85 @@ public slots:
|
||||
void refreshAccounts() override;
|
||||
void refreshBalances() override;
|
||||
QString getBalance(QString accountIdHex, bool isPublic) override;
|
||||
bool createNewDefault(QString password) override;
|
||||
bool createNew(QString configPath, QString storagePath, QString password) override;
|
||||
QString submitSwap(QVariantMap snapshot) override;
|
||||
QString submitLiquidity(QVariantMap snapshot) override;
|
||||
QString createNewDefault(QString password) override;
|
||||
QString createNew(QString configPath, QString storagePath, QString password) override;
|
||||
bool openExisting() override;
|
||||
void disconnectWallet() override;
|
||||
bool changeSequencerAddr(QString url) override;
|
||||
void copyToClipboard(QString text) override;
|
||||
|
||||
private:
|
||||
// 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).
|
||||
// Canonical LEZ wallet home shared with the wallet UI and other apps.
|
||||
static QString defaultWalletHome();
|
||||
QString defaultConfigPath() const;
|
||||
QString defaultStoragePath() const;
|
||||
|
||||
void persistConfigPath(const QString& path);
|
||||
void persistStoragePath(const QString& path);
|
||||
QJsonArray listAccounts();
|
||||
void openOrAdoptWallet();
|
||||
bool adoptOpenWallet();
|
||||
void refreshBlockHeights();
|
||||
void refreshSequencerAddr();
|
||||
void loadDeploymentConfig();
|
||||
void selectDeploymentForNetwork(const QString& network);
|
||||
void selectDeploymentForChain(const QString& network,
|
||||
const QString& blockHash,
|
||||
const QString& blockSignature);
|
||||
void clearDeploymentSelection(const QString& network);
|
||||
void setDeploymentIdentityPendingIfNeeded(bool pending);
|
||||
void verifyDeploymentTransactions();
|
||||
void refreshDeploymentWalletState();
|
||||
void updateDeploymentNetworkMatched();
|
||||
QJsonObject configuredTokenDefinition(const QString& symbol, int fallbackIndex) const;
|
||||
QString accountIdHex(const QString& accountId) const;
|
||||
QStringList accountIdHexList(const QStringList& accountIds, QString* error) const;
|
||||
struct PoolChainState {
|
||||
double reserveA = 0;
|
||||
double reserveB = 0;
|
||||
double totalLpSupply = 0;
|
||||
double feeBps = 0;
|
||||
bool found = false;
|
||||
};
|
||||
PoolChainState poolChainState() const;
|
||||
struct WalletFungibleHolding {
|
||||
QString accountIdHex;
|
||||
double balance = 0;
|
||||
bool found = false;
|
||||
bool ambiguous = false;
|
||||
};
|
||||
WalletFungibleHolding walletFungibleHolding(const QString& definitionAccountId,
|
||||
const QString& accountIdFilterHex = {}) const;
|
||||
QString selectedWalletAccountIdHex(const QVariantMap& snapshot, QString* error) const;
|
||||
QString submitAmmTransaction(const QStringList& accountIds,
|
||||
const QVariantList& signingRequirements,
|
||||
const QVariantList& instruction);
|
||||
void saveWallet();
|
||||
|
||||
// Probe the configured sequencer over HTTP and update sequencerReachable.
|
||||
void checkReachability();
|
||||
void probeChainIdentity(const QString& network);
|
||||
|
||||
AccountModel* m_accountModel;
|
||||
|
||||
LogosAPI* m_logosAPI;
|
||||
LogosModules* m_logos;
|
||||
QJsonArray m_tokenChains;
|
||||
QJsonArray m_ammChains;
|
||||
QJsonArray m_programChainGroups;
|
||||
QString m_activeDeploymentNetwork;
|
||||
bool m_activeDeploymentConfigured = false;
|
||||
bool m_activeDeploymentDeployed = false;
|
||||
bool m_identityProbeInFlight = false;
|
||||
QStringList m_requiredDeploymentTransactions;
|
||||
int m_pendingDeploymentChecks = 0;
|
||||
quint64 m_deploymentCheckGeneration = 0;
|
||||
quint64 m_reachabilityProbeGeneration = 0;
|
||||
quint64 m_chainIdentityProbeGeneration = 0;
|
||||
bool m_deploymentChecksFailed = false;
|
||||
QJsonArray m_tokenDefinitions;
|
||||
QJsonObject m_poolConfig;
|
||||
|
||||
QNetworkAccessManager* m_net;
|
||||
QTimer* m_reachabilityTimer;
|
||||
|
||||
@ -16,6 +16,16 @@ class AmmUiBackend
|
||||
// Defaults true so the UI doesn't flash a warning before the first check.
|
||||
PROP(bool sequencerReachable READONLY)
|
||||
|
||||
// Chain-backed deployment state for the configured AMM pool.
|
||||
PROP(QVariantList deploymentTokens READONLY)
|
||||
PROP(QVariantMap deploymentPool READONLY)
|
||||
PROP(bool deploymentNetworkMatched READONLY)
|
||||
PROP(bool deploymentIdentityPending READONLY)
|
||||
|
||||
// AMM transactions. Returns wallet JSON: { success, tx_hash, error }.
|
||||
SLOT(QString submitSwap(QVariantMap snapshot))
|
||||
SLOT(QString submitLiquidity(QVariantMap snapshot))
|
||||
|
||||
// Account management
|
||||
SLOT(QString createAccountPublic())
|
||||
SLOT(QString createAccountPrivate())
|
||||
@ -24,10 +34,12 @@ class AmmUiBackend
|
||||
SLOT(QString getBalance(QString accountIdHex, bool isPublic))
|
||||
|
||||
// Wallet lifecycle. createNewDefault() is the happy path: it creates a
|
||||
// fresh per-app wallet at walletHome with no path picking. createNew()
|
||||
// keeps explicit paths for an "advanced" flow.
|
||||
SLOT(bool createNewDefault(QString password))
|
||||
SLOT(bool createNew(QString configPath, QString storagePath, QString password))
|
||||
// fresh wallet at the canonical LEZ wallet home with no path picking.
|
||||
// createNew() exists for the QtRO contract but only accepts canonical
|
||||
// config/storage filenames. Both return the new BIP39 mnemonic, or an
|
||||
// empty string on failure.
|
||||
SLOT(QString createNewDefault(QString password))
|
||||
SLOT(QString createNew(QString configPath, QString storagePath, QString password))
|
||||
|
||||
// Re-open the existing on-disk wallet after a disconnect.
|
||||
SLOT(bool openExisting())
|
||||
@ -38,7 +50,4 @@ class AmmUiBackend
|
||||
// Settings. Rewrites the wallet config's sequencer_addr and re-opens the
|
||||
// wallet so the new network takes effect immediately.
|
||||
SLOT(bool changeSequencerAddr(QString url))
|
||||
|
||||
// Misc
|
||||
SLOT(void copyToClipboard(QString text))
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user