tokenary/Shared/Services/DappRequestProcessor.swift
2023-11-01 13:25:56 +03:00

225 lines
12 KiB
Swift

// Copyright © 2022 Tokenary. All rights reserved.
import Foundation
import WalletCore
struct DappRequestProcessor {
private static let walletsManager = WalletsManager.shared
private static let ethereum = Ethereum.shared
static func processSafariRequest(_ request: SafariRequest, completion: @escaping () -> Void) -> DappRequestAction {
guard ExtensionBridge.hasRequest(id: request.id) else {
respond(to: request, error: Strings.somethingWentWrong, completion: completion)
return .none
}
switch request.body {
case let .ethereum(body):
return process(request: request, ethereumRequest: body, completion: completion)
case let .unknown(body):
switch body.method {
case .justShowApp:
ExtensionBridge.respond(response: ResponseToExtension(for: request))
return .justShowApp
case .switchAccount:
let preselectedAccounts = body.providerConfigurations.compactMap { (configuration) -> SpecificWalletAccount? in
guard let coin = CoinType.correspondingToInpageProvider(configuration.provider) else { return nil }
return walletsManager.getSpecificAccount(coin: coin, address: configuration.address)
}
let chainId = body.providerConfigurations.compactMap { $0.chainId }.first
let network = Networks.withChainIdHex(chainId)
let initiallyConnectedProviders = Set(body.providerConfigurations.map { $0.provider })
let action = SelectAccountAction(peer: request.peerMeta,
coinType: nil,
selectedAccounts: Set(preselectedAccounts),
initiallyConnectedProviders: initiallyConnectedProviders,
network: network,
source: .safariExtension) { chain, specificWalletAccounts in
if let chain = chain, let specificWalletAccounts = specificWalletAccounts {
var specificProviderBodies = [ResponseToExtension.Body]()
for specificWalletAccount in specificWalletAccounts {
let account = specificWalletAccount.account
switch account.coin {
case .ethereum:
let responseBody = ResponseToExtension.Ethereum(results: [account.address],
chainId: chain.chainIdHexString,
rpcURL: chain.nodeURLString)
specificProviderBodies.append(.ethereum(responseBody))
default:
fatalError("Can't select that coin")
}
}
let providersToDisconnect = initiallyConnectedProviders.filter { provider in
if let coin = CoinType.correspondingToInpageProvider(provider),
specificWalletAccounts.contains(where: { $0.account.coin == coin }) {
return false
} else {
return true
}
}
let body = ResponseToExtension.Multiple(bodies: specificProviderBodies, providersToDisconnect: Array(providersToDisconnect))
respond(to: request, body: .multiple(body), completion: completion)
} else {
respond(to: request, error: Strings.canceled, completion: completion)
}
}
return .switchAccount(action)
}
}
}
private static func process(request: SafariRequest, ethereumRequest: SafariRequest.Ethereum, completion: @escaping () -> Void) -> DappRequestAction {
let peerMeta = request.peerMeta
lazy var account = walletsManager.getAccount(coin: .ethereum, address: ethereumRequest.address)
lazy var privateKey = walletsManager.getPrivateKey(coin: .ethereum, address: ethereumRequest.address)
switch ethereumRequest.method {
case .requestAccounts:
let action = SelectAccountAction(peer: peerMeta,
coinType: .ethereum,
selectedAccounts: Set(walletsManager.suggestedAccounts(coin: .ethereum)),
initiallyConnectedProviders: Set(),
network: nil,
source: .safariExtension) { chain, specificWalletAccounts in
if let chain = chain, let specificWalletAccount = specificWalletAccounts?.first, specificWalletAccount.account.coin == .ethereum {
let responseBody = ResponseToExtension.Ethereum(results: [specificWalletAccount.account.address], chainId: chain.chainIdHexString, rpcURL: chain.nodeURLString)
respond(to: request, body: .ethereum(responseBody), completion: completion)
} else {
respond(to: request, error: Strings.canceled, completion: completion)
}
}
return .selectAccount(action)
case .signTypedMessage:
if let raw = ethereumRequest.raw,
let account = account,
let privateKey = privateKey {
let action = SignMessageAction(provider: request.provider, subject: .signTypedData, account: account, meta: raw, peerMeta: peerMeta) { approved in
if approved {
signTypedData(privateKey: privateKey, raw: raw, request: request, completion: completion)
} else {
respond(to: request, error: Strings.failedToSign, completion: completion)
}
}
return .approveMessage(action)
} else {
respond(to: request, error: Strings.somethingWentWrong, completion: completion)
}
case .signMessage:
if let data = ethereumRequest.message,
let account = account,
let privateKey = privateKey {
let action = SignMessageAction(provider: request.provider, subject: .signMessage, account: account, meta: data.hexString, peerMeta: peerMeta) { approved in
if approved {
signMessage(privateKey: privateKey, data: data, request: request, completion: completion)
} else {
respond(to: request, error: Strings.failedToSign, completion: completion)
}
}
return .approveMessage(action)
} else {
respond(to: request, error: Strings.somethingWentWrong, completion: completion)
}
case .signPersonalMessage:
if let data = ethereumRequest.message,
let account = account,
let privateKey = privateKey {
let text = String(data: data, encoding: .utf8) ?? data.hexString
let action = SignMessageAction(provider: request.provider, subject: .signPersonalMessage, account: account, meta: text, peerMeta: peerMeta) { approved in
if approved {
signPersonalMessage(privateKey: privateKey, data: data, request: request, completion: completion)
} else {
respond(to: request, error: Strings.failedToSign, completion: completion)
}
}
return .approveMessage(action)
} else {
respond(to: request, error: Strings.somethingWentWrong, completion: completion)
}
case .signTransaction:
if let transaction = ethereumRequest.transaction,
let chainId = ethereumRequest.currentChainId,
let chain = Networks.withChainId(chainId),
let account = account,
let privateKey = privateKey {
let action = SendTransactionAction(provider: request.provider,
transaction: transaction,
chain: chain,
account: account,
peerMeta: peerMeta) { transaction in
if let transaction = transaction {
sendTransaction(privateKey: privateKey, transaction: transaction, network: chain, request: request, completion: completion)
} else {
respond(to: request, error: Strings.canceled, completion: completion)
}
}
return .approveTransaction(action)
} else {
respond(to: request, error: Strings.somethingWentWrong, completion: completion)
}
case .ecRecover:
if let (signature, message) = ethereumRequest.signatureAndMessage,
let recovered = ethereum.recover(signature: signature, message: message) {
respond(to: request, body: .ethereum(.init(result: recovered)), completion: completion)
} else {
respond(to: request, error: Strings.failedToVerify, completion: completion)
}
case .addEthereumChain, .switchEthereumChain, .watchAsset:
respond(to: request, error: Strings.somethingWentWrong, completion: completion)
}
return .none
}
private static func signTypedData(privateKey: PrivateKey, raw: String, request: SafariRequest, completion: () -> Void) {
if let signed = try? ethereum.sign(typedData: raw, privateKey: privateKey) {
respond(to: request, body: .ethereum(.init(result: signed)), completion: completion)
} else {
respond(to: request, error: Strings.failedToSign, completion: completion)
}
}
private static func signMessage(privateKey: PrivateKey, data: Data, request: SafariRequest, completion: () -> Void) {
if let signed = try? ethereum.sign(data: data, privateKey: privateKey) {
respond(to: request, body: .ethereum(.init(result: signed)), completion: completion)
} else {
respond(to: request, error: Strings.failedToSign, completion: completion)
}
}
private static func signPersonalMessage(privateKey: PrivateKey, data: Data, request: SafariRequest, completion: () -> Void) {
if let signed = try? ethereum.signPersonalMessage(data: data, privateKey: privateKey) {
respond(to: request, body: .ethereum(.init(result: signed)), completion: completion)
} else {
respond(to: request, error: Strings.failedToSign, completion: completion)
}
}
private static func sendTransaction(privateKey: PrivateKey, transaction: Transaction, network: EthereumNetwork, request: SafariRequest, completion: @escaping () -> Void) {
ethereum.send(transaction: transaction, privateKey: privateKey, network: network) { hash in
if let hash = hash {
DappRequestProcessor.respond(to: request, body: .ethereum(.init(result: hash)), completion: completion)
} else {
respond(to: request, error: Strings.failedToSend, completion: completion)
}
}
}
private static func respond(to safariRequest: SafariRequest, body: ResponseToExtension.Body, completion: () -> Void) {
let response = ResponseToExtension(for: safariRequest, body: body)
sendResponse(response, completion: completion)
}
private static func respond(to safariRequest: SafariRequest, error: String, completion: () -> Void) {
let response = ResponseToExtension(for: safariRequest, error: error)
sendResponse(response, completion: completion)
}
private static func sendResponse(_ response: ResponseToExtension, completion: () -> Void) {
ExtensionBridge.respond(response: response)
completion()
}
}