mirror of
https://github.com/lil-org/tokenary.git
synced 2024-12-02 09:33:49 +03:00
225 lines
12 KiB
Swift
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()
|
|
}
|
|
|
|
}
|