Show live swap status, explorer links, and full kyc.rip route details

This commit is contained in:
2026-10-26 21:31:41 +08:00
parent 2f70931cba
commit 3ff5385a9b
13 changed files with 759 additions and 150 deletions
+36 -6
View File
@@ -109,7 +109,8 @@ final class AppModel: ObservableObject {
return try await SwapService.shared.estimate(from: from.rawValue, to: to.rawValue, amount: amountNumber)
}
func createSwap(route: SwapRoute, amountFrom: String, from: Chain, to: Chain) async throws -> SwapOrder {
func createSwap(route: SwapRoute, estimateID: String, amountFrom: String,
from: Chain, to: Chain) async throws -> SwapOrder {
guard let amountNumber = Double(amountFrom.trimmingCharacters(in: .whitespaces)), amountNumber > 0 else {
throw SwapError.api("Enter a valid amount")
}
@@ -117,6 +118,7 @@ final class AppModel: ObservableObject {
throw SwapError.api("No \(to.title) wallet to receive into")
}
let order = try await SwapService.shared.createOrder(route: route,
estimateID: estimateID,
from: from.rawValue,
to: to.rawValue,
addressTo: destination,
@@ -127,23 +129,36 @@ final class AppModel: ObservableObject {
}
/// Sends the swap deposit using the same payment path as a normal send.
func sendSwapDeposit(_ order: SwapOrder) async throws {
func sendSwapDeposit(_ order: SwapOrder, accountIndex: Int = 0) async throws {
try await send(to: order.depositAddress,
amount: String(order.depositAmount),
tag: order.depositMemo ?? "",
priority: .defaultPriority)
priority: .defaultPriority,
accountIndex: accountIndex)
}
func refreshSwapStatus(_ order: SwapOrder) async -> SwapOrder {
guard let updated = try? await SwapService.shared.orderStatus(order) else { return order }
var order = order
order.status = updated.status
order.txIn = updated.txIn ?? order.txIn
order.txOut = updated.txOut ?? order.txOut
if order.txIn?.isEmpty == true { order.txIn = nil }
if order.txOut?.isEmpty == true { order.txOut = nil }
order.supportUrl = updated.supportUrl ?? order.supportUrl
if let index = swapOrders.firstIndex(where: { $0.id == order.id }) {
swapOrders[index] = order
}
WalletStore.saveSwapOrder(order)
return order
}
/// Persists a local change to an order (for example, the deposit was sent).
func saveSwapOrder(_ order: SwapOrder) {
WalletStore.saveSwapOrder(order)
loadSwapOrders()
}
func removeSwapOrder(id: String) {
WalletStore.removeSwapOrder(id: id)
loadSwapOrders()
@@ -197,7 +212,11 @@ final class AppModel: ObservableObject {
if let xmrWallet {
await openMonero(afterRestore: false, wallet: xmrWallet)
}
if xrpWallet != nil || moneroReady {
// Restore the chain the user was last on, as long as that wallet exists.
let savedChain = UserDefaults.standard.string(forKey: "koins.chain").flatMap(Chain.init(rawValue:))
if let savedChain, (savedChain == .xrp ? xrpWallet : xmrWallet) != nil {
chain = savedChain
} else if xrpWallet != nil || moneroReady {
chain = xrpWallet != nil ? .xrp : .xmr
}
if xrpWallet != nil || xmrWallet != nil, faceIDEnabled, Biometrics.isAvailable {
@@ -245,6 +264,7 @@ final class AppModel: ObservableObject {
func select(_ newChain: Chain) async {
chain = newChain
UserDefaults.standard.set(newChain.rawValue, forKey: "koins.chain")
refreshToken = UUID()
errorMessage = nil
statusMessage = nil
@@ -475,7 +495,8 @@ final class AppModel: ObservableObject {
// MARK: - Sends
func send(to: String, amount: String, tag: String, priority: TransactionPriority) async throws {
func send(to: String, amount: String, tag: String, priority: TransactionPriority,
accountIndex: Int = 0) async throws {
try await requireAuth(reason: "Authorize this payment")
let destinationTag = UInt32(tag.trimmingCharacters(in: .whitespaces))
if chain == .xrp {
@@ -491,7 +512,10 @@ final class AppModel: ObservableObject {
await refresh(force: true)
return
}
_ = try await MoneroService.shared.send(address: to, amountXMR: amount, priority: priority)
_ = try await MoneroService.shared.send(address: to,
amountXMR: amount,
priority: priority,
accountIndex: accountIndex)
statusMessage = "Payment sent"
await refresh(force: true)
}
@@ -539,6 +563,12 @@ final class AppModel: ObservableObject {
await refresh(force: true)
}
/// The account a payment should leave from by default: the first one with
/// spendable funds, otherwise the primary account.
var defaultMoneroAccountIndex: Int {
moneroAccounts.first { (Decimal(string: $0.unlocked) ?? 0) > 0 }?.index ?? 0
}
/// Refreshes the account/subaddress tree independently of the active chain.
func loadAccounts() async {
guard moneroReady else { return }