Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #400 from horizontalsystems/add_erc20_support
- Loading branch information
Showing
14 changed files
with
359 additions
and
161 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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
157
BankWallet/BankWallet/Core/Adapters/EthereumAdapter.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} | ||
|
||
} |
Oops, something went wrong.