feat(gui): DFX.swiss integration (#451)

* feat(gui): Monero wallet

* progress

* refactor

* progress, dont delete wallet, re-fetch approvals and background periodically

* show transaction history correctly

* Enable fetching tx hashes

* Try add the wallet listener event callbacks, not working

* fix: Redeem XMR to internal main wallet, not temp wallet

* feat(monero-sys): Support signing messages

* feat(gui): DFX.swiss integration

* refactor: format, slight refactorings

* progress

* type safety

* refactoring of callback system

* make free floating functions generic

* refactor: Format files

* refactor(gui): Split wallet components and redesign balanceOverview component

* refactor(gui): Add action buttons and transaction section

* wrapper event listener

* progress, compiles

* works!

* WORKS! Event received on balance change

* refactor: format and slight refactorings and comments

* refactor(gui): Start with implementation of send dialog

- new number input
- new button variant and size

* add @tauri-apps/plugin-dialog

* feat(gui): Add permissions for file dialog

* fix(monero-harness): Compile issue

* feat(gui): Extract seed from Monero wallet and use for derivation, allow opening existing wallet file

* feat(gui): Always refresh the approval list from frontend when resolving

* fix(monero-rpc-pool): Implement Into<String> for ServerInfo

* fix(monero-sys): Use oneshot channel for all wallets

* feat(gui, monero-sys): Display recently opened wallets

* small refactors

* fix(gui): Enable background_sync, display temp "Loading..." if values are null

* feat(gui): Remove headers from pages, show selected navigation item

* feat(gui): Explicitly tell user if no swaps have been made yet

* feat(gui): send sync and history updates

* feat(gui): Fetch monero wallet details when context becomes availiable

* feat(gui): Display Monero primary address without modal

* feat(gui): Make "swap" button on wallet page take you to "/swap"

* feat(gui): Rework send modal, adjust number input, added send to field

* feat(gui): set block restore height, not working

* refactor(gui): Optimize number input and add support for switching between currency

* feat(gui): Display real fiat currency prices in send modal

* feat(gui): Add error message for too high send amount

* feat(gui): Modern UI for SeedSelectionDialog

* feat(gui): Wrap MoneroWalletActions

* wip

* refactoring approval callback

* feat(gui): Send Direction of Transaction in History to Frontend

* feat(gui): Let user approve transaction before publishing

* feat: Display 8 digits for Monero amounts by default

* feat(monero-sys): Store pending (non published) transactions in Mutex map inside wallet thread

This allows seperating signing and publishing transactions cleanly

* dprint fmt

* fix(gui): Refresh Monero wallet history C++ struct before serializing

* feat(monero-rpc-pool): Fail after three JSON-RPC errors

* feat(monero-sys): Add wrapper around verify_wallet_password

* feat(gui): Allow opening password-protected Wallets

* refactor: fmt, remove receive button

* fix(gui): Convert to XMR before converting into Fiat

* feat(gui): Add dialog for setting restore height

* feat(gui): block height can be changed, blocks when too low

* refactor(monero-sys): Remove old WalletListener code

* feat(gui): Continually ask for user to select wallet and enter password, if user rejects, offer to select different wallet

* refactor(swap): Extract "select Monero wallet" into own function

* refactor(tauri): Dont kill monero-wallet-rpc

* refactor(tauri): Avoid multiple concurrent Contexts starting

* refactor: Change "Cancel" to "Change wallet" on PasswordEntryDialog

* feat(gui): show curent block height, fix blockage

* Cargo.lock update

* refactor(monero-sys): Use match instead of is_err() and expect(...)

* refactor: better context for WalletHandle constructor method errors handling

* refactor(monero-sys): Common open_with<F>(path: String, daemon: Daemon, wallet_op: F) function

* feat: check empty password before requeston password for wallet

* feat: Remove "Checking for available remote nodes" from frontend

* feat(gui): Allow sweeping entire Monero balance

* feat(monero-rpc-pool): Keep alive TCP connections, do not record JSON-RPC errors as failure if >=3 nodes failed

If >=3 nodes failed we assume it was an actual issue on our side, not an issue with the node

* refactor(swap): Remove dead code

* add comment to WalletHandleListener::on_refreshed{...}

* feat(gui): show current block height in the field

* refactor: remove unused UserCancelledError;

* refactor: No Arc<Mutex<_>> for Pending TXs map

* refactor: remove redundant } catch (error) {

* feat: add our new crates to `OUR_CRATES` in tracing util

* fix(gui): Add math.ceil to piconero conversion to ensure integer

* fix(gui): Close menu when option is clicked

* review and improve/reduce uses of unsafe, also remove unique_ptr wrapper around TransactionHistory to avoid double free

* fix(gui): Use monero amount from units.tsx

* fix(gui): Use PromiseInvokeButton for simplification for approving of send transaction

* update comment, rename function

* refactor(gui): Fix alignment of amounts

* refactor(gui): Remove sending and refreshing states from wallet

* fix(cli, gui): use old seed flow on no tauri, fix minor issues in gui

* fix: use the new named function

* refactor(gui): Add skeletons for monero wallet when still loading

* fix

* get working

* feat(gui): Add tooltip to buy monero button

* refactor: Format files

* refactor(gui): Do not store logs in redux-persist

---------

Co-authored-by: Maksim Kirillov <maksim.kirillov@staticlabs.de>
Co-authored-by: b-enedict <benedict.seuss@gmail.com>
Co-authored-by: einliterflasche <einliterflasche@pm.me>
This commit is contained in:
Mohan 2025-07-28 11:00:33 +02:00 committed by GitHub
parent 591d0b8e20
commit 69ddd2486d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 658 additions and 81 deletions

View file

@ -29,9 +29,12 @@ tauri-plugin-process = "^2.0.0"
tauri-plugin-shell = "^2.0.0"
tauri-plugin-store = "^2.0.0"
tauri-plugin-updater = "^2.0.0"
tokio = { workspace = true, features = ["rt"] }
tokio-util = { version = "0.7", features = ["rt"] }
tracing = { workspace = true }
uuid = { workspace = true }
zip = "4.0.0"
dfx-swiss-sdk = { git = "https://github.com/eigenwallet/dfx-swiss-rs", subdir = "dfx-swiss-sdk" }
[target."cfg(not(any(target_os = \"android\", target_os = \"ios\")))".dependencies]
tauri-plugin-cli = "^2.0.0"

View file

@ -8,13 +8,14 @@ use swap::cli::{
request::{
BalanceArgs, BuyXmrArgs, CancelAndRefundArgs, CheckElectrumNodeArgs,
CheckElectrumNodeResponse, CheckMoneroNodeArgs, CheckMoneroNodeResponse, CheckSeedArgs,
CheckSeedResponse, ExportBitcoinWalletArgs, GetCurrentSwapArgs, GetDataDirArgs,
GetHistoryArgs, GetLogsArgs, GetMoneroAddressesArgs, GetMoneroBalanceArgs,
GetMoneroHistoryArgs, GetMoneroMainAddressArgs, GetMoneroSyncProgressArgs,
GetPendingApprovalsResponse, GetRestoreHeightArgs, GetSwapInfoArgs,
GetSwapInfosAllArgs, ListSellersArgs, MoneroRecoveryArgs, RedactArgs,
RejectApprovalArgs, RejectApprovalResponse, ResolveApprovalArgs, ResumeSwapArgs,
SendMoneroArgs, SetRestoreHeightArgs, SuspendCurrentSwapArgs, WithdrawBtcArgs,
CheckSeedResponse, DfxAuthenticateResponse, ExportBitcoinWalletArgs,
GetCurrentSwapArgs, GetDataDirArgs, GetHistoryArgs, GetLogsArgs,
GetMoneroAddressesArgs, GetMoneroBalanceArgs, GetMoneroHistoryArgs,
GetMoneroMainAddressArgs, GetMoneroSyncProgressArgs, GetPendingApprovalsResponse,
GetRestoreHeightArgs, GetSwapInfoArgs, GetSwapInfosAllArgs, ListSellersArgs,
MoneroRecoveryArgs, RedactArgs, RejectApprovalArgs, RejectApprovalResponse,
ResolveApprovalArgs, ResumeSwapArgs, SendMoneroArgs, SetRestoreHeightArgs,
SuspendCurrentSwapArgs, WithdrawBtcArgs,
},
tauri_bindings::{TauriContextStatusEvent, TauriEmitter, TauriHandle, TauriSettings},
Context, ContextBuilder,
@ -209,7 +210,8 @@ pub fn run() {
get_pending_approvals,
set_monero_restore_height,
reject_approval_request,
get_restore_height
get_restore_height,
dfx_authenticate,
])
.setup(setup)
.build(tauri::generate_context!())
@ -461,3 +463,92 @@ async fn initialize_context(
}
}
}
#[tauri::command]
async fn dfx_authenticate(
state: tauri::State<'_, State>,
) -> Result<DfxAuthenticateResponse, String> {
use dfx_swiss_sdk::{DfxClient, SignRequest};
use tokio::sync::{mpsc, oneshot};
use tokio_util::task::AbortOnDropHandle;
let context = state.try_get_context()?;
// Get the monero wallet manager
let monero_manager = context
.monero_manager
.as_ref()
.ok_or("Monero wallet manager not available for DFX authentication")?;
let wallet = monero_manager.main_wallet().await;
let address = wallet.main_address().await.to_string();
// Create channel for authentication
let (auth_tx, mut auth_rx) = mpsc::channel::<(SignRequest, oneshot::Sender<String>)>(10);
// Create DFX client
let mut client = DfxClient::new(address, Some("https://api.dfx.swiss".to_string()), auth_tx);
// Start signing task with AbortOnDropHandle
let signing_task = tokio::spawn(async move {
tracing::info!("DFX signing service started and listening for requests");
while let Some((sign_request, response_tx)) = auth_rx.recv().await {
tracing::debug!(
message = %sign_request.message,
blockchains = ?sign_request.blockchains,
"Received DFX signing request"
);
// Sign the message using the main Monero wallet
let signature = match wallet
.sign_message(&sign_request.message, None, false)
.await
{
Ok(sig) => {
tracing::debug!(
signature_preview = %&sig[..std::cmp::min(50, sig.len())],
"Message signed successfully for DFX"
);
sig
}
Err(e) => {
tracing::error!(error = ?e, "Failed to sign message for DFX");
continue;
}
};
// Send signature back to DFX client
if let Err(_) = response_tx.send(signature) {
tracing::warn!("Failed to send signature response through channel to DFX client");
}
}
tracing::info!("DFX signing service stopped");
});
// Create AbortOnDropHandle so the task gets cleaned up
let _abort_handle = AbortOnDropHandle::new(signing_task);
// Authenticate with DFX
tracing::info!("Starting DFX authentication...");
client
.authenticate()
.await
.map_err(|e| format!("Failed to authenticate with DFX: {}", e))?;
let access_token = client
.access_token
.as_ref()
.ok_or("No access token available after authentication")?
.clone();
let kyc_url = format!("https://app.dfx.swiss/buy?session={}", access_token);
tracing::info!("DFX authentication completed successfully");
Ok(DfxAuthenticateResponse {
access_token,
kyc_url,
})
}