mirror of
https://github.com/lil-org/tokenary.git
synced 2024-12-02 09:33:49 +03:00
Make Ethereum, Keychain and AccountsService singletons
This commit is contained in:
parent
8dd2a476f7
commit
b9a9b6787c
@ -11,7 +11,8 @@ class Agent: NSObject {
|
||||
|
||||
private override init() { super.init() }
|
||||
private var statusBarItem: NSStatusItem!
|
||||
private var hasPassword = Keychain.password != nil
|
||||
private let accountsService = AccountsService.shared
|
||||
private var hasPassword = Keychain.shared.password != nil
|
||||
private var didEnterPasswordOnStart = false
|
||||
var statusBarButtonIsBlocked = false
|
||||
|
||||
@ -50,7 +51,7 @@ class Agent: NSObject {
|
||||
|
||||
let windowController = Window.showNew()
|
||||
let completion = onSelectedAccount(session: wcSession)
|
||||
let accounts = AccountsService.getAccounts()
|
||||
let accounts = accountsService.getAccounts()
|
||||
if !accounts.isEmpty {
|
||||
let accountsList = AccountsListViewController.with(preloadedAccounts: accounts)
|
||||
accountsList.onSelectedAccount = completion
|
||||
|
@ -11,14 +11,17 @@ struct Ethereum {
|
||||
case failedToSendTransaction
|
||||
}
|
||||
|
||||
private static let queue = DispatchQueue(label: "Ethereum", qos: .default)
|
||||
private let queue = DispatchQueue(label: "Ethereum", qos: .default)
|
||||
private init() {}
|
||||
|
||||
private static let network: Network = AlchemyNetwork(
|
||||
static let shared = Ethereum()
|
||||
|
||||
private let network: Network = AlchemyNetwork(
|
||||
chain: "mainnet",
|
||||
apiKey: Secrets.alchemy
|
||||
)
|
||||
|
||||
static func sign(message: String, account: Account) throws -> String {
|
||||
func sign(message: String, account: Account) throws -> String {
|
||||
let ethPrivateKey = EthPrivateKey(hex: account.privateKey)
|
||||
|
||||
let signature = SECP256k1Signature(
|
||||
@ -36,14 +39,14 @@ struct Ethereum {
|
||||
return data.toPrefixedHexString()
|
||||
}
|
||||
|
||||
static func signPersonal(message: String, account: Account) throws -> String {
|
||||
func signPersonal(message: String, account: Account) throws -> String {
|
||||
let ethPrivateKey = EthPrivateKey(hex: account.privateKey)
|
||||
let signed = SignedPersonalMessageBytes(message: message, signerKey: ethPrivateKey)
|
||||
let data = try signed.value().toPrefixedHexString()
|
||||
return data
|
||||
}
|
||||
|
||||
static func sign(typedData: String, account: Account) throws -> String {
|
||||
func sign(typedData: String, account: Account) throws -> String {
|
||||
let data = try EIP712TypedData(jsonString: typedData)
|
||||
let hash = EIP712Hash(domain: data.domain, typedData: data)
|
||||
let privateKey = EthPrivateKey(hex: account.privateKey)
|
||||
@ -51,7 +54,7 @@ struct Ethereum {
|
||||
return try signer.signatureData(hash: hash).toPrefixedHexString()
|
||||
}
|
||||
|
||||
static func send(transaction: Transaction, account: Account) throws -> String {
|
||||
func send(transaction: Transaction, account: Account) throws -> String {
|
||||
let bytes = signedTransactionBytes(transaction: transaction, account: account)
|
||||
let response = try SendRawTransactionProcedure(network: network, transactionBytes: bytes).call()
|
||||
guard let hash = response["result"].string else {
|
||||
@ -60,7 +63,7 @@ struct Ethereum {
|
||||
return hash
|
||||
}
|
||||
|
||||
private static func signedTransactionBytes(transaction: Transaction, account: Account) -> EthContractCallBytes {
|
||||
private func signedTransactionBytes(transaction: Transaction, account: Account) -> EthContractCallBytes {
|
||||
let senderKey = EthPrivateKey(hex: account.privateKey)
|
||||
let contractAddress = EthAddress(hex: transaction.to)
|
||||
let functionCall = BytesFromHexString(hex: transaction.data)
|
||||
@ -98,7 +101,7 @@ struct Ethereum {
|
||||
return bytes
|
||||
}
|
||||
|
||||
static func prepareTransaction(_ transaction: Transaction, completion: @escaping (Transaction) -> Void) {
|
||||
func prepareTransaction(_ transaction: Transaction, completion: @escaping (Transaction) -> Void) {
|
||||
var transaction = transaction
|
||||
|
||||
if transaction.nonce == nil {
|
||||
@ -130,7 +133,7 @@ struct Ethereum {
|
||||
|
||||
}
|
||||
|
||||
private static func getGas(from: String, to: String, gasPrice: String, weiAmount: EthNumber, data: String, completion: @escaping (String?) -> Void) {
|
||||
private func getGas(from: String, to: String, gasPrice: String, weiAmount: EthNumber, data: String, completion: @escaping (String?) -> Void) {
|
||||
queue.async {
|
||||
let gas = try? EthGasEstimate(
|
||||
network: network,
|
||||
@ -154,7 +157,7 @@ struct Ethereum {
|
||||
}
|
||||
}
|
||||
|
||||
private static func getGasPrice(completion: @escaping (String?) -> Void) {
|
||||
private func getGasPrice(completion: @escaping (String?) -> Void) {
|
||||
queue.async {
|
||||
let gasPrice = try? EthGasPrice(network: network).value().toHexString()
|
||||
DispatchQueue.main.async {
|
||||
@ -163,7 +166,7 @@ struct Ethereum {
|
||||
}
|
||||
}
|
||||
|
||||
private static func getNonce(from: String, completion: @escaping (String?) -> Void) {
|
||||
private func getNonce(from: String, completion: @escaping (String?) -> Void) {
|
||||
queue.async {
|
||||
let nonce = try? EthTransactions(network: network, address: EthAddress(hex: from), blockChainState: PendingBlockChainState()).count().value().toHexString()
|
||||
DispatchQueue.main.async {
|
||||
|
@ -5,6 +5,7 @@ import Cocoa
|
||||
class AccountsListViewController: NSViewController {
|
||||
|
||||
private let agent = Agent.shared
|
||||
private let accountsService = AccountsService.shared
|
||||
private var accounts = [Account]()
|
||||
|
||||
var onSelectedAccount: ((Account) -> Void)?
|
||||
@ -58,7 +59,7 @@ class AccountsListViewController: NSViewController {
|
||||
}
|
||||
|
||||
private func reloadAccounts() {
|
||||
accounts = AccountsService.getAccounts()
|
||||
accounts = accountsService.getAccounts()
|
||||
}
|
||||
|
||||
deinit {
|
||||
@ -95,7 +96,7 @@ class AccountsListViewController: NSViewController {
|
||||
}
|
||||
|
||||
@objc private func didClickCreateAccount() {
|
||||
AccountsService.createAccount()
|
||||
accountsService.createAccount()
|
||||
reloadAccounts()
|
||||
tableView.reloadData()
|
||||
// TODO: show backup phrase
|
||||
@ -177,7 +178,7 @@ class AccountsListViewController: NSViewController {
|
||||
}
|
||||
|
||||
private func removeAccountAtIndex(_ index: Int) {
|
||||
AccountsService.removeAccount(accounts[index])
|
||||
accountsService.removeAccount(accounts[index])
|
||||
accounts.remove(at: index)
|
||||
tableView.reloadData()
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ class ApproveTransactionViewController: NSViewController {
|
||||
}
|
||||
|
||||
private let gasService = GasService.shared
|
||||
private let ethereum = Ethereum.shared
|
||||
private let priceService = PriceService.shared
|
||||
private var currentGasInfo: GasService.Info?
|
||||
private var transaction: Transaction!
|
||||
@ -57,7 +58,7 @@ class ApproveTransactionViewController: NSViewController {
|
||||
}
|
||||
|
||||
private func prepareTransaction() {
|
||||
Ethereum.prepareTransaction(transaction) { [weak self] updated in
|
||||
ethereum.prepareTransaction(transaction) { [weak self] updated in
|
||||
self?.transaction = updated
|
||||
self?.updateInterface()
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import Cocoa
|
||||
|
||||
class ImportViewController: NSViewController {
|
||||
|
||||
private let accountsService = AccountsService.shared
|
||||
var onSelectedAccount: ((Account) -> Void)?
|
||||
|
||||
@IBOutlet weak var textField: NSTextField! {
|
||||
@ -19,8 +20,8 @@ class ImportViewController: NSViewController {
|
||||
}
|
||||
|
||||
@IBAction func actionButtonTapped(_ sender: Any) {
|
||||
let account = AccountsService.addAccount(input: textField.stringValue)
|
||||
if let account = account, AccountsService.getAccounts().count == 1, let onSelectedAccount = onSelectedAccount {
|
||||
let account = accountsService.addAccount(input: textField.stringValue)
|
||||
if let account = account, accountsService.getAccounts().count == 1, let onSelectedAccount = onSelectedAccount {
|
||||
onSelectedAccount(account)
|
||||
} else {
|
||||
showAccountsList()
|
||||
@ -42,7 +43,7 @@ class ImportViewController: NSViewController {
|
||||
extension ImportViewController: NSTextFieldDelegate {
|
||||
|
||||
func controlTextDidChange(_ obj: Notification) {
|
||||
okButton.isEnabled = AccountsService.validateAccountInput(textField.stringValue)
|
||||
okButton.isEnabled = accountsService.validateAccountInput(textField.stringValue)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ class PasswordViewController: NSViewController {
|
||||
case create, repeatAfterCreate, enter
|
||||
}
|
||||
|
||||
private let keychain = Keychain.shared
|
||||
private var mode = Mode.create
|
||||
private var reason: String?
|
||||
private var passwordToRepeat: String?
|
||||
@ -65,11 +66,11 @@ class PasswordViewController: NSViewController {
|
||||
case .repeatAfterCreate:
|
||||
let repeated = passwordTextField.stringValue
|
||||
if repeated == passwordToRepeat {
|
||||
Keychain.save(password: repeated)
|
||||
keychain.save(password: repeated)
|
||||
completion?(true)
|
||||
}
|
||||
case .enter:
|
||||
if Keychain.password == passwordTextField.stringValue {
|
||||
if keychain.password == passwordTextField.stringValue {
|
||||
completion?(true)
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,13 @@ import WalletCore
|
||||
|
||||
struct AccountsService {
|
||||
|
||||
static func validateAccountInput(_ input: String) -> Bool {
|
||||
private init() {}
|
||||
private let keychain = Keychain.shared
|
||||
|
||||
static let shared = AccountsService()
|
||||
|
||||
|
||||
func validateAccountInput(_ input: String) -> Bool {
|
||||
if Mnemonic.isValid(mnemonic: input) {
|
||||
return true
|
||||
} else if let data = Data(hexString: input) {
|
||||
@ -15,14 +21,14 @@ struct AccountsService {
|
||||
}
|
||||
}
|
||||
|
||||
static func createAccount() {
|
||||
guard let password = Keychain.password?.data(using: .utf8) else { return }
|
||||
func createAccount() {
|
||||
guard let password = keychain.password?.data(using: .utf8) else { return }
|
||||
let key = StoredKey(name: "", password: password)
|
||||
guard let privateKey = key.wallet(password: password)?.getKeyForCoin(coin: .ethereum) else { return }
|
||||
_ = saveAccount(privateKey: privateKey)
|
||||
}
|
||||
|
||||
static func addAccount(input: String) -> Account? {
|
||||
func addAccount(input: String) -> Account? {
|
||||
let key: PrivateKey
|
||||
if Mnemonic.isValid(mnemonic: input) {
|
||||
key = HDWallet(mnemonic: input, passphrase: "").getKeyForCoin(coin: .ethereum)
|
||||
@ -42,28 +48,28 @@ struct AccountsService {
|
||||
return account
|
||||
}
|
||||
|
||||
private static func saveAccount(privateKey: PrivateKey) -> Account? {
|
||||
private func saveAccount(privateKey: PrivateKey) -> Account? {
|
||||
let address = CoinType.ethereum.deriveAddress(privateKey: privateKey).lowercased()
|
||||
// TODO: use checksum address
|
||||
let account = Account(privateKey: privateKey.data.hexString, address: address)
|
||||
var accounts = getAccounts()
|
||||
guard !accounts.contains(where: { $0.address == address }) else { return nil }
|
||||
accounts.append(account)
|
||||
Keychain.save(accounts: accounts)
|
||||
keychain.save(accounts: accounts)
|
||||
return account
|
||||
}
|
||||
|
||||
static func removeAccount(_ account: Account) {
|
||||
func removeAccount(_ account: Account) {
|
||||
var accounts = getAccounts()
|
||||
accounts.removeAll(where: {$0.address == account.address })
|
||||
Keychain.save(accounts: accounts)
|
||||
keychain.save(accounts: accounts)
|
||||
}
|
||||
|
||||
static func getAccounts() -> [Account] {
|
||||
return Keychain.accounts
|
||||
func getAccounts() -> [Account] {
|
||||
return keychain.accounts
|
||||
}
|
||||
|
||||
static func getAccountForAddress(_ address: String) -> Account? {
|
||||
func getAccountForAddress(_ address: String) -> Account? {
|
||||
let allAccounts = getAccounts()
|
||||
return allAccounts.first(where: { $0.address == address.lowercased() })
|
||||
}
|
||||
|
@ -4,14 +4,18 @@ import Foundation
|
||||
|
||||
struct Keychain {
|
||||
|
||||
private static let prefix = "ink.encrypted.macos."
|
||||
private let prefix = "ink.encrypted.macos."
|
||||
|
||||
private init() {}
|
||||
|
||||
static let shared = Keychain()
|
||||
|
||||
private enum Key: String {
|
||||
case accounts = "ethereum.keys"
|
||||
case password = "password"
|
||||
}
|
||||
|
||||
static var password: String? {
|
||||
var password: String? {
|
||||
if let data = get(key: .password), let password = String(data: data, encoding: .utf8) {
|
||||
return password
|
||||
} else {
|
||||
@ -19,12 +23,12 @@ struct Keychain {
|
||||
}
|
||||
}
|
||||
|
||||
static func save(password: String) {
|
||||
func save(password: String) {
|
||||
guard let data = password.data(using: .utf8) else { return }
|
||||
save(data: data, key: .password)
|
||||
}
|
||||
|
||||
static var accounts: [Account] {
|
||||
var accounts: [Account] {
|
||||
if let data = get(key: .accounts), let accounts = try? JSONDecoder().decode([Account].self, from: data) {
|
||||
return accounts
|
||||
} else {
|
||||
@ -32,14 +36,14 @@ struct Keychain {
|
||||
}
|
||||
}
|
||||
|
||||
static func save(accounts: [Account]) {
|
||||
func save(accounts: [Account]) {
|
||||
guard let data = try? JSONEncoder().encode(accounts) else { return }
|
||||
save(data: data, key: .accounts)
|
||||
}
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private static func save(data: Data, key: Key) {
|
||||
private func save(data: Data, key: Key) {
|
||||
let query = [kSecClass as String: kSecClassGenericPassword as String,
|
||||
kSecAttrAccount as String: prefix + key.rawValue,
|
||||
kSecValueData as String: data] as [String: Any]
|
||||
@ -47,7 +51,7 @@ struct Keychain {
|
||||
SecItemAdd(query as CFDictionary, nil)
|
||||
}
|
||||
|
||||
private static func get(key: Key) -> Data? {
|
||||
private func get(key: Key) -> Data? {
|
||||
guard let returnDataQueryValue = kCFBooleanTrue else { return nil }
|
||||
let query = [kSecClass as String: kSecClassGenericPassword,
|
||||
kSecAttrAccount as String: prefix + key.rawValue,
|
||||
|
@ -7,6 +7,9 @@ class WalletConnect {
|
||||
|
||||
private let sessionStorage = SessionStorage.shared
|
||||
private let networkMonitor = NetworkMonitor.shared
|
||||
private let ethereum = Ethereum.shared
|
||||
private let accountsService = AccountsService.shared
|
||||
|
||||
static let shared = WalletConnect()
|
||||
|
||||
private init() {
|
||||
@ -174,11 +177,11 @@ class WalletConnect {
|
||||
}
|
||||
|
||||
private func sendTransaction(_ transaction: Transaction, address: String, requestId: Int64, interactor: WCInteractor?) {
|
||||
guard let account = AccountsService.getAccountForAddress(address) else {
|
||||
guard let account = accountsService.getAccountForAddress(address) else {
|
||||
rejectRequest(id: requestId, interactor: interactor, message: "Something went wrong.")
|
||||
return
|
||||
}
|
||||
guard let hash = try? Ethereum.send(transaction: transaction, account: account) else {
|
||||
guard let hash = try? ethereum.send(transaction: transaction, account: account) else {
|
||||
rejectRequest(id: requestId, interactor: interactor, message: "Failed to send")
|
||||
return
|
||||
}
|
||||
@ -186,18 +189,18 @@ class WalletConnect {
|
||||
}
|
||||
|
||||
private func sign(id: Int64, message: String?, payload: WCEthereumSignPayload, address: String, interactor: WCInteractor?) {
|
||||
guard let message = message, let account = AccountsService.getAccountForAddress(address) else {
|
||||
guard let message = message, let account = accountsService.getAccountForAddress(address) else {
|
||||
rejectRequest(id: id, interactor: interactor, message: "Something went wrong.")
|
||||
return
|
||||
}
|
||||
var signed: String?
|
||||
switch payload {
|
||||
case .personalSign:
|
||||
signed = try? Ethereum.signPersonal(message: message, account: account)
|
||||
signed = try? ethereum.signPersonal(message: message, account: account)
|
||||
case .signTypeData:
|
||||
signed = try? Ethereum.sign(typedData: message, account: account)
|
||||
signed = try? ethereum.sign(typedData: message, account: account)
|
||||
case .sign:
|
||||
signed = try? Ethereum.sign(message: message, account: account)
|
||||
signed = try? ethereum.sign(message: message, account: account)
|
||||
}
|
||||
guard let result = signed else {
|
||||
rejectRequest(id: id, interactor: interactor, message: "Something went wrong.")
|
||||
|
Loading…
Reference in New Issue
Block a user