Issue #857
Use Solana.swift
and Mnemonic
seed phrase. For production, change endpoint to mainnet
import UIKit
import Solana
import KeychainAccess
enum SolanaError: Swift.Error {
case accountFailed
case unauthorized
}
final class SolanaClient {
static let shared = SolanaClient()
final class SolanaClient {
static let shared = SolanaClient()
private let solana: Solana
private let accountStorage = KeychainAccountStorage()
private let seedPharser = SeedPhraser()
private let endpoint: RPCEndpoint = .devnetSolana
private let network: NetworkingRouter
init() {
self.network = NetworkingRouter(endpoint: .devnetSolana)
self.solana = Solana(
router: network,
accountStorage: accountStorage
)
}
func getAccount() async throws -> Account {
try await withCheckedThrowingContinuation { continuation in
DispatchQueue.global().async {
if let acccount = try? self.solana.auth.account.get() {
continuation.resume(with: .success(acccount))
} else {
continuation.resume(with: .failure(SolanaError.accountNotFound))
}
}
}
}
func createAccount() async throws -> Account {
try await restoreAccount(seedPhrase: seedPharser.generate())
}
func restoreAccount(seedPhrase: [String]) async throws -> Account {
try await withCheckedThrowingContinuation { continuation in
DispatchQueue.global().async {
if let account = Account(
phrase: seedPhrase,
network: self.endpoint.network,
derivablePath: .default
) {
_ = self.solana.auth.save(account)
continuation.resume(with: .success(account))
} else {
continuation.resume(with: .failure(SolanaError.accountFailed))
}
}
}
}
func deleteAccount() async throws {
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
let result = accountStorage.clear()
continuation.resume(with: result)
}
}
}
struct KeychainAccountStorage: SolanaAccountStorage {
private let key = "SolanaWallet_TokenKey"
private let keychain = Keychain(service: "com.onmyway133.myapp.solana.account")
func save(_ account: Account) -> Result<Void, Error> {
do {
let data = try JSONEncoder().encode(account)
try keychain.set(data, key: key)
return .success(())
} catch {
return .failure(error)
}
}
var account: Result<Account, Error> {
do {
guard let data = try keychain.getData(key) else {
return .failure(SolanaError.unauthorized)
}
let account = try JSONDecoder().decode(Account.self, from: data)
return .success(account)
} catch {
return .failure(SolanaError.unauthorized)
}
}
func clear() -> Result<Void, Error> {
try? keychain.removeAll()
return .success(())
}
}
struct SeedPhraser {
func validate(seedPhrase: [String]) -> Bool {
Mnemonic.isValid(phrase: seedPhrase)
}
func generate(strength: Int = 256) -> [String] {
let mnemonic = Mnemonic(strength: strength, wordlist: Wordlists.english)
return mnemonic.phrase
}
}