Skip to content
This repository has been archived by the owner on May 10, 2024. It is now read-only.

Fix 5247: No rounding if users want to send/swap full token balance #5253

Merged
merged 2 commits into from
Apr 14, 2022
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
15 changes: 7 additions & 8 deletions BraveWallet/Crypto/BuySendSwap/SendTokenView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,13 @@ struct SendTokenView: View {

@Environment(\.presentationMode) @Binding private var presentationMode

@State private var amountInput = ""
@State private var isShowingScanner = false
@State private var isShowingError = false

@ScaledMetric private var length: CGFloat = 16.0

private var isSendDisabled: Bool {
guard let sendAmount = BDouble(amountInput),
guard let sendAmount = BDouble(sendTokenStore.sendAmount),
let balance = sendTokenStore.selectedSendTokenBalance,
let token = sendTokenStore.selectedSendToken,
!sendTokenStore.isMakingTx
Expand All @@ -31,13 +30,13 @@ struct SendTokenView: View {
}

let weiFormatter = WeiFormatter(decimalFormatStyle: .decimals(precision: Int(token.decimals)))
if weiFormatter.weiString(from: amountInput, radix: .decimal, decimals: Int(token.decimals)) == nil {
if weiFormatter.weiString(from: sendTokenStore.sendAmount, radix: .decimal, decimals: Int(token.decimals)) == nil {
return true
}

return sendAmount == 0
|| sendAmount > balance
|| amountInput.isEmpty
|| sendTokenStore.sendAmount.isEmpty
|| sendTokenStore.addressError != nil
}

Expand Down Expand Up @@ -73,7 +72,7 @@ struct SendTokenView: View {
.font(.title3.weight(.semibold))
.foregroundColor(Color(.braveLabel))
Spacer()
Text(String(format: "%.04f", sendTokenStore.selectedSendTokenBalance ?? 0))
Text(sendTokenStore.selectedSendTokenBalance?.decimalDescription ?? "0.0000")
.font(.title3.weight(.semibold))
.foregroundColor(Color(.braveLabel))
}
Expand All @@ -91,7 +90,7 @@ struct SendTokenView: View {
)
),
footer: ShortcutAmountGrid(action: { amount in
amountInput = "\((sendTokenStore.selectedSendTokenBalance ?? 0) * amount.rawValue)"
sendTokenStore.suggestedAmountTapped(amount)
})
.listRowInsets(.zero)
.padding(.bottom, 8)
Expand All @@ -100,7 +99,7 @@ struct SendTokenView: View {
String.localizedStringWithFormat(
Strings.Wallet.amountInCurrency,
sendTokenStore.selectedSendToken?.symbol ?? ""),
text: $amountInput
text: $sendTokenStore.sendAmount
)
.keyboardType(.decimalPad)
}
Expand Down Expand Up @@ -157,7 +156,7 @@ struct SendTokenView: View {
WalletLoadingButton(
isLoading: sendTokenStore.isMakingTx,
action: {
sendTokenStore.sendToken(amount: amountInput) { success in
sendTokenStore.sendToken(amount: sendTokenStore.sendAmount) { success in
isShowingError = !success
}
},
Expand Down
3 changes: 1 addition & 2 deletions BraveWallet/Crypto/BuySendSwap/SwapCryptoView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -256,8 +256,7 @@ struct SwapCryptoView: View {
),
footer: VStack(spacing: 20) {
ShortcutAmountGrid(action: { amount in
swapTokensStore.sellAmount = ((swapTokensStore.selectedFromTokenBalance ?? 0) * amount.rawValue)
.decimalExpansion(precisionAfterDecimalPoint: Int(swapTokensStore.selectedFromToken?.decimals ?? 18))
swapTokensStore.suggestedAmountTapped(amount)
})
Button(action: {
swapTokensStore.swapSelectedTokens()
Expand Down
77 changes: 21 additions & 56 deletions BraveWallet/Crypto/Stores/SendTokenStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import Foundation
import BraveCore
import Shared
import BigNumber

/// A store contains data for sending tokens
public class SendTokenStore: ObservableObject {
Expand All @@ -18,7 +19,7 @@ public class SendTokenStore: ObservableObject {
}
}
/// The current selected token balance. Default with nil value.
@Published var selectedSendTokenBalance: Double?
@Published var selectedSendTokenBalance: BDouble?
/// A boolean indicates if this store is making an unapproved tx
@Published var isMakingTx = false
/// The destination account address
Expand All @@ -34,6 +35,8 @@ public class SendTokenStore: ObservableObject {
}
/// An error for input send address. Nil for no error.
@Published var addressError: AddressError?
/// The amount the user inputs to send
@Published var sendAmount = ""

enum AddressError: LocalizedError {
case sameAsFromAddress
Expand Down Expand Up @@ -110,6 +113,16 @@ public class SendTokenStore: ObservableObject {
}
}
}

func suggestedAmountTapped(_ amount: ShortcutAmountGrid.Amount) {
var decimalPoint = 6
var rounded = true
if amount == .all {
decimalPoint = Int(selectedSendToken?.decimals ?? 18)
rounded = false
}
sendAmount = ((selectedSendTokenBalance ?? 0) * amount.rawValue).decimalExpansion(precisionAfterDecimalPoint: decimalPoint, rounded: rounded)
}

private func fetchAssetBalance() {
guard let token = selectedSendToken else {
Expand All @@ -134,61 +147,13 @@ public class SendTokenStore: ObservableObject {
self.selectedSendTokenBalance = nil
return
}

let balanceFormatter = WeiFormatter(decimalFormatStyle: .balance)
func updateBalance(_ status: BraveWallet.ProviderError, _ balance: String) {
guard status == .success,
let decimalString = balanceFormatter.decimalString(
for: balance.removingHexPrefix,
radix: .hex,
decimals: Int(token.decimals)
), !decimalString.isEmpty, let decimal = Double(decimalString)
else {
return
}
selectedSendTokenBalance = decimal
}

self.rpcService.network { network in
// Get balance for ETH token
if token.symbol == network.symbol {
self.rpcService.balance(accountAddress, coin: .eth, chainId: network.chainId) { balance, status, _ in
guard status == .success else {
self.selectedSendTokenBalance = nil
return
}
updateBalance(status, balance)
}
}
// Get balance for erc20 token
else if token.isErc20 {
self.rpcService.erc20TokenBalance(
token.contractAddress,
address: accountAddress,
chainId: network.chainId
) { balance, status, _ in
guard status == .success else {
self.selectedSendTokenBalance = nil
return
}
updateBalance(status, balance)
}
}
// Get balance for erc721 token
else if token.isErc721 {
self.rpcService.erc721TokenBalance(
token.contractAddress,
tokenId: token.id,
accountAddress: accountAddress,
chainId: network.chainId
) { balance, status, _ in
guard status == .success else {
self.selectedSendTokenBalance = nil
return
}
updateBalance(status, balance)
}
}

rpcService.balance(
for: token,
in: accountAddress,
decimalFormatStyle: .decimals(precision: Int(token.decimals))
) { [weak self] balance in
self?.selectedSendTokenBalance = balance
}
}
}
Expand Down
43 changes: 31 additions & 12 deletions BraveWallet/Crypto/Stores/SwapTokenStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -176,9 +176,13 @@ public class SwapTokenStore: ObservableObject {
completion(nil)
return
}

rpcService.balance(for: token, in: account) { balance in
completion(balance.map { BDouble($0) })

rpcService.balance(
for: token,
in: account.address,
decimalFormatStyle: .decimals(precision: Int(token.decimals))
) { balance in
completion(balance)
}
}

Expand Down Expand Up @@ -356,13 +360,13 @@ public class SwapTokenStore: ObservableObject {
checkBalanceShowError(swapResponse: response)
}

private func createERC20SwapTransaction(_ spenderAddress: String) {
private func createERC20ApprovalTransaction(_ spenderAddress: String) {
let weiFormatter = WeiFormatter(decimalFormatStyle: .decimals(precision: 18))
guard
let fromToken = selectedFromToken,
let accountInfo = accountInfo,
let balanceInWeiHex = weiFormatter.weiString(
from: selectedFromTokenBalance?.decimalDescription ?? "0",
from: selectedFromTokenBalance?.decimalExpansion(precisionAfterDecimalPoint: Int(fromToken.decimals), rounded: false) ?? "0",
radix: .hex,
decimals: Int(fromToken.decimals)
)
Expand Down Expand Up @@ -517,7 +521,7 @@ public class SwapTokenStore: ObservableObject {
ownerAddress: ownerAddress,
spenderAddress: spenderAddress
) { allowance, status, _ in
let weiFormatter = WeiFormatter(decimalFormatStyle: .decimals(precision: 18))
let weiFormatter = WeiFormatter(decimalFormatStyle: .decimals(precision: Int(fromToken.decimals)))
let allowanceValue = BDouble(weiFormatter.decimalString(for: allowance.removingHexPrefix, radix: .hex, decimals: Int(fromToken.decimals)) ?? "") ?? 0
guard status == .success, amountToSend > allowanceValue else { return } // no problem with its allowance
self?.state = .lowAllowance(spenderAddress)
Expand Down Expand Up @@ -546,7 +550,7 @@ public class SwapTokenStore: ObservableObject {
// will never come here
break
case .lowAllowance(let spenderAddress):
createERC20SwapTransaction(spenderAddress)
createERC20ApprovalTransaction(spenderAddress)
case .swap:
createETHSwapTransaction()
}
Expand Down Expand Up @@ -592,17 +596,16 @@ public class SwapTokenStore: ObservableObject {

func updateSelectedTokens(in network: BraveWallet.NetworkInfo) {
if let fromToken = selectedFromToken { // refresh balance
rpcService.balance(for: fromToken, in: accountInfo) { [weak self] balance in
self?.selectedFromTokenBalance = BDouble(balance ?? 0)
fetchTokenBalance(for: fromToken) { [weak self] balance in
self?.selectedFromTokenBalance = balance ?? 0
}
} else {
selectedFromToken = allTokens.first(where: { $0.symbol == network.symbol })
}

if let toToken = selectedToToken {
rpcService.balance(for: toToken, in: accountInfo) { [weak self] balance in
self?.selectedToTokenBalance = BDouble(balance ?? 0)
completion?()
fetchTokenBalance(for: toToken) { [weak self] balance in
self?.selectedToTokenBalance = balance ?? 0
}
} else {
if network.chainId == BraveWallet.MainnetChainId {
Expand Down Expand Up @@ -651,6 +654,17 @@ public class SwapTokenStore: ObservableObject {
}
}
}

func suggestedAmountTapped(_ amount: ShortcutAmountGrid.Amount) {
var decimalPoint = 6
var rounded = true
if amount == .all {
decimalPoint = Int(selectedFromToken?.decimals ?? 18)
rounded = false
}
sellAmount = ((selectedFromTokenBalance ?? 0) * amount.rawValue)
.decimalExpansion(precisionAfterDecimalPoint: decimalPoint, rounded: rounded)
}

#if DEBUG
func setUpTest() {
Expand All @@ -660,6 +674,11 @@ public class SwapTokenStore: ObservableObject {
sellAmount = "0.01"
selectedFromTokenBalance = 0.02
}

func setUpTestForRounding() {
accountInfo = .init()
selectedFromToken = .previewToken
}
#endif
}

Expand Down
51 changes: 51 additions & 0 deletions BraveWallet/Extensions/RpcServiceExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import Foundation
import BraveCore
import BigNumber

extension BraveWalletJsonRpcService {
/// Obtain the decimal balance of an `BlockchainToken` for a given account
Expand Down Expand Up @@ -54,4 +55,54 @@ extension BraveWalletJsonRpcService {
}
}
}

/// Obtain the decimal balance in `BDouble` of an `BlockchainToken` for a given account
/// with certain decimal format style
///
/// If the call fails for some reason or the resulting wei cannot be converted,
/// `completion` will be called with `nil`
func balance(
for token: BraveWallet.BlockchainToken,
in accountAddress: String,
decimalFormatStyle: WeiFormatter.DecimalFormatStyle,
completion: @escaping (BDouble?) -> Void
) {
let convert: (String, BraveWallet.ProviderError, String) -> Void = { wei, status, _ in
guard status == .success && !wei.isEmpty else {
completion(nil)
return
}
let formatter = WeiFormatter(decimalFormatStyle: decimalFormatStyle)
if let valueString = formatter.decimalString(
for: wei.removingHexPrefix,
radix: .hex,
decimals: Int(token.decimals)
) {
completion(BDouble(valueString))
} else {
completion(nil)
}
}
network { [self] network in
if token.symbol == network.symbol {
balance(accountAddress, coin: .eth, chainId: network.chainId, completion: convert)
} else if token.isErc20 {
erc20TokenBalance(
token.contractAddress(in: network),
address: accountAddress,
chainId: network.chainId,
completion: convert
)
} else if token.isErc721 {
erc721TokenBalance(
token.contractAddress,
tokenId: token.tokenId,
accountAddress: accountAddress,
chainId: network.chainId,
completion: convert)
} else {
completion(nil)
}
}
}
}
Loading