SPB Git

spb/os-vault Public

Self-custody, multi-chain crypto wallet for macOS. One recovery phrase, six chain families, zero API keys — nothing leaves your Mac.

Swift 96% Shell 3.4% Makefile 0.6%
7.3 KB · 194 lines swift
Raw Blame History
1import Foundation23// MARK: - TokenRepository45public extension SolanaAPIClient {6    // MARK: - Convenience methods78    func getTokenAccountsByOwner(9        pubkey: String,10        params: OwnerInfoParams?,11        configs: RequestConfiguration?12    ) async throws -> [TokenAccount<TokenAccountState>] {13        try await getTokenAccountsByOwner(14            pubkey: pubkey,15            params: params,16            configs: configs,17            decodingTo: TokenAccountState.self18        )19    }2021    func getMinimumBalanceForRentExemption(span: UInt64) async throws -> UInt64 {22        try await getMinimumBalanceForRentExemption(dataLength: span, commitment: "recent")23    }2425    func getRecentBlockhash() async throws -> String {26        try await getRecentBlockhash(commitment: nil)27    }2829    func observeSignatureStatus(signature: String) -> AsyncStream<PendingTransactionStatus> {30        observeSignatureStatus(signature: signature, timeout: 60, delay: 2)31    }3233    /// Get fee per signature34    func getLamportsPerSignature() async throws -> UInt64? {35        try await getFees(commitment: nil).feeCalculator?.lamportsPerSignature36    }3738    /// Convenience method for request(method:params:) with no params39    func request<Entity>(method: String) async throws -> Entity where Entity: Decodable {40        try await request(method: method, params: [])41    }4243    func getMultipleMintDatas<M: MintLayoutState>(44        mintAddresses: [String],45        commitment: Commitment,46        mintType _: M.Type47    ) async throws -> [String: M] {48        let accounts: [BufferInfo<M>?] = try await getMultipleAccounts(49            pubkeys: mintAddresses,50            commitment: commitment51        )5253        var mintDict = [String: M]()5455        for (index, address) in mintAddresses.enumerated() {56            let account = accounts[index] as BufferInfo<M>?57            mintDict[address] = account?.data58        }5960        return mintDict61    }6263    /// Wait until transaction is confirmed, return even when there is one or more confirmations and request timed out64    /// - Parameters:65    ///   - signature: signature of the transaction66    ///   - ignoreStatus: ignore status and return true even when observation is timed out67    func waitForConfirmation(signature: String, ignoreStatus: Bool, timeout: Int = 60, delay: Int = 2) async throws {68        var statuses = [PendingTransactionStatus]()69        for try await status in observeSignatureStatus(signature: signature, timeout: timeout, delay: delay) {70            statuses.append(status)71        }7273        // if the status is important74        if !ignoreStatus {75            guard let lastStatus = statuses.last else {76                throw TransactionConfirmationError.unconfirmed77            }78            switch lastStatus {79            case .confirmed, .finalized:80                return81            default:82                throw TransactionConfirmationError.unconfirmed83            }84        }85    }8687    // MARK: - Additional methods8889    func checkIfAssociatedTokenAccountExists(90        owner: PublicKey,91        mint: String,92        tokenProgramId: PublicKey93    ) async throws -> Bool {94        let mintAddress = try mint.toPublicKey()9596        let associatedTokenAccount = try PublicKey.associatedTokenAddress(97            walletAddress: owner,98            tokenMintAddress: mintAddress,99            tokenProgramId: tokenProgramId100        )101102        let bufferInfo: BufferInfo<TokenAccountState>? = try await getAccountInfo(account: associatedTokenAccount103            .base58EncodedString)104        return bufferInfo?.data.mint == mintAddress105    }106107    /// Method checks account validation108    /// - Parameters:109    ///  - account: Public key of an account110    /// - Throws: TokenRepositoryError111    /// - Returns wether account is valid112    ///113    func checkAccountValidation(account: String) async throws -> Bool {114        try (await getAccountInfo(account: account) as BufferInfo<EmptyInfo>?) != nil115    }116117    func findSPLTokenDestinationAddress(118        mintAddress: String,119        destinationAddress: String,120        tokenProgramId: PublicKey121    ) async throws -> SPLTokenDestinationAddress {122        var address: String123        var accountInfo: BufferInfo<TokenAccountState>?124        do {125            accountInfo = try await getAccountInfoThrowable(account: destinationAddress)126            let toTokenMint = accountInfo?.data.mint.base58EncodedString127            // detect if destination address is already a SPLToken address128            if mintAddress == toTokenMint {129                address = destinationAddress130                // detect if destination address is a SOL address131            } else if accountInfo?.owner == SystemProgram.id.base58EncodedString {132                let owner = try PublicKey(string: destinationAddress)133                let tokenMint = try PublicKey(string: mintAddress)134                // create associated token address135                address = try PublicKey.associatedTokenAddress(136                    walletAddress: owner,137                    tokenMintAddress: tokenMint,138                    tokenProgramId: tokenProgramId139                ).base58EncodedString140            } else {141                throw PublicKeyError.invalidAddress(destinationAddress)142            }143        } catch let error as APIClientError where error == .couldNotRetrieveAccountInfo {144            let owner = try PublicKey(string: destinationAddress)145            let tokenMint = try PublicKey(string: mintAddress)146            // create associated token address147            address = try PublicKey.associatedTokenAddress(148                walletAddress: owner,149                tokenMintAddress: tokenMint,150                tokenProgramId: tokenProgramId151            ).base58EncodedString152        } catch {153            throw error154        }155156        // address needs here157        let toPublicKey = try PublicKey(string: address)158        // if destination address is an SOL account address159        var isUnregisteredAsocciatedToken = false160        if destinationAddress != toPublicKey.base58EncodedString {161            // check if associated address is already registered162            let info: BufferInfo<TokenAccountState>?163            do {164                info = try await getAccountInfoThrowable(account: toPublicKey.base58EncodedString)165            } catch {166                info = nil167            }168            isUnregisteredAsocciatedToken = true169170            // if associated token account has been registered171            if PublicKey.isSPLTokenProgram(info?.owner),172               info?.data != nil173            {174                isUnregisteredAsocciatedToken = false175            }176        }177        return (destination: toPublicKey, isUnregisteredAsocciatedToken: isUnregisteredAsocciatedToken)178    }179180    /// Returns all information associated with the account of provided Pubkey181    /// - Parameters:182    ///  - account: Pubkey of account to query, as base-58 encoded string183    /// - Throws: APIClientError184    /// - Returns The result will be an BufferInfo185    /// - SeeAlso https://docs.solana.com/developing/clients/jsonrpc-api#getaccountinfo186    func getAccountInfoThrowable<T: BufferLayout>(account: String) async throws -> BufferInfo<T> {187        let info: BufferInfo<T>? = try await getAccountInfo(account: account)188        guard let info = info else {189            throw APIClientError.couldNotRetrieveAccountInfo190        }191        return info192    }193}194