diff --git a/BlockchainSdk/Blockchains/Solana/SolanaNetworkService.swift b/BlockchainSdk/Blockchains/Solana/SolanaNetworkService.swift index 68ef6684d..4c12c5dc3 100644 --- a/BlockchainSdk/Blockchains/Solana/SolanaNetworkService.swift +++ b/BlockchainSdk/Blockchains/Solana/SolanaNetworkService.swift @@ -15,17 +15,17 @@ class SolanaNetworkService { var host: String { hostProvider.host } - + private let solanaSdk: Solana private let blockchain: Blockchain private let hostProvider: HostProvider - + init(solanaSdk: Solana, blockchain: Blockchain, hostProvider: HostProvider) { self.solanaSdk = solanaSdk self.blockchain = blockchain self.hostProvider = hostProvider } - + func getInfo(accountId: String, tokens: [Token], transactionIDs: [String]) -> AnyPublisher { Publishers.Zip4( mainAccountInfo(accountId: accountId), @@ -33,22 +33,22 @@ class SolanaNetworkService { tokenAccountsInfo(accountId: accountId, programId: .token2022ProgramId), confirmedTransactions(among: transactionIDs) ) - .tryMap { [weak self] mainAccount, splTokenAccounts, token2022Accounts, confirmedTransactionIDs in - guard let self = self else { - throw WalletError.empty - } - - let tokenAccounts = splTokenAccounts + token2022Accounts - return self.mapInfo( - mainAccountInfo: mainAccount, - tokenAccountsInfo: tokenAccounts, - tokens: tokens, - confirmedTransactionIDs: confirmedTransactionIDs - ) + .tryMap { [weak self] mainAccount, splTokenAccounts, token2022Accounts, confirmedTransactionIDs in + guard let self = self else { + throw WalletError.empty } - .eraseToAnyPublisher() + + let tokenAccounts = splTokenAccounts + token2022Accounts + return self.mapInfo( + mainAccountInfo: mainAccount, + tokenAccountsInfo: tokenAccounts, + tokens: tokens, + confirmedTransactionIDs: confirmedTransactionIDs + ) + } + .eraseToAnyPublisher() } - + func sendSol( amount: UInt64, computeUnitLimit: UInt32?, @@ -64,10 +64,12 @@ class SolanaNetworkService { allowUnfundedRecipient: true, signer: signer ) + .eraseToAnyPublisher() } func sendRaw(base64serializedTransaction: String) -> AnyPublisher { solanaSdk.api.sendTransaction(serializedTransaction: base64serializedTransaction) + .eraseToAnyPublisher() } func sendSplToken(amount: UInt64, computeUnitLimit: UInt32?, computeUnitPrice: UInt64?, sourceTokenAddress: String, destinationAddress: String, token: Token, tokenProgramId: PublicKey, signer: SolanaTransactionSigner) -> AnyPublisher { @@ -83,8 +85,9 @@ class SolanaNetworkService { allowUnfundedRecipient: true, signer: signer ) + .eraseToAnyPublisher() } - + func getFeeForMessage( amount: UInt64, computeUnitLimit: UInt32?, @@ -100,7 +103,7 @@ class SolanaNetworkService { allowUnfundedRecipient: true, fromPublicKey: fromPublicKey ) - .flatMap { [solanaSdk] message -> AnyPublisher in + .flatMap { [solanaSdk] (message, _) -> AnyPublisher in solanaSdk.api.getFeeForMessage(message) } .map { [blockchain] in @@ -108,37 +111,37 @@ class SolanaNetworkService { } .eraseToAnyPublisher() } - + func transactionFee(numberOfSignatures: Int) -> AnyPublisher { solanaSdk.api.getFees(commitment: nil) .tryMap { [weak self] fee in guard let self = self else { throw WalletError.empty } - + guard let lamportsPerSignature = fee.feeCalculator?.lamportsPerSignature else { throw BlockchainSdkError.failedToLoadFee } - + return Decimal(lamportsPerSignature) * Decimal(numberOfSignatures) / self.blockchain.decimalValue } .eraseToAnyPublisher() } - + // This fee is deducted from the transaction amount itself (!) func mainAccountCreationFee() -> AnyPublisher { minimalBalanceForRentExemption(dataLength: 0) } - + func mainAccountCreationFee(dataLength: UInt64) -> AnyPublisher { minimalBalanceForRentExemption(dataLength: dataLength) } - + func accountRentFeePerEpoch() -> AnyPublisher { // https://docs.solana.com/developing/programming-model/accounts#calculation-of-rent let minimumAccountSizeInBytes = Decimal(128) let numberOfEpochs = Decimal(1) - + let rentInLamportPerByteEpoch: Decimal if blockchain.isTestnet { // Solana Testnet uses the same value as Mainnet. @@ -148,12 +151,12 @@ class SolanaNetworkService { rentInLamportPerByteEpoch = Decimal(19.055441478439427) } let lamportsInSol = blockchain.decimalValue - + let rent = minimumAccountSizeInBytes * numberOfEpochs * rentInLamportPerByteEpoch / lamportsInSol - + return Just(rent).setFailureType(to: Error.self).eraseToAnyPublisher() } - + func minimalBalanceForRentExemption(dataLength: UInt64 = 0) -> AnyPublisher { // The accounts metadata size (128) is already factored in solanaSdk.api.getMinimumBalanceForRentExemption(dataLength: dataLength) @@ -161,12 +164,12 @@ class SolanaNetworkService { guard let self = self else { throw WalletError.empty } - + return Decimal(balanceInLamports) / self.blockchain.decimalValue } .eraseToAnyPublisher() } - + func tokenProgramId(contractAddress: String) -> AnyPublisher { solanaSdk.api.getAccountInfo(account: contractAddress, decodedTo: AccountInfo.self) .tryMap { accountInfo in @@ -174,7 +177,7 @@ class SolanaNetworkService { .tokenProgramId, .token2022ProgramId ] - + for tokenProgramId in tokenProgramIds { if tokenProgramId.base58EncodedString == accountInfo.owner { return tokenProgramId @@ -184,7 +187,7 @@ class SolanaNetworkService { } .eraseToAnyPublisher() } - + private func mainAccountInfo(accountId: String) -> AnyPublisher { solanaSdk.api.getAccountInfo(account: accountId, decodedTo: AccountInfo.self) .tryMap { info in @@ -204,23 +207,23 @@ class SolanaNetworkService { break } } - + throw error }.eraseToAnyPublisher() } - + private func tokenAccountsInfo(accountId: String, programId: PublicKey) -> AnyPublisher<[TokenAccount], Error> { let configs = RequestConfiguration(commitment: "recent", encoding: "jsonParsed") - + return solanaSdk.api.getTokenAccountsByOwner(pubkey: accountId, programId: programId.base58EncodedString, configs: configs) .eraseToAnyPublisher() } - + private func confirmedTransactions(among transactionIDs: [String]) -> AnyPublisher<[String], Error> { guard !transactionIDs.isEmpty else { return .justWithError(output: []) } - + return solanaSdk.api.getSignatureStatuses(pubkeys: transactionIDs) .map { statuses in zip(transactionIDs, statuses) @@ -234,7 +237,7 @@ class SolanaNetworkService { } .eraseToAnyPublisher() } - + private func mapInfo( mainAccountInfo: SolanaMainAccountInfoResponse, tokenAccountsInfo: [TokenAccount], @@ -243,7 +246,7 @@ class SolanaNetworkService { ) -> SolanaAccountInfoResponse { let balance = (Decimal(mainAccountInfo.balance) / blockchain.decimalValue).rounded(blockchain: blockchain) let accountExists = mainAccountInfo.accountExists - + let tokenInfoResponses: [SolanaTokenAccountInfoResponse] = tokenAccountsInfo.compactMap { guard let info = $0.account.data.value?.parsed.info, @@ -252,19 +255,19 @@ class SolanaNetworkService { else { return nil } - + let address = $0.pubkey let mint = info.mint let amount = (integerAmount / token.decimalValue).rounded(scale: token.decimalCount) - + return SolanaTokenAccountInfoResponse(address: address, mint: mint, balance: amount, space: $0.account.space) } - + let tokenPairs = tokenInfoResponses.map { ($0.mint, $0) } let tokensByMint = Dictionary(tokenPairs) { token1, _ in return token1 } - + return SolanaAccountInfoResponse( balance: balance, accountExists: accountExists, diff --git a/BlockchainSdk/Blockchains/Solana/SolanaSdk+.swift b/BlockchainSdk/Blockchains/Solana/SolanaSdk+.swift index 859377a37..8839c01f4 100644 --- a/BlockchainSdk/Blockchains/Solana/SolanaSdk+.swift +++ b/BlockchainSdk/Blockchains/Solana/SolanaSdk+.swift @@ -58,7 +58,7 @@ extension Api { func getMinimumBalanceForRentExemption( dataLength: UInt64, - commitment: Commitment? = "recent" + commitment: Commitment? = nil ) -> AnyPublisher { Deferred { Future { [weak self] promise in @@ -164,7 +164,7 @@ extension Api { return } - self.sendTransaction(serializedTransaction: serializedTransaction, configs: configs) { + self.sendTransaction(serializedTransaction: serializedTransaction, configs: configs, startSendingTimestamp: Date()) { switch $0 { case .failure(let error): promise(.failure(error)) @@ -186,7 +186,7 @@ extension Action { computeUnitPrice: UInt64?, allowUnfundedRecipient: Bool = false, fromPublicKey: PublicKey - ) -> AnyPublisher { + ) -> AnyPublisher<(String, Date), Error> { Deferred { Future { [weak self] promise in guard let self else { diff --git a/Podfile b/Podfile index 33dff0b67..792eddc8f 100644 --- a/Podfile +++ b/Podfile @@ -27,7 +27,7 @@ target 'BlockchainSdk' do pod 'BinanceChain', :git => 'https://github.com/tangem/swiftbinancechain.git', :tag => '0.0.11' #pod 'BinanceChain', :path => '../SwiftBinanceChain' - pod 'Solana.Swift', :git => 'https://github.com/tangem/Solana.Swift', :tag => '1.2.0-tangem10' + pod 'Solana.Swift', :git => 'https://github.com/tangem/Solana.Swift', :tag => '1.2.0-tangem11' #pod 'Solana.Swift', :path => '../Solana.Swift' pod 'SwiftyJSON', :git => 'https://github.com/tangem/SwiftyJSON.git', :tag => '5.0.1-tangem1' diff --git a/Podfile.lock b/Podfile.lock index ac2f41c41..fdfa7eea6 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -34,7 +34,7 @@ DEPENDENCIES: - BitcoinCore.swift (from `https://github.com/tangem/bitcoincore.git`, tag `0.0.20`) - Moya (= 15.0.0) - Sodium (= 0.9.1) - - Solana.Swift (from `https://github.com/tangem/Solana.Swift`, tag `1.2.0-tangem10`) + - Solana.Swift (from `https://github.com/tangem/Solana.Swift`, tag `1.2.0-tangem11`) - stellar-ios-mac-sdk (= 2.5.4) - SwiftCBOR (= 0.4.5) - SwiftyJSON (from `https://github.com/tangem/SwiftyJSON.git`, tag `5.0.1-tangem1`) @@ -60,7 +60,7 @@ EXTERNAL SOURCES: :tag: 0.0.20 Solana.Swift: :git: https://github.com/tangem/Solana.Swift - :tag: 1.2.0-tangem10 + :tag: 1.2.0-tangem11 SwiftyJSON: :git: https://github.com/tangem/SwiftyJSON.git :tag: 5.0.1-tangem1 @@ -77,7 +77,7 @@ CHECKOUT OPTIONS: :tag: 0.0.20 Solana.Swift: :git: https://github.com/tangem/Solana.Swift - :tag: 1.2.0-tangem10 + :tag: 1.2.0-tangem11 SwiftyJSON: :git: https://github.com/tangem/SwiftyJSON.git :tag: 5.0.1-tangem1 @@ -100,6 +100,6 @@ SPEC CHECKSUMS: SwiftyJSON: c0bd75b5969785b3d9ae41d5709fe75bc5de4002 TangemSdk: f3adf8d0c18bf7a76406462fa31c0efa9f2efa93 -PODFILE CHECKSUM: 52792b492481e3a4e4dcf8ac0ecb1cffe929ad2c +PODFILE CHECKSUM: c3bf95e55c682fb33df4ea9627e21a434869279d COCOAPODS: 1.15.2