Skip to content

Commit

Permalink
ERC20 tokens support (#141)
Browse files Browse the repository at this point in the history
- implement available balance in adapters
- add fee error for ERC20
  • Loading branch information
mNizhurin committed Feb 7, 2019
1 parent 62418ac commit 0ad12f8
Show file tree
Hide file tree
Showing 8 changed files with 162 additions and 135 deletions.
19 changes: 17 additions & 2 deletions BankWallet/BankWallet/Core/Adapters/BitcoinAdapter.swift
Expand Up @@ -4,6 +4,7 @@ import RxSwift

class BitcoinAdapter {
let decimal = 8
let feeCoinCode: CoinCode? = nil

let coin: Coin

Expand Down Expand Up @@ -110,13 +111,19 @@ extension BitcoinAdapter: IAdapter {
}
}

func fee(for value: Decimal, address: String?, senderPay: Bool) throws -> Decimal {
func availableBalance(for address: String?) -> Decimal {
return max(0, balance - fee(for: balance, address: address, senderPay: true))
}

func fee(for value: Decimal, address: String?, senderPay: Bool) -> Decimal {
do {
let amount = convertToSatoshi(value: value)
let fee = try bitcoinKit.fee(for: amount, toAddress: address, senderPay: senderPay)
return Decimal(fee) / coinRate
} catch SelectorError.notEnough(let maxFee) {
throw FeeError.insufficientAmount(fee: Decimal(maxFee) / coinRate)
return Decimal(maxFee) / coinRate
} catch {
return 0
}
}

Expand All @@ -129,6 +136,14 @@ extension BitcoinAdapter: IAdapter {
try bitcoinKit.validate(address: address)
}

func validate(amount: Decimal, address: String?, senderPay: Bool) -> [SendStateError] {
var errors = [SendStateError]()
if amount > availableBalance(for: address) {
errors.append(.insufficientAmount)
}
return errors
}

func parse(paymentAddress: String) -> PaymentRequestAddress {
let paymentData = bitcoinKit.parse(paymentAddress: paymentAddress)
return PaymentRequestAddress(address: paymentData.address, amount: paymentData.amount.map { Decimal($0) })
Expand Down
22 changes: 17 additions & 5 deletions BankWallet/BankWallet/Core/Adapters/Erc20Adapter.swift
Expand Up @@ -3,6 +3,7 @@ import RxSwift

class Erc20Adapter: EthereumBaseAdapter {
let contractAddress: String
let feeCoinCode: CoinCode? = "ETH"

init(coin: Coin, ethereumKit: EthereumKit, contractAddress: String, decimal: Int) {
self.contractAddress = contractAddress
Expand Down Expand Up @@ -36,12 +37,23 @@ extension Erc20Adapter: IAdapter {
ethereumKit.erc20Send(to: address, contractAddress: contractAddress, value: value, gasPrice: nil, completion: completion)
}

func fee(for value: Decimal, address: String?, senderPay: Bool) throws -> Decimal {
let fee = ethereumKit.erc20Fee
if ethereumKit.balance > 0, ethereumKit.balance - fee < 0 {
throw FeeError.insufficientAmount(fee: fee)
func availableBalance(for address: String?) -> Decimal {
return balance
}

func fee(for value: Decimal, address: String?, senderPay: Bool) -> Decimal {
return ethereumKit.erc20Fee
}

func validate(amount: Decimal, address: String?, senderPay: Bool) -> [SendStateError] {
var errors = [SendStateError]()
if amount > availableBalance(for: address) {
errors.append(.insufficientAmount)
}
if ethereumKit.balance < fee(for: amount, address: address, senderPay: senderPay) {
errors.append(.insufficientFeeBalance)
}
return fee
return errors
}

}
Expand Down
19 changes: 14 additions & 5 deletions BankWallet/BankWallet/Core/Adapters/EthereumAdapter.swift
Expand Up @@ -2,6 +2,7 @@ import HSEthereumKit
import RxSwift

class EthereumAdapter: EthereumBaseAdapter {
let feeCoinCode: CoinCode? = nil

init(coin: Coin, ethereumKit: EthereumKit) {
super.init(coin: coin, ethereumKit: ethereumKit, decimal: 18)
Expand Down Expand Up @@ -32,12 +33,20 @@ extension EthereumAdapter: IAdapter {
ethereumKit.send(to: address, value: value, gasPrice: nil, completion: completion)
}

func fee(for value: Decimal, address: String?, senderPay: Bool) throws -> Decimal {
let fee = ethereumKit.fee
if balance > 0, balance - value - fee < 0 {
throw FeeError.insufficientAmount(fee: fee)
func availableBalance(for address: String?) -> Decimal {
return max(0, balance - fee(for: balance, address: address, senderPay: true))
}

func fee(for value: Decimal, address: String?, senderPay: Bool) -> Decimal {
return ethereumKit.fee
}

func validate(amount: Decimal, address: String?, senderPay: Bool) -> [SendStateError] {
var errors = [SendStateError]()
if amount > availableBalance(for: address) {
errors.append(.insufficientAmount)
}
return fee
return errors
}

}
Expand Down
7 changes: 3 additions & 4 deletions BankWallet/BankWallet/Core/Managers/AppConfigProvider.swift
Expand Up @@ -47,11 +47,10 @@ class AppConfigProvider: IAppConfigProvider {
]

var defaultCoins: [Coin] {
let suffix = testMode ? "t" : ""
return [
Coin(title: "Bitcoin", code: "BTC\(suffix)", type: .bitcoin),
Coin(title: "Bitcoin Cash", code: "BCH\(suffix)", type: .bitcoinCash),
Coin(title: "Ethereum", code: "ETH\(suffix)", type: .ethereum),
Coin(title: "Bitcoin", code: "BTC", type: .bitcoin),
Coin(title: "Bitcoin Cash", code: "BCH", type: .bitcoinCash),
Coin(title: "Ethereum", code: "ETH", type: .ethereum),
]
}

Expand Down
9 changes: 4 additions & 5 deletions BankWallet/BankWallet/Core/Protocols.swift
Expand Up @@ -77,12 +77,9 @@ enum AdapterState {
case notSynced
}

enum FeeError: Error {
case insufficientAmount(fee: Decimal)
}

protocol IAdapter: class {
var coin: Coin { get }
var feeCoinCode: CoinCode? { get }

var decimal: Int { get }
var balance: Decimal { get }
Expand Down Expand Up @@ -111,8 +108,10 @@ protocol IAdapter: class {

func send(to address: String, value: Decimal, completion: ((Error?) -> ())?)

func fee(for value: Decimal, address: String?, senderPay: Bool) throws -> Decimal
func availableBalance(for address: String?) -> Decimal
func fee(for value: Decimal, address: String?, senderPay: Bool) -> Decimal
func validate(address: String) throws
func validate(amount: Decimal, address: String?, senderPay: Bool) -> [SendStateError]
func parse(paymentAddress: String) -> PaymentRequestAddress

var receiveAddress: String { get }
Expand Down
45 changes: 15 additions & 30 deletions BankWallet/BankWallet/Modules/Send/SendInteractor.swift
Expand Up @@ -85,60 +85,45 @@ extension SendInteractor: ISendInteractor {
}
}

var feeValue: Decimal?
if let coinValue = sendState.coinValue {
do {
let value = try adapter.fee(for: coinValue.value, address: input.address, senderPay: true)
feeValue = value
} catch FeeError.insufficientAmount(let fee) {
feeValue = fee
sendState.amountError = createAmountError(forInput: input, fee: fee)
} catch {
print("unhandled error: \(error)")
let errors = adapter.validate(amount: sendState.coinValue?.value ?? 0, address: input.address, senderPay: true)
errors.forEach {
switch($0) {
case .insufficientAmount: sendState.amountError = createAmountError(forInput: input)
case .insufficientFeeBalance: break//sendState.feeError = createAmountError(forInput: input)
}
}
if let feeValue = feeValue {
if let coinValue = sendState.coinValue {
let feeValue = adapter.fee(for: coinValue.value, address: input.address, senderPay: true)
sendState.feeCoinValue = CoinValue(coinCode: coinCode, value: feeValue)
}

if let rateValue = state.rateValue, let feeCoinValue = sendState.feeCoinValue {
sendState.feeCurrencyValue = CurrencyValue(currency: baseCurrency, value: rateValue * feeCoinValue.value)
}

return sendState
}

private func createAmountError(forInput input: SendUserInput, fee: Decimal) -> AmountError? {
var balanceMinusFee = state.adapter.balance - fee
if balanceMinusFee < 0 {
balanceMinusFee = 0
}
private func createAmountError(forInput input: SendUserInput) -> AmountError? {
let availableBalance = state.adapter.availableBalance(for: input.address)
switch input.inputType {
case .coin:
return AmountError.insufficientAmount(amountInfo: .coinValue(coinValue: CoinValue(coinCode: coin.code, value: balanceMinusFee)))
return .insufficientAmount(amountInfo: .coinValue(coinValue: CoinValue(coinCode: coin.code, value: availableBalance)))
case .currency:
return state.rateValue.map {
let currencyBalanceMinusFee = balanceMinusFee * $0
return AmountError.insufficientAmount(amountInfo: .currencyValue(currencyValue: CurrencyValue(currency: currencyManager.baseCurrency, value: currencyBalanceMinusFee)))
let currencyBalanceMinusFee = availableBalance * $0
return .insufficientAmount(amountInfo: .currencyValue(currencyValue: CurrencyValue(currency: currencyManager.baseCurrency, value: currencyBalanceMinusFee)))
}
}
}

func totalBalanceMinusFee(forInputType input: SendInputType, address: String?) -> Decimal {
var fee: Decimal
do {
fee = try state.adapter.fee(for: state.adapter.balance, address: address, senderPay: false)
} catch {
print(error)
return 0
}
let balanceMinusFee = state.adapter.balance - fee
let availableBalance = state.adapter.availableBalance(for: address)
switch input {
case .coin:
return balanceMinusFee
return availableBalance
case .currency:
return state.rateValue.map {
return balanceMinusFee * $0
return availableBalance * $0
} ?? 0
}
}
Expand Down
6 changes: 6 additions & 0 deletions BankWallet/BankWallet/Modules/Send/SendModule.swift
Expand Up @@ -74,6 +74,11 @@ enum SendInputType: String {
case currency = "currency"
}

enum SendStateError {
case insufficientAmount
case insufficientFeeBalance
}

enum AmountError: Error {
case insufficientAmount(amountInfo: AmountInfo)
}
Expand Down Expand Up @@ -118,6 +123,7 @@ class SendState {
var coinValue: CoinValue?
var currencyValue: CurrencyValue?
var amountError: AmountError?
var feeError: AmountError?
var address: String?
var addressError: AddressError?
var feeCoinValue: CoinValue?
Expand Down

0 comments on commit 0ad12f8

Please sign in to comment.