Files
2022-12-13 22:51:28 +03:00

434 lines
17 KiB
Swift

//
// 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<Network.Model.Token>()
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<Network.Model.Token>()
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<String>(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<Int>]()
var currentFinal = [OrderedSet<Int>]()
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<Int>]()
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<Network.Model.Token>()
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<String, Error>) -> 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
}
}
}