Skip to content

Commit

Permalink
Merge pull request #400 from horizontalsystems/add_erc20_support
Browse files Browse the repository at this point in the history
Add erc20 token support using coin manager. Step #1 (#141)
  • Loading branch information
ealymbaev committed Feb 4, 2019
2 parents b4fc385 + 06c023c commit 380fc99
Show file tree
Hide file tree
Showing 14 changed files with 359 additions and 161 deletions.
36 changes: 30 additions & 6 deletions BankWallet/BankWallet.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions BankWallet/BankWallet/Core/Adapters/BitcoinAdapter.swift
Expand Up @@ -3,6 +3,8 @@ import HSBitcoinKit
import RxSwift

class BitcoinAdapter {
let decimal = 8

private let bitcoinKit: BitcoinKit
private let transactionCompletionThreshold = 6
private let coinRate: Decimal = pow(10, 8)
Expand Down Expand Up @@ -73,6 +75,9 @@ extension BitcoinAdapter: IAdapter {
try? bitcoinKit.start()
}

func stop() {
}

func refresh() {
// not called
}
Expand Down
59 changes: 59 additions & 0 deletions BankWallet/BankWallet/Core/Adapters/Erc20Adapter.swift
@@ -0,0 +1,59 @@
import HSEthereumKit
import RxSwift

class Erc20Adapter: EthereumBaseAdapter {
let contractAddress: String

init(ethereumKit: EthereumKit, contractAddress: String, decimal: Int) {
self.contractAddress = contractAddress

super.init(ethereumKit: ethereumKit, decimal: decimal)

ethereumKit.register(token: self)
}

override func transactionsObservable(hashFrom: String?, limit: Int) -> Single<[EthereumTransaction]> {
return ethereumKit.erc20Transactions(contractAddress: contractAddress, fromHash: hashFrom, limit: limit)
}

}

extension Erc20Adapter: IAdapter {

func stop() {
ethereumKit.unregister(contractAddress: contractAddress)
}

var balance: Decimal {
return ethereumKit.erc20Balance(contractAddress: contractAddress)
}

func refresh() {
ethereumKit.erc20Refresh(contractAddress: contractAddress)
}

func send(to address: String, value: Decimal, completion: ((Error?) -> ())?) {
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)
}
return fee
}

}

extension Erc20Adapter: Erc20KitDelegate {

}

extension Erc20Adapter {

static func adapter(ethereumKit: EthereumKit, contractAddress: String, decimal: Int) -> Erc20Adapter {
return Erc20Adapter(ethereumKit: ethereumKit, contractAddress: contractAddress, decimal: decimal)
}

}
157 changes: 13 additions & 144 deletions BankWallet/BankWallet/Core/Adapters/EthereumAdapter.swift
@@ -1,186 +1,55 @@
import HSEthereumKit
import RxSwift

class EthereumAdapter {
private let ethereumKit: EthereumKit
private let transactionCompletionThreshold = 12
private let coinRate: Decimal = pow(10, 18)
private let gWeiMultiply: Decimal = pow(10, 9)
class EthereumAdapter: EthereumBaseAdapter {

let lastBlockHeightUpdatedSignal = Signal()
let transactionRecordsSubject = PublishSubject<[TransactionRecord]>()
init(ethereumKit: EthereumKit) {
super.init(ethereumKit: ethereumKit, decimal: 18)

private(set) var state: AdapterState = .syncing(progressSubject: nil)

let balanceUpdatedSignal = Signal()
let stateUpdatedSignal = Signal()

init(words: [String], coin: EthereumKit.Coin) {
let infuraKey = Bundle.main.object(forInfoDictionaryKey: "InfuraApiKey") as? String
let etherscanKey = Bundle.main.object(forInfoDictionaryKey: "EtherscanApiKey") as? String

ethereumKit = EthereumKit(withWords: words, coin: coin, infuraKey: infuraKey ?? "", etherscanKey: etherscanKey ?? "", debugPrints: false)
ethereumKit.delegate = self
}

private func transactionRecord(fromTransaction transaction: EthereumTransaction) -> TransactionRecord {
let amountEther = convertToValue(amount: transaction.value) ?? 0
let mineAddress = ethereumKit.receiveAddress.lowercased()

let from = TransactionAddress(
address: transaction.from,
mine: transaction.from.lowercased() == mineAddress
)

let to = TransactionAddress(
address: transaction.to,
mine: transaction.to.lowercased() == mineAddress
)

return TransactionRecord(
transactionHash: transaction.txHash,
blockHeight: transaction.blockNumber,
amount: amountEther * (from.mine ? -1 : 1),
timestamp: Double(transaction.timestamp),
from: [from],
to: [to]
)
}

private func convertToValue(amount: String) -> Decimal? {
if let result = Decimal(string: amount) {
return result / pow(10, 18)
}
return nil
override func transactionsObservable(hashFrom: String?, limit: Int) -> Single<[EthereumTransaction]> {
return ethereumKit.transactions(fromHash: hashFrom, limit: limit)
}

}

extension EthereumAdapter: IAdapter {

var balance: Decimal {
return Decimal(Double(ethereumKit.balance)) / coinRate
}

var confirmationsThreshold: Int {
return 12
func stop() {
}

var lastBlockHeight: Int? {
return ethereumKit.lastBlockHeight
}

var debugInfo: String {
return ethereumKit.debugInfo
}

var refreshable: Bool {
return true
}

func start() {
ethereumKit.start()
var balance: Decimal {
return ethereumKit.balance
}

func refresh() {
ethereumKit.refresh()
}

func clear() {
try? ethereumKit.clear()
}

func send(to address: String, value: Decimal, completion: ((Error?) -> ())?) {
ethereumKit.send(to: address, value: NSDecimalNumber(decimal: value).doubleValue, completion: completion)
ethereumKit.send(to: address, value: value, gasPrice: nil, completion: completion)
}

func fee(for value: Decimal, address: String?, senderPay: Bool) throws -> Decimal {
// ethereum fee comes in GWei integer value

let fee = Decimal(ethereumKit.fee) * gWeiMultiply / coinRate
let balance = Decimal(Double(ethereumKit.balance)) / coinRate
if balance > 0, balance - value - (senderPay ? fee : 0) < 0 {
let fee = ethereumKit.fee
if balance > 0, balance - value - fee < 0 {
throw FeeError.insufficientAmount(fee: fee)
}
return fee
}

func validate(address: String) throws {
try ethereumKit.validate(address: address)
}

func parse(paymentAddress: String) -> PaymentRequestAddress {
return PaymentRequestAddress(address: paymentAddress)
}

var receiveAddress: String {
return ethereumKit.receiveAddress
}

func transactionsSingle(hashFrom: String?, limit: Int) -> Single<[TransactionRecord]> {
return ethereumKit.transactions(fromHash: hashFrom, limit: limit)
.map { [weak self] transactions -> [TransactionRecord] in
return transactions.compactMap {
self?.transactionRecord(fromTransaction: $0)
}
}
}

}

extension EthereumAdapter: EthereumKitDelegate {

public func transactionsUpdated(ethereumKit: EthereumKit, inserted: [EthereumTransaction], updated: [EthereumTransaction], deleted: [Int]) {
var records = [TransactionRecord]()

for info in inserted {
records.append(transactionRecord(fromTransaction: info))
}
for info in updated {
records.append(transactionRecord(fromTransaction: info))
}

transactionRecordsSubject.onNext(records)
}

public func balanceUpdated(ethereumKit: EthereumKit, balance: BInt) {
balanceUpdatedSignal.notify()
}

public func kitStateUpdated(state: EthereumKit.KitState) {
switch state {
case .synced:
if case .synced = self.state {
// do nothing
} else {
self.state = .synced
stateUpdatedSignal.notify()
lastBlockHeightUpdatedSignal.notify()
}
case .notSynced:
if case .notSynced = self.state {
// do nothing
} else {
self.state = .notSynced
stateUpdatedSignal.notify()
}
case .syncing:
if case .syncing = self.state {
// do nothing
} else {
self.state = .syncing(progressSubject: nil)
stateUpdatedSignal.notify()
}
}
}

}

extension EthereumAdapter {

static func ethereumAdapter(words: [String], testMode: Bool) -> EthereumAdapter {
let network: EthereumKit.NetworkType = testMode ? .testNet : .mainNet
return EthereumAdapter(words: words, coin: .ethereum(network: network))
static func ethereumAdapter(ethereumKit: EthereumKit) -> EthereumAdapter {
return EthereumAdapter(ethereumKit: ethereumKit)
}

}

0 comments on commit 380fc99

Please sign in to comment.