Files
raspberry/iOS/Wallet/Sources/Resources/Service/Setup/ResourcesServiceSetupRAM.swift
2022-12-21 20:26:26 +03:00

276 lines
9.5 KiB
Swift

//
// ResourcesServiceSetupRAM.swift
// Wallet
//
// Created by Igor Danich on 19.08.2020.
// Copyright © 2020 List. All rights reserved.
//
import Foundation
import Alamofire
import BigInt
extension Resources.Service.Setup {
class RAM: Common.Service.Provider {
var didUpdate: (() -> Void)?
var didReload: (() -> Void)?
var title: String { Resources.Model.Performance.Kind.ram.title }
enum Action: String, CommonMenuMappable {
case buy
case sell
var title: String { "resources.setup.action.\(rawValue)".localized }
func toMenu() -> Common.Model.Menu { .menu(uuid: rawValue, title: title) }
}
enum ResourcesSetupError: Error {
case dataError
case other(Error)
}
private enum Locals {
static let minimumRamAmount: Decimal = 0.02
}
let actions: [Action] = [.buy, .sell]
private var _action: Action?
var action: Action? {
get { _action }
set { _action = newValue;clear() }
}
enum Buy: String, CommonMenuMappable {
case eos = "resources.setup.buy.eos"
case kb = "resources.setup.buy.kb"
func toMenu() -> Common.Model.Menu { .menu(uuid: rawValue, title: rawValue.localized) }
}
let buys: [Buy] = [.eos, .kb]
private var _buy = Buy.kb
var buy: Buy {
get { _buy }
set {
_buy = newValue
_ramAmount = ""
update()
}
}
var token: Network.Model.Token? {
didSet {
amountSend = ""
amountRecieve = ""
balance = token?.amount
update()
}
}
var amountSend: String = ""
var amountRecieve: String = ""
var ramPrices: [RAMPrice] = []
var balances: [Network.Model.Token] { Wallet.Service.Tokens.shared.balances.collection }
var balance: Decimal?
var balanceText: String {
Wallet.Service.Tokens.shared.balances.collection.first(where: {
$0.contract == ApplicationEnvironment.shared().current.contract(.eosioToken) && $0.symbol.uppercased() == "EOS"
})?.description ?? ""
}
private var _isOtherEnabled = false
var isOtherEnabled: Bool {
get { _isOtherEnabled }
set {
_isOtherEnabled = newValue
_buy = .kb
_ramAmount = ""
update()
}
}
private var _otherAccount = ""
var otherAccount: String {
get { _otherAccount }
set { _otherAccount = newValue;update() }
}
private(set) var isValidAmount = false
private(set) var isValid = false
private var _ramAmount = ""
var ramAmount: String {
get { _ramAmount }
set { _ramAmount = newValue;update() }
}
private(set) var submitText = ""
var ramPrecision: Int { action == nil ? 0 : (action == .buy ? (buy == .eos ? 4 : 2) : 2) }
let performance: Resources.Service.Performance
init(account: String) {
performance = .init(account: account)
}
func fetch(completion: @escaping (ResourcesSetupError?) -> Void) {
performance.fetch { [weak self] _ in
self?.getRamPrice { error in
self?.didReload?()
self?.update()
completion(error)
}
}
}
func clear(force: Bool = false) {
if force {
_action = nil
performance.clear()
}
_isOtherEnabled = false
_otherAccount = ""
_ramAmount = ""
_buy = .kb
isValid = false
submitText = ""
token = nil
didReload?()
update()
}
private func update() {
submitText = ""
isValid = false
isValidAmount = false
switch action {
case .buy:
submitText = L10n.Resources.Setup.Submit.buy(amountRecieve.toDecimal().toString(max: 2, symbol: L10n.Resources.Performance.Unit.kb))
if let balance = balance {
var isValidTokenInput = 0..<balance ~= self.amountSend.toDecimal()
self.isValidAmount = isValidTokenInput
if self.isOtherEnabled {
isValidTokenInput = isValidTokenInput && self.otherAccount != ""
}
self.isValid = isValidTokenInput && self.amountRecieve.toDecimal() > 0
}
case .sell:
self.submitText = L10n.Resources.Setup.Submit.sell(self.ramAmount.toDecimal().toString(max: 2, symbol: L10n.Resources.Performance.Unit.kb))
self.isValid = self.ramAmount.toDecimal() >= Locals.minimumRamAmount
&& Int(self.ramAmount.toDouble()*1024) <= self.performance[.ram].available
case .none:
break
}
didUpdate?()
}
func update(receive: String) {
amountRecieve = receive
guard let selectedToken = self.token,
let ramPrice = ramPrices.first(where: { $0.token == selectedToken.symbol }),
let price = ramPrice.price.toOptionalDecimal(), price > 0 else {
return
}
amountSend = (receive.toDecimal() * price * 1024 /*bytes in kb*/)
.toReadebleString(precisionMax: ramPrice.precision)
update()
}
func update(send: String) {
amountSend = send
guard let selectedToken = self.token,
let ramPrice = ramPrices.first(where: { $0.token == selectedToken.symbol }),
let price = ramPrice.price.toOptionalDecimal(), price > 0 else {
return
}
amountRecieve = (send.toDecimal() / price / 1024 /*bytes in kb*/)
.toReadebleString(precisionMax: 2)
update()
}
func getRamPrice(completion: @escaping (ResourcesSetupError?) -> Void) {
let environment = ApplicationEnvironment.shared().current
AF.request(environment.backend.urlPath + "/ram", headers: HTTPHeaders(environment.headers))
.response { (data) in
guard let data = data.data else {
completion(ResourcesSetupError.dataError)
return
}
do {
self.ramPrices = try JSONDecoder().decode([RAMPrice].self, from: data)
completion(nil)
} catch {
completion(ResourcesSetupError.other(error))
}
}
}
func submit(privateKey: String, _ completion: @escaping Completion.Network) {
guard let action = action, isValid else { return }
switch action {
case .buy:
guard let username = Accounts().current?.name,
let token = token else { return }
let data = Network.Model.Blockchain.Transfer(
from: username,
to: ApplicationEnvironment.shared().current.contract(.malinkaReceiver),
quantity: amountSend.toDecimal().toCanonicalString(precision: token.precision, symbol: token.symbol),
memo: "buyram:\(isOtherEnabled ? otherAccount : username)"
)
Network.Service.Blockchain.execute(
contract: token.contract,
action: .transfer,
data: data,
privateKeys: [privateKey]
) { (result) in
completion(.error(result))
}
case .sell:
Network.Service.Blockchain.execute(contract: .eosio, action: .sellram, data: [
"account" : performance.account,
"bytes" : "\(Int(ramAmount.toDouble()*1024))"
], privateKeys: [privateKey]) { (result) in
completion(.error(result))
}
}
}
func getPricePerKilobyte(kbs: Decimal) async -> Decimal? {
let rammarket = await Network.table.fetch(
code: ApplicationEnvironment.shared().current.contract(.eosio),
table: "rammarket",
scope: ApplicationEnvironment.shared().current.contract(.eosio),
limit: 1,
type: RAMMarketPrices.self
)
let eosBalance = rammarket.first?.quote.balance.amount.toDecimal() ?? 0
let ramBalance = rammarket.first?.base.balance.amount.toDecimal() ?? 0
return ramBalance > 0 ? eosBalance / ramBalance /* / 1024 */ : 0
}
private struct RAMMarketPrices: Decodable {
let quote: RAMMarketPrice
let base: RAMMarketPrice
struct RAMMarketPrice: Decodable {
let balance: String
}
}
struct RAMPrice: Decodable {
let code: String
let token: String
let precision: Int
let price: String
}
}
}