// // SwapServiceMain.swift // Wallet // // Created by Saveliy Stavitsky on 3/18/21. // Copyright © 2021 AM. All rights reserved. // import Foundation import SwiftGraph import OrderedCollections extension Array where Element == Swap.Model.Pool { func process(startToken: Network.Model.Token?, amount: Decimal, forward: Bool, reverse: Bool) -> Decimal { var amount = amount var token = startToken?.symbol//forward ? tokenGive : tokenGet for pool in forward ? self : self.reversed() { if pool.token1Light?.symbol == token { amount = reverse ? pool.processReceive(amount: amount, forward: true) : pool.processSend(amount: amount, forward: true) if amount <= 0 { return 0 } token = pool.token2Light?.symbol } else { amount = reverse ? pool.processReceive(amount: amount, forward: false) : pool.processSend(amount: amount, forward: false) if amount <= 0 { return 0 } token = pool.token1Light?.symbol } } return amount } } extension Swap.Service { final class Pools { var didUpdate: (() -> Void)? var balances: [Network.Model.Token] { Wallet.Service.Tokens.shared.balances.collection.filter({ $0.swappableContracts == true }) } private(set) var collection = [Swap.Model.Pool]() { didSet { if let preselectTokenGive = preselectTokenGive { if collection.reduce([], { $0 + [$1.token1, $1.token2] }).compactMap({ $0 }).contains(preselectTokenGive) { tokenGive = preselectTokenGive } else { showPreselectError = true didUpdate?() } } graph = UnweightedUniqueElementsGraph() for swap in collection.filter({ $0.token1 != nil && $0.token2 != nil }) { graph.addVertex(swap.token1!) graph.addVertex(swap.token2!) graph.addEdge(from: swap.token1!, to: swap.token2!) } } } var preselectTokenGive: Network.Model.Token? var showPreselectError: Bool = false var tokenGive: Network.Model.Token? { didSet { amountSend = "" amountReceive = "" amountMinimum = "" allRouts = calcAllRouts() if let route = allRouts.first { selectedBestRoute = route } } } var tokenGet: Network.Model.Token? { didSet { amountSend = "" amountReceive = "" amountMinimum = "" allRouts = calcAllRouts() if let route = allRouts.first { selectedBestRoute = route } } } var tokensFrom: [Network.Model.Token] { graph.vertices // .filter({ balances.contains($0) }) .compactMap({ balances.first($0) }) } var tokensTo: [Network.Model.Token] { if let from = tokenGive { var tokens = Set() let sdsffs = graph.findAllBfs(from: from, goalTest: { print($0) tokens.insert($0) return true }) print(sdsffs) tokens.remove(from) return Array(tokens) // .compactMap({ balances.first($0) }) .map({ if let token = balances.first($0) { return token } else { var token = $0 token.amount = 0 return token } }) .sorted(by: { $0.amount == $1.amount ? $0.symbol < $1.symbol : $0.amount > $1.amount }) .filter({ $0.swappableContracts == true }) } else { return [] } } private var selectedBestRoute: [Swap.Model.Pool] = [] func bestRoute(startToken: Network.Model.Token?, amount: Decimal, forward: Bool, reverse: Bool) -> [Swap.Model.Pool] { print(allRouts.count) var bestRoute = allRouts.first ?? [] var bestValue = allRouts.first?.process(startToken: startToken, amount: amount, forward: forward, reverse: reverse) ?? 0 for route in allRouts { let value = route.process(startToken: startToken, amount: amount, forward: forward, reverse: reverse) if reverse { if bestValue == 0 || (value < bestValue && value > 0) { bestValue = value bestRoute = route } } else { if bestValue < value { bestValue = value bestRoute = route } } } return bestRoute } private var allRouts: [[Swap.Model.Pool]] = [] private func calcAllRouts() -> [[Swap.Model.Pool]] { if let from = tokenGive, let to = tokenGet { let currenciesList = Array(Set(collection .reduce([String](), { $0 + [$1.token1Light!.id, $1.token2Light!.id] }))) // let poolsList = collection.reduce([(String, Int)](), { // $0 + [("\(currenciesList.firstIndex(of: $1.token1!.symbol)!)-\(currenciesList.firstIndex(of: $1.token2!.symbol)!)", $1.id)] // }) let fromIndex = currenciesList.firstIndex(of: "\(from.symbol)#\(from.contract)")! let toIndex = currenciesList.firstIndex(of: "\(to.symbol)#\(to.contract)")! let sppedCollection = collection.map({ SpeedPool(id: $0.id, token1Code: currenciesList.firstIndex(of: $0.token1Light!.id)!, token2Code: currenciesList.firstIndex(of: $0.token2Light!.id)! ) }) let allCollections = sppedCollection + sppedCollection.map({ $0.reversed() }) var outFinal = [OrderedSet]() var currentFinal = [OrderedSet]() allCollections .filter({ $0.token1 == fromIndex }) .forEach({ if $0.token2 == toIndex { outFinal.append(OrderedSet([$0.token1, $0.token2])) } else { currentFinal.append(OrderedSet([$0.token1, $0.token2])) } }) // for (iteration, _) in allCollections.enumerated() { for iteration in 0..<4 { print("\(iteration) = \(currentFinal.count) / \(outFinal.count)") var newCurrentFinal = [OrderedSet]() for checkPool in allCollections { for chain in currentFinal { if //!chain.contains(checkPool.token1Code) && !chain.contains(checkPool.token2) { if chain.last == checkPool.token1 { if checkPool.token2 == toIndex { outFinal.append(OrderedSet(chain.elements + [checkPool.token1, checkPool.token2])) } else { newCurrentFinal.append(OrderedSet(chain.elements + [checkPool.token1, checkPool.token2])) } } } } } currentFinal = newCurrentFinal } var allCollectionsDict = [Int: SpeedPool]() for pool in allCollections { allCollectionsDict[pool.token1 * 10000 + pool.token2] = pool } var allUsusalPools = [Int: Swap.Model.Pool]() for pool in collection { allUsusalPools[pool.id] = pool } return outFinal.map({ chain in var newChain = [SpeedPool]() for index in Array(0..<(chain.count - 1)) { let token1 = chain.elements[index] let token2 = chain.elements[index + 1] newChain.append(allCollectionsDict[token1 * 10000 + token2]!) } return newChain.map({ speedPool in allUsusalPools[speedPool.id]! }) }) } else { return [] } } func processRate(amount: Decimal) -> Decimal { var newAmountReceive: Decimal guard let tokenGive = tokenGive, let tokenGet = tokenGet else { return 0 } newAmountReceive = selectedBestRoute.process(startToken: tokenGive, amount: amount, forward: true, reverse: false) if newAmountReceive < 0 || newAmountReceive == Decimal.nan { return 0 } else { return newAmountReceive } } var route: [Network.Model.Token] { var tokens = [tokenGive] for pool in selectedBestRoute { if pool.token1 == tokens.last { tokens.append(pool.token2) } else { tokens.append(pool.token1) } } return tokens.compactMap({ $0 }) } let minOtherAmount = "800".toDecimal() private(set) var amountSend: String = "" var amountSendError: String? { if let tokenGive = self.tokenGive, let amountSend = amountSend.toOptionalDecimal() { if (balances.first(tokenGive)?.amount ?? 0) < amountSend { return L10n.Swap.Main.Give.error } else if amountSend < Decimal(1.0) / pow(10, tokenGive.precision) * minOtherAmount { return L10n.Swap.Main.Give.tooSmallOther } else { return nil } } else { return nil } } private(set) var amountReceive: String = "" var amountReceiveError: String? { if let tokenGet = self.tokenGet, let amountReceive = amountReceive.toOptionalDecimal() { if (route.last?.amount ?? 0) < amountReceive { return L10n.Swap.Main.Get.error } else { return nil } } else { return nil } } private(set) var amountMinimum: String = "" // var amountMinimumShowError: Bool { // if let amount = amountReceive { // return amount * 0.95 < amountMinimum ?? 0 // } else { // return false // } // } func update(receive: String) { guard let tokenGive = self.tokenGive, let tokenGet = self.tokenGet else { return } if let amount = receive.toOptionalDecimal() { amountReceive = receive.normalized(precision: tokenGet.precision) amountMinimum = (amount * 0.99).toString(max: tokenGet.precision, group: false) selectedBestRoute = bestRoute(startToken: tokenGet, amount: amount, forward: false, reverse: true) var newAmountSend = selectedBestRoute.process(startToken: tokenGet, amount: amount, forward: false, reverse: true) newAmountSend = newAmountSend > 0 ? newAmountSend : 0 amountSend = newAmountSend.toString(max: tokenGive.precision, group: false) } else if receive.isEmpty { amountSend = "" amountReceive = "" amountMinimum = "" } didUpdate?() } func update(send: String) { guard let tokenGive = self.tokenGive, let tokenGet = self.tokenGet else { return } if let amount = send.toOptionalDecimal() { amountSend = send.normalized(precision: tokenGive.precision) selectedBestRoute = bestRoute(startToken: tokenGive, amount: amount, forward: true, reverse: false) var newAmountReceive = selectedBestRoute.process(startToken: tokenGive, amount: amount, forward: true, reverse: false) newAmountReceive = newAmountReceive > 0 ? newAmountReceive : 0 amountReceive = newAmountReceive.toString(max: tokenGet.precision, group: false) amountMinimum = (newAmountReceive * 0.99).toString(max: tokenGet.precision, group: false) } else if send.isEmpty { amountSend = "" amountReceive = "" amountMinimum = "" } didUpdate?() } func update(minimum: String) { guard let tokenGet = self.tokenGet else { return } if minimum.toOptionalDecimal() != nil { amountMinimum = minimum.normalized(precision: tokenGet.precision) } else if minimum.isEmpty { amountMinimum = "" } didUpdate?() } private(set) var graph = UnweightedUniqueElementsGraph() func fetch(_ completion: @escaping (NSError?) -> Void) { Network.table.fetch( code: ApplicationEnvironment.shared().current.contract(.swap), table: "pools", scope: ApplicationEnvironment.shared().current.contract(.swap), limit: 1000, type: Swap.Model.Pool.self ) { (result) in self.collection = result .filter({ ($0.token1?.amount ?? 0) > 0 || ($0.token2?.amount ?? 0) > 0 }) completion(nil) self.didUpdate?() } } func submit(privateKey: String, _ completion: @escaping (Result) -> Void) { guard let tokenGive = self.tokenGive, let tokenGet = self.tokenGet, let amountSend = self.amountSend.toOptionalDecimal() // let contract = ApplicationEnvironment.shared().current.contract(tokenGive.contract) else { return } var actions = [(contract: String, action: Network.Model.Blockchain.Action, data: Codable)]() if !balances.contains(tokenGet) { if tokenGet.contract == "pizzatotoken" { actions.append( (contract: tokenGet.contract, action: .signup, data: [ "owner": Accounts().current?.name ?? "" ]) ) } else { actions.append( (contract: tokenGet.contract, action: .open, data: [ "owner": Accounts().current?.name ?? "", "symbol": "\(tokenGet.precision),\(tokenGet.symbol)", "ram_payer": Accounts().current?.name ?? "" ]) ) } } let route = selectedBestRoute.map({ "\($0.id)" }).joined(separator: "-") let min = amountMinimum .toOptionalDecimal()? .toString(precision: tokenGet) .split(separator: " ").first? .replacingOccurrences(of: ".", with: "") .replacingOccurrences(of: ",", with: "") ?? "" actions.append( (contract: tokenGive.contract, action: .transfer, data: [ "from": Accounts().current?.name ?? "", "to": ApplicationEnvironment.shared().current.contract(.swap), "quantity": "\(amountSend.toString(precision: tokenGive))", "memo": "swap:\(route)\(min.isEmpty ? "" : ";min:\(min)")" ])) Network.Service.Blockchain.execute(actions: actions, privateKeys: [privateKey]) { (result) in switch result { case .success(let trxId): delayed(4) { Wallet.Service.Tokens.shared.balances.fetch { (_) in completion(.success(trxId)) } } case .failure(let err): completion(.failure(err)) } } } func getToken(_ token: Network.Model.Token) -> Network.Model.Token? { if let value = balances.filter({ $0.symbol == token.symbol }).first { return value } return nil } } }