Let user choose the server to use WalletConnect against; no longer connecting to the one set in the dapp browser

pull/2413/head
Hwee-Boon Yar 4 years ago
parent f0b87e5f9f
commit de1d029768
  1. 14
      AlphaWallet/InCoordinator.swift
  2. 2
      AlphaWallet/Localization/en.lproj/Localizable.strings
  3. 2
      AlphaWallet/Localization/es.lproj/Localizable.strings
  4. 2
      AlphaWallet/Localization/ja.lproj/Localizable.strings
  5. 2
      AlphaWallet/Localization/ko.lproj/Localizable.strings
  6. 2
      AlphaWallet/Localization/zh-Hans.lproj/Localizable.strings
  7. 1
      AlphaWallet/WalletConnect/Controllers/TransactionConfirmationCoordinatorBridgeToPromise.swift
  8. 81
      AlphaWallet/WalletConnect/Coordinator/WalletConnectCoordinator.swift
  9. 79
      AlphaWallet/WalletConnect/WalletConnectServer.swift
  10. 7
      AlphaWalletTests/Settings/ConfigTests.swift

@ -119,14 +119,10 @@ class InCoordinator: NSObject, Coordinator {
return service
}()
private var walletConnectConfiguration: WalletConnectServer.Configuration {
let server = RPCServer(chainID: config.server.chainID)
return WalletConnectServer.Configuration(wallet: wallet, rpcServer: server)
}
lazy var walletConnectCoordinator: WalletConnectCoordinator = {
let coordinator = WalletConnectCoordinator(keystore: keystore, configuration: walletConnectConfiguration, navigationController: navigationController, analyticsCoordinator: analyticsCoordinator, config: config)
let coordinator = WalletConnectCoordinator(keystore: keystore, sessions: walletSessions, navigationController: navigationController, analyticsCoordinator: analyticsCoordinator, config: config)
addCoordinator(coordinator)
return coordinator
}()
@ -152,9 +148,6 @@ class InCoordinator: NSObject, Coordinator {
//self.assetDefinitionStore.enableFetchXMLForContractInPasteboard()
super.init()
addCoordinator(walletConnectCoordinator)
walletConnectCoordinator.start()
}
func start() {
@ -363,7 +356,6 @@ class InCoordinator: NSObject, Coordinator {
)
walletSessions[each] = session
}
walletConnectCoordinator.sessions = walletSessions
}
//Setup functions has to be called in the right order as they may rely on eg. wallet sessions being available. Wrong order should be immediately apparent with crash on startup. So don't worry
@ -396,9 +388,9 @@ class InCoordinator: NSObject, Coordinator {
func showTabBar(for account: Wallet) {
keystore.recentlyUsedWallet = account
wallet = account
walletConnectCoordinator.set(configuration: walletConnectConfiguration)
setupResourcesOnMultiChain()
fetchEthereumEvents()
walletConnectCoordinator.disconnect()
//TODO creating many objects here. Messy. Improve?
let realm = self.realm(forAccount: wallet)

@ -526,6 +526,6 @@ You can check the latest gas price on gasnow.org";
"walletConnect.session.connectedURL" = "Connected to";
"walletConnect.session.disconnect" = "Disconnect";
"walletConnect.session.signedTransactions" = "Signed Transactions";
"start" = "Start";
"walletConnect.start" = "%@\n\nStart WalletConnect session on:";
"walletConnect.failure.title" = "Wallet connect failure";
"walletConnect.sendRawTransaction.title" = "Send raw transaction";

@ -526,6 +526,6 @@ You can check the latest gas price on gasnow.org";
"walletConnect.session.connectedURL" = "Connected to";
"walletConnect.session.disconnect" = "Disconnect";
"walletConnect.session.signedTransactions" = "Signed Transactions";
"start" = "Start";
"walletConnect.start" = "%@\n\nStart WalletConnect session on:";
"walletConnect.failure.title" = "Wallet connect failure";
"walletConnect.sendRawTransaction.title" = "Send raw transaction";

@ -526,6 +526,6 @@ You can check the latest gas price on gasnow.org";
"walletConnect.session.connectedURL" = "Connected to";
"walletConnect.session.disconnect" = "Disconnect";
"walletConnect.session.signedTransactions" = "Signed Transactions";
"start" = "Start";
"walletConnect.start" = "%@\n\nStart WalletConnect session on:";
"walletConnect.failure.title" = "Wallet connect failure";
"walletConnect.sendRawTransaction.title" = "Send raw transaction";

@ -526,6 +526,6 @@ You can check the latest gas price on gasnow.org";
"walletConnect.session.connectedURL" = "Connected to";
"walletConnect.session.disconnect" = "Disconnect";
"walletConnect.session.signedTransactions" = "Signed Transactions";
"start" = "Start";
"walletConnect.start" = "%@\n\nStart WalletConnect session on:";
"walletConnect.failure.title" = "Wallet connect failure";
"walletConnect.sendRawTransaction.title" = "Send raw transaction";

@ -526,6 +526,6 @@ You can check the latest gas price on gasnow.org";
"walletConnect.session.connectedURL" = "Connected to";
"walletConnect.session.disconnect" = "Disconnect";
"walletConnect.session.signedTransactions" = "Signed Transactions";
"start" = "Start";
"walletConnect.start" = "%@\n\nStart WalletConnect session on:";
"walletConnect.failure.title" = "Wallet connect failure";
"walletConnect.sendRawTransaction.title" = "Send raw transaction";

@ -92,6 +92,7 @@ extension UIViewController {
extension TransactionConfirmationCoordinator {
//session contains account already
static func promise(_ navigationController: UINavigationController, session: WalletSession, coordinator: Coordinator, account: AlphaWallet.Address, transaction: UnconfirmedTransaction, configuration: TransactionConfirmationConfiguration, analyticsCoordinator: AnalyticsCoordinator?) -> Promise<ConfirmResult> {
let bridge = TransactionConfirmationCoordinatorBridgeToPromise(navigationController, session: session, coordinator: coordinator, analyticsCoordinator: analyticsCoordinator)
return bridge.promise(account: account, transaction: transaction, configuration: configuration)

@ -20,7 +20,7 @@ class WalletConnectCoordinator: NSObject, Coordinator {
}
private lazy var server: WalletConnectServer = {
let server = WalletConnectServer(configuration: configuration, config: config)
let server = WalletConnectServer(wallet: sessions.anyValue.account.address)
server.delegate = self
return server
}()
@ -31,37 +31,32 @@ class WalletConnectCoordinator: NSObject, Coordinator {
var connection: Subscribable<WalletConnectServerConnection> {
return server.connection
}
private var session: WalletSession {
return sessions[configuration.rpcServer]
}
private let keystore: Keystore
private var configuration: WalletConnectServer.Configuration
private let sessions: ServerDictionary<WalletSession>
private let analyticsCoordinator: AnalyticsCoordinator?
private let config: Config
var sessions: ServerDictionary<WalletSession> = .init()
init(keystore: Keystore, configuration: WalletConnectServer.Configuration, navigationController: UINavigationController, analyticsCoordinator: AnalyticsCoordinator?, config: Config) {
private var serverChoices: [RPCServer] {
ServersCoordinator.serversOrdered.filter { config.enabledServers.contains($0) }
}
init(keystore: Keystore, sessions: ServerDictionary<WalletSession>, navigationController: UINavigationController, analyticsCoordinator: AnalyticsCoordinator?, config: Config) {
self.config = config
self.configuration = configuration
self.sessions = sessions
self.keystore = keystore
self.navigationController = navigationController
self.analyticsCoordinator = analyticsCoordinator
super.init()
start()
}
func start() {
private func start() {
if let session = UserDefaults.standard.lastSession {
try? server.reconnect(session: session)
}
}
func set(configuration conf: WalletConnectServer.Configuration) {
disconnect()
configuration = conf
server.set(configuration: conf)
}
private func disconnect() {
func disconnect() {
switch server.connection.value {
case .connected(let session):
try? server.disconnect(session: session)
@ -101,20 +96,27 @@ extension WalletConnectCoordinator: WalletConnectSessionCoordinatorDelegate {
extension WalletConnectCoordinator: WalletConnectServerDelegate {
func server(_ server: WalletConnectServer, action: WalletConnectServer.Action, request: WalletConnectRequest) {
guard let rpcServer = server.urlToServer[request.url] else {
server.reject(request)
return
}
let session = sessions[rpcServer]
firstly {
return Promise<AlphaWallet.Address> { seal in
if case .real(let account) = session.account.type {
seal.fulfill(account)
} else {
Promise<Void> { seal in
switch session.account.type {
case .real:
seal.fulfill(())
case .watch:
seal.reject(PMKError.cancelled)
}
}
}.then { account -> Promise<WalletConnectServer.Callback> in
}.then { Void -> Promise<WalletConnectServer.Callback> in
let account = session.account.address
switch action.type {
case .signTransaction(let transaction):
return self.executeTransaction(account: account, callbackID: action.id, url: action.url, transaction: transaction, type: .sign)
return self.executeTransaction(session: session, callbackID: action.id, url: action.url, transaction: transaction, type: .sign)
case .sendTransaction(let transaction):
return self.executeTransaction(account: account, callbackID: action.id, url: action.url, transaction: transaction, type: .signThenSend)
return self.executeTransaction(session: session, callbackID: action.id, url: action.url, transaction: transaction, type: .signThenSend)
case .signMessage(let hexMessage):
return self.signMessage(with: .message(hexMessage.toHexData), account: account, callbackID: action.id, url: action.url)
case .signPersonalMessage(let hexMessage):
@ -122,9 +124,9 @@ extension WalletConnectCoordinator: WalletConnectServerDelegate {
case .signTypedMessage(let typedData):
return self.signMessage(with: .eip712(typedData), account: account, callbackID: action.id, url: action.url)
case .sendRawTransaction(let raw):
return self.send(rawTransaction: raw, callbackID: action.id, url: action.url)
return self.sendRawTransaction(session: session, rawTransaction: raw, callbackID: action.id, url: action.url)
case .getTransactionCount:
//NOTE: there is case like in example client app when client whants to get nonce first and then send transaction,
//NOTE: there is case like in example client app when client wants to get nonce first and then send transaction,
//here i suppose we need to show UI to user to allow to enter this nonce value
let data = "0x117".toHexData
return .value(.init(id: action.id, url: action.url, value: .getTransactionCount(data)))
@ -151,13 +153,12 @@ extension WalletConnectCoordinator: WalletConnectServerDelegate {
}
}
private func executeTransaction(account: AlphaWallet.Address, callbackID id: WalletConnectRequestID, url: WalletConnectURL, transaction: UnconfirmedTransaction, type: ConfirmType) -> Promise<WalletConnectServer.Callback> {
private func executeTransaction(session: WalletSession, callbackID id: WalletConnectRequestID, url: WalletConnectURL, transaction: UnconfirmedTransaction, type: ConfirmType) -> Promise<WalletConnectServer.Callback> {
return Promise { seal in
let dummyPrice: Subscribable<Double> = Subscribable<Double>(nil)
let configuration: TransactionConfirmationConfiguration = .dappTransaction(confirmType: type, keystore: keystore, ethPrice: dummyPrice)
TransactionConfirmationCoordinator.promise(navigationController, session: session, coordinator: self, account: account, transaction: transaction, configuration: configuration, analyticsCoordinator: analyticsCoordinator).map { data -> WalletConnectServer.Callback in
TransactionConfirmationCoordinator.promise(navigationController, session: session, coordinator: self, account: session.account.address, transaction: transaction, configuration: configuration, analyticsCoordinator: analyticsCoordinator).map { data -> WalletConnectServer.Callback in
switch data {
case .signedTransaction(let data):
return .init(id: id, url: url, value: .signTransaction(data))
@ -196,18 +197,18 @@ extension WalletConnectCoordinator: WalletConnectServerDelegate {
navigationController.displaySuccess(message: R.string.localizable.walletConnectFailureTitle())
}
func server(_ server: WalletConnectServer, shouldConnectFor connection: WalletConnectConnection, completion: @escaping (Bool) -> Void) {
func server(_ server: WalletConnectServer, shouldConnectFor connection: WalletConnectConnection, completion: @escaping (WalletConnectServer.ConnectionChoice) -> Void) {
showConnectToSession(title: connection.name, url: connection.url, completion: completion)
}
//TODO after we support sendRawTransaction in dapps (and hence a proper UI, be it the actionsheet for transaction confirmation or a simple prompt), let's modify this to use the same flow
private func send(rawTransaction: String, callbackID id: WalletConnectRequestID, url: WalletConnectURL) -> Promise<WalletConnectServer.Callback> {
private func sendRawTransaction(session: WalletSession, rawTransaction: String, callbackID id: WalletConnectRequestID, url: WalletConnectURL) -> Promise<WalletConnectServer.Callback> {
return firstly {
showSignRawTransaction(title: R.string.localizable.walletConnectSendRawTransactionTitle(), message: rawTransaction)
}.then { shouldSend -> Promise<ConfirmResult> in
guard shouldSend else { return .init(error: DAppError.cancelled) }
let coordinator = SendTransactionCoordinator(session: self.session, keystore: self.keystore, confirmType: .sign)
let coordinator = SendTransactionCoordinator(session: session, keystore: self.keystore, confirmType: .sign)
return coordinator.send(rawTransaction: rawTransaction)
}.map { data in
switch data {
@ -250,18 +251,20 @@ extension WalletConnectCoordinator: WalletConnectServerDelegate {
}
}
private func showConnectToSession(title: String, url: String, completion: @escaping (Bool) -> Void) {
private func showConnectToSession(title: String, url: String, completion: @escaping (WalletConnectServer.ConnectionChoice) -> Void) {
let style: UIAlertController.Style = UIDevice.current.userInterfaceIdiom == .pad ? .alert : .actionSheet
let alertViewController = UIAlertController(title: title, message: url, preferredStyle: style)
let startAction = UIAlertAction(title: R.string.localizable.start(), style: .default) { _ in
completion(true)
let alertViewController = UIAlertController(title: title, message: R.string.localizable.walletConnectStart(url), preferredStyle: style)
for each in serverChoices {
let action = UIAlertAction(title: each.name, style: .default) { _ in
completion(.connect(each))
}
alertViewController.addAction(action)
}
let cancelAction = UIAlertAction(title: R.string.localizable.cancel(), style: .cancel) { _ in
completion(false)
completion(.cancel)
}
alertViewController.addAction(startAction)
alertViewController.addAction(cancelAction)
navigationController.present(alertViewController, animated: true)

@ -17,7 +17,7 @@ enum WalletConnectError: Error {
}
protocol WalletConnectServerDelegate: class {
func server(_ server: WalletConnectServer, shouldConnectFor connection: WalletConnectConnection, completion: @escaping (Bool) -> Void)
func server(_ server: WalletConnectServer, shouldConnectFor connection: WalletConnectConnection, completion: @escaping (WalletConnectServer.ConnectionChoice) -> Void)
func server(_ server: WalletConnectServer, action: WalletConnectServer.Action, request: WalletConnectRequest)
func server(_ server: WalletConnectServer, didFail error: Error)
}
@ -37,10 +37,27 @@ extension WalletConnectSession {
}
class WalletConnectServer {
enum ConnectionChoice {
case connect(RPCServer)
case cancel
var shouldProceeed: Bool {
switch self {
case .connect:
return true
case .cancel:
return false
}
}
struct Configuration {
let wallet: Wallet
let rpcServer: RPCServer
var server: RPCServer? {
switch self {
case .connect(let server):
return server
case .cancel:
return nil
}
}
}
private enum Keys {
@ -50,23 +67,19 @@ class WalletConnectServer {
private let walletMeta = Session.ClientMeta(name: Keys.server, description: nil, icons: [], url: Config.gnosisURL)
private lazy var server: Server = Server(delegate: self)
private var configuration: Configuration
private let config: Config
private let wallet: AlphaWallet.Address
var urlToServer: [WCURL: RPCServer] = .init()
var sessions: Subscribable<[WalletConnectSession]> = Subscribable([])
var connection: Subscribable<WalletConnectServerConnection> = Subscribable(.disconnected)
weak var delegate: WalletConnectServerDelegate?
init(configuration: Configuration, config: Config) {
self.configuration = configuration
self.config = config
init(wallet: AlphaWallet.Address) {
self.wallet = wallet
sessions.value = server.openSessions()
server.register(handler: self)
}
func set(configuration: Configuration) {
self.configuration = configuration
server.register(handler: self)
}
func connect(url: WalletConnectURL) throws {
@ -101,6 +114,17 @@ class WalletConnectServer {
private func peerId(approved: Bool) -> String {
return approved ? UUID().uuidString : String()
}
private func walletInfo(_ wallet: AlphaWallet.Address, choice: WalletConnectServer.ConnectionChoice) -> Session.WalletInfo {
return Session.WalletInfo(
approved: choice.shouldProceeed,
accounts: [wallet.eip55String],
//When there's no server (because user chose to cancel), it shouldn't matter whether the fallback (mainnet) is enabled
chainId: choice.server?.chainID ?? RPCServer.main.chainID,
peerId: peerId(approved: choice.shouldProceeed),
peerMeta: walletMeta
)
}
}
extension WalletConnectServer: RequestHandler {
@ -128,7 +152,8 @@ extension WalletConnectServer: RequestHandler {
return .init(error: WalletConnectError.connectionInvalid)
}
let token = TokensDataStore.token(forServer: configuration.rpcServer)
guard let rpcServer = urlToServer[request.url] else { return .init(error: WalletConnectError.connectionInvalid) }
let token = TokensDataStore.token(forServer: rpcServer)
let transactionType: TransactionType = .dapp(token, session.requester)
do {
@ -166,17 +191,6 @@ extension WalletConnectServer: RequestHandler {
}
extension WalletConnectServer: ServerDelegate {
func walletInfo(_ wallet: Wallet, approved: Bool) -> Session.WalletInfo {
return Session.WalletInfo(
approved: approved,
accounts: [wallet.address.eip55String],
chainId: configuration.rpcServer.chainID,
peerId: peerId(approved: approved),
peerMeta: walletMeta
)
}
func server(_ server: Server, didFailToConnect url: WalletConnectURL) {
DispatchQueue.main.async {
guard var sessions = self.sessions.value, let delegate = self.delegate else { return }
@ -199,22 +213,13 @@ extension WalletConnectServer: ServerDelegate {
if let delegate = self.delegate {
let connection = WalletConnectConnection(dAppInfo: session.dAppInfo, url: session.url.absoluteString)
delegate.server(self, shouldConnectFor: connection) { [weak self] isApproved in
delegate.server(self, shouldConnectFor: connection) { [weak self] choice in
guard let strongSelf = self else { return }
print(session)
if let chainIdToConnect = session.walletInfo?.chainId {
let rpcServer = RPCServer(chainID: chainIdToConnect)
guard strongSelf.config.enabledServers.contains(rpcServer) else { return }
}
let info = strongSelf.walletInfo(strongSelf.configuration.wallet, approved: isApproved)
let info = strongSelf.walletInfo(strongSelf.wallet, choice: choice)
completion(info)
}
} else {
let info = self.walletInfo(self.configuration.wallet, approved: false)
let info = self.walletInfo(self.wallet, choice: .cancel)
completion(info)
}
}

@ -7,7 +7,10 @@ extension WalletConnectCoordinator {
static func fake() -> WalletConnectCoordinator {
let keystore = FakeEtherKeystore()
return .init(keystore: keystore, configuration: .init(wallet: .make(), rpcServer: .main), navigationController: .init(), analyticsCoordinator: nil, config: .make())
var sessions = ServerDictionary<WalletSession>()
let session = WalletSession.make()
sessions[session.server] = session
return .init(keystore: keystore, sessions: sessions, navigationController: .init(), analyticsCoordinator: nil, config: .make())
}
}
@ -42,7 +45,7 @@ class ConfigTests: XCTestCase {
XCTAssertEqual(vc1.title, "Wallet")
Config.setLocale(AppLocale.simplifiedChinese)
let vc2 = TokensViewController(
sessions: sessions,
account: .make(),

Loading…
Cancel
Save