mirror of
https://github.com/logos-blockchain/lez-programs.git
synced 2026-08-08 15:13:14 +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)
|
cmake_minimum_required(VERSION 3.14)
|
||||||
project(AmmUiPlugin LANGUAGES CXX)
|
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})
|
if(DEFINED ENV{LOGOS_MODULE_BUILDER_ROOT})
|
||||||
include($ENV{LOGOS_MODULE_BUILDER_ROOT}/cmake/LogosModule.cmake)
|
include($ENV{LOGOS_MODULE_BUILDER_ROOT}/cmake/LogosModule.cmake)
|
||||||
else()
|
else()
|
||||||
@ -26,4 +45,17 @@ logos_module(
|
|||||||
LINK_LIBRARIES
|
LINK_LIBRARIES
|
||||||
Qt6::Gui
|
Qt6::Gui
|
||||||
Qt6::Network
|
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
|
startup the backend **adopts** the already-open wallet (see
|
||||||
`openOrAdoptWallet()`), surfacing **shared** accounts across apps.
|
`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
|
> Follow-up: the wallet FFI requires explicit `config_path`/`storage_path` even
|
||||||
> though the wallet crate already defines defaults (`~/.lee/wallet`,
|
> though the wallet crate already defines defaults (`~/.lee/wallet`,
|
||||||
> `from_path_or_initialize_default`). A `wallet_ffi_create_new_default()` /
|
> `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": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"logos-module-builder": "logos-module-builder",
|
"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": {
|
"rust-overlay": {
|
||||||
|
|||||||
@ -3,18 +3,79 @@
|
|||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
logos-module-builder.url = "github:logos-co/logos-module-builder";
|
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
|
# Core wallet module (the LEZ wallet FFI Qt plugin). The input name must
|
||||||
# match the metadata.json `dependencies` entry so the builder can resolve
|
# 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.
|
# 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";
|
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 {
|
logos-module-builder.lib.mkLogosQmlModule {
|
||||||
src = ./.;
|
src = ./.;
|
||||||
configFile = ./metadata.json;
|
configFile = ./metadata.json;
|
||||||
flakeInputs = inputs;
|
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")
|
readonly property var accountModel: logos.model("amm_ui", "accountModel")
|
||||||
|
|
||||||
property bool ready: false
|
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 {
|
Connections {
|
||||||
target: logos
|
target: logos
|
||||||
@ -36,20 +48,25 @@ Item {
|
|||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
z: 101
|
z: 101
|
||||||
|
|
||||||
readonly property bool show: root.ready
|
readonly property bool show: root.deploymentIdentityPending
|
||||||
&& root.backend
|
|| root.unsupportedChain
|
||||||
&& root.backend.isWalletOpen
|
|| (root.ready
|
||||||
&& root.backend.sequencerAddr.length > 0
|
&& root.backend
|
||||||
&& !root.backend.sequencerReachable
|
&& root.backend.isWalletOpen
|
||||||
|
&& root.backend.sequencerAddr.length > 0
|
||||||
|
&& !root.backend.sequencerReachable)
|
||||||
|
|
||||||
height: show ? 32 : 0
|
height: show ? 32 : 0
|
||||||
visible: height > 0
|
visible: height > 0
|
||||||
clip: true
|
clip: true
|
||||||
color: Theme.palette.warning
|
color: Theme.palette.warning
|
||||||
|
Accessible.role: Accessible.AlertMessage
|
||||||
|
Accessible.name: bannerText.text
|
||||||
|
|
||||||
Behavior on height { NumberAnimation { duration: 150; easing.type: Easing.OutCubic } }
|
Behavior on height { NumberAnimation { duration: 150; easing.type: Easing.OutCubic } }
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
|
id: bannerText
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
width: parent.width - 40
|
width: parent.width - 40
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
@ -57,7 +74,11 @@ Item {
|
|||||||
font.pixelSize: 12
|
font.pixelSize: 12
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
color: Theme.palette.background
|
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 {
|
SwapPage {
|
||||||
anchors.fill: parent
|
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
|
visible: navbar.currentIndex === 0
|
||||||
}
|
}
|
||||||
|
|
||||||
LiquidityPage {
|
LiquidityPage {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
backend: root.ready ? root.backend : null
|
||||||
|
poolConfig: root.deploymentPoolConfig
|
||||||
|
unsupportedChain: root.unsupportedChain
|
||||||
|
selectedWalletAccount: navbar.selectedAddress
|
||||||
visible: navbar.currentIndex === 1
|
visible: navbar.currentIndex === 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,6 +24,11 @@ Item {
|
|||||||
|
|
||||||
implicitHeight: 56
|
implicitHeight: 56
|
||||||
|
|
||||||
|
function selectTab(index) {
|
||||||
|
root.currentIndex = index
|
||||||
|
root.tabChanged(index)
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: Theme.palette.background
|
color: Theme.palette.background
|
||||||
@ -63,12 +68,20 @@ Item {
|
|||||||
delegate: Rectangle {
|
delegate: Rectangle {
|
||||||
readonly property bool active: root.currentIndex === index
|
readonly property bool active: root.currentIndex === index
|
||||||
|
|
||||||
|
activeFocusOnTab: true
|
||||||
height: 36
|
height: 36
|
||||||
width: tabLabel.implicitWidth + 28
|
width: tabLabel.implicitWidth + 28
|
||||||
radius: 18
|
radius: 18
|
||||||
color: active ? Theme.palette.backgroundSecondary : "transparent"
|
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 } }
|
Behavior on color { ColorAnimation { duration: 150 } }
|
||||||
|
Keys.onReturnPressed: root.selectTab(index)
|
||||||
|
Keys.onSpacePressed: root.selectTab(index)
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
id: tabLabel
|
id: tabLabel
|
||||||
@ -84,10 +97,7 @@ Item {
|
|||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: {
|
onClicked: root.selectTab(index)
|
||||||
root.currentIndex = index
|
|
||||||
root.tabChanged(index)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import "../../state"
|
|||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
required property DummyPoolState poolState
|
required property PoolState poolState
|
||||||
|
|
||||||
property real slippageTolerancePercent: 0.5
|
property real slippageTolerancePercent: 0.5
|
||||||
property string amountA: ""
|
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 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 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 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") : ""
|
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)
|
signal slippageToleranceChangeRequested(real tolerancePercent)
|
||||||
@ -207,13 +207,16 @@ Rectangle {
|
|||||||
return {
|
return {
|
||||||
"action": "add",
|
"action": "add",
|
||||||
"actualA": root.preview.actualA,
|
"actualA": root.preview.actualA,
|
||||||
|
"actualAValue": Math.floor(root.preview.actualA),
|
||||||
"actualB": root.preview.actualB,
|
"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),
|
"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,
|
"deltaLp": root.preview.deltaLp,
|
||||||
"depositA": root.poolState.formatTokenAmount(root.preview.actualA, root.poolState.tokenA),
|
"depositA": root.poolState.formatTokenAmount(root.preview.actualA, root.poolState.tokenA),
|
||||||
"depositB": root.poolState.formatTokenAmount(root.preview.actualB, root.poolState.tokenB),
|
"depositB": root.poolState.formatTokenAmount(root.preview.actualB, root.poolState.tokenB),
|
||||||
"feeTier": root.poolState.feeTier,
|
"feeTier": root.poolState.feeTier,
|
||||||
"minLpReceived": root.poolState.formatLpAmount(root.minLpReceived),
|
"minLpReceived": root.poolState.formatLpAmount(root.minLpReceived),
|
||||||
|
"minLpReceivedAmount": root.minLpReceived,
|
||||||
"slippageTolerance": root.poolState.formatPercent(root.slippageTolerancePercent),
|
"slippageTolerance": root.poolState.formatPercent(root.slippageTolerancePercent),
|
||||||
"tokenA": root.poolState.tokenA,
|
"tokenA": root.poolState.tokenA,
|
||||||
"tokenB": root.poolState.tokenB
|
"tokenB": root.poolState.tokenB
|
||||||
|
|||||||
@ -206,7 +206,7 @@ FocusScope {
|
|||||||
activeFocusOnTab: true
|
activeFocusOnTab: true
|
||||||
focusPolicy: Qt.StrongFocus
|
focusPolicy: Qt.StrongFocus
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
text: qsTr("Confirm")
|
text: qsTr("Submit")
|
||||||
|
|
||||||
Accessible.name: confirmButton.text
|
Accessible.name: confirmButton.text
|
||||||
|
|
||||||
|
|||||||
@ -5,8 +5,8 @@ import "../../state"
|
|||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
required property DummyPoolState poolState
|
required property PoolState poolState
|
||||||
readonly property string estimateHelp: qsTr("This value is an estimate from the current dummy reserves and your share of total LP supply.")
|
readonly property string estimateHelp: qsTr("This value is estimated from the current testnet reserves and your share of total LP supply.")
|
||||||
|
|
||||||
color: "#151515"
|
color: "#151515"
|
||||||
implicitHeight: content.implicitHeight + 20
|
implicitHeight: content.implicitHeight + 20
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import "../../state"
|
|||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
required property DummyPoolState poolState
|
required property PoolState poolState
|
||||||
|
|
||||||
property real slippageTolerancePercent: 0.5
|
property real slippageTolerancePercent: 0.5
|
||||||
property int burnAmount: 0
|
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 minReceivedIsZero: root.burnAmount > 0 && (root.minTokenAReceived === 0 || root.minTokenBReceived === 0)
|
||||||
readonly property bool canSubmit: root.hasLpTokens && root.burnAmount > 0 && !root.minReceivedIsZero
|
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 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 slippageToleranceChangeRequested(real tolerancePercent)
|
||||||
signal removeLiquidityRequested(var snapshot)
|
signal removeLiquidityRequested(var snapshot)
|
||||||
@ -478,7 +478,9 @@ Rectangle {
|
|||||||
"burnPercent": root.poolState.formatPercent(root.removePercent),
|
"burnPercent": root.poolState.formatPercent(root.removePercent),
|
||||||
"burnText": root.poolState.formatLpAmount(root.preview.burnedLp),
|
"burnText": root.poolState.formatLpAmount(root.preview.burnedLp),
|
||||||
"minTokenAReceived": root.poolState.formatTokenAmount(root.minTokenAReceived, root.poolState.tokenA),
|
"minTokenAReceived": root.poolState.formatTokenAmount(root.minTokenAReceived, root.poolState.tokenA),
|
||||||
|
"minTokenAReceivedAmount": root.minTokenAReceived,
|
||||||
"minTokenBReceived": root.poolState.formatTokenAmount(root.minTokenBReceived, root.poolState.tokenB),
|
"minTokenBReceived": root.poolState.formatTokenAmount(root.minTokenBReceived, root.poolState.tokenB),
|
||||||
|
"minTokenBReceivedAmount": root.minTokenBReceived,
|
||||||
"postRemovalShare": root.poolState.formatPoolShare(root.preview.newUserShare),
|
"postRemovalShare": root.poolState.formatPoolShare(root.preview.newUserShare),
|
||||||
"slippageTolerance": root.poolState.formatPercent(root.slippageTolerancePercent),
|
"slippageTolerance": root.poolState.formatPercent(root.slippageTolerancePercent),
|
||||||
"tokenA": root.poolState.tokenA,
|
"tokenA": root.poolState.tokenA,
|
||||||
|
|||||||
@ -69,13 +69,13 @@ Rectangle {
|
|||||||
color: "#E7E1D8"
|
color: "#E7E1D8"
|
||||||
font.bold: true
|
font.bold: true
|
||||||
font.pixelSize: 18
|
font.pixelSize: 18
|
||||||
inputMethodHints: Qt.ImhFormattedNumbersOnly
|
inputMethodHints: Qt.ImhDigitsOnly
|
||||||
placeholderText: qsTr("0")
|
placeholderText: qsTr("0")
|
||||||
selectByMouse: true
|
selectByMouse: true
|
||||||
selectedTextColor: "#151515"
|
selectedTextColor: "#151515"
|
||||||
selectionColor: "#F26A21"
|
selectionColor: "#F26A21"
|
||||||
validator: RegularExpressionValidator {
|
validator: RegularExpressionValidator {
|
||||||
regularExpression: /[0-9]*([.][0-9]*)?/
|
regularExpression: /[0-9]*/
|
||||||
}
|
}
|
||||||
|
|
||||||
Accessible.name: root.label
|
Accessible.name: root.label
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import QtQuick 2.15
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
import QtQuick.Layouts 1.15
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
@ -6,8 +7,11 @@ Item {
|
|||||||
|
|
||||||
property string message: ""
|
property string message: ""
|
||||||
property string detail: ""
|
property string detail: ""
|
||||||
|
property string transactionId: ""
|
||||||
|
property string status: "success"
|
||||||
|
property bool copied: false
|
||||||
property bool open: false
|
property bool open: false
|
||||||
property int duration: 3600
|
property int duration: 7000
|
||||||
|
|
||||||
height: implicitHeight
|
height: implicitHeight
|
||||||
implicitHeight: toast.implicitHeight
|
implicitHeight: toast.implicitHeight
|
||||||
@ -15,9 +19,28 @@ Item {
|
|||||||
visible: root.open || fadeOut.running
|
visible: root.open || fadeOut.running
|
||||||
z: 30
|
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.message = nextMessage;
|
||||||
root.detail = nextDetail || "";
|
root.detail = nextDetail || "";
|
||||||
|
root.transactionId = nextTransactionId || "";
|
||||||
|
root.status = nextStatus || "success";
|
||||||
|
root.copied = false;
|
||||||
root.open = true;
|
root.open = true;
|
||||||
dismissTimer.restart();
|
dismissTimer.restart();
|
||||||
}
|
}
|
||||||
@ -28,7 +51,12 @@ Item {
|
|||||||
interval: root.duration
|
interval: root.duration
|
||||||
repeat: false
|
repeat: false
|
||||||
|
|
||||||
onTriggered: root.open = false
|
onTriggered: {
|
||||||
|
if (copyButton.activeFocus || hoverGuard.containsMouse)
|
||||||
|
restart()
|
||||||
|
else
|
||||||
|
root.open = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
@ -47,8 +75,19 @@ Item {
|
|||||||
color: "#20201F"
|
color: "#20201F"
|
||||||
implicitHeight: Math.max(50, toastContent.implicitHeight + 18)
|
implicitHeight: Math.max(50, toastContent.implicitHeight + 18)
|
||||||
radius: 8
|
radius: 8
|
||||||
border.color: "#4D3A2E"
|
border.color: root.status === "error" ? "#6A2E2E" : "#4D3A2E"
|
||||||
border.width: 1
|
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 {
|
RowLayout {
|
||||||
id: toastContent
|
id: toastContent
|
||||||
@ -62,7 +101,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
color: "#78C88D"
|
color: root.status === "error" ? "#D75C5C" : "#78C88D"
|
||||||
radius: 6
|
radius: 6
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignTop
|
Layout.alignment: Qt.AlignTop
|
||||||
@ -90,14 +129,59 @@ Item {
|
|||||||
|
|
||||||
Text {
|
Text {
|
||||||
color: "#B8ADA3"
|
color: "#B8ADA3"
|
||||||
elide: Text.ElideRight
|
|
||||||
font.pixelSize: 12
|
font.pixelSize: 12
|
||||||
|
maximumLineCount: 3
|
||||||
text: root.detail
|
text: root.detail
|
||||||
visible: root.detail.length > 0
|
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
|
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 buyInput: ""
|
||||||
property string editingSide: "sell"
|
property string editingSide: "sell"
|
||||||
property real slippageTolerancePercent: 0.5
|
property real slippageTolerancePercent: 0.5
|
||||||
|
property int feeBps: 30
|
||||||
|
|
||||||
DummySwapState {
|
SwapState {
|
||||||
id: swapState
|
id: swapState
|
||||||
feeBps: 30
|
feeBps: root.feeBps
|
||||||
}
|
}
|
||||||
|
|
||||||
signal requestTokenSelect(string side)
|
signal requestTokenSelect(string side)
|
||||||
@ -77,9 +78,11 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function formatAmountValue(val) {
|
function formatAmountValue(val) {
|
||||||
if (val >= 1) return val.toFixed(2)
|
return Math.floor(val).toString()
|
||||||
if (val >= 0.0001) return val.toFixed(6)
|
}
|
||||||
return val.toFixed(8)
|
|
||||||
|
function formatMaxSentValue(val) {
|
||||||
|
return Math.ceil(val).toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly property string sellDisplay: editingSide === "sell"
|
readonly property string sellDisplay: editingSide === "sell"
|
||||||
@ -107,8 +110,12 @@ Rectangle {
|
|||||||
"sellToken": sellToken ? sellToken.symbol : "",
|
"sellToken": sellToken ? sellToken.symbol : "",
|
||||||
"buyToken": buyToken ? buyToken.symbol : "",
|
"buyToken": buyToken ? buyToken.symbol : "",
|
||||||
"sellAmount": formatAmountValue(parsedSellAmount),
|
"sellAmount": formatAmountValue(parsedSellAmount),
|
||||||
|
"sellAmountValue": formatAmountValue(parsedSellAmount),
|
||||||
"buyAmount": formatAmountValue(parsedBuyAmount),
|
"buyAmount": formatAmountValue(parsedBuyAmount),
|
||||||
|
"buyAmountValue": formatAmountValue(parsedBuyAmount),
|
||||||
"minReceived": formatAmountValue(minReceivedAmount),
|
"minReceived": formatAmountValue(minReceivedAmount),
|
||||||
|
"minReceivedAmountValue": formatAmountValue(minReceivedAmount),
|
||||||
|
"maxSentAmountValue": formatMaxSentValue(swapState.maxSent(parsedSellAmount, slippageTolerancePercent)),
|
||||||
"feeAmount": swapState.formatTokenAmount(feeAmount, sellToken ? sellToken.symbol : ""),
|
"feeAmount": swapState.formatTokenAmount(feeAmount, sellToken ? sellToken.symbol : ""),
|
||||||
"priceImpactPercent": swapState.formatPercent(priceImpactPercent),
|
"priceImpactPercent": swapState.formatPercent(priceImpactPercent),
|
||||||
"priceImpactPercentValue": priceImpactPercent,
|
"priceImpactPercentValue": priceImpactPercent,
|
||||||
|
|||||||
@ -195,7 +195,7 @@ FocusScope {
|
|||||||
activeFocusOnTab: true
|
activeFocusOnTab: true
|
||||||
focusPolicy: Qt.StrongFocus
|
focusPolicy: Qt.StrongFocus
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
text: qsTr("Confirm Swap")
|
text: qsTr("Submit")
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.minimumHeight: 48
|
Layout.minimumHeight: 48
|
||||||
onClicked: root.confirm()
|
onClicked: root.confirm()
|
||||||
|
|||||||
@ -46,7 +46,7 @@ Rectangle {
|
|||||||
|
|
||||||
Item {
|
Item {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
height: 44
|
Layout.preferredHeight: 44
|
||||||
|
|
||||||
TextInput {
|
TextInput {
|
||||||
id: tiInput
|
id: tiInput
|
||||||
@ -58,7 +58,7 @@ Rectangle {
|
|||||||
clip: true
|
clip: true
|
||||||
onTextEdited: root.inputEdited(text)
|
onTextEdited: root.inputEdited(text)
|
||||||
validator: RegularExpressionValidator {
|
validator: RegularExpressionValidator {
|
||||||
regularExpression: /^[0-9]*\.?[0-9]*$/
|
regularExpression: /^[0-9]*$/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,10 +81,10 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
height: 40
|
Layout.preferredHeight: 40
|
||||||
radius: 20
|
radius: 20
|
||||||
color: tokenBtnHover.containsMouse ? theme.colors.panelHoverBg : theme.colors.panelBg
|
color: tokenBtnHover.containsMouse ? theme.colors.panelHoverBg : theme.colors.panelBg
|
||||||
implicitWidth: tokenBtnRow.implicitWidth + 24
|
Layout.preferredWidth: tokenBtnRow.implicitWidth + 24
|
||||||
Behavior on color { ColorAnimation { duration: 120 } }
|
Behavior on color { ColorAnimation { duration: 120 } }
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
@ -93,7 +93,9 @@ Rectangle {
|
|||||||
spacing: 6
|
spacing: 6
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: 24; height: 24; radius: 12
|
Layout.preferredWidth: 24
|
||||||
|
Layout.preferredHeight: 24
|
||||||
|
radius: 12
|
||||||
color: root.token ? root.token.color : theme.colors.noTokenCircle
|
color: root.token ? root.token.color : theme.colors.noTokenCircle
|
||||||
visible: root.token !== null
|
visible: root.token !== null
|
||||||
Text {
|
Text {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import QtQuick
|
import QtQuick 2.15
|
||||||
import QtQml
|
import QtQml 2.15
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls 2.15
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
import Logos.Theme
|
import Logos.Theme
|
||||||
import Logos.Controls
|
import Logos.Controls
|
||||||
@ -12,8 +12,8 @@ import Logos.Controls
|
|||||||
// clicking it opens a popup (top-right, just under the
|
// clicking it opens a popup (top-right, just under the
|
||||||
// button) holding the account selector, create-account and
|
// button) holding the account selector, create-account and
|
||||||
// disconnect actions.
|
// disconnect actions.
|
||||||
// The selected account address is exposed via selectedAddress for the
|
// selectedAddress stays in the wallet module's raw hex format for backend
|
||||||
// trade/liquidity flows to use as the "from" account.
|
// calls; selectedDisplayAddress is the base58 format shown/copied in the UI.
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
@ -22,11 +22,13 @@ Item {
|
|||||||
property var accountModel: null
|
property var accountModel: null
|
||||||
|
|
||||||
readonly property bool connected: backend !== null && backend.isWalletOpen
|
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
|
// Index of the active account. selectedAddress/selectedName are derived from
|
||||||
// the model mirror below so they stay valid while the popup (and its list)
|
// the model mirror below so they stay valid while the popup (and its list)
|
||||||
// is closed.
|
// is closed.
|
||||||
property int selectedIndex: 0
|
property int selectedIndex: 0
|
||||||
|
property string selectedAccountId: ""
|
||||||
|
|
||||||
// Non-visual mirror of the account model: realizes every row regardless of
|
// Non-visual mirror of the account model: realizes every row regardless of
|
||||||
// popup visibility, so the active account is addressable by index at all
|
// popup visibility, so the active account is addressable by index at all
|
||||||
@ -36,10 +38,13 @@ Item {
|
|||||||
model: root.accountModel
|
model: root.accountModel
|
||||||
delegate: QtObject {
|
delegate: QtObject {
|
||||||
readonly property string address: model.address ?? ""
|
readonly property string address: model.address ?? ""
|
||||||
|
readonly property string displayAddress: model.displayAddress ?? ""
|
||||||
readonly property string name: model.name ?? ""
|
readonly property string name: model.name ?? ""
|
||||||
readonly property string balance: model.balance ?? ""
|
readonly property string balance: model.balance ?? ""
|
||||||
readonly property bool isPublic: model.isPublic ?? false
|
readonly property bool isPublic: model.isPublic ?? false
|
||||||
}
|
}
|
||||||
|
onObjectAdded: root.clampSelection()
|
||||||
|
onObjectRemoved: root.clampSelection()
|
||||||
}
|
}
|
||||||
|
|
||||||
function entryAt(i) {
|
function entryAt(i) {
|
||||||
@ -48,26 +53,75 @@ Item {
|
|||||||
|
|
||||||
readonly property string selectedAddress: {
|
readonly property string selectedAddress: {
|
||||||
const e = root.entryAt(root.selectedIndex)
|
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: {
|
readonly property string selectedName: {
|
||||||
const e = root.entryAt(root.selectedIndex)
|
const e = root.entryAt(root.selectedIndex)
|
||||||
return e ? e.name : ""
|
return e && e.isPublic ? e.name : qsTr("No public account")
|
||||||
}
|
}
|
||||||
readonly property string selectedBalance: {
|
readonly property string selectedBalance: {
|
||||||
const e = root.entryAt(root.selectedIndex)
|
const e = root.entryAt(root.selectedIndex)
|
||||||
return e ? e.balance : ""
|
return e && e.isPublic ? e.balance : ""
|
||||||
}
|
}
|
||||||
readonly property bool selectedIsPublic: {
|
// Keep the selection within bounds as accounts are added/removed.
|
||||||
const e = root.entryAt(root.selectedIndex)
|
function firstPublicIndex() {
|
||||||
return e ? e.isPublic : false
|
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() {
|
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
|
if (root.selectedIndex < 0) root.selectedIndex = 0
|
||||||
else if (root.selectedIndex >= accounts.count) root.selectedIndex = accounts.count - 1
|
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 {
|
Connections {
|
||||||
target: root.accountModel
|
target: root.accountModel
|
||||||
@ -97,9 +151,25 @@ Item {
|
|||||||
clipboardProxy.text = ""
|
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
|
implicitWidth: root.connected ? connectedButton.width : connectButton.width
|
||||||
implicitHeight: 40
|
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 ────────────────────────────────────────────
|
// ── Disconnected: Connect ────────────────────────────────────────────
|
||||||
LogosButton {
|
LogosButton {
|
||||||
id: connectButton
|
id: connectButton
|
||||||
@ -110,36 +180,37 @@ Item {
|
|||||||
enabled: root.backend !== null
|
enabled: root.backend !== null
|
||||||
text: qsTr("Connect")
|
text: qsTr("Connect")
|
||||||
onClicked: {
|
onClicked: {
|
||||||
// Re-open an existing wallet; only show the create modal on first run.
|
if (!root.backend) return
|
||||||
if (root.backend && root.backend.walletExists)
|
logos.watch(root.backend.openExisting(),
|
||||||
logos.watch(root.backend.openExisting(),
|
function(ok) {
|
||||||
function(ok) { if (!ok) console.warn("openExisting failed") },
|
if (!ok) root.handleOpenExistingFailure("")
|
||||||
function(error) { console.warn("openExisting error:", error) })
|
},
|
||||||
else
|
function(error) {
|
||||||
createWalletDialog.open()
|
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 ─────────────
|
// ── Connected: address pill that toggles the wallet menu ─────────────
|
||||||
Rectangle {
|
Button {
|
||||||
id: connectedButton
|
id: connectedButton
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
visible: root.connected
|
visible: root.connected
|
||||||
implicitHeight: 40
|
implicitHeight: 40
|
||||||
implicitWidth: connectedRow.implicitWidth + Theme.spacing.medium * 2
|
implicitWidth: connectedRow.implicitWidth + leftPadding + rightPadding
|
||||||
radius: height / 2
|
leftPadding: Theme.spacing.medium
|
||||||
// Keep an opaque dark fill in both states: the navbar is white, and the
|
rightPadding: Theme.spacing.medium
|
||||||
// active "muted" fill is translucent gray, which renders light over white
|
topPadding: 0
|
||||||
// and makes the white label unreadable. Signal "open" with an accent
|
bottomPadding: 0
|
||||||
// border instead.
|
text: root.selectedDisplayAddress.length > 0
|
||||||
color: Theme.palette.backgroundSecondary
|
? root.truncated(root.selectedDisplayAddress)
|
||||||
border.width: 1
|
: qsTr("No public account")
|
||||||
border.color: walletMenu.opened ? Theme.palette.overlayOrange : "transparent"
|
Accessible.name: text
|
||||||
|
|
||||||
RowLayout {
|
contentItem: RowLayout {
|
||||||
id: connectedRow
|
id: connectedRow
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: Theme.spacing.small
|
spacing: Theme.spacing.small
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@ -149,7 +220,7 @@ Item {
|
|||||||
color: "#39c06a"
|
color: "#39c06a"
|
||||||
}
|
}
|
||||||
LogosText {
|
LogosText {
|
||||||
text: root.truncated(root.selectedAddress) || qsTr("Connected")
|
text: connectedButton.text
|
||||||
font.pixelSize: Theme.typography.secondaryText
|
font.pixelSize: Theme.typography.secondaryText
|
||||||
color: Theme.palette.text
|
color: Theme.palette.text
|
||||||
}
|
}
|
||||||
@ -160,19 +231,26 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
background: Rectangle {
|
||||||
anchors.fill: parent
|
radius: height / 2
|
||||||
cursorShape: Qt.PointingHandCursor
|
// Keep an opaque dark fill in both states: the navbar is white, and the
|
||||||
// CloseOnPressOutside already dismisses the popup on this same press
|
// active "muted" fill is translucent gray, which renders light over white
|
||||||
// (the button is outside it), so `opened` is false by the time this
|
// and makes the white label unreadable. Signal "open" with an accent
|
||||||
// fires. Without the recency guard the dismissing click would just
|
// border instead.
|
||||||
// reopen it. If it just closed, leave it closed.
|
color: Theme.palette.backgroundSecondary
|
||||||
onClicked: {
|
border.width: 1
|
||||||
if (walletMenu.opened || (Date.now() - walletMenu.lastClosedMs) < 200)
|
border.color: walletMenu.opened || connectedButton.activeFocus ? Theme.palette.overlayOrange : "transparent"
|
||||||
walletMenu.close()
|
}
|
||||||
else
|
|
||||||
walletMenu.open()
|
// 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
|
id: walletMenu
|
||||||
parent: connectedButton
|
parent: connectedButton
|
||||||
y: connectedButton.height + Theme.spacing.small
|
y: connectedButton.height + Theme.spacing.small
|
||||||
x: connectedButton.width - width // right-align under the button
|
x: {
|
||||||
width: 360
|
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
|
padding: Theme.spacing.medium
|
||||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||||
|
|
||||||
@ -239,14 +327,17 @@ Item {
|
|||||||
|
|
||||||
WalletIconButton {
|
WalletIconButton {
|
||||||
iconSource: Qt.resolvedUrl("icons/account.svg")
|
iconSource: Qt.resolvedUrl("icons/account.svg")
|
||||||
|
accessibleName: qsTr("Show accounts")
|
||||||
onClicked: viewStack.push(accountsView)
|
onClicked: viewStack.push(accountsView)
|
||||||
}
|
}
|
||||||
WalletIconButton {
|
WalletIconButton {
|
||||||
iconSource: Qt.resolvedUrl("icons/settings.svg")
|
iconSource: Qt.resolvedUrl("icons/settings.svg")
|
||||||
|
accessibleName: qsTr("Open wallet settings")
|
||||||
onClicked: viewStack.push(settingsView)
|
onClicked: viewStack.push(settingsView)
|
||||||
}
|
}
|
||||||
WalletIconButton {
|
WalletIconButton {
|
||||||
iconSource: Qt.resolvedUrl("icons/power.svg")
|
iconSource: Qt.resolvedUrl("icons/power.svg")
|
||||||
|
accessibleName: qsTr("Disconnect wallet")
|
||||||
onClicked: {
|
onClicked: {
|
||||||
walletMenu.close()
|
walletMenu.close()
|
||||||
if (root.backend) root.backend.disconnectWallet()
|
if (root.backend) root.backend.disconnectWallet()
|
||||||
@ -279,12 +370,13 @@ Item {
|
|||||||
Rectangle {
|
Rectangle {
|
||||||
Layout.preferredWidth: tagLabel.implicitWidth + Theme.spacing.small * 2
|
Layout.preferredWidth: tagLabel.implicitWidth + Theme.spacing.small * 2
|
||||||
Layout.preferredHeight: tagLabel.implicitHeight + 4
|
Layout.preferredHeight: tagLabel.implicitHeight + 4
|
||||||
|
visible: root.selectedAddress.length > 0
|
||||||
radius: 4
|
radius: 4
|
||||||
color: Theme.palette.backgroundSecondary
|
color: Theme.palette.backgroundSecondary
|
||||||
LogosText {
|
LogosText {
|
||||||
id: tagLabel
|
id: tagLabel
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: root.selectedIsPublic ? qsTr("Public") : qsTr("Private")
|
text: qsTr("Public")
|
||||||
font.pixelSize: Theme.typography.secondaryText
|
font.pixelSize: Theme.typography.secondaryText
|
||||||
color: Theme.palette.textSecondary
|
color: Theme.palette.textSecondary
|
||||||
}
|
}
|
||||||
@ -302,7 +394,7 @@ Item {
|
|||||||
LogosText {
|
LogosText {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
text: root.selectedAddress
|
text: root.selectedDisplayAddress
|
||||||
font.pixelSize: Theme.typography.secondaryText
|
font.pixelSize: Theme.typography.secondaryText
|
||||||
color: Theme.palette.textMuted
|
color: Theme.palette.textMuted
|
||||||
elide: Text.ElideMiddle
|
elide: Text.ElideMiddle
|
||||||
@ -310,8 +402,9 @@ Item {
|
|||||||
LogosCopyButton {
|
LogosCopyButton {
|
||||||
Layout.preferredHeight: 40
|
Layout.preferredHeight: 40
|
||||||
Layout.preferredWidth: 40
|
Layout.preferredWidth: 40
|
||||||
visible: root.selectedAddress.length > 0
|
accessibleName: qsTr("Copy selected account address")
|
||||||
onCopyText: root.copyToClipboard(root.selectedAddress)
|
visible: root.selectedDisplayAddress.length > 0
|
||||||
|
onCopyText: root.copyToClipboard(root.selectedDisplayAddress)
|
||||||
icon.color: Theme.palette.textMuted
|
icon.color: Theme.palette.textMuted
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -334,6 +427,7 @@ Item {
|
|||||||
|
|
||||||
WalletIconButton {
|
WalletIconButton {
|
||||||
iconSource: Qt.resolvedUrl("icons/back.svg")
|
iconSource: Qt.resolvedUrl("icons/back.svg")
|
||||||
|
accessibleName: qsTr("Back")
|
||||||
onClicked: viewStack.pop()
|
onClicked: viewStack.pop()
|
||||||
}
|
}
|
||||||
LogosText {
|
LogosText {
|
||||||
@ -356,9 +450,12 @@ Item {
|
|||||||
|
|
||||||
delegate: AccountDelegate {
|
delegate: AccountDelegate {
|
||||||
width: ListView.view.width
|
width: ListView.view.width
|
||||||
highlighted: index === root.selectedIndex
|
selectable: model.isPublic ?? false
|
||||||
|
highlighted: selectable && index === root.selectedIndex
|
||||||
onClicked: {
|
onClicked: {
|
||||||
root.selectedIndex = index
|
if (!selectable)
|
||||||
|
return
|
||||||
|
root.selectIndex(index)
|
||||||
viewStack.pop()
|
viewStack.pop()
|
||||||
}
|
}
|
||||||
onCopyRequested: (text) => root.copyToClipboard(text)
|
onCopyRequested: (text) => root.copyToClipboard(text)
|
||||||
@ -367,7 +464,7 @@ Item {
|
|||||||
|
|
||||||
LogosButton {
|
LogosButton {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
height: 40
|
Layout.preferredHeight: 40
|
||||||
text: qsTr("Add")
|
text: qsTr("Add")
|
||||||
// Leave the wallet menu open behind the (modal) dialog.
|
// Leave the wallet menu open behind the (modal) dialog.
|
||||||
onClicked: createAccountDialog.open()
|
onClicked: createAccountDialog.open()
|
||||||
@ -389,6 +486,7 @@ Item {
|
|||||||
|
|
||||||
WalletIconButton {
|
WalletIconButton {
|
||||||
iconSource: Qt.resolvedUrl("icons/back.svg")
|
iconSource: Qt.resolvedUrl("icons/back.svg")
|
||||||
|
accessibleName: qsTr("Back")
|
||||||
onClicked: viewStack.pop()
|
onClicked: viewStack.pop()
|
||||||
}
|
}
|
||||||
LogosText {
|
LogosText {
|
||||||
@ -422,11 +520,13 @@ Item {
|
|||||||
font.pixelSize: Theme.typography.secondaryText
|
font.pixelSize: Theme.typography.secondaryText
|
||||||
property bool ok: false
|
property bool ok: false
|
||||||
color: ok ? Theme.palette.success : Theme.palette.error
|
color: ok ? Theme.palette.success : Theme.palette.error
|
||||||
|
Accessible.role: Accessible.AlertMessage
|
||||||
|
Accessible.name: text
|
||||||
}
|
}
|
||||||
|
|
||||||
LogosButton {
|
LogosButton {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
height: 40
|
Layout.preferredHeight: 40
|
||||||
text: qsTr("Save")
|
text: qsTr("Save")
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (!root.backend) return
|
if (!root.backend) return
|
||||||
@ -454,16 +554,95 @@ Item {
|
|||||||
onCreateWallet: function(password) {
|
onCreateWallet: function(password) {
|
||||||
if (!root.backend) return
|
if (!root.backend) return
|
||||||
logos.watch(root.backend.createNewDefault(password),
|
logos.watch(root.backend.createNewDefault(password),
|
||||||
function(ok) {
|
function(result) {
|
||||||
if (ok) createWalletDialog.close()
|
const mnemonic = String(result || "")
|
||||||
else createWalletDialog.createError = qsTr("Failed to create wallet. Please try again.")
|
if (mnemonic.length > 0) {
|
||||||
|
createWalletDialog.close()
|
||||||
|
backupWalletDialog.mnemonic = mnemonic
|
||||||
|
backupWalletDialog.open()
|
||||||
|
} else {
|
||||||
|
createWalletDialog.createError = qsTr("Failed to create wallet. Please try again.")
|
||||||
|
}
|
||||||
},
|
},
|
||||||
function(error) {
|
function(error) {
|
||||||
createWalletDialog.createError = qsTr("Error creating wallet: %1").arg(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 {
|
CreateAccountDialog {
|
||||||
id: createAccountDialog
|
id: createAccountDialog
|
||||||
onCreatePublicRequested: {
|
onCreatePublicRequested: {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import QtQuick
|
import QtQuick 2.15
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls 2.15
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
import Logos.Theme
|
import Logos.Theme
|
||||||
import Logos.Controls
|
import Logos.Controls
|
||||||
@ -13,6 +13,18 @@ ItemDelegate {
|
|||||||
// to its QML-side clipboard helper (AccountControl.copyToClipboard).
|
// to its QML-side clipboard helper (AccountControl.copyToClipboard).
|
||||||
signal copyRequested(string text)
|
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
|
leftPadding: Theme.spacing.medium
|
||||||
rightPadding: Theme.spacing.medium
|
rightPadding: Theme.spacing.medium
|
||||||
topPadding: Theme.spacing.medium
|
topPadding: Theme.spacing.medium
|
||||||
@ -67,7 +79,7 @@ ItemDelegate {
|
|||||||
id: addressLabel
|
id: addressLabel
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
text: model.address ?? ""
|
text: root.displayAddress
|
||||||
font.pixelSize: Theme.typography.secondaryText
|
font.pixelSize: Theme.typography.secondaryText
|
||||||
color: Theme.palette.textMuted
|
color: Theme.palette.textMuted
|
||||||
elide: Text.ElideMiddle
|
elide: Text.ElideMiddle
|
||||||
@ -75,8 +87,9 @@ ItemDelegate {
|
|||||||
LogosCopyButton {
|
LogosCopyButton {
|
||||||
Layout.preferredHeight: 40
|
Layout.preferredHeight: 40
|
||||||
Layout.preferredWidth: 40
|
Layout.preferredWidth: 40
|
||||||
onCopyText: root.copyRequested(model.address)
|
accessibleName: qsTr("Copy account address")
|
||||||
visible: addressLabel.text
|
onCopyText: root.copyRequested(root.displayAddress)
|
||||||
|
visible: root.displayAddress.length > 0
|
||||||
icon.color: Theme.palette.textMuted
|
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 2.15
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls 2.15
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
import Logos.Theme
|
import Logos.Theme
|
||||||
import Logos.Controls
|
import Logos.Controls
|
||||||
@ -9,6 +9,8 @@ import Logos.Controls
|
|||||||
Popup {
|
Popup {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
readonly property real viewportMargin: Theme.spacing.large
|
||||||
|
|
||||||
signal createPublicRequested()
|
signal createPublicRequested()
|
||||||
signal createPrivateRequested()
|
signal createPrivateRequested()
|
||||||
|
|
||||||
@ -21,7 +23,7 @@ Popup {
|
|||||||
// this popup is declared inside.
|
// this popup is declared inside.
|
||||||
parent: Overlay.overlay
|
parent: Overlay.overlay
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
width: 360
|
width: Math.min(360, Math.max(0, parent ? parent.width - root.viewportMargin * 2 : 360))
|
||||||
|
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
color: Theme.palette.backgroundSecondary
|
color: Theme.palette.backgroundSecondary
|
||||||
@ -74,6 +76,8 @@ Popup {
|
|||||||
LogosSwitch {
|
LogosSwitch {
|
||||||
id: privateSwitch
|
id: privateSwitch
|
||||||
checked: false
|
checked: false
|
||||||
|
Accessible.name: qsTr("Create private account")
|
||||||
|
Accessible.description: qsTr("Private balance and activity.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,19 +1,20 @@
|
|||||||
import QtQuick
|
import QtQuick 2.15
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls 2.15
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
import Logos.Theme
|
import Logos.Theme
|
||||||
import Logos.Controls
|
import Logos.Controls
|
||||||
|
|
||||||
// Password-only wallet creation modal. Storage/config live at the per-app
|
// Password-only wallet creation modal. Storage/config live at the canonical
|
||||||
// default (backend.walletHome) — no path picking. Opened from the navbar
|
// LEZ wallet home (backend.walletHome), with no path picking. Opened from the
|
||||||
// "Connect" button.
|
// navbar "Connect" button.
|
||||||
Popup {
|
Popup {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
// Where the wallet will be stored, shown for transparency.
|
// Where the wallet will be stored, shown for transparency.
|
||||||
property string walletHome: ""
|
property string walletHome: ""
|
||||||
property string createError: ""
|
property string createError: ""
|
||||||
|
readonly property real viewportMargin: Theme.spacing.large
|
||||||
|
|
||||||
signal createWallet(string password)
|
signal createWallet(string password)
|
||||||
|
|
||||||
@ -25,7 +26,7 @@ Popup {
|
|||||||
// this popup is declared inside.
|
// this popup is declared inside.
|
||||||
parent: Overlay.overlay
|
parent: Overlay.overlay
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
width: 380
|
width: Math.min(380, Math.max(0, parent ? parent.width - root.viewportMargin * 2 : 380))
|
||||||
|
|
||||||
onOpened: {
|
onOpened: {
|
||||||
passwordField.text = ""
|
passwordField.text = ""
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import QtQuick
|
import QtQuick 2.15
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls 2.15
|
||||||
|
|
||||||
import Logos.Theme
|
import Logos.Theme
|
||||||
|
|
||||||
@ -8,13 +8,17 @@ Button {
|
|||||||
|
|
||||||
signal copyText()
|
signal copyText()
|
||||||
|
|
||||||
|
property string accessibleName: ""
|
||||||
|
property string iconSource: Qt.resolvedUrl("icons/copy.svg")
|
||||||
|
property bool copied: false
|
||||||
|
|
||||||
implicitWidth: 24
|
implicitWidth: 24
|
||||||
implicitHeight: 24
|
implicitHeight: 24
|
||||||
|
text: root.copied ? qsTr("Copied") : root.accessibleName
|
||||||
|
Accessible.name: text
|
||||||
display: AbstractButton.IconOnly
|
display: AbstractButton.IconOnly
|
||||||
flat: true
|
flat: true
|
||||||
|
|
||||||
property string iconSource: Qt.resolvedUrl("icons/copy.svg")
|
|
||||||
|
|
||||||
icon.source: root.iconSource
|
icon.source: root.iconSource
|
||||||
icon.width: 24
|
icon.width: 24
|
||||||
icon.height: 24
|
icon.height: 24
|
||||||
@ -22,6 +26,7 @@ Button {
|
|||||||
|
|
||||||
function reset() {
|
function reset() {
|
||||||
iconSource = Qt.resolvedUrl("icons/copy.svg")
|
iconSource = Qt.resolvedUrl("icons/copy.svg")
|
||||||
|
copied = false
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
@ -34,6 +39,7 @@ Button {
|
|||||||
onClicked: {
|
onClicked: {
|
||||||
root.copyText()
|
root.copyText()
|
||||||
root.iconSource = Qt.resolvedUrl("icons/checkmark.svg")
|
root.iconSource = Qt.resolvedUrl("icons/checkmark.svg")
|
||||||
|
root.copied = true
|
||||||
resetTimer.restart()
|
resetTimer.restart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import QtQuick
|
import QtQuick 2.15
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls 2.15
|
||||||
|
|
||||||
import Logos.Theme
|
import Logos.Theme
|
||||||
|
|
||||||
@ -12,9 +12,12 @@ Button {
|
|||||||
property url iconSource
|
property url iconSource
|
||||||
property color iconColor: Theme.palette.textSecondary
|
property color iconColor: Theme.palette.textSecondary
|
||||||
property int iconSize: 18
|
property int iconSize: 18
|
||||||
|
property string accessibleName: ""
|
||||||
|
|
||||||
implicitWidth: 32
|
implicitWidth: 32
|
||||||
implicitHeight: 32
|
implicitHeight: 32
|
||||||
|
text: root.accessibleName
|
||||||
|
Accessible.name: root.accessibleName
|
||||||
display: AbstractButton.IconOnly
|
display: AbstractButton.IconOnly
|
||||||
flat: true
|
flat: true
|
||||||
|
|
||||||
|
|||||||
@ -7,19 +7,27 @@ import "../state"
|
|||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
property var poolConfig: ({})
|
||||||
|
property var backend: null
|
||||||
|
property bool unsupportedChain: false
|
||||||
|
property string selectedWalletAccount: ""
|
||||||
property int activeLiquidityTab: 0
|
property int activeLiquidityTab: 0
|
||||||
property real slippageTolerancePercent: 0.5
|
property real slippageTolerancePercent: 0.5
|
||||||
|
readonly property bool hasPoolConfig: !root.unsupportedChain && !!root.poolConfig.account
|
||||||
readonly property int pageMargin: 16
|
readonly property int pageMargin: 16
|
||||||
readonly property int preferredCardWidth: 492
|
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 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
|
width: parent ? parent.width : implicitWidth
|
||||||
height: parent ? parent.height : implicitHeight
|
height: parent ? parent.height : implicitHeight
|
||||||
implicitWidth: root.preferredCardWidth + root.pageMargin * 2
|
implicitWidth: root.preferredCardWidth + root.pageMargin * 2
|
||||||
implicitHeight: pageCard.implicitHeight + root.pageMargin * 2
|
implicitHeight: pageCard.implicitHeight + root.pageMargin * 2
|
||||||
|
|
||||||
DummyPoolState {
|
PoolState {
|
||||||
id: poolState
|
id: poolState
|
||||||
|
Component.onCompleted: loadConfig(root.poolConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@ -32,6 +40,7 @@ Item {
|
|||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
clip: true
|
clip: true
|
||||||
|
visible: !root.unsupportedChain
|
||||||
contentHeight: Math.max(height, pageCard.y + pageCard.implicitHeight + root.pageMargin)
|
contentHeight: Math.max(height, pageCard.y + pageCard.implicitHeight + root.pageMargin)
|
||||||
contentWidth: width
|
contentWidth: width
|
||||||
enabled: !confirmationDialog.visible
|
enabled: !confirmationDialog.visible
|
||||||
@ -54,6 +63,7 @@ Item {
|
|||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 12
|
anchors.margins: 12
|
||||||
|
enabled: root.hasPoolConfig
|
||||||
spacing: 10
|
spacing: 10
|
||||||
|
|
||||||
RowLayout {
|
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 {
|
LiquidityConfirmationDialog {
|
||||||
id: confirmationDialog
|
id: confirmationDialog
|
||||||
|
|
||||||
@ -173,17 +192,69 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function confirmLiquidityAction(snapshot) {
|
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") {
|
if (snapshot.action === "add") {
|
||||||
poolState.applyAddLiquidity(snapshot.actualA, snapshot.actualB, snapshot.deltaLp);
|
|
||||||
addLiquidityForm.resetForm();
|
addLiquidityForm.resetForm();
|
||||||
successToast.show(qsTr("Liquidity added"), qsTr("Position updated"));
|
successToast.show(qsTr("Add liquidity submitted"),
|
||||||
|
qsTr("Position refreshed from chain"),
|
||||||
|
transactionId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (snapshot.action === "remove") {
|
if (snapshot.action === "remove") {
|
||||||
poolState.applyRemoveLiquidity(snapshot.withdrawA, snapshot.withdrawB, snapshot.burnAmount);
|
|
||||||
removeLiquidityForm.resetForm();
|
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 2.15
|
||||||
import QtQuick.Controls 2.15
|
|
||||||
import QtQuick.Layouts 1.15
|
import QtQuick.Layouts 1.15
|
||||||
import "../components/shared"
|
import "../components/shared"
|
||||||
import "../components/swap"
|
import "../components/swap"
|
||||||
import "../state"
|
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property var tokens: [
|
property var tokens: []
|
||||||
{ symbol: "TOK1", name: "Token 1", color: "#627eea", letter: "E", address: "0x0000000000000000000000000000000000000000", usdPrice: 2392.70, balance: 4.25, reserve: 850 },
|
property var poolConfig: ({})
|
||||||
{ symbol: "TOK2", name: "Token 2", color: "#2775ca", letter: "$", address: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", usdPrice: 1.00, balance: 12480, reserve: 2400000 },
|
property var backend: null
|
||||||
{ symbol: "TOK3", name: "Token 3", color: "#26a17b", letter: "T", address: "0xdac17f958d2ee523a2206206994597c13d831ec7", usdPrice: 1.00, balance: 320, reserve: 1800000 },
|
property bool unsupportedChain: false
|
||||||
{ symbol: "TOK4", name: "Token 4", color: "#f7931a", letter: "B", address: "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599", usdPrice: 63500, balance: 0.18, reserve: 42 },
|
property string selectedWalletAccount: ""
|
||||||
{ symbol: "TOK5", name: "Token 5", color: "#627eea", letter: "E", address: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", usdPrice: 2392.70, balance: 0, reserve: 600 },
|
readonly property string poolAccount: poolConfig.account || ""
|
||||||
{ symbol: "TOK6", name: "Token 6", color: "#9b59b6", letter: "L", address: "0x1337000000000000000000000000000000000cafe", usdPrice: 0.42, balance: 5400, reserve: 950000 }
|
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 {
|
QtObject {
|
||||||
id: theme
|
id: theme
|
||||||
@ -92,10 +132,12 @@ Item {
|
|||||||
|
|
||||||
SwapCard {
|
SwapCard {
|
||||||
id: swapCard
|
id: swapCard
|
||||||
|
visible: !root.unsupportedChain
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
theme: theme
|
theme: theme
|
||||||
tokens: root.tokens
|
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) {
|
onRequestTokenSelect: function(side) {
|
||||||
tokenModal.targetSide = side
|
tokenModal.targetSide = side
|
||||||
@ -108,13 +150,26 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
|
visible: !root.unsupportedChain
|
||||||
Layout.alignment: Qt.AlignHCenter
|
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
|
textFormat: Text.RichText
|
||||||
color: theme.colors.textSecondary
|
color: theme.colors.textSecondary
|
||||||
font.pixelSize: 15
|
font.pixelSize: 15
|
||||||
horizontalAlignment: Text.AlignHCenter
|
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 {
|
TokenSelectorModal {
|
||||||
@ -150,13 +205,39 @@ Item {
|
|||||||
theme: theme
|
theme: theme
|
||||||
|
|
||||||
onConfirmed: function(snapshot) {
|
onConfirmed: function(snapshot) {
|
||||||
swapCard.resetAmounts()
|
if (!root.backend) {
|
||||||
swapToast.show(qsTr("Swap submitted"),
|
swapToast.show(qsTr("Swap failed"), qsTr("Backend is not ready"), "", "error")
|
||||||
qsTr("%1 %2 → %3 %4")
|
return
|
||||||
.arg(snapshot.sellAmount)
|
}
|
||||||
.arg(snapshot.sellToken)
|
|
||||||
.arg(snapshot.minReceived)
|
const expectedSubmissionToken = root.submissionToken()
|
||||||
.arg(snapshot.buyToken))
|
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 {
|
QtObject {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property string tokenA: "USDC"
|
property string tokenA: ""
|
||||||
property string tokenB: "ETH"
|
property string tokenB: ""
|
||||||
property string feeTier: "0.30%"
|
property string feeTier: "0%"
|
||||||
property real userLpBalance: 1118033
|
property real userLpBalance: 0
|
||||||
property real reserveA: 1000000
|
property real reserveA: 0
|
||||||
property real reserveB: 500
|
property real reserveB: 0
|
||||||
property real totalLpSupply: 22360679
|
property real totalLpSupply: 0
|
||||||
property real walletBalanceA: 60000
|
property real walletBalanceA: 0
|
||||||
property real walletBalanceB: 20
|
property real walletBalanceB: 0
|
||||||
readonly property real minimumLiquidity: 1000
|
readonly property real minimumLiquidity: 1000
|
||||||
|
|
||||||
readonly property real poolShare: totalLpSupply > 0 ? userLpBalance / totalLpSupply : 0
|
readonly property real poolShare: totalLpSupply > 0 ? userLpBalance / totalLpSupply : 0
|
||||||
@ -19,38 +19,16 @@ QtObject {
|
|||||||
readonly property real userOwnedB: reserveB * poolShare
|
readonly property real userOwnedB: reserveB * poolShare
|
||||||
readonly property real tokenAPerTokenB: reserveB > 0 ? Math.floor(reserveA / reserveB) : 0
|
readonly property real tokenAPerTokenB: reserveB > 0 ? Math.floor(reserveA / reserveB) : 0
|
||||||
|
|
||||||
function applyAddLiquidity(actualA, actualB, mintedLp) {
|
function loadConfig(config) {
|
||||||
const safeA = Math.max(0, Number(actualA) || 0);
|
tokenA = config.tokenA || "";
|
||||||
const safeB = Math.max(0, Number(actualB) || 0);
|
tokenB = config.tokenB || "";
|
||||||
const safeLp = Math.max(0, Number(mintedLp) || 0);
|
feeTier = config.feeTier || "0%";
|
||||||
|
userLpBalance = Number(config.userLpBalance) || 0;
|
||||||
reserveA += safeA;
|
reserveA = Number(config.reserveA) || 0;
|
||||||
reserveB += safeB;
|
reserveB = Number(config.reserveB) || 0;
|
||||||
totalLpSupply += safeLp;
|
totalLpSupply = Number(config.totalLpSupply) || 0;
|
||||||
userLpBalance += safeLp;
|
walletBalanceA = Number(config.walletBalanceA) || 0;
|
||||||
}
|
walletBalanceB = Number(config.walletBalanceB) || 0;
|
||||||
|
|
||||||
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 parseAmount(value) {
|
function parseAmount(value) {
|
||||||
@ -66,7 +44,7 @@ QtObject {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return reserveB * parseAmount(amountA) / reserveA;
|
return Math.floor(reserveB * parseAmount(amountA) / reserveA);
|
||||||
}
|
}
|
||||||
|
|
||||||
function amountAForB(amountB) {
|
function amountAForB(amountB) {
|
||||||
@ -74,16 +52,16 @@ QtObject {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return reserveA * parseAmount(amountB) / reserveB;
|
return Math.floor(reserveA * parseAmount(amountB) / reserveB);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addLiquidityPreview(maxA, maxB) {
|
function addLiquidityPreview(maxA, maxB) {
|
||||||
const safeMaxA = parseAmount(maxA);
|
const safeMaxA = floorAmount(maxA);
|
||||||
const safeMaxB = parseAmount(maxB);
|
const safeMaxB = floorAmount(maxB);
|
||||||
const idealA = reserveB > 0 ? reserveA * safeMaxB / reserveB : 0;
|
const idealA = reserveB > 0 ? reserveA * safeMaxB / reserveB : 0;
|
||||||
const idealB = reserveA > 0 ? reserveB * safeMaxA / reserveA : 0;
|
const idealB = reserveA > 0 ? reserveB * safeMaxA / reserveA : 0;
|
||||||
const actualA = Math.min(idealA, safeMaxA);
|
const actualA = Math.floor(Math.min(idealA, safeMaxA));
|
||||||
const actualB = Math.min(idealB, safeMaxB);
|
const actualB = Math.floor(Math.min(idealB, safeMaxB));
|
||||||
const lpFromA = reserveA > 0 ? Math.floor(totalLpSupply * actualA / reserveA) : 0;
|
const lpFromA = reserveA > 0 ? Math.floor(totalLpSupply * actualA / reserveA) : 0;
|
||||||
const lpFromB = reserveB > 0 ? Math.floor(totalLpSupply * actualB / reserveB) : 0;
|
const lpFromB = reserveB > 0 ? Math.floor(totalLpSupply * actualB / reserveB) : 0;
|
||||||
|
|
||||||
@ -101,7 +79,7 @@ QtObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function clampBurnAmount(value) {
|
function clampBurnAmount(value) {
|
||||||
return Math.min(floorAmount(value), Math.max(0, floorAmount(userLpBalance)));
|
return Math.min(floorAmount(value), floorAmount(userLpBalance));
|
||||||
}
|
}
|
||||||
|
|
||||||
function clampSlippageTolerancePercent(value) {
|
function clampSlippageTolerancePercent(value) {
|
||||||
@ -118,10 +96,6 @@ QtObject {
|
|||||||
function burnAmountForPercent(percent) {
|
function burnAmountForPercent(percent) {
|
||||||
const safePercent = Math.max(0, Math.min(100, Number(percent) || 0));
|
const safePercent = Math.max(0, Math.min(100, Number(percent) || 0));
|
||||||
|
|
||||||
if (safePercent === 100) {
|
|
||||||
return clampBurnAmount(userLpBalance);
|
|
||||||
}
|
|
||||||
|
|
||||||
return clampBurnAmount(Math.floor(userLpBalance * safePercent / 100));
|
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
|
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();
|
return QVariant();
|
||||||
|
|
||||||
const AccountEntry& e = m_entries.at(index.row());
|
const AccountEntry& e = m_entries.at(index.row());
|
||||||
switch (role) {
|
switch (role) {
|
||||||
case NameRole: return e.name;
|
case NameRole: return e.name;
|
||||||
case AddressRole: return e.address;
|
case AddressRole: return e.address;
|
||||||
case BalanceRole: return e.balance;
|
case DisplayAddressRole: return e.displayAddress;
|
||||||
case IsPublicRole: return e.isPublic;
|
case BalanceRole: return e.balance;
|
||||||
default: return QVariant();
|
case IsPublicRole: return e.isPublic;
|
||||||
|
default: return QVariant();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QHash<int, QByteArray> AccountModel::roleNames() const
|
QHash<int, QByteArray> AccountModel::roleNames() const
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
{ NameRole, "name" },
|
{ NameRole, "name" },
|
||||||
{ AddressRole, "address" },
|
{ AddressRole, "address" },
|
||||||
{ BalanceRole, "balance" },
|
{ DisplayAddressRole, "displayAddress" },
|
||||||
{ IsPublicRole, "isPublic" }
|
{ BalanceRole, "balance" },
|
||||||
|
{ IsPublicRole, "isPublic" }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,9 +54,13 @@ void AccountModel::replaceFromJsonArray(const QJsonArray& arr)
|
|||||||
if (v.isObject()) {
|
if (v.isObject()) {
|
||||||
const QJsonObject obj = v.toObject();
|
const QJsonObject obj = v.toObject();
|
||||||
e.address = obj.value(QStringLiteral("account_id")).toString();
|
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);
|
e.isPublic = obj.value(QStringLiteral("is_public")).toBool(true);
|
||||||
} else {
|
} else {
|
||||||
e.address = v.toString();
|
e.address = v.toString();
|
||||||
|
e.displayAddress = e.address;
|
||||||
e.isPublic = true;
|
e.isPublic = true;
|
||||||
}
|
}
|
||||||
m_entries.append(e);
|
m_entries.append(e);
|
||||||
|
|||||||
@ -11,6 +11,7 @@
|
|||||||
struct AccountEntry {
|
struct AccountEntry {
|
||||||
QString name;
|
QString name;
|
||||||
QString address;
|
QString address;
|
||||||
|
QString displayAddress;
|
||||||
QString balance;
|
QString balance;
|
||||||
bool isPublic = true;
|
bool isPublic = true;
|
||||||
};
|
};
|
||||||
@ -24,6 +25,7 @@ public:
|
|||||||
enum Role {
|
enum Role {
|
||||||
NameRole = Qt::UserRole + 1,
|
NameRole = Qt::UserRole + 1,
|
||||||
AddressRole,
|
AddressRole,
|
||||||
|
DisplayAddressRole,
|
||||||
BalanceRole,
|
BalanceRole,
|
||||||
IsPublicRole
|
IsPublicRole
|
||||||
};
|
};
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -2,7 +2,11 @@
|
|||||||
#define AMM_UI_BACKEND_H
|
#define AMM_UI_BACKEND_H
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonObject>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QVariantList>
|
||||||
|
#include <QVariantMap>
|
||||||
|
|
||||||
#include "rep_AmmUiBackend_source.h"
|
#include "rep_AmmUiBackend_source.h"
|
||||||
|
|
||||||
@ -34,35 +38,85 @@ public slots:
|
|||||||
void refreshAccounts() override;
|
void refreshAccounts() override;
|
||||||
void refreshBalances() override;
|
void refreshBalances() override;
|
||||||
QString getBalance(QString accountIdHex, bool isPublic) override;
|
QString getBalance(QString accountIdHex, bool isPublic) override;
|
||||||
bool createNewDefault(QString password) override;
|
QString submitSwap(QVariantMap snapshot) override;
|
||||||
bool createNew(QString configPath, QString storagePath, QString password) override;
|
QString submitLiquidity(QVariantMap snapshot) override;
|
||||||
|
QString createNewDefault(QString password) override;
|
||||||
|
QString createNew(QString configPath, QString storagePath, QString password) override;
|
||||||
bool openExisting() override;
|
bool openExisting() override;
|
||||||
void disconnectWallet() override;
|
void disconnectWallet() override;
|
||||||
bool changeSequencerAddr(QString url) override;
|
bool changeSequencerAddr(QString url) override;
|
||||||
void copyToClipboard(QString text) override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Per-app wallet home (kept distinct from the wallet's canonical
|
// Canonical LEZ wallet home shared with the wallet UI and other apps.
|
||||||
// ~/.lee/wallet so standalone instances stay isolated; Basecamp sharing
|
|
||||||
// is handled by adopting an already-open shared wallet on startup).
|
|
||||||
static QString defaultWalletHome();
|
static QString defaultWalletHome();
|
||||||
QString defaultConfigPath() const;
|
QString defaultConfigPath() const;
|
||||||
QString defaultStoragePath() const;
|
QString defaultStoragePath() const;
|
||||||
|
|
||||||
void persistConfigPath(const QString& path);
|
void persistConfigPath(const QString& path);
|
||||||
void persistStoragePath(const QString& path);
|
void persistStoragePath(const QString& path);
|
||||||
|
QJsonArray listAccounts();
|
||||||
void openOrAdoptWallet();
|
void openOrAdoptWallet();
|
||||||
|
bool adoptOpenWallet();
|
||||||
void refreshBlockHeights();
|
void refreshBlockHeights();
|
||||||
void refreshSequencerAddr();
|
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();
|
void saveWallet();
|
||||||
|
|
||||||
// Probe the configured sequencer over HTTP and update sequencerReachable.
|
// Probe the configured sequencer over HTTP and update sequencerReachable.
|
||||||
void checkReachability();
|
void checkReachability();
|
||||||
|
void probeChainIdentity(const QString& network);
|
||||||
|
|
||||||
AccountModel* m_accountModel;
|
AccountModel* m_accountModel;
|
||||||
|
|
||||||
LogosAPI* m_logosAPI;
|
LogosAPI* m_logosAPI;
|
||||||
LogosModules* m_logos;
|
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;
|
QNetworkAccessManager* m_net;
|
||||||
QTimer* m_reachabilityTimer;
|
QTimer* m_reachabilityTimer;
|
||||||
|
|||||||
@ -16,6 +16,16 @@ class AmmUiBackend
|
|||||||
// Defaults true so the UI doesn't flash a warning before the first check.
|
// Defaults true so the UI doesn't flash a warning before the first check.
|
||||||
PROP(bool sequencerReachable READONLY)
|
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
|
// Account management
|
||||||
SLOT(QString createAccountPublic())
|
SLOT(QString createAccountPublic())
|
||||||
SLOT(QString createAccountPrivate())
|
SLOT(QString createAccountPrivate())
|
||||||
@ -24,10 +34,12 @@ class AmmUiBackend
|
|||||||
SLOT(QString getBalance(QString accountIdHex, bool isPublic))
|
SLOT(QString getBalance(QString accountIdHex, bool isPublic))
|
||||||
|
|
||||||
// Wallet lifecycle. createNewDefault() is the happy path: it creates a
|
// Wallet lifecycle. createNewDefault() is the happy path: it creates a
|
||||||
// fresh per-app wallet at walletHome with no path picking. createNew()
|
// fresh wallet at the canonical LEZ wallet home with no path picking.
|
||||||
// keeps explicit paths for an "advanced" flow.
|
// createNew() exists for the QtRO contract but only accepts canonical
|
||||||
SLOT(bool createNewDefault(QString password))
|
// config/storage filenames. Both return the new BIP39 mnemonic, or an
|
||||||
SLOT(bool createNew(QString configPath, QString storagePath, QString password))
|
// 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.
|
// Re-open the existing on-disk wallet after a disconnect.
|
||||||
SLOT(bool openExisting())
|
SLOT(bool openExisting())
|
||||||
@ -38,7 +50,4 @@ class AmmUiBackend
|
|||||||
// Settings. Rewrites the wallet config's sequencer_addr and re-opens the
|
// Settings. Rewrites the wallet config's sequencer_addr and re-opens the
|
||||||
// wallet so the new network takes effect immediately.
|
// wallet so the new network takes effect immediately.
|
||||||
SLOT(bool changeSequencerAddr(QString url))
|
SLOT(bool changeSequencerAddr(QString url))
|
||||||
|
|
||||||
// Misc
|
|
||||||
SLOT(void copyToClipboard(QString text))
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user