Merge branch 'main'

This commit is contained in:
Vadim Zakharenko 2021-08-11 11:10:54 +03:00
commit f60c334f30
13 changed files with 239 additions and 79 deletions

View File

@ -38,6 +38,8 @@
2C901C4A2689F01700D0926A /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C901C492689F01700D0926A /* Strings.swift */; };
2C901C4D268A033100D0926A /* GasService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C901C4C268A033100D0926A /* GasService.swift */; };
2C917429267D2A6E00049075 /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C917428267D2A6E00049075 /* Keychain.swift */; };
2C9F0B6526BDC9AF008FA3D6 /* EthereumNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C9F0B6426BDC9AF008FA3D6 /* EthereumNetwork.swift */; };
2C9F0B6826BDCB2E008FA3D6 /* EthereumChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C9F0B6726BDCB2E008FA3D6 /* EthereumChain.swift */; };
2CC0CDBE2692027E0072922A /* PriceService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CC0CDBD2692027E0072922A /* PriceService.swift */; };
2CC8946F269A2E8C00879245 /* SessionStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CC8946E269A2E8C00879245 /* SessionStorage.swift */; };
2CC89471269A334A00879245 /* UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CC89470269A334A00879245 /* UserDefaults.swift */; };
@ -84,6 +86,8 @@
2C901C492689F01700D0926A /* Strings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Strings.swift; sourceTree = "<group>"; };
2C901C4C268A033100D0926A /* GasService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GasService.swift; sourceTree = "<group>"; };
2C917428267D2A6E00049075 /* Keychain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keychain.swift; sourceTree = "<group>"; };
2C9F0B6426BDC9AF008FA3D6 /* EthereumNetwork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthereumNetwork.swift; sourceTree = "<group>"; };
2C9F0B6726BDCB2E008FA3D6 /* EthereumChain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthereumChain.swift; sourceTree = "<group>"; };
2CC0CDBD2692027E0072922A /* PriceService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PriceService.swift; sourceTree = "<group>"; };
2CC8946E269A2E8C00879245 /* SessionStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionStorage.swift; sourceTree = "<group>"; };
2CC89470269A334A00879245 /* UserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaults.swift; sourceTree = "<group>"; };
@ -189,6 +193,8 @@
isa = PBXGroup;
children = (
2C1995552674D0F300A8E370 /* Ethereum.swift */,
2C9F0B6426BDC9AF008FA3D6 /* EthereumNetwork.swift */,
2C9F0B6726BDCB2E008FA3D6 /* EthereumChain.swift */,
2CE3D014267F73E80032A62E /* LegacyAccountWithKey.swift */,
2CE3D011267F73C00032A62E /* Transaction.swift */,
);
@ -396,11 +402,13 @@
2CC0CDBE2692027E0072922A /* PriceService.swift in Sources */,
2C8A09C6267513FC00993638 /* Agent.swift in Sources */,
2C8A09D42675184700993638 /* Window.swift in Sources */,
2C9F0B6526BDC9AF008FA3D6 /* EthereumNetwork.swift in Sources */,
2C208A9F26813408005BA500 /* Secrets.swift in Sources */,
2CC8946F269A2E8C00879245 /* SessionStorage.swift in Sources */,
0DC850E726B73A5900809E82 /* AuthenticationReason.swift in Sources */,
2CD0669126B5537B00728C20 /* InkWallet.swift in Sources */,
2C8A09D726751A0C00993638 /* WalletConnect.swift in Sources */,
2C9F0B6826BDCB2E008FA3D6 /* EthereumChain.swift in Sources */,
2C03D1D2269B407900EF10EA /* NetworkMonitor.swift in Sources */,
2C8A09E326757FC000993638 /* AccountCellView.swift in Sources */,
2C6B964C26B9D92500D2C819 /* NSColor.swift in Sources */,
@ -570,7 +578,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 23;
CURRENT_PROJECT_VERSION = 26;
DEVELOPMENT_TEAM = XWNXDSM6BU;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = "$(SRCROOT)/Encrypted Ink/Supporting Files/Info.plist";
@ -579,7 +587,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.15;
MARKETING_VERSION = 1.0.4;
MARKETING_VERSION = 1.0.7;
PRODUCT_BUNDLE_IDENTIFIER = ink.encrypted.macos;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -597,7 +605,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 23;
CURRENT_PROJECT_VERSION = 26;
DEVELOPMENT_TEAM = XWNXDSM6BU;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = "$(SRCROOT)/Encrypted Ink/Supporting Files/Info.plist";
@ -606,7 +614,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.15;
MARKETING_VERSION = 1.0.4;
MARKETING_VERSION = 1.0.7;
PRODUCT_BUNDLE_IDENTIFIER = ink.encrypted.macos;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";

View File

@ -77,9 +77,9 @@ class Agent: NSObject {
windowController.contentViewController = accountsList
}
func showApprove(transaction: Transaction, peerMeta: WCPeerMeta?, completion: @escaping (Transaction?) -> Void) {
func showApprove(transaction: Transaction, chain: EthereumChain, peerMeta: WCPeerMeta?, completion: @escaping (Transaction?) -> Void) {
let windowController = Window.showNew()
let approveViewController = ApproveTransactionViewController.with(transaction: transaction, peerMeta: peerMeta) { [weak self] transaction in
let approveViewController = ApproveTransactionViewController.with(transaction: transaction, chain: chain, peerMeta: peerMeta) { [weak self] transaction in
if transaction != nil {
self?.askAuthentication(on: windowController.window, onStart: false, reason: .sendTransaction) { success in
completion(success ? transaction : nil)
@ -119,7 +119,7 @@ class Agent: NSObject {
showInitialScreen(wcSession: session)
}
func getWalletSelectionCompletionIfShouldSelect() -> ((InkWallet) -> Void)? {
func getWalletSelectionCompletionIfShouldSelect() -> ((Int, InkWallet) -> Void)? {
let session = getSessionFromPasteboard()
return onSelectedWallet(session: session)
}
@ -131,7 +131,7 @@ class Agent: NSObject {
let howToItem = NSMenuItem(title: "How to WalletConnect?", action: #selector(showInstructionsAlert), keyEquivalent: "")
let mailItem = NSMenuItem(title: "Drop us a line…", action: #selector(didSelectMailMenuItem), keyEquivalent: "")
let githubItem = NSMenuItem(title: "View on GitHub…", action: #selector(didSelectGitHubMenuItem), keyEquivalent: "")
let twitterItem = NSMenuItem(title: "Follow on Twitter…", action: #selector(didSelectTwitterMenuItem), keyEquivalent: "")
let twitterItem = NSMenuItem(title: "View on Twitter…", action: #selector(didSelectTwitterMenuItem), keyEquivalent: "")
let quitItem = NSMenuItem(title: "Quit", action: #selector(didSelectQuitMenuItem), keyEquivalent: "q")
showItem.attributedTitle = NSAttributedString(string: "👀 Show Encrypted Ink", attributes: [.font: NSFont.systemFont(ofSize: 15, weight: .semibold)])
@ -222,10 +222,10 @@ class Agent: NSObject {
}
}
private func onSelectedWallet(session: WCSession?) -> ((InkWallet) -> Void)? {
private func onSelectedWallet(session: WCSession?) -> ((Int, InkWallet) -> Void)? {
guard let session = session else { return nil }
return { [weak self] wallet in
self?.connectWallet(session: session, wallet: wallet)
return { [weak self] chainId, wallet in
self?.connectWallet(session: session, chainId: chainId, wallet: wallet)
}
}
@ -284,12 +284,12 @@ class Agent: NSObject {
}
}
private func connectWallet(session: WCSession, wallet: InkWallet) {
private func connectWallet(session: WCSession, chainId: Int, wallet: InkWallet) {
let windowController = Window.showNew()
let window = windowController.window
windowController.contentViewController = WaitingViewController.withReason("Connecting")
WalletConnect.shared.connect(session: session, walletId: wallet.id) { [weak window] _ in
WalletConnect.shared.connect(session: session, chainId: chainId, walletId: wallet.id) { [weak window] _ in
if window?.isVisible == true {
Window.closeAllAndActivateBrowser()
}

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="18122" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="17701" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="18122"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17701"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
@ -818,13 +818,13 @@ DQ
</textFieldCell>
</textField>
<scrollView autohidesScrollers="YES" horizontalLineScroll="40" horizontalPageScroll="0.0" verticalLineScroll="40" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" horizontalScrollElasticity="none" translatesAutoresizingMaskIntoConstraints="NO" id="7bs-Kr-ija">
<rect key="frame" x="0.0" y="0.0" width="250" height="272"/>
<rect key="frame" x="0.0" y="0.0" width="250" height="247"/>
<clipView key="contentView" ambiguous="YES" id="RjU-hi-SHx">
<rect key="frame" x="1" y="1" width="248" height="270"/>
<rect key="frame" x="1" y="1" width="248" height="245"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView verticalHuggingPriority="750" ambiguous="YES" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" columnReordering="NO" columnResizing="NO" multipleSelection="NO" autosaveColumns="NO" typeSelect="NO" rowHeight="40" rowSizeStyle="automatic" viewBased="YES" id="glA-FK-Kdd" customClass="RightClickTableView" customModule="Encrypted_Ink" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="248" height="270"/>
<rect key="frame" x="0.0" y="0.0" width="248" height="245"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
@ -928,21 +928,49 @@ DQ
<action selector="addButtonTapped:" target="29s-Rd-OUf" id="MKz-Ok-RUY"/>
</connections>
</button>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="YAr-gV-Zhp">
<rect key="frame" x="0.0" y="247" width="250" height="40"/>
<subviews>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="WBh-Qb-dhA">
<rect key="frame" x="47" y="6" width="157" height="25"/>
<popUpButtonCell key="cell" type="push" bezelStyle="rounded" alignment="center" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" id="A9Y-mW-lqk">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="bIv-hX-xgQ"/>
</popUpButtonCell>
<connections>
<action selector="chainButtonSelectionChanged:" target="29s-Rd-OUf" id="saG-Ti-VJD"/>
</connections>
</popUpButton>
</subviews>
<constraints>
<constraint firstItem="WBh-Qb-dhA" firstAttribute="leading" secondItem="YAr-gV-Zhp" secondAttribute="leading" constant="50" id="9XO-wX-7KM"/>
<constraint firstAttribute="height" constant="40" id="HaA-yk-eCj"/>
<constraint firstItem="WBh-Qb-dhA" firstAttribute="centerX" secondItem="YAr-gV-Zhp" secondAttribute="centerX" id="qJ6-QA-Eul"/>
<constraint firstItem="WBh-Qb-dhA" firstAttribute="centerY" secondItem="YAr-gV-Zhp" secondAttribute="centerY" id="yed-DU-Dih"/>
</constraints>
</customView>
</subviews>
<constraints>
<constraint firstItem="ngQ-Bn-Kwd" firstAttribute="leading" secondItem="dkh-kG-EFj" secondAttribute="trailing" constant="10" id="2jH-ov-jln"/>
<constraint firstItem="7bs-Kr-ija" firstAttribute="leading" secondItem="Yjc-Zm-uZY" secondAttribute="leading" id="3n0-jB-KwV"/>
<constraint firstAttribute="trailing" secondItem="7bs-Kr-ija" secondAttribute="trailing" id="6m3-S7-WJp"/>
<constraint firstAttribute="trailing" secondItem="ngQ-Bn-Kwd" secondAttribute="trailing" constant="12" id="Ln4-b5-NfT"/>
<constraint firstItem="7bs-Kr-ija" firstAttribute="top" secondItem="YAr-gV-Zhp" secondAttribute="bottom" id="N0R-Qm-X3z"/>
<constraint firstItem="ngQ-Bn-Kwd" firstAttribute="firstBaseline" secondItem="dkh-kG-EFj" secondAttribute="firstBaseline" id="RDE-f4-Heb"/>
<constraint firstItem="YAr-gV-Zhp" firstAttribute="top" secondItem="dkh-kG-EFj" secondAttribute="bottom" constant="5" id="eSI-xz-s5L"/>
<constraint firstItem="YAr-gV-Zhp" firstAttribute="centerX" secondItem="Yjc-Zm-uZY" secondAttribute="centerX" id="ju9-P1-aBa"/>
<constraint firstItem="YAr-gV-Zhp" firstAttribute="width" secondItem="Yjc-Zm-uZY" secondAttribute="width" id="mzg-kX-V5F"/>
<constraint firstItem="dkh-kG-EFj" firstAttribute="top" secondItem="Yjc-Zm-uZY" secondAttribute="top" constant="24" id="oaa-yR-g0U"/>
<constraint firstItem="dkh-kG-EFj" firstAttribute="centerX" secondItem="Yjc-Zm-uZY" secondAttribute="centerX" id="qoF-Dr-xWA"/>
<constraint firstItem="7bs-Kr-ija" firstAttribute="top" secondItem="dkh-kG-EFj" secondAttribute="bottom" constant="20" id="w9v-GA-ZtN"/>
<constraint firstAttribute="bottom" secondItem="7bs-Kr-ija" secondAttribute="bottom" id="zCE-cx-u4r"/>
</constraints>
</view>
<connections>
<outlet property="addButton" destination="ngQ-Bn-Kwd" id="FlT-oq-C5e"/>
<outlet property="chainButton" destination="WBh-Qb-dhA" id="Fyv-PN-1Ln"/>
<outlet property="chainButtonContainer" destination="YAr-gV-Zhp" id="0qy-nB-KfB"/>
<outlet property="chainButtonHeightConstraint" destination="HaA-yk-eCj" id="2KM-mU-OO6"/>
<outlet property="tableView" destination="glA-FK-Kdd" id="9aW-Qr-UuF"/>
<outlet property="titleLabel" destination="dkh-kG-EFj" id="xDn-bP-HWG"/>
</connections>
@ -1599,11 +1627,13 @@ DQ
<connections>
<outlet property="fastSpeedLabel" destination="WJV-V3-GWm" id="lzX-wu-MdJ"/>
<outlet property="gweiLabel" destination="xby-TM-Wec" id="3LN-nr-WNA"/>
<outlet property="infoTextViewBottomConstraint" destination="fxj-xB-6jl" id="f2p-uL-Wac"/>
<outlet property="metaTextView" destination="EuZ-5b-EGk" id="UyV-K8-7Og"/>
<outlet property="okButton" destination="JBO-6Q-5eC" id="JHm-Kl-fys"/>
<outlet property="peerLogoImageView" destination="g8t-xf-H1l" id="CqE-7d-LAU"/>
<outlet property="peerNameLabel" destination="Ji7-6C-gUp" id="J9c-ml-tXd"/>
<outlet property="slowSpeedLabel" destination="Jcz-Um-efA" id="TBN-lQ-Njc"/>
<outlet property="speedContainerStackView" destination="pLD-2z-PFP" id="PV3-l7-3pR"/>
<outlet property="speedSlider" destination="oWb-y6-1VO" id="cLg-Yg-DPd"/>
<outlet property="titleLabel" destination="g2G-lh-X1F" id="NWt-Lq-go1"/>
</connections>

View File

@ -19,10 +19,7 @@ struct Ethereum {
static let shared = Ethereum()
private let network: Network = AlchemyNetwork(
chain: "mainnet",
apiKey: Secrets.alchemy
)
private let networks = EthereumNetwork.allByChain
func sign(message: String, wallet: InkWallet) throws -> String {
guard let privateKeyString = wallet.ethereumPrivateKeyString else { throw Error.keyNotFound }
@ -58,8 +55,9 @@ struct Ethereum {
return signed
}
func send(transaction: Transaction, wallet: InkWallet) throws -> String {
let bytes = try signedTransactionBytes(transaction: transaction, wallet: wallet)
func send(transaction: Transaction, wallet: InkWallet, chain: EthereumChain) throws -> String {
guard let network = networks[chain] else { throw Error.invalidInputData }
let bytes = try signedTransactionBytes(transaction: transaction, wallet: wallet, chain: chain)
let response = try SendRawTransactionProcedure(network: network, transactionBytes: bytes).call()
guard let hash = response["result"].string else {
throw Error.failedToSendTransaction
@ -67,7 +65,8 @@ struct Ethereum {
return hash
}
private func signedTransactionBytes(transaction: Transaction, wallet: InkWallet) throws -> EthContractCallBytes {
private func signedTransactionBytes(transaction: Transaction, wallet: InkWallet, chain: EthereumChain) throws -> EthContractCallBytes {
guard let network = networks[chain] else { throw Error.invalidInputData }
guard let privateKeyString = wallet.ethereumPrivateKeyString else { throw Error.keyNotFound }
let senderKey = EthPrivateKey(hex: privateKeyString)
let contractAddress = EthAddress(hex: transaction.to)
@ -106,11 +105,11 @@ struct Ethereum {
return bytes
}
func prepareTransaction(_ transaction: Transaction, completion: @escaping (Transaction) -> Void) {
func prepareTransaction(_ transaction: Transaction, chain: EthereumChain, completion: @escaping (Transaction) -> Void) {
var transaction = transaction
if transaction.nonce == nil {
getNonce(from: transaction.from) { nonce in
getNonce(chain: chain, from: transaction.from) { nonce in
transaction.nonce = nonce
completion(transaction)
}
@ -118,7 +117,7 @@ struct Ethereum {
func getGasIfNeeded(gasPrice: String) {
guard transaction.gas == nil else { return }
getGas(from: transaction.from, to: transaction.to, gasPrice: gasPrice, weiAmount: transaction.weiAmount, data: transaction.data) { gas in
getGas(chain: chain, from: transaction.from, to: transaction.to, gasPrice: gasPrice, weiAmount: transaction.weiAmount, data: transaction.data) { gas in
transaction.gas = gas
completion(transaction)
}
@ -127,7 +126,7 @@ struct Ethereum {
if let gasPrice = transaction.gasPrice {
getGasIfNeeded(gasPrice: gasPrice)
} else {
getGasPrice { gasPrice in
getGasPrice(chain: chain) { gasPrice in
transaction.gasPrice = gasPrice
completion(transaction)
if let gasPrice = gasPrice {
@ -138,7 +137,8 @@ struct Ethereum {
}
private func getGas(from: String, to: String, gasPrice: String, weiAmount: EthNumber, data: String, completion: @escaping (String?) -> Void) {
private func getGas(chain: EthereumChain, from: String, to: String, gasPrice: String, weiAmount: EthNumber, data: String, completion: @escaping (String?) -> Void) {
guard let network = networks[chain] else { return }
queue.async {
let gas = try? EthGasEstimate(
network: network,
@ -162,7 +162,8 @@ struct Ethereum {
}
}
private func getGasPrice(completion: @escaping (String?) -> Void) {
private func getGasPrice(chain: EthereumChain, completion: @escaping (String?) -> Void) {
guard let network = networks[chain] else { return }
queue.async {
let gasPrice = try? EthGasPrice(network: network).value().toHexString()
DispatchQueue.main.async {
@ -171,7 +172,8 @@ struct Ethereum {
}
}
private func getNonce(from: String, completion: @escaping (String?) -> Void) {
private func getNonce(chain: EthereumChain, from: String, completion: @escaping (String?) -> Void) {
guard let network = networks[chain] else { return }
queue.async {
let nonce = try? EthTransactions(network: network, address: EthAddress(hex: from), blockChainState: PendingBlockChainState()).count().value().toHexString()
DispatchQueue.main.async {

View File

@ -0,0 +1,33 @@
// Copyright © 2021 Encrypted Ink. All rights reserved.
import Foundation
enum EthereumChain: Int {
case main = 1
case arbitrum = 42161
case polygon = 137
case optimism = 10
case binance = 56
var id: Int {
return rawValue
}
static let all: [EthereumChain] = [.main, .polygon, .optimism, .binance]
var name: String {
switch self {
case .main:
return "Ethereum Mainnet"
case .arbitrum:
return "Arbitrum"
case .optimism:
return "Optimism"
case .polygon:
return "Polygon"
case .binance:
return "Binance Smart Chain"
}
}
}

View File

@ -0,0 +1,30 @@
// Copyright © 2021 Encrypted Ink. All rights reserved.
import Foundation
import Web3Swift
final class EthereumNetwork: Network {
static let allByChain: [EthereumChain: Network] = [
.main: EthereumNetwork(url: "https://eth-mainnet.alchemyapi.io/v2/" + Secrets.alchemy),
.polygon: EthereumNetwork(url: "https://polygon-mainnet.g.alchemy.com/v2/" + Secrets.alchemy),
.arbitrum: EthereumNetwork(url: "https://arb-mainnet.g.alchemy.com/v2/" + Secrets.alchemy),
.optimism: EthereumNetwork(url: "https://mainnet.optimism.io"),
.binance: EthereumNetwork(url: "https://bsc-dataseed.binance.org/")
]
private let origin: GethNetwork
init(url: String) {
origin = GethNetwork(url: url)
}
func id() throws -> IntegerScalar {
return try origin.id()
}
func call(method: String, params: [EthParameter]) throws -> Data {
return try origin.call(method: method, params: params)
}
}

View File

@ -31,24 +31,39 @@ struct Transaction {
return Int(current)
}
func description(ethPrice: Double?) -> String {
func description(chain: EthereumChain, ethPrice: Double?) -> String {
let symbol: String
let showUSDPrice: Bool
switch chain {
case .main, .arbitrum, .optimism:
symbol = "ETH"
showUSDPrice = true
case .polygon:
symbol = "MATIC"
showUSDPrice = false
case .binance:
symbol = "BNB"
showUSDPrice = false
}
let fee: String
if let gasPrice = gasPrice,
let gas = gas,
let a = try? EthNumber(hex: gasPrice).value().toNormalizedDecimal(power: 18),
let b = try? EthNumber(hex: gas).value().toDecimal() {
let c = NSDecimalNumber(decimal: a).multiplying(by: NSDecimalNumber(decimal: b))
let costString = cost(value: c, price: ethPrice)
fee = c.stringValue.prefix(7) + " ETH" + costString
let costString = showUSDPrice ? cost(value: c, price: ethPrice) : ""
fee = c.stringValue.prefix(7) + " \(symbol)" + costString
} else {
fee = "Calculating…"
}
var result = [String]()
if let decimal = try? weiAmount.value().toNormalizedDecimal(power: 18) {
let decimalNumber = NSDecimalNumber(decimal: decimal)
let costString = cost(value: decimalNumber, price: ethPrice)
let costString = showUSDPrice ? cost(value: decimalNumber, price: ethPrice) : ""
if let value = ethString(decimalNumber: decimalNumber) {
result.append("\(value) ETH" + costString)
result.append("\(value) \(symbol)" + costString)
}
}
result.append("Fee: " + fee)

View File

@ -8,7 +8,8 @@ class AccountsListViewController: NSViewController {
private let walletsManager = WalletsManager.shared
private var cellModels = [CellModel]()
var onSelectedWallet: ((InkWallet) -> Void)?
private var chain = EthereumChain.main
var onSelectedWallet: ((Int, InkWallet) -> Void)?
var newWalletId: String?
enum CellModel {
@ -29,6 +30,9 @@ class AccountsListViewController: NSViewController {
}
}
@IBOutlet weak var chainButtonHeightConstraint: NSLayoutConstraint!
@IBOutlet weak var chainButtonContainer: NSView!
@IBOutlet weak var chainButton: NSPopUpButton!
@IBOutlet weak var addButton: NSButton! {
didSet {
let menu = NSMenu()
@ -53,7 +57,7 @@ class AccountsListViewController: NSViewController {
super.viewDidLoad()
setupAccountsMenu()
reloadTitle()
reloadHeader()
updateCellModels()
NotificationCenter.default.addObserver(self, selector: #selector(didBecomeActive), name: NSApplication.didBecomeActiveNotification, object: nil)
}
@ -76,9 +80,15 @@ class AccountsListViewController: NSViewController {
tableView.menu = menu
}
private func reloadTitle() {
titleLabel.stringValue = onSelectedWallet != nil && !wallets.isEmpty ? "Select\nAccount" : "Accounts"
private func reloadHeader() {
let canSelectAccount = onSelectedWallet != nil && !wallets.isEmpty
titleLabel.stringValue = canSelectAccount ? "Select\nAccount" : "Accounts"
addButton.isHidden = wallets.isEmpty
chainButtonHeightConstraint.constant = canSelectAccount ? 40 : 15
chainButtonContainer.isHidden = !canSelectAccount
if canSelectAccount, chainButton.numberOfItems == 0 {
chainButton.addItems(withTitles: EthereumChain.all.map { $0.name })
}
}
@objc private func didBecomeActive() {
@ -86,7 +96,14 @@ class AccountsListViewController: NSViewController {
if let completion = agent.getWalletSelectionCompletionIfShouldSelect() {
onSelectedWallet = completion
}
reloadTitle()
reloadHeader()
}
@IBAction func chainButtonSelectionChanged(_ sender: Any) {
guard let selectedItem = chainButton.selectedItem else { return }
let index = chainButton.index(of: selectedItem)
guard index >= 0 && index < EthereumChain.all.count else { return }
chain = EthereumChain.all[index]
}
@IBAction func addButtonTapped(_ sender: NSButton) {
@ -107,13 +124,25 @@ class AccountsListViewController: NSViewController {
}
@objc private func didClickCreateAccount() {
let wallet = try? walletsManager.createWallet()
newWalletId = wallet?.id
reloadTitle()
let alert = Alert()
alert.messageText = "Back up new account."
alert.informativeText = "You will see 12 secret words."
alert.alertStyle = .critical
alert.addButton(withTitle: "OK")
alert.addButton(withTitle: "Cancel")
if alert.runModal() == .alertFirstButtonReturn {
createNewAccountAndShowSecretWords()
}
}
private func createNewAccountAndShowSecretWords() {
guard let wallet = try? walletsManager.createWallet() else { return }
newWalletId = wallet.id
reloadHeader()
updateCellModels()
tableView.reloadData()
blinkNewWalletCellIfNeeded()
// TODO: show backup phrase
showKey(wallet: wallet, mnemonic: true)
}
private func blinkNewWalletCellIfNeeded() {
@ -184,8 +213,10 @@ class AccountsListViewController: NSViewController {
}
private func showKey(index: Int, mnemonic: Bool) {
let wallet = wallets[index]
showKey(wallet: wallets[index], mnemonic: mnemonic)
}
private func showKey(wallet: InkWallet, mnemonic: Bool) {
let secret: String
if mnemonic, let mnemonicString = try? walletsManager.exportMnemonic(wallet: wallet) {
secret = mnemonicString
@ -213,7 +244,7 @@ class AccountsListViewController: NSViewController {
private func removeAccountAtIndex(_ index: Int) {
let wallet = wallets[index]
try? walletsManager.delete(wallet: wallet)
reloadTitle()
reloadHeader()
updateCellModels()
tableView.reloadData()
}
@ -240,7 +271,7 @@ extension AccountsListViewController: NSTableViewDelegate {
case .wallet:
let wallet = wallets[row]
if let onSelectedWallet = onSelectedWallet {
onSelectedWallet(wallet)
onSelectedWallet(chain.id, wallet)
} else {
Timer.scheduledTimer(withTimeInterval: 0.01, repeats: false) { [weak self] _ in
var point = NSEvent.mouseLocation

View File

@ -6,7 +6,10 @@ import Kingfisher
class ApproveTransactionViewController: NSViewController {
@IBOutlet weak var infoTextViewBottomConstraint: NSLayoutConstraint!
@IBOutlet weak var speedContainerStackView: NSStackView!
@IBOutlet weak var gweiLabel: NSTextField!
@IBOutlet weak var titleLabel: NSTextField!
@IBOutlet var metaTextView: NSTextView!
@IBOutlet weak var okButton: NSButton!
@ -26,12 +29,14 @@ class ApproveTransactionViewController: NSViewController {
private let priceService = PriceService.shared
private var currentGasInfo: GasService.Info?
private var transaction: Transaction!
private var chain: EthereumChain!
private var completion: ((Transaction?) -> Void)!
private var didEnableSpeedConfiguration = false
private var peerMeta: WCPeerMeta?
static func with(transaction: Transaction, peerMeta: WCPeerMeta?, completion: @escaping (Transaction?) -> Void) -> ApproveTransactionViewController {
static func with(transaction: Transaction, chain: EthereumChain, peerMeta: WCPeerMeta?, completion: @escaping (Transaction?) -> Void) -> ApproveTransactionViewController {
let new = instantiate(ApproveTransactionViewController.self)
new.chain = chain
new.transaction = transaction
new.completion = completion
new.peerMeta = peerMeta
@ -58,15 +63,21 @@ class ApproveTransactionViewController: NSViewController {
}
private func prepareTransaction() {
ethereum.prepareTransaction(transaction) { [weak self] updated in
ethereum.prepareTransaction(transaction, chain: chain) { [weak self] updated in
self?.transaction = updated
self?.updateInterface()
}
}
private func updateInterface() {
if chain != .main {
speedContainerStackView.isHidden = true
gweiLabel.isHidden = true
infoTextViewBottomConstraint.constant = 30
}
enableSpeedConfigurationIfNeeded()
let meta = transaction.description(ethPrice: priceService.currentPrice)
let meta = transaction.description(chain: chain, ethPrice: priceService.currentPrice)
if metaTextView.string != meta {
metaTextView.string = meta
}

View File

@ -5,7 +5,7 @@ import Cocoa
class ImportViewController: NSViewController {
private let walletsManager = WalletsManager.shared
var onSelectedWallet: ((InkWallet) -> Void)?
var onSelectedWallet: ((Int, InkWallet) -> Void)?
private var inputValidationResult = WalletsManager.InputValidationResult.invalid
@IBOutlet weak var textField: NSTextField! {

View File

@ -7,6 +7,7 @@ class SessionStorage {
struct Item: Codable {
let session: WCSession
let chainId: Int?
let walletId: String
let clientId: String
let sessionDetails: WCSessionRequestParam
@ -51,8 +52,8 @@ class SessionStorage {
}
}
func add(interactor: WCInteractor, walletId: String, sessionDetails: WCSessionRequestParam) {
let item = Item(session: interactor.session, walletId: walletId, clientId: interactor.clientId, sessionDetails: sessionDetails)
func add(interactor: WCInteractor, chainId: Int, walletId: String, sessionDetails: WCSessionRequestParam) {
let item = Item(session: interactor.session, chainId: chainId, walletId: walletId, clientId: interactor.clientId, sessionDetails: sessionDetails)
WCSessionStore.store(interactor.session, peerId: sessionDetails.peerId, peerMeta: sessionDetails.peerMeta)
Defaults.storedSessions[interactor.clientId] = item
didInteractWith(clientId: interactor.clientId)

View File

@ -24,10 +24,10 @@ class WalletConnect {
return WCSession.from(string: link)
}
func connect(session: WCSession, walletId: String, uuid: UUID = UUID(), completion: @escaping ((Bool) -> Void)) {
func connect(session: WCSession, chainId: Int, walletId: String, uuid: UUID = UUID(), completion: @escaping ((Bool) -> Void)) {
let clientMeta = WCPeerMeta(name: "Encrypted Ink", url: "https://encrypted.ink", description: "Ethereum agent for macOS", icons: ["https://encrypted.ink/icon.png"])
let interactor = WCInteractor(session: session, meta: clientMeta, uuid: uuid)
configure(interactor: interactor, walletId: walletId)
configure(interactor: interactor, chainId: chainId, walletId: walletId)
interactor.connect().done { connected in
completion(connected)
@ -45,7 +45,7 @@ class WalletConnect {
for item in items {
guard let uuid = UUID(uuidString: item.clientId) else { continue }
connect(session: item.session, walletId: item.walletId, uuid: uuid) { _ in }
connect(session: item.session, chainId: item.chainId ?? 1, walletId: item.walletId, uuid: uuid) { _ in }
peers[item.clientId] = item.sessionDetails.peerMeta
}
}
@ -83,17 +83,16 @@ class WalletConnect {
return peers[id]
}
private func configure(interactor: WCInteractor, walletId: String) {
private func configure(interactor: WCInteractor, chainId: Int, walletId: String) {
guard let address = walletsManager.getWallet(id: walletId)?.ethereumAddress else { return }
let accounts = [address]
let chainId = 1
interactor.onError = { _ in }
interactor.onSessionRequest = { [weak self, weak interactor] (id, peerParam) in
guard let interactor = interactor else { return }
self?.peers[interactor.clientId] = peerParam.peerMeta
self?.sessionStorage.add(interactor: interactor, walletId: walletId, sessionDetails: peerParam)
self?.sessionStorage.add(interactor: interactor, chainId: chainId, walletId: walletId, sessionDetails: peerParam)
interactor.approveSession(accounts: accounts, chainId: chainId).cauterize()
}
@ -109,7 +108,7 @@ class WalletConnect {
}
interactor.eth.onTransaction = { [weak self, weak interactor] (id, event, transaction) in
self?.approveTransaction(id: id, wct: transaction, walletId: walletId, interactor: interactor)
self?.approveTransaction(id: id, wct: transaction, walletId: walletId, chainId: chainId, interactor: interactor)
self?.sessionStorage.didInteractWith(clientId: interactor?.clientId)
}
}
@ -125,17 +124,17 @@ class WalletConnect {
}
}
private func approveTransaction(id: Int64, wct: WCEthereumTransaction, walletId: String, interactor: WCInteractor?) {
guard let to = wct.to else {
private func approveTransaction(id: Int64, wct: WCEthereumTransaction, walletId: String, chainId: Int, interactor: WCInteractor?) {
guard let to = wct.to, let chain = EthereumChain(rawValue: chainId) else {
rejectRequest(id: id, interactor: interactor, message: "Something went wrong.")
return
}
let peer = getPeerOfInteractor(interactor)
let transaction = Transaction(from: wct.from, to: to, nonce: wct.nonce, gasPrice: wct.gasPrice, gas: wct.gas, value: wct.value, data: wct.data)
Agent.shared.showApprove(transaction: transaction, peerMeta: peer) { [weak self, weak interactor] transaction in
Agent.shared.showApprove(transaction: transaction, chain: chain, peerMeta: peer) { [weak self, weak interactor] transaction in
if let transaction = transaction {
self?.sendTransaction(transaction, walletId: walletId, requestId: id, interactor: interactor)
self?.sendTransaction(transaction, walletId: walletId, chainId: chainId, requestId: id, interactor: interactor)
} else {
self?.rejectRequest(id: id, interactor: interactor, message: "Cancelled")
}
@ -174,12 +173,12 @@ class WalletConnect {
interactor?.rejectRequest(id: id, message: message).cauterize()
}
private func sendTransaction(_ transaction: Transaction, walletId: String, requestId: Int64, interactor: WCInteractor?) {
guard let wallet = walletsManager.getWallet(id: walletId) else {
private func sendTransaction(_ transaction: Transaction, walletId: String, chainId: Int, requestId: Int64, interactor: WCInteractor?) {
guard let wallet = walletsManager.getWallet(id: walletId), let chain = EthereumChain(rawValue: chainId) else {
rejectRequest(id: requestId, interactor: interactor, message: "Something went wrong.")
return
}
guard let hash = try? ethereum.send(transaction: transaction, wallet: wallet) else {
guard let hash = try? ethereum.send(transaction: transaction, wallet: wallet, chain: chain) else {
rejectRequest(id: requestId, interactor: interactor, message: "Failed to send")
return
}

View File

@ -2,7 +2,7 @@ PODS:
- BigInt (5.2.0)
- BlockiesSwift (0.1.2)
- CryptoSwift (1.4.1)
- Kingfisher (6.3.0)
- Kingfisher (6.3.1)
- PromiseKit (6.15.3):
- PromiseKit/CorePromise (= 6.15.3)
- PromiseKit/Foundation (= 6.15.3)
@ -14,11 +14,11 @@ PODS:
- Starscream (3.1.1)
- SwiftProtobuf (1.17.0)
- SwiftyJSON (4.3.0)
- TrustWalletCore (2.6.16):
- TrustWalletCore/Core (= 2.6.16)
- TrustWalletCore/Core (2.6.16):
- TrustWalletCore (2.6.17):
- TrustWalletCore/Core (= 2.6.17)
- TrustWalletCore/Core (2.6.17):
- TrustWalletCore/Types
- TrustWalletCore/Types (2.6.16):
- TrustWalletCore/Types (2.6.17):
- SwiftProtobuf
- WalletConnect (0.1.0):
- CryptoSwift
@ -70,13 +70,13 @@ SPEC CHECKSUMS:
BigInt: f668a80089607f521586bbe29513d708491ef2f7
BlockiesSwift: 22d8d56dd187e6bfd16cb8c8fbd4fd4896c3e65d
CryptoSwift: 0bc800a7e6a24c4fc9ebeab97d44b0d5f73a78bd
Kingfisher: 6c3df386db71d82c0817a429d2c9421a77396529
Kingfisher: 016c8b653a35add51dd34a3aba36b580041acc74
PromiseKit: 3b2b6995e51a954c46dbc550ce3da44fbfb563c5
secp256k1.swift: a7e7a214f6db6ce5db32cc6b2b45e5c4dd633634
Starscream: 4bb2f9942274833f7b4d296a55504dcfc7edb7b0
SwiftProtobuf: 9c85136c6ba74b0a1b84279dbf0f6db8efb714e0
SwiftyJSON: 6faa0040f8b59dead0ee07436cbf76b73c08fd08
TrustWalletCore: 9d3842328ccb07c05abaf7e1cb2c19bdc3d52331
TrustWalletCore: 48472eb39dab56bc5a94e626b24160c88bfb1ebf
WalletConnect: 1df75d4355b1cacfc27d7ef2416fae43862d0eb4
Web3Swift.io: 18fd06aed9d56df9c704f9c6f87b06675bb05b53