|
|
|
@ -5,7 +5,7 @@ import Alamofire |
|
|
|
|
import BigInt |
|
|
|
|
import Realm |
|
|
|
|
import TrustKeystore |
|
|
|
|
//import web3swift |
|
|
|
|
import web3swift |
|
|
|
|
|
|
|
|
|
protocol UniversalLinkCoordinatorDelegate: class { |
|
|
|
|
func viewControllerForPresenting(in coordinator: UniversalLinkCoordinator) -> UIViewController? |
|
|
|
@ -22,6 +22,7 @@ class UniversalLinkCoordinator: Coordinator { |
|
|
|
|
var ethBalance: Subscribable<BigInt>? |
|
|
|
|
var hasCompleted = false |
|
|
|
|
var addressOfNewWallet: String? |
|
|
|
|
private var getStormbirdBalanceCoordinator: GetStormBirdBalanceCoordinator? |
|
|
|
|
|
|
|
|
|
init(config: Config) { |
|
|
|
|
self.config = config |
|
|
|
@ -119,8 +120,7 @@ class UniversalLinkCoordinator: Coordinator { |
|
|
|
|
return true |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
//Returns true if handled |
|
|
|
|
|
|
|
|
|
//Returns true if handled |
|
|
|
|
func handleUniversalLink(url: URL) -> Bool { |
|
|
|
|
let prefix = UniversalLinkHandler().urlPrefix |
|
|
|
|
let matchedPrefix = url.description.hasPrefix(prefix) |
|
|
|
@ -137,113 +137,120 @@ class UniversalLinkCoordinator: Coordinator { |
|
|
|
|
let isStormBirdContract = xmlAddress.eip55String.sameContract(as: signedOrder.order.contractAddress) |
|
|
|
|
importTicketViewController?.url = url |
|
|
|
|
importTicketViewController?.contract = signedOrder.order.contractAddress |
|
|
|
|
//let recoveredSigner = web3.personal.ecrecover(personalMessage: Data(bytes: signedOrder.message), signature: signature) |
|
|
|
|
getTicketDetailsAndEcRecover(signedOrder: signedOrder) { result in |
|
|
|
|
if let goodResult = result { |
|
|
|
|
//user can pay gas for free import links if they are not covered by our server |
|
|
|
|
if signedOrder.order.price > 0 || !isStormBirdContract { |
|
|
|
|
if let balance = self.ethBalance { |
|
|
|
|
balance.subscribeOnce { value in |
|
|
|
|
if value > signedOrder.order.price { |
|
|
|
|
let _ = self.handlePaidUniversalLink(signedOrder: signedOrder, ticketHolder: goodResult) |
|
|
|
|
} else { |
|
|
|
|
if let price = self.ethPrice { |
|
|
|
|
if price.value == nil { |
|
|
|
|
let ethCost = self.convert(ethCost: signedOrder.order.price) |
|
|
|
|
self.showImportError(errorMessage: R.string.localizable.aClaimTicketFailedNotEnoughEthTitle(), ticketHolder: goodResult, ethCost: ethCost.description) |
|
|
|
|
} |
|
|
|
|
price.subscribe { [weak self] value in |
|
|
|
|
if let celf = self { |
|
|
|
|
if let price = price.value { |
|
|
|
|
let (ethCost, dollarCost) = celf.convert(ethCost: signedOrder.order.price, rate: price) |
|
|
|
|
celf.showImportError(errorMessage: R.string.localizable.aClaimTicketFailedNotEnoughEthTitle(), ticketHolder: goodResult, ethCost: ethCost.description, dollarCost: dollarCost.description) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
//need to hash message here because the web3swift implementation adds prefix |
|
|
|
|
let messageHash = Data(bytes: signedOrder.message).sha3(.keccak256) |
|
|
|
|
//note: web3swift takes the v value as v - 27, so we need to manually convert this |
|
|
|
|
let vValue = signedOrder.signature.drop0x.substring(from: 128) |
|
|
|
|
let vInt = Int(vValue, radix: 16)! - 27 |
|
|
|
|
let vString = "0" + String(vInt) |
|
|
|
|
let signature = "0x" + signedOrder.signature.drop0x.substring(to: 128) + vString |
|
|
|
|
let nodeURL = Config().rpcURL |
|
|
|
|
let recoveredSigner = web3(provider: Web3HttpProvider(nodeURL)!).personal.ecrecover( |
|
|
|
|
hash: messageHash, |
|
|
|
|
signature: Data(bytes: signature.hexa2Bytes) |
|
|
|
|
) |
|
|
|
|
switch recoveredSigner { |
|
|
|
|
case .success(let ethereumAddress): |
|
|
|
|
//TODO extract method for the whole .success? Quite long |
|
|
|
|
//TODO return false? |
|
|
|
|
guard let recoverAddress = Address(string: ethereumAddress.address) else { return false } |
|
|
|
|
let contractAsAddress = Address(string: signedOrder.order.contractAddress)! |
|
|
|
|
//gather signer address balance |
|
|
|
|
let web3Swift = Web3Swift() |
|
|
|
|
web3Swift.start() |
|
|
|
|
getStormbirdBalanceCoordinator = GetStormBirdBalanceCoordinator(web3: web3Swift) |
|
|
|
|
getStormbirdBalanceCoordinator?.getStormBirdBalance(for: recoverAddress, contract: contractAsAddress) { result in |
|
|
|
|
//filter null tickets |
|
|
|
|
let filteredTokens = self.checkERC875TokensAreAvailable( |
|
|
|
|
indices: signedOrder.order.indices, |
|
|
|
|
balance: try! result.dematerialize() |
|
|
|
|
) |
|
|
|
|
if filteredTokens.isEmpty { |
|
|
|
|
self.showImportError(errorMessage: R.string.localizable.aClaimTicketInvalidLinkTryAgain()) |
|
|
|
|
} |
|
|
|
|
else { |
|
|
|
|
|
|
|
|
|
let ticketHolder = self.sortTickets( |
|
|
|
|
filteredTokens, |
|
|
|
|
signedOrder.order.indices, |
|
|
|
|
signedOrder.order.contractAddress |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
if signedOrder.order.price > 0 || !isStormBirdContract { |
|
|
|
|
self.handlePaidImports(signedOrder: signedOrder, ticketHolder: ticketHolder) |
|
|
|
|
} else { |
|
|
|
|
//free transfer |
|
|
|
|
let _ = self.usePaymentServerForFreeTransferLinks( |
|
|
|
|
signedOrder: signedOrder, |
|
|
|
|
ticketHolder: goodResult |
|
|
|
|
ticketHolder: ticketHolder |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
self.showImportError(errorMessage: R.string.localizable.aClaimTicketInvalidLinkTryAgain()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
case .failure(let error): |
|
|
|
|
//TODO handle. Show error maybe? |
|
|
|
|
NSLog("xxx error during ecrecover: \(error.localizedDescription)") |
|
|
|
|
//TODO return true or false? |
|
|
|
|
return false |
|
|
|
|
} |
|
|
|
|
return true |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func importPaidSignedOrder(signedOrder: SignedOrder, tokenObject: TokenObject) { |
|
|
|
|
updateImportTicketController(with: .processing) |
|
|
|
|
delegate?.importPaidSignedOrder(signedOrder: signedOrder, tokenObject: tokenObject) { successful in |
|
|
|
|
if self.importTicketViewController != nil { |
|
|
|
|
if let vc = self.importTicketViewController, var _ = vc.viewModel { |
|
|
|
|
if successful { |
|
|
|
|
self.showImportSuccessful() |
|
|
|
|
} else { |
|
|
|
|
//TODO Pass in error message |
|
|
|
|
self.showImportError(errorMessage: R.string.localizable.aClaimTicketFailedTitle()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private func handlePaidImports(signedOrder: SignedOrder, ticketHolder: TicketHolder) { |
|
|
|
|
if let balance = self.ethBalance { |
|
|
|
|
balance.subscribeOnce { value in |
|
|
|
|
if value > signedOrder.order.price { |
|
|
|
|
let _ = self.handlePaidUniversalLink(signedOrder: signedOrder, ticketHolder: ticketHolder) |
|
|
|
|
} else { |
|
|
|
|
if let price = self.ethPrice { |
|
|
|
|
if price.value == nil { |
|
|
|
|
let ethCost = self.convert(ethCost: signedOrder.order.price) |
|
|
|
|
self.showImportError( |
|
|
|
|
errorMessage: R.string.localizable.aClaimTicketFailedNotEnoughEthTitle(), |
|
|
|
|
ticketHolder: ticketHolder, |
|
|
|
|
ethCost: ethCost.description |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
price.subscribe { [weak self] value in |
|
|
|
|
if let celf = self { |
|
|
|
|
if let price = price.value { |
|
|
|
|
let (ethCost, dollarCost) = celf.convert(ethCost: signedOrder.order.price, rate: price) |
|
|
|
|
celf.showImportError(errorMessage: R.string.localizable.aClaimTicketFailedNotEnoughEthTitle(), ticketHolder: ticketHolder, ethCost: ethCost.description, dollarCost: dollarCost.description) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private func stringEncodeIndices(_ indices: [UInt16]) -> String { |
|
|
|
|
return indices.map(String.init).joined(separator: ",") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private func getTicketDetailsAndEcRecover( |
|
|
|
|
signedOrder: SignedOrder, |
|
|
|
|
completion: @escaping( _ response: TicketHolder?) -> Void |
|
|
|
|
) { |
|
|
|
|
let indices = signedOrder.order.indices |
|
|
|
|
let parameters = createHTTPParametersForPaymentServer( |
|
|
|
|
signedOrder: signedOrder, |
|
|
|
|
isForTransfer: false |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
Alamofire.request(Constants.getTicketInfoFromServer, method: .get, parameters: parameters).responseJSON { response in |
|
|
|
|
if let data = response.data, let utf8Text = String(data: data, encoding: .utf8) { |
|
|
|
|
if let statusCode = response.response?.statusCode { |
|
|
|
|
if statusCode > 299 { |
|
|
|
|
completion(nil) |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
var array = utf8Text.split(separator: ",").map(String.init) |
|
|
|
|
if array.isEmpty || array[0] == "invalid indices" { |
|
|
|
|
completion(nil) |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
//start at one to slice off address |
|
|
|
|
let bytes32Tickets = Array(array[1...]) |
|
|
|
|
completion( |
|
|
|
|
self.sortTickets( |
|
|
|
|
bytes32Tickets, |
|
|
|
|
indices, |
|
|
|
|
signedOrder.order.contractAddress |
|
|
|
|
) |
|
|
|
|
) |
|
|
|
|
} else { |
|
|
|
|
completion(nil) |
|
|
|
|
|
|
|
|
|
func checkERC875TokensAreAvailable(indices: [UInt16], balance: [String]) -> [String] { |
|
|
|
|
var filteredTokens = [String]() |
|
|
|
|
if balance.count < indices.count { |
|
|
|
|
return [String]() |
|
|
|
|
} |
|
|
|
|
for i in 0..<indices.count { |
|
|
|
|
let token: String = balance[Int(indices[i])] |
|
|
|
|
//all of the indices provided should map to a valid non null ticket |
|
|
|
|
if token == Constants.nullTicket { |
|
|
|
|
//if null ticket at any index then the deal cannot happen |
|
|
|
|
return [String]() |
|
|
|
|
} |
|
|
|
|
filteredTokens.append(token) |
|
|
|
|
} |
|
|
|
|
return filteredTokens |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private func sortTickets(_ bytes32Tickets: [String], _ indices: [UInt16], _ contractAddress: String) -> TicketHolder { |
|
|
|
|
var tickets = [Ticket]() |
|
|
|
|
let xmlHandler = XMLHandler() |
|
|
|
|
for i in 0...bytes32Tickets.count - 1 { |
|
|
|
|
for i in 0..<bytes32Tickets.count { |
|
|
|
|
let ticket = bytes32Tickets[i] |
|
|
|
|
if let tokenId = BigUInt(ticket, radix: 16) { |
|
|
|
|
if let tokenId = BigUInt(ticket.drop0x, radix: 16) { |
|
|
|
|
let ticket = xmlHandler.getFifaInfoForTicket(tokenId: tokenId, index: UInt16(i)) |
|
|
|
|
tickets.append(ticket) |
|
|
|
|
} |
|
|
|
@ -305,6 +312,23 @@ class UniversalLinkCoordinator: Coordinator { |
|
|
|
|
private func showImportError(errorMessage: String, ticketHolder: TicketHolder? = nil, ethCost: String? = nil, dollarCost: String? = nil) { |
|
|
|
|
updateImportTicketController(with: .failed(errorMessage: errorMessage), ticketHolder: ticketHolder, ethCost: ethCost, dollarCost: dollarCost) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func importPaidSignedOrder(signedOrder: SignedOrder, tokenObject: TokenObject) { |
|
|
|
|
updateImportTicketController(with: .processing) |
|
|
|
|
delegate?.importPaidSignedOrder(signedOrder: signedOrder, tokenObject: tokenObject) { successful in |
|
|
|
|
if self.importTicketViewController != nil { |
|
|
|
|
if let vc = self.importTicketViewController, var _ = vc.viewModel { |
|
|
|
|
if successful { |
|
|
|
|
self.showImportSuccessful() |
|
|
|
|
} else { |
|
|
|
|
//TODO Pass in error message |
|
|
|
|
self.showImportError(errorMessage: R.string.localizable.aClaimTicketFailedTitle()) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
//handling free transfers, sell links cannot be handled here |
|
|
|
|
private func importUniversalLink(query: String, parameters: Parameters) { |
|
|
|
|