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
    }
}

Read more