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