Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(crowdnode): improve withdrawal precision #614

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions DashWallet.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
110C679A29227948006B580C /* UINavigationController+CrowdNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 110C679929227948006B580C /* UINavigationController+CrowdNode.swift */; };
110D1781298BA9AF005BEB30 /* WKWebView+CrowdNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 110D1780298BA9AF005BEB30 /* WKWebView+CrowdNode.swift */; };
110D1784298E68A8005BEB30 /* OnlineAccountInfoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 110D1783298E68A8005BEB30 /* OnlineAccountInfoController.swift */; };
111B8C00299BD973004A4129 /* WithdrawalConfirmationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 111B8BFF299BD973004A4129 /* WithdrawalConfirmationController.swift */; };
111C3C4C296C51B500788E18 /* WithdrawalLimitsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 111C3C4B296C51B500788E18 /* WithdrawalLimitsController.swift */; };
111C3C4E296C52F800788E18 /* WithdrawalLimit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 111C3C4D296C52F800788E18 /* WithdrawalLimit.swift */; };
111C3C50296D5A4700788E18 /* TxWithinTimePeriod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 111C3C4F296D5A4700788E18 /* TxWithinTimePeriod.swift */; };
Expand Down Expand Up @@ -1586,7 +1585,6 @@
110C679929227948006B580C /* UINavigationController+CrowdNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationController+CrowdNode.swift"; sourceTree = "<group>"; };
110D1780298BA9AF005BEB30 /* WKWebView+CrowdNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WKWebView+CrowdNode.swift"; sourceTree = "<group>"; };
110D1783298E68A8005BEB30 /* OnlineAccountInfoController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnlineAccountInfoController.swift; sourceTree = "<group>"; };
111B8BFF299BD973004A4129 /* WithdrawalConfirmationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WithdrawalConfirmationController.swift; sourceTree = "<group>"; };
111C3C4B296C51B500788E18 /* WithdrawalLimitsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WithdrawalLimitsController.swift; sourceTree = "<group>"; };
111C3C4D296C52F800788E18 /* WithdrawalLimit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WithdrawalLimit.swift; sourceTree = "<group>"; };
111C3C4F296D5A4700788E18 /* TxWithinTimePeriod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TxWithinTimePeriod.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3208,7 +3206,6 @@
1147687D294B789800FB1EEE /* CrowdNodePortalViewController.swift */,
111C3C4B296C51B500788E18 /* WithdrawalLimitsController.swift */,
11ED906A29681773003784F9 /* StakingInfoDialogController.swift */,
111B8BFF299BD973004A4129 /* WithdrawalConfirmationController.swift */,
);
path = Portal;
sourceTree = "<group>";
Expand Down Expand Up @@ -8447,7 +8444,7 @@
47AE8C1128C5430300490F5E /* PointOfUseInfoViewController.swift in Sources */,
47C661B428FDCF7800028A8D /* ActionButtonViewController.swift in Sources */,
4774DCE528F4668B008CF87D /* PortalServiceItemCell.swift in Sources */,
111B8C00299BD973004A4129 /* WithdrawalConfirmationController.swift in Sources */,
2A7AF35624824F97001D74F9 /* DWDPImageStatusCell.m in Sources */,
2A7A7BC92347E0D700451078 /* DWBaseFormTableViewCell.m in Sources */,
4751CAC7296FAEBB00F63AC4 /* AccountCell.swift in Sources */,
2A913EA823A79AD2006A2A59 /* DWTransactionListDataProviderStub.m in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@

import Moya

public enum MessageType: Int {
case registerEmail = 1
case withdrawal = 4
}

// MARK: - CrowdNodeEndpoint

Expand All @@ -27,7 +31,7 @@ public enum CrowdNodeEndpoint {
case isAddressInUse(String)
case addressStatus(String)
case hasDefaultEmail(String)
case sendSignedMessage(address: String, message: String, signature: String)
case sendSignedMessage(address: String, message: String, signature: String, messagetype: MessageType)
case getMessages(String)
}

Expand All @@ -47,8 +51,7 @@ extension CrowdNodeEndpoint: TargetType {
case .isAddressInUse(let address): return "odata/apiaddresses/IsApiAddressInUse(address='\(address)')"
case .addressStatus(let address): return "odata/apiaddresses/AddressStatus(address='\(address)')"
case .hasDefaultEmail(let address): return "odata/apiaddresses/UsingDefaultApiEmail(address='\(address)')"
case .sendSignedMessage(let address, let message,
let signature): return "odata/apimessages/SendMessage(address='\(address)',message='\(message)',signature='\(signature)',messagetype=1)"
case .sendSignedMessage(let address, let message, let signature, let messagetype): return "odata/apimessages/SendMessage(address='\(address)',message='\(message)',signature='\(signature)',messagetype=\(messagetype.rawValue))"
case .getMessages(let address): return "odata/apimessages/GetMessages(address='\(address)')"
}
}
Expand Down
42 changes: 13 additions & 29 deletions DashWallet/Sources/Models/CrowdNode/CrowdNode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -418,41 +418,25 @@ extension CrowdNode {
}
}

func withdraw(amount: UInt64) async throws {
func withdraw(amount: UInt64, signature: String) async throws {
guard !accountAddress.isEmpty else { return }
guard amount <= balance else { return }

try checkWithdrawalLimits(amount)

let requestPermil = calculateWithdrawalPermil(forAmount: amount)
let requestValue = CrowdNode.apiOffset + UInt64(requestPermil)
let requiredTopUp = requestValue + TX_FEE_PER_INPUT
let account = DWEnvironment.sharedInstance().currentAccount
let finalTopUp = min(account.maxOutputAmount, requiredTopUp)

let topUpTx = try await topUpAccount(accountAddress, finalTopUp)
DSLogger.log("CrowdNode withdraw topup tx hash: \(topUpTx.txHashHexString)")

let withdrawTx = try await sendCoinsService.sendCoins(address: CrowdNode.crowdNodeAddress,
amount: requestValue,
inputSelector: SingleInputAddressSelector(candidates: [topUpTx],
address: accountAddress))
DSLogger.log("CrowdNode withdraw tx hash: \(withdrawTx.txHashHexString)")

Task {
let receivedWithdrawalTx = CrowdNodeWithdrawalReceivedTx()
let errorResponse = CrowdNodeErrorResponse(errorValue: requestValue,
accountAddress: accountAddress)
let withdrawalDeniedResponse = CrowdNodeResponse(responseCode: ApiCode.withdrawalDenied,
accountAddress: accountAddress)

let responseTx = await txObserver.first(filters: errorResponse, withdrawalDeniedResponse, receivedWithdrawalTx)
DSLogger.log("CrowdNode withdraw response tx hash: \(responseTx.txHashHexString)")
DSLogger.log("CrowdNode: request withdrawal")
let result = try await webService.requestWithdrawal(address: accountAddress, amount: amount, signature: signature)

if errorResponse.matches(tx: responseTx) || withdrawalDeniedResponse.matches(tx: responseTx) {
handleError(error: CrowdNode.Error.withdraw)
if result.messageStatus.lowercased() == kMessageReceivedStatus {
DSLogger.log("CrowdNode: withdrawal request sent successfully")
refreshBalance(afterWithdrawal: true)
} else {
DSLogger.log("CrowdNode: sendMessage not received, status: \(String(describing: result.messageStatus)). Result: \(String(describing: result.result))")

if let msg = result.result {
handleError(error: CrowdNode.Error.messageStatus(error: msg))
} else {
refreshBalance(afterWithdrawal: true)
handleError(error: CrowdNode.Error.withdraw)
}
}
}
Expand Down Expand Up @@ -644,7 +628,7 @@ extension CrowdNode {
guard !accountAddress.isEmpty else { return }

DSLogger.log("CrowdNode: sending signed email message")
let result = try await webService.sendSignedMessage(address: accountAddress, message: email, signature: signature)
let result = try await webService.registerEmail(address: accountAddress, email: email, signature: signature)

if result.messageStatus.lowercased() == kMessageReceivedStatus {
DSLogger.log("CrowdNode: signed email sent successfully")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,14 @@ extension CrowdNodeService {
}
}

func sendSignedMessage(address: String, message: String, signature: String) async throws -> MessageStatus {
func registerEmail(address: String, email: String, signature: String) async throws -> MessageStatus {
let encodedSignature = signature.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)
return try await httpClient.request(.sendSignedMessage(address: address, message: message, signature: encodedSignature!))
return try await httpClient.request(.sendSignedMessage(address: address, message: email, signature: encodedSignature!, messagetype: MessageType.registerEmail))
}

func requestWithdrawal(address: String, amount: UInt64, signature: String) async throws -> MessageStatus {
let encodedSignature = signature.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)
return try await httpClient.request(.sendSignedMessage(address: address, message: amount.value, signature: encodedSignature!, messagetype: MessageType.withdrawal))
}

func isDefaultEmail(address: String) async -> Bool {
Expand Down
15 changes: 13 additions & 2 deletions DashWallet/Sources/UI/CrowdNode/CrowdNodeModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -284,8 +284,19 @@ extension CrowdNodeModel {

func withdraw(amount: UInt64) async throws -> Bool {
guard amount > 0 && walletBalance >= CrowdNode.minimumLeftoverBalance else { return false }
try await crowdNode.withdraw(amount: amount)
return true

let wallet = DWEnvironment.sharedInstance().currentWallet
let result = await wallet.seed(withPrompt: NSLocalizedString("Sign the message", comment: "CrowdNode"), forAmount: 1)

if !result.1 {
if let key = wallet.privateKey(forAddress: crowdNode.accountAddress, fromSeed: result.0!) {
let signature = DSKeyManager.signMesasageDigest(key, digest: amount.value.magicDigest())
try await crowdNode.withdraw(amount: amount, signature: signature.base64EncodedString())
return true
}
}

return false
}

func adjustedWithdrawalAmount(requestedAmount: UInt64) -> UInt64 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,27 +128,21 @@ final class CrowdNodeTransferController: SendAmountViewController, NetworkReacha
}

private func handleWithdraw(amount: UInt64) {
let vc = WithdrawalConfirmationController.controller(amount: amount, currency: model.localCurrencyCode)
vc.confirmedHandler = { [weak self] in
guard let wSelf = self else { return }

Task {
wSelf.showActivityIndicator()

do {
if try await wSelf.viewModel.withdraw(amount: amount) {
wSelf.showSuccessfulStatus()
}
} catch CrowdNode.Error.withdrawLimit(_, let period) {
wSelf.showWithdrawalLimitsError(period: period)
} catch {
wSelf.showErrorStatus(err: error)
showActivityIndicator()

Task {
do {
if try await viewModel.withdraw(amount: amount) {
showSuccessfulStatus()
}

wSelf.hideActivityIndicator()
} catch CrowdNode.Error.withdrawLimit(_, let period) {
showWithdrawalLimitsError(period: period)
} catch {
showErrorStatus(err: error)
}

hideActivityIndicator()
}
present(vc, animated: true, completion: nil)
}

deinit {
Expand Down
Loading