diff --git a/Packages/KeyAppKit/Package.swift b/Packages/KeyAppKit/Package.swift index 7316361758..781875a4c3 100644 --- a/Packages/KeyAppKit/Package.swift +++ b/Packages/KeyAppKit/Package.swift @@ -112,7 +112,7 @@ let package = Package( ), ], dependencies: [ - .package(url: "https://github.com/p2p-org/solana-swift", branch: "main"), + .package(url: "https://github.com/p2p-org/solana-swift", branch: "feature/token-2022"), .package(url: "https://github.com/krzyzanowskim/CryptoSwift.git", .upToNextMajor(from: "1.6.0")), .package(url: "https://github.com/Boilertalk/Web3.swift.git", from: "0.6.0"), // .package(url: "https://github.com/trustwallet/wallet-core", branch: "master"), diff --git a/Packages/KeyAppKit/Sources/FeeRelayerSwift/Helpers/SolanaAPIClient+FeeRelayer.swift b/Packages/KeyAppKit/Sources/FeeRelayerSwift/Helpers/SolanaAPIClient+FeeRelayer.swift index 25ee570bb8..76c9465a1c 100644 --- a/Packages/KeyAppKit/Sources/FeeRelayerSwift/Helpers/SolanaAPIClient+FeeRelayer.swift +++ b/Packages/KeyAppKit/Sources/FeeRelayerSwift/Helpers/SolanaAPIClient+FeeRelayer.swift @@ -16,7 +16,11 @@ extension SolanaAPIClient { // The account doesn't exists if account == nil { - return try PublicKey.associatedTokenAddress(walletAddress: address, tokenMintAddress: mint) + return try PublicKey.associatedTokenAddress( + walletAddress: address, + tokenMintAddress: mint, + tokenProgramId: TokenProgram.id + ) } // The account is already token account @@ -28,7 +32,11 @@ extension SolanaAPIClient { guard account?.owner != SystemProgram.id.base58EncodedString else { throw FeeRelayerError.wrongAddress } - return try PublicKey.associatedTokenAddress(walletAddress: address, tokenMintAddress: mint) + return try PublicKey.associatedTokenAddress( + walletAddress: address, + tokenMintAddress: mint, + tokenProgramId: TokenProgram.id + ) } func isAccountExists(_ address: PublicKey) async throws -> Bool { diff --git a/Packages/KeyAppKit/Sources/FeeRelayerSwift/Models/TokenAccount.swift b/Packages/KeyAppKit/Sources/FeeRelayerSwift/Models/TokenAccount.swift index 47edc3a1b3..4b16898aff 100644 --- a/Packages/KeyAppKit/Sources/FeeRelayerSwift/Models/TokenAccount.swift +++ b/Packages/KeyAppKit/Sources/FeeRelayerSwift/Models/TokenAccount.swift @@ -3,9 +3,14 @@ import SolanaSwift /// A basic class that represents SPL TokenMetadata. public struct TokenAccount: Equatable, Codable { - public init(address: PublicKey, mint: PublicKey) { + public init( + address: PublicKey, + mint: PublicKey, + minimumTokenAccountBalance: UInt64 + ) { self.address = address self.mint = mint + self.minimumTokenAccountBalance = minimumTokenAccountBalance } /// A address of spl token. @@ -13,4 +18,7 @@ public struct TokenAccount: Equatable, Codable { /// A mint address for spl token. public let mint: PublicKey + + /// Mint rent for token + public let minimumTokenAccountBalance: UInt64 } diff --git a/Packages/KeyAppKit/Sources/FeeRelayerSwift/Relay/Context/RelayContext.swift b/Packages/KeyAppKit/Sources/FeeRelayerSwift/Relay/Context/RelayContext.swift index 79c5d9a4d3..b250faca8c 100644 --- a/Packages/KeyAppKit/Sources/FeeRelayerSwift/Relay/Context/RelayContext.swift +++ b/Packages/KeyAppKit/Sources/FeeRelayerSwift/Relay/Context/RelayContext.swift @@ -2,7 +2,6 @@ import Foundation import SolanaSwift public struct RelayContext: Hashable, Codable { - public let minimumTokenAccountBalance: UInt64 public let minimumRelayAccountBalance: UInt64 public let feePayerAddress: PublicKey public let lamportsPerSignature: UInt64 @@ -10,14 +9,12 @@ public struct RelayContext: Hashable, Codable { public var usageStatus: UsageStatus public init( - minimumTokenAccountBalance: UInt64, minimumRelayAccountBalance: UInt64, feePayerAddress: PublicKey, lamportsPerSignature: UInt64, relayAccountStatus: RelayAccountStatus, usageStatus: UsageStatus ) { - self.minimumTokenAccountBalance = minimumTokenAccountBalance self.minimumRelayAccountBalance = minimumRelayAccountBalance self.feePayerAddress = feePayerAddress self.lamportsPerSignature = lamportsPerSignature @@ -26,7 +23,6 @@ public struct RelayContext: Hashable, Codable { } public func hash(into hasher: inout Hasher) { - hasher.combine(minimumTokenAccountBalance) hasher.combine(minimumRelayAccountBalance) hasher.combine(feePayerAddress) hasher.combine(lamportsPerSignature) diff --git a/Packages/KeyAppKit/Sources/FeeRelayerSwift/Relay/Context/RelayContextManagerImpl.swift b/Packages/KeyAppKit/Sources/FeeRelayerSwift/Relay/Context/RelayContextManagerImpl.swift index 455f30445a..72f572068e 100644 --- a/Packages/KeyAppKit/Sources/FeeRelayerSwift/Relay/Context/RelayContextManagerImpl.swift +++ b/Packages/KeyAppKit/Sources/FeeRelayerSwift/Relay/Context/RelayContextManagerImpl.swift @@ -57,14 +57,12 @@ public class RelayContextManagerImpl: RelayContextManager { // retrieve RelayContext let ( - minimumTokenAccountBalance, minimumRelayAccountBalance, lamportsPerSignature, feePayerAddress, relayAccountStatus, usageStatus ) = try await( - solanaAPIClient.getMinimumBalanceForRentExemption(span: 165), solanaAPIClient.getMinimumBalanceForRentExemption(span: 0), solanaAPIClient.getFees(commitment: nil).feeCalculator?.lamportsPerSignature ?? 0, feeRelayerAPIClient.getFeePayerPubkey(), @@ -77,7 +75,6 @@ public class RelayContextManagerImpl: RelayContextManager { ) return try RelayContext( - minimumTokenAccountBalance: minimumTokenAccountBalance, minimumRelayAccountBalance: minimumRelayAccountBalance, feePayerAddress: PublicKey(string: feePayerAddress), lamportsPerSignature: lamportsPerSignature, diff --git a/Packages/KeyAppKit/Sources/FeeRelayerSwift/Relay/Helpers/DestinationAnalysator.swift b/Packages/KeyAppKit/Sources/FeeRelayerSwift/Relay/Helpers/DestinationAnalysator.swift index 6286b7b000..7367efc6ab 100644 --- a/Packages/KeyAppKit/Sources/FeeRelayerSwift/Relay/Helpers/DestinationAnalysator.swift +++ b/Packages/KeyAppKit/Sources/FeeRelayerSwift/Relay/Helpers/DestinationAnalysator.swift @@ -46,7 +46,7 @@ public class DestinationAnalysatorImpl: DestinationAnalysator { // Check destination address is exist. let info: BufferInfo? = try? await solanaAPIClient .getAccountInfo(account: address.base58EncodedString) - let needsCreateDestinationTokenAccount = info?.owner != TokenProgram.id.base58EncodedString + let needsCreateDestinationTokenAccount = !PublicKey.isSPLTokenOrToken2022ProgramId(info?.owner) return .splAccount(needsCreation: needsCreateDestinationTokenAccount) } diff --git a/Packages/KeyAppKit/Sources/FeeRelayerSwift/Relay/Helpers/TransitTokenAccountManager.swift b/Packages/KeyAppKit/Sources/FeeRelayerSwift/Relay/Helpers/TransitTokenAccountManager.swift index 32e562b6f6..c677d70d44 100644 --- a/Packages/KeyAppKit/Sources/FeeRelayerSwift/Relay/Helpers/TransitTokenAccountManager.swift +++ b/Packages/KeyAppKit/Sources/FeeRelayerSwift/Relay/Helpers/TransitTokenAccountManager.swift @@ -29,7 +29,8 @@ public class TransitTokenAccountManagerImpl: TransitTokenAccountManager { return TokenAccount( address: transitTokenAccountAddress, - mint: transitTokenMintPubkey + mint: transitTokenMintPubkey, + minimumTokenAccountBalance: getTransitTokenMintRentExemption(pools: pools) ?? 2_039_280 ) } @@ -39,6 +40,11 @@ public class TransitTokenAccountManagerImpl: TransitTokenAccountManager { return try PublicKey(string: orcaSwap.getMint(tokenName: interTokenName)) } + func getTransitTokenMintRentExemption(pools: PoolsPair) -> UInt64? { + guard pools.count == 2 else { return nil } + return pools[0].tokenBMinimumBalanceForRentExemption + } + public func checkIfNeedsCreateTransitTokenAccount(transitToken: TokenAccount?) async throws -> Bool? { guard let transitToken = transitToken else { return nil } diff --git a/Packages/KeyAppKit/Sources/FeeRelayerSwift/Relay/RelaySwap/TransactionBuilder/SwapTransactionBuilderImpl/SwapTransactionBuilderImpl+Checks/SwapTransactionBuilderImpl+CheckDestination.swift b/Packages/KeyAppKit/Sources/FeeRelayerSwift/Relay/RelaySwap/TransactionBuilder/SwapTransactionBuilderImpl/SwapTransactionBuilderImpl+Checks/SwapTransactionBuilderImpl+CheckDestination.swift index 1255a36d30..ac2b9ed9d0 100644 --- a/Packages/KeyAppKit/Sources/FeeRelayerSwift/Relay/RelaySwap/TransactionBuilder/SwapTransactionBuilderImpl/SwapTransactionBuilderImpl+Checks/SwapTransactionBuilderImpl+CheckDestination.swift +++ b/Packages/KeyAppKit/Sources/FeeRelayerSwift/Relay/RelaySwap/TransactionBuilder/SwapTransactionBuilderImpl/SwapTransactionBuilderImpl+Checks/SwapTransactionBuilderImpl+CheckDestination.swift @@ -50,14 +50,16 @@ extension SwapTransactionBuilderImpl { // For other token, get associated token address let associatedAddress = try PublicKey.associatedTokenAddress( walletAddress: owner.publicKey, - tokenMintAddress: destinationMint + tokenMintAddress: destinationMint, + tokenProgramId: TokenProgram.id ) if needsCreation { let instruction = try AssociatedTokenProgram.createAssociatedTokenAccountInstruction( mint: destinationMint, owner: owner.publicKey, - payer: feePayerAddress + payer: feePayerAddress, + tokenProgramId: TokenProgram.id ) // SPECIAL CASE WHEN WE SWAP FROM SOL TO NON-CREATED SPL TOKEN, THEN WE NEEDS ADDITIONAL TRANSACTION diff --git a/Packages/KeyAppKit/Sources/FeeRelayerSwift/Relay/RelaySwap/TransactionBuilder/SwapTransactionBuilderImpl/SwapTransactionBuilderImpl.swift b/Packages/KeyAppKit/Sources/FeeRelayerSwift/Relay/RelaySwap/TransactionBuilder/SwapTransactionBuilderImpl/SwapTransactionBuilderImpl.swift index 69fe8d7bbe..7869058e74 100644 --- a/Packages/KeyAppKit/Sources/FeeRelayerSwift/Relay/RelaySwap/TransactionBuilder/SwapTransactionBuilderImpl/SwapTransactionBuilderImpl.swift +++ b/Packages/KeyAppKit/Sources/FeeRelayerSwift/Relay/RelaySwap/TransactionBuilder/SwapTransactionBuilderImpl/SwapTransactionBuilderImpl.swift @@ -43,7 +43,8 @@ public class SwapTransactionBuilderImpl: SwapTransactionBuilder { // assert userSource let associatedToken = try PublicKey.associatedTokenAddress( walletAddress: feePayerAddress, - tokenMintAddress: sourceTokenAccount.mint + tokenMintAddress: sourceTokenAccount.mint, + tokenProgramId: TokenProgram.id ) guard output.userSource != associatedToken else { throw FeeRelayerError.wrongAddress } diff --git a/Packages/KeyAppKit/Sources/FeeRelayerSwift/Relay/Service/TopUpTransactionBuilder/TopUpTransactionBuilderImpl.swift b/Packages/KeyAppKit/Sources/FeeRelayerSwift/Relay/Service/TopUpTransactionBuilder/TopUpTransactionBuilderImpl.swift index 641d17abfe..3f9387c355 100644 --- a/Packages/KeyAppKit/Sources/FeeRelayerSwift/Relay/Service/TopUpTransactionBuilder/TopUpTransactionBuilderImpl.swift +++ b/Packages/KeyAppKit/Sources/FeeRelayerSwift/Relay/Service/TopUpTransactionBuilder/TopUpTransactionBuilderImpl.swift @@ -43,7 +43,8 @@ class TopUpTransactionBuilderImpl: TopUpTransactionBuilder { let feePayerAddress = context.feePayerAddress let associatedTokenAddress = try PublicKey.associatedTokenAddress( walletAddress: feePayerAddress, - tokenMintAddress: sourceTokenMintAddress + tokenMintAddress: sourceTokenMintAddress, + tokenProgramId: TokenProgram.id ) ?! FeeRelayerError.unknown let network = solanaApiClient.endpoint.network @@ -102,7 +103,7 @@ class TopUpTransactionBuilderImpl: TopUpTransactionBuilder { switch swap.swapData { case let swap as DirectSwapData: - expectedFee.accountBalances += context.minimumTokenAccountBalance + expectedFee.accountBalances += sourceToken.minimumTokenAccountBalance // approve if let userTransferAuthority = userTransferAuthority { instructions.append( @@ -154,7 +155,7 @@ class TopUpTransactionBuilderImpl: TopUpTransactionBuilder { } // Destination WSOL account funding - expectedFee.accountBalances += context.minimumTokenAccountBalance + expectedFee.accountBalances += sourceToken.minimumTokenAccountBalance // top up try instructions.append( diff --git a/Packages/KeyAppKit/Sources/KeyAppBusiness/Solana/Socket/RealtimeSolanaAccountService.swift b/Packages/KeyAppKit/Sources/KeyAppBusiness/Solana/Socket/RealtimeSolanaAccountService.swift index b54532d86d..f2d84b2232 100644 --- a/Packages/KeyAppKit/Sources/KeyAppBusiness/Solana/Socket/RealtimeSolanaAccountService.swift +++ b/Packages/KeyAppKit/Sources/KeyAppBusiness/Solana/Socket/RealtimeSolanaAccountService.swift @@ -241,11 +241,30 @@ final class RealtimeSolanaAccountServiceImpl: RealtimeSolanaAccountService { ] ) + let splToken2022AccountChange = solanaWebSocketMethod.programSubscribe( + program: Token2022Program.id.base58EncodedString, + commitment: "confirmed", + encoding: "base64", + filters: [ + [ + "dataSize": 165, + ], + [ + "memcmp": [ + "offset": 32, + "bytes": owner, + ] as [String: Any], + ], + ] + ) + let nativeAccountChangeRequest = try JSONSerialization.data(withJSONObject: nativeAccountChange) let splAccountChangeRequest = try JSONSerialization.data(withJSONObject: splAccountChange) + let splToken2022AccountChangeRequest = try JSONSerialization.data(withJSONObject: splToken2022AccountChange) ws.send(nativeAccountChangeRequest.bytes) ws.send(splAccountChangeRequest.bytes) + ws.send(splToken2022AccountChangeRequest.bytes) return nil } catch { @@ -267,6 +286,7 @@ final class RealtimeSolanaAccountServiceImpl: RealtimeSolanaAccountService { apiClient.getBalance(account: owner, commitment: "confirmed"), apiClient.getAccountBalances( for: owner, + withToken2022: true, tokensRepository: tokensService, commitment: "confirmed" ) @@ -277,7 +297,9 @@ final class RealtimeSolanaAccountServiceImpl: RealtimeSolanaAccountService { let solanaAccount = try SolanaAccount( address: owner, lamports: balance, - token: await tokensService.nativeToken + token: await tokensService.nativeToken, + minRentExemption: nil, + tokenProgramId: nil ) let accounts = [solanaAccount] + resolved @@ -289,7 +311,9 @@ final class RealtimeSolanaAccountServiceImpl: RealtimeSolanaAccountService { return SolanaAccount( address: pubKey, lamports: accountBalance.lamports ?? 0, - token: accountBalance.token + token: accountBalance.token, + minRentExemption: accountBalance.minimumBalanceForRentExemption, + tokenProgramId: accountBalance.tokenProgramId ) } .compactMap { $0 } @@ -328,10 +352,13 @@ final class RealtimeSolanaAccountServiceImpl: RealtimeSolanaAccountService { // TODO: Add case when token info is invalid if let token { + let minRentExempt = value.account.lamports let splAccount = SolanaAccount( address: pubKey, lamports: tokenAccountData.lamports, - token: token + token: token, + minRentExemption: minRentExempt, + tokenProgramId: value.account.owner ) accountsSubject.send(splAccount) } @@ -347,7 +374,9 @@ final class RealtimeSolanaAccountServiceImpl: RealtimeSolanaAccountService { let nativeSolanaAccount = try SolanaAccount( address: owner, lamports: notification.result.value.lamports, - token: await tokensService.nativeToken + token: await tokensService.nativeToken, + minRentExemption: nil, + tokenProgramId: nil ) accountsSubject.send(nativeSolanaAccount) } diff --git a/Packages/KeyAppKit/Sources/KeyAppBusiness/Solana/SolanaAccountsService.swift b/Packages/KeyAppKit/Sources/KeyAppBusiness/Solana/SolanaAccountsService.swift index c9b7790654..aef0bbda20 100644 --- a/Packages/KeyAppKit/Sources/KeyAppBusiness/Solana/SolanaAccountsService.swift +++ b/Packages/KeyAppKit/Sources/KeyAppBusiness/Solana/SolanaAccountsService.swift @@ -263,6 +263,7 @@ class SolanaAccountAsyncValue: AsyncValue<[SolanaAccount]> { solanaAPIClient.getBalance(account: accountAddress, commitment: "confirmed"), solanaAPIClient.getAccountBalances( for: accountAddress, + withToken2022: true, tokensRepository: tokensService, commitment: "confirmed" ) @@ -271,7 +272,9 @@ class SolanaAccountAsyncValue: AsyncValue<[SolanaAccount]> { let solanaAccount = try SolanaAccount( address: accountAddress, lamports: balance, - token: await tokensService.nativeToken + token: await tokensService.nativeToken, + minRentExemption: nil, + tokenProgramId: nil ) newAccounts = [solanaAccount] + resolved @@ -283,7 +286,9 @@ class SolanaAccountAsyncValue: AsyncValue<[SolanaAccount]> { return SolanaAccount( address: pubkey, lamports: accountBalance.lamports ?? 0, - token: accountBalance.token + token: accountBalance.token, + minRentExemption: accountBalance.minimumBalanceForRentExemption, + tokenProgramId: accountBalance.tokenProgramId ) } .compactMap { $0 } diff --git a/Packages/KeyAppKit/Sources/KeyAppKitCore/Blockchain/Solana/SolanaAccount.swift b/Packages/KeyAppKit/Sources/KeyAppKitCore/Blockchain/Solana/SolanaAccount.swift index d833d536a2..332791b7a1 100644 --- a/Packages/KeyAppKit/Sources/KeyAppKitCore/Blockchain/Solana/SolanaAccount.swift +++ b/Packages/KeyAppKit/Sources/KeyAppKitCore/Blockchain/Solana/SolanaAccount.swift @@ -16,19 +16,42 @@ public struct SolanaAccount: Identifiable, Equatable, Hashable { /// The fetched price at current moment of time. public var price: TokenPrice? - public init(address: String, lamports: Lamports, token: SolanaToken, price: TokenPrice? = nil) { + public var minRentExemption: UInt64? + + public var tokenProgramId: String? + + // MARK: - Intializers + + public init( + address: String, + lamports: Lamports, + token: SolanaToken, + price: TokenPrice? = nil, + minRentExemption: UInt64?, + tokenProgramId: String? + ) { self.address = address self.lamports = lamports self.token = token self.price = price + self.minRentExemption = minRentExemption + self.tokenProgramId = tokenProgramId } - @available(*, deprecated) - public init(pubkey: String? = nil, lamports: Lamports? = nil, token: TokenMetadata) { - address = pubkey ?? "" - self.lamports = lamports ?? 0 - self.token = token - price = nil + public static func classicSPLTokenAccount( + address: String, + lamports: Lamports, + token: SolanaToken, + price: TokenPrice? = nil + ) -> Self { + .init( + address: address, + lamports: lamports, + token: token, + price: price, + minRentExemption: 2_039_280, + tokenProgramId: TokenProgram.id.base58EncodedString + ) } public var cryptoAmount: CryptoAmount { @@ -47,6 +70,18 @@ public extension SolanaAccount { token.mintAddress } + var isNative: Bool { + token.isNative + } + + var symbol: String { + token.symbol + } + + var decimals: Decimals { + token.decimals + } + @available(*, deprecated, renamed: "address") var pubkey: String? { get { @@ -81,6 +116,12 @@ public extension SolanaAccount { @available(*, deprecated, message: "Legacy code") static func nativeSolana(pubkey: String?, lamport: Lamports?) -> Self { - .init(pubkey: pubkey, lamports: lamport, token: .nativeSolana) + .init( + address: pubkey ?? "", + lamports: lamport ?? 0, + token: .nativeSolana, + minRentExemption: nil, + tokenProgramId: nil + ) } } diff --git a/Packages/KeyAppKit/Sources/OrcaSwapSwift/Models/BalancesCache.swift b/Packages/KeyAppKit/Sources/OrcaSwapSwift/Models/Caches.swift similarity index 52% rename from Packages/KeyAppKit/Sources/OrcaSwapSwift/Models/BalancesCache.swift rename to Packages/KeyAppKit/Sources/OrcaSwapSwift/Models/Caches.swift index 2f8fb16108..c77206d09d 100644 --- a/Packages/KeyAppKit/Sources/OrcaSwapSwift/Models/BalancesCache.swift +++ b/Packages/KeyAppKit/Sources/OrcaSwapSwift/Models/Caches.swift @@ -16,3 +16,19 @@ actor BalancesCache { balancesCache[key] = value } } + +actor MinRentCache { + var minRentCache = [String: UInt64]() + + func getTokenABalance(pool: Pool) -> UInt64? { + pool.tokenAMinimumBalanceForRentExemption ?? minRentCache[pool.tokenAccountA] + } + + func getTokenBBalance(pool: Pool) -> UInt64? { + pool.tokenBMinimumBalanceForRentExemption ?? minRentCache[pool.tokenAccountB] + } + + func save(key: String, value: UInt64) { + minRentCache[key] = value + } +} diff --git a/Packages/KeyAppKit/Sources/OrcaSwapSwift/Models/Pool.swift b/Packages/KeyAppKit/Sources/OrcaSwapSwift/Models/Pool.swift index f55b4a17cc..3a6d5960c0 100644 --- a/Packages/KeyAppKit/Sources/OrcaSwapSwift/Models/Pool.swift +++ b/Packages/KeyAppKit/Sources/OrcaSwapSwift/Models/Pool.swift @@ -33,6 +33,8 @@ public struct Pool: Codable, Equatable { // balance (lazy load) var tokenABalance: TokenAccountBalance? var tokenBBalance: TokenAccountBalance? + public var tokenAMinimumBalanceForRentExemption: UInt64? + public var tokenBMinimumBalanceForRentExemption: UInt64? var isStable: Bool? @@ -268,6 +270,7 @@ extension Pool { destinationAccountInstructions = try await blockchainClient.prepareForCreatingAssociatedTokenAccount( owner: owner, mint: toMint, + tokenProgramId: TokenProgram.id, feePayer: feePayer ?? owner, closeAfterward: false ) diff --git a/Packages/KeyAppKit/Sources/OrcaSwapSwift/OrcaSwap/OrcaSwap+Extensions.swift b/Packages/KeyAppKit/Sources/OrcaSwapSwift/OrcaSwap/OrcaSwap+Extensions.swift index 0f5ae55e2b..c2f6897c3b 100644 --- a/Packages/KeyAppKit/Sources/OrcaSwapSwift/OrcaSwap/OrcaSwap+Extensions.swift +++ b/Packages/KeyAppKit/Sources/OrcaSwapSwift/OrcaSwap/OrcaSwap+Extensions.swift @@ -81,10 +81,16 @@ extension OrcaSwap { (tokenABalance, tokenBBalance) = (tab, tbb) } else { try Task.checkCancellation() - (tokenABalance, tokenBBalance) = try await( - solanaClient.getTokenAccountBalance(pubkey: pool.tokenAccountA, commitment: nil), - solanaClient.getTokenAccountBalance(pubkey: pool.tokenAccountB, commitment: nil) + let pool = pool + async let tokenABalanceResult = solanaClient.getTokenAccountBalance( + pubkey: pool.tokenAccountA, + commitment: nil ) + async let tokenBBalanceResult = solanaClient.getTokenAccountBalance( + pubkey: pool.tokenAccountB, + commitment: nil + ) + (tokenABalance, tokenBBalance) = try await(tokenABalanceResult, tokenBBalanceResult) } await balancesCache.save(key: pool.tokenAccountA, value: tokenABalance) @@ -93,6 +99,31 @@ extension OrcaSwap { pool.tokenABalance = tokenABalance pool.tokenBBalance = tokenBBalance + // get minrent exemption + let (tokenAMinRent, tokenBMinRent): (UInt64, UInt64) + if let tab = await minRentCache.getTokenABalance(pool: pool), + let tbb = await minRentCache.getTokenBBalance(pool: pool) + { + (tokenAMinRent, tokenBMinRent) = (tab, tbb) + } else { + try Task.checkCancellation() + let pool = pool + async let tokenAMinRentResult: BufferInfo? = solanaClient + .getAccountInfo(account: pool.tokenAccountA) + async let tokenBMinRentResult: BufferInfo? = solanaClient + .getAccountInfo(account: pool.tokenAccountB) + (tokenAMinRent, tokenBMinRent) = try await( + tokenAMinRentResult?.lamports ?? 2_039_280, + tokenBMinRentResult?.lamports ?? 2_039_280 + ) + } + + await minRentCache.save(key: pool.tokenAccountA, value: tokenAMinRent) + await minRentCache.save(key: pool.tokenAccountB, value: tokenBMinRent) + + pool.tokenAMinimumBalanceForRentExemption = tokenAMinRent + pool.tokenBMinimumBalanceForRentExemption = tokenBMinRent + return pool } } diff --git a/Packages/KeyAppKit/Sources/OrcaSwapSwift/OrcaSwap/OrcaSwap.swift b/Packages/KeyAppKit/Sources/OrcaSwapSwift/OrcaSwap/OrcaSwap.swift index 64467a28d3..21860c2780 100644 --- a/Packages/KeyAppKit/Sources/OrcaSwapSwift/OrcaSwap/OrcaSwap.swift +++ b/Packages/KeyAppKit/Sources/OrcaSwapSwift/OrcaSwap/OrcaSwap.swift @@ -12,6 +12,7 @@ public class OrcaSwap: OrcaSwapType { var info: SwapInfo? let balancesCache = BalancesCache() + let minRentCache = MinRentCache() let locker = NSLock() // MARK: - Initializer @@ -286,7 +287,11 @@ public class OrcaSwap: OrcaSwapType { // Check if intermediary token creation is needed else { isIntermediaryTokenCreated = try await solanaClient - .checkIfAssociatedTokenAccountExists(owner: owner, mint: mint) + .checkIfAssociatedTokenAccountExists( + owner: owner, + mint: mint, + tokenProgramId: TokenProgram.id + ) } } @@ -610,6 +615,7 @@ public class OrcaSwap: OrcaSwapType { .prepareForCreatingAssociatedTokenAccount( owner: owner.publicKey, mint: destinationMint, + tokenProgramId: TokenProgram.id, feePayer: feePayer ?? owner.publicKey, closeAfterward: false ) @@ -620,7 +626,7 @@ public class OrcaSwap: OrcaSwapType { from: owner.publicKey, amount: 0, payer: feePayer ?? owner.publicKey, - minRentExemption: nil + minRentExemption: await solanaClient.getMinimumBalanceForRentExemption(span: 165) ), createDestinationTokenAccountInstructionsRequest ) @@ -629,6 +635,7 @@ public class OrcaSwap: OrcaSwapType { blockchainClient.prepareForCreatingAssociatedTokenAccount( owner: owner.publicKey, mint: intermediaryTokenMint, + tokenProgramId: TokenProgram.id, feePayer: feePayer ?? owner.publicKey, closeAfterward: true ), diff --git a/Packages/KeyAppKit/Sources/Send/Action/SendAction.swift b/Packages/KeyAppKit/Sources/Send/Action/SendAction.swift index 61e0f26561..693d62db04 100644 --- a/Packages/KeyAppKit/Sources/Send/Action/SendAction.swift +++ b/Packages/KeyAppKit/Sources/Send/Action/SendAction.swift @@ -81,7 +81,10 @@ public class SendActionServiceImpl: SendActionService { let currency = wallet.token.mintAddress // get paying fee token - let payingFeeToken = try? getPayingFeeToken(feeWallet: feeWallet) + let payingFeeToken = try? getPayingFeeToken( + feeWallet: feeWallet, + minimumTokenAccountBalance: wallet.minRentExemption ?? 2_039_280 + ) // prepare sending to Solana (returning legacy transaction) var (preparedTransaction, useFeeRelayer) = try await prepareForSendingToSolanaNetworkViaRelayMethod( @@ -155,7 +158,6 @@ public class SendActionServiceImpl: SendActionService { payingFeeToken: FeeRelayerSwift.TokenAccount?, recentBlockhash: String? = nil, lamportsPerSignature _: Lamports? = nil, - minRentExemption: Lamports? = nil, memo: String? ) async throws -> (preparedTransaction: PreparedTransaction, useFeeRelayer: Bool) { let amount = amount.toLamport(decimals: wallet.token.decimals) @@ -191,13 +193,15 @@ public class SendActionServiceImpl: SendActionService { preparedTransaction = try await blockchainClient.prepareSendingSPLTokens( account: account, mintAddress: wallet.token.mintAddress, + tokenProgramId: PublicKey(string: wallet.tokenProgramId), decimals: wallet.token.decimals, from: sender, to: receiver, amount: amount, feePayer: feePayer, transferChecked: useFeeRelayer, // create transferChecked instruction when using fee relayer - minRentExemption: minRentExemption + lamportsPerSignature: context.lamportsPerSignature, + minRentExemption: wallet.minRentExemption ?? 2_039_280 ).preparedTransaction } @@ -222,7 +226,10 @@ public class SendActionServiceImpl: SendActionService { context.usageStatus.isFreeTransactionFeeAvailable(transactionFee: expectedTransactionFee) == false } - private func getPayingFeeToken(feeWallet: SolanaAccount?) throws -> FeeRelayerSwift.TokenAccount? { + private func getPayingFeeToken( + feeWallet: SolanaAccount?, + minimumTokenAccountBalance: UInt64 + ) throws -> FeeRelayerSwift.TokenAccount? { if let feeWallet = feeWallet { let addressString = feeWallet.address guard let address = try? PublicKey(string: addressString), @@ -230,7 +237,11 @@ public class SendActionServiceImpl: SendActionService { else { throw SendError.invalidPayingFeeWallet } - return .init(address: address, mint: mintAddress) + return .init( + address: address, + mint: mintAddress, + minimumTokenAccountBalance: minimumTokenAccountBalance + ) } return nil } diff --git a/Packages/KeyAppKit/Sources/Send/Input/SendInputBusinessLogic+ChangeToken.swift b/Packages/KeyAppKit/Sources/Send/Input/SendInputBusinessLogic+ChangeToken.swift index d624ca4988..6efdcf2716 100644 --- a/Packages/KeyAppKit/Sources/Send/Input/SendInputBusinessLogic+ChangeToken.swift +++ b/Packages/KeyAppKit/Sources/Send/Input/SendInputBusinessLogic+ChangeToken.swift @@ -6,7 +6,7 @@ import SolanaSwift extension SendInputBusinessLogic { static func changeToken( state: SendInputState, - token: TokenMetadata, + token: SolanaAccount, services: SendInputServices ) async -> SendInputState { guard let feeRelayerContext = state.feeRelayerContext else { @@ -81,9 +81,9 @@ extension SendInputBusinessLogic { static func autoSelectTokenFee( userWallets: [SolanaAccount], feeInSol: FeeAmount, - token: TokenMetadata, + token: SolanaAccount, services: SendInputServices - ) async -> (token: TokenMetadata, fee: FeeAmount?) { + ) async -> (token: SolanaAccount, fee: FeeAmount?) { var preferOrder = ["SOL": 2] if !preferOrder.keys.contains(token.symbol) { preferOrder[token.symbol] = 1 @@ -113,13 +113,13 @@ extension SendInputBusinessLogic { )) ?? .zero if feeInToken.total <= wallet.lamports { - return (wallet.token, feeInToken) + return (wallet, feeInToken) } } catch { continue } } - return (.nativeSolana, feeInSol) + return (.nativeSolana(pubkey: nil, lamport: nil), feeInSol) } } diff --git a/Packages/KeyAppKit/Sources/Send/Input/SendInputBusinessLogic+ChangeTokenFee.swift b/Packages/KeyAppKit/Sources/Send/Input/SendInputBusinessLogic+ChangeTokenFee.swift index 3026b0f69e..2d105fccf8 100644 --- a/Packages/KeyAppKit/Sources/Send/Input/SendInputBusinessLogic+ChangeTokenFee.swift +++ b/Packages/KeyAppKit/Sources/Send/Input/SendInputBusinessLogic+ChangeTokenFee.swift @@ -1,11 +1,12 @@ import FeeRelayerSwift import Foundation +import KeyAppKitCore import SolanaSwift extension SendInputBusinessLogic { static func changeFeeToken( state: SendInputState, - feeToken: TokenMetadata, + feeToken: SolanaAccount, services: SendInputServices ) async -> SendInputState { guard let feeRelayerContext = state.feeRelayerContext else { diff --git a/Packages/KeyAppKit/Sources/Send/Input/SendInputBusinessLogic+Initializing.swift b/Packages/KeyAppKit/Sources/Send/Input/SendInputBusinessLogic+Initializing.swift index 3668dfde1d..1fc28963f8 100644 --- a/Packages/KeyAppKit/Sources/Send/Input/SendInputBusinessLogic+Initializing.swift +++ b/Packages/KeyAppKit/Sources/Send/Input/SendInputBusinessLogic+Initializing.swift @@ -17,7 +17,7 @@ extension SendInputBusinessLogic { recipientAdditionalInfo = try await .init( walletAccount: services.solanaAPIClient.getAccountInfo(account: state.recipient.address), splAccounts: services.solanaAPIClient - .getTokenAccountsByOwner( + .getTokenAccountsByOwnerWithToken2022( pubkey: state.recipient.address, params: .init( mint: nil, @@ -30,7 +30,7 @@ extension SendInputBusinessLogic { recipientAdditionalInfo = try await .init( walletAccount: services.solanaAPIClient.getAccountInfo(account: walletAddress.base58EncodedString), splAccounts: services.solanaAPIClient - .getTokenAccountsByOwner( + .getTokenAccountsByOwnerWithToken2022( pubkey: walletAddress.base58EncodedString, params: .init( mint: nil, diff --git a/Packages/KeyAppKit/Sources/Send/Input/SendInputState.swift b/Packages/KeyAppKit/Sources/Send/Input/SendInputState.swift index 48c2ef5e8b..1c8d076c67 100644 --- a/Packages/KeyAppKit/Sources/Send/Input/SendInputState.swift +++ b/Packages/KeyAppKit/Sources/Send/Input/SendInputState.swift @@ -32,8 +32,8 @@ public enum SendInputAction: Equatable { case changeAmountInFiat(Double) case changeAmountInToken(Double) - case changeUserToken(TokenMetadata) - case changeFeeToken(TokenMetadata) + case changeUserToken(SolanaAccount) + case changeFeeToken(SolanaAccount) } public struct SendInputServices { @@ -97,7 +97,7 @@ public struct SendInputState: Equatable { public let recipient: Recipient public let recipientAdditionalInfo: RecipientAdditionalInfo - public let token: TokenMetadata + public let token: SolanaAccount public let userWalletEnvironments: UserWalletEnvironments public let amountInFiat: Double @@ -107,7 +107,7 @@ public struct SendInputState: Equatable { public let fee: FeeAmount /// Selected fee token - public let tokenFee: TokenMetadata + public let tokenFee: SolanaAccount /// Amount fee in Token (Converted from amount fee in SOL) public let feeInToken: FeeAmount @@ -127,12 +127,12 @@ public struct SendInputState: Equatable { status: Status, recipient: Recipient, recipientAdditionalInfo: RecipientAdditionalInfo, - token: TokenMetadata, + token: SolanaAccount, userWalletEnvironments: UserWalletEnvironments, amountInFiat: Double, amountInToken: Double, fee: FeeAmount, - tokenFee: TokenMetadata, + tokenFee: SolanaAccount, feeInToken: FeeAmount, feeRelayerContext: RelayContext?, sendViaLinkSeed: String? @@ -155,8 +155,8 @@ public struct SendInputState: Equatable { status: Status = .requiredInitialize, recipient: Recipient, recipientAdditionalInfo: RecipientAdditionalInfo = .zero, - token: TokenMetadata, - feeToken: TokenMetadata, + token: SolanaAccount, + feeToken: SolanaAccount, userWalletState: UserWalletEnvironments, feeRelayerContext: RelayContext? = nil, sendViaLinkSeed: String? @@ -181,12 +181,12 @@ public struct SendInputState: Equatable { status: Status? = nil, recipient: Recipient? = nil, recipientAdditionalInfo: RecipientAdditionalInfo? = nil, - token: TokenMetadata? = nil, + token: SolanaAccount? = nil, userWalletEnvironments: UserWalletEnvironments? = nil, amountInFiat: Double? = nil, amountInToken: Double? = nil, fee: FeeAmount? = nil, - tokenFee: TokenMetadata? = nil, + tokenFee: SolanaAccount? = nil, feeInToken: FeeAmount? = nil, feeRelayerContext: RelayContext? = nil, sendViaLinkSeed: String?? = nil diff --git a/Packages/KeyAppKit/Sources/Send/Input/Services/SendFeeCalculator.swift b/Packages/KeyAppKit/Sources/Send/Input/Services/SendFeeCalculator.swift index 76e8462c1f..28d65ec734 100644 --- a/Packages/KeyAppKit/Sources/Send/Input/Services/SendFeeCalculator.swift +++ b/Packages/KeyAppKit/Sources/Send/Input/Services/SendFeeCalculator.swift @@ -1,10 +1,11 @@ import FeeRelayerSwift +import KeyAppKitCore import OrcaSwapSwift import SolanaSwift public protocol SendFeeCalculator: AnyObject { func getFees( - from token: TokenMetadata, + from token: SolanaAccount, recipient: Recipient, recipientAdditionalInfo: SendInputState.RecipientAdditionalInfo, payingTokenMint: String?, @@ -15,12 +16,14 @@ public protocol SendFeeCalculator: AnyObject { public class SendFeeCalculatorImpl: SendFeeCalculator { private let feeRelayerCalculator: RelayFeeCalculator - public init(feeRelayerCalculator: RelayFeeCalculator) { self.feeRelayerCalculator = feeRelayerCalculator } + public init(feeRelayerCalculator: RelayFeeCalculator) { + self.feeRelayerCalculator = feeRelayerCalculator + } // MARK: - Fees calculator public func getFees( - from token: TokenMetadata, + from token: SolanaAccount, recipient: Recipient, recipientAdditionalInfo: SendInputState.RecipientAdditionalInfo, payingTokenMint: String?, @@ -44,7 +47,8 @@ public class SendFeeCalculatorImpl: SendFeeCalculator { case let .solanaTokenAddress(walletAddress, _): let associatedAccount = try PublicKey.associatedTokenAddress( walletAddress: walletAddress, - tokenMintAddress: PublicKey(string: token.mintAddress) + tokenMintAddress: PublicKey(string: token.mintAddress), + tokenProgramId: PublicKey(string: token.tokenProgramId) ) isAssociatedTokenUnregister = !recipientAdditionalInfo.splAccounts @@ -52,7 +56,8 @@ public class SendFeeCalculatorImpl: SendFeeCalculator { case .solanaAddress, .username: let associatedAccount = try PublicKey.associatedTokenAddress( walletAddress: PublicKey(string: recipient.address), - tokenMintAddress: PublicKey(string: token.mintAddress) + tokenMintAddress: PublicKey(string: token.mintAddress), + tokenProgramId: PublicKey(string: token.tokenProgramId) ) isAssociatedTokenUnregister = !recipientAdditionalInfo.splAccounts @@ -71,7 +76,7 @@ public class SendFeeCalculatorImpl: SendFeeCalculator { let expectedFee = FeeAmount( transaction: transactionFee, - accountBalances: isAssociatedTokenUnregister ? context.minimumTokenAccountBalance : 0 + accountBalances: isAssociatedTokenUnregister ? token.minRentExemption ?? 0 : 0 ) // TODO: - Remove later: Send as SendViaLink when link creation available diff --git a/Packages/KeyAppKit/Sources/Send/RecipientSearch/RecipientSearchServiceImpl/RecipientSearchServiceImpl+searchBySolanaAddress.swift b/Packages/KeyAppKit/Sources/Send/RecipientSearch/RecipientSearchServiceImpl/RecipientSearchServiceImpl+searchBySolanaAddress.swift index 5e76405dae..e69ce3201f 100644 --- a/Packages/KeyAppKit/Sources/Send/RecipientSearch/RecipientSearchServiceImpl/RecipientSearchServiceImpl+searchBySolanaAddress.swift +++ b/Packages/KeyAppKit/Sources/Send/RecipientSearch/RecipientSearchServiceImpl/RecipientSearchServiceImpl+searchBySolanaAddress.swift @@ -88,29 +88,29 @@ extension RecipientSearchServiceImpl { } } } else { - let splAccounts = try await solanaClient.getTokenAccountsByOwner( - pubkey: address.base58EncodedString, - params: .init( - mint: nil, - programId: TokenProgram.id.base58EncodedString - ), - configs: .init(encoding: "base64") - ) - - if splAccounts.isEmpty { - // This account doesn't exits in blockchain - return .ok([.init( - address: addressBase58, - category: .solanaAddress, - attributes: [.funds, attributes] - )]) - } else { - return .ok([.init( - address: addressBase58, - category: .solanaAddress, - attributes: [.funds, attributes] - )]) - } +// let splAccounts = try await solanaClient.getTokenAccountsByOwner( +// pubkey: address.base58EncodedString, +// params: .init( +// mint: nil, +// programId: TokenProgram.id.base58EncodedString +// ), +// configs: .init(encoding: "base64") +// ) +// +// if splAccounts.isEmpty { +// // This account doesn't exits in blockchain +// return .ok([.init( +// address: addressBase58, +// category: .solanaAddress, +// attributes: [.funds, attributes] +// )]) +// } else { + return .ok([.init( + address: addressBase58, + category: .solanaAddress, + attributes: [.funds, attributes] + )]) +// } } } catch let error as SolanaSwift.APIClientError { return handleSolanaAPIClientError(error) diff --git a/Packages/KeyAppKit/Sources/Send/RecipientSearch/RecipientSearchServiceImpl/RecipientSearchServiceImpl.swift b/Packages/KeyAppKit/Sources/Send/RecipientSearch/RecipientSearchServiceImpl/RecipientSearchServiceImpl.swift index 8f48146012..f5803e946f 100644 --- a/Packages/KeyAppKit/Sources/Send/RecipientSearch/RecipientSearchServiceImpl/RecipientSearchServiceImpl.swift +++ b/Packages/KeyAppKit/Sources/Send/RecipientSearch/RecipientSearchServiceImpl/RecipientSearchServiceImpl.swift @@ -8,7 +8,11 @@ public class RecipientSearchServiceImpl: RecipientSearchService { let solanaClient: SolanaAPIClient let swapService: SwapService - public init(nameService: NameService, solanaClient: SolanaAPIClient, swapService: SwapService) { + public init( + nameService: NameService, + solanaClient: SolanaAPIClient, + swapService: SwapService + ) { self.nameService = nameService self.solanaClient = solanaClient self.swapService = swapService diff --git a/Packages/KeyAppKit/Sources/Send/SendViaLink/SendViaLinkDataService.swift b/Packages/KeyAppKit/Sources/Send/SendViaLink/SendViaLinkDataService.swift index b176be09a6..67c79c1528 100644 --- a/Packages/KeyAppKit/Sources/Send/SendViaLink/SendViaLinkDataService.swift +++ b/Packages/KeyAppKit/Sources/Send/SendViaLink/SendViaLinkDataService.swift @@ -351,7 +351,7 @@ public final class SendViaLinkDataServiceImpl: SendViaLinkDataService { } // 2. Get token accounts by owner - let tokenAccounts = try await solanaAPIClient.getTokenAccountsByOwner( + let tokenAccounts = try await solanaAPIClient.getTokenAccountsByOwnerWithToken2022( pubkey: keypair.publicKey.base58EncodedString, params: .init( mint: nil, @@ -442,7 +442,8 @@ public final class SendViaLinkDataServiceImpl: SendViaLinkDataService { // get associated token address let splDestination = try await solanaAPIClient.findSPLTokenDestinationAddress( mintAddress: mintAddress.base58EncodedString, - destinationAddress: receiver.base58EncodedString + destinationAddress: receiver.base58EncodedString, + tokenProgramId: TokenProgram.id ) // form instruction @@ -456,7 +457,8 @@ public final class SendViaLinkDataServiceImpl: SendViaLinkDataService { .createAssociatedTokenAccountInstruction( mint: mintAddress, owner: receiver, - payer: feePayer + payer: feePayer, + tokenProgramId: TokenProgram.id ) ) accountsCreationFee += minRentExemption diff --git a/Packages/KeyAppKit/Sources/Send/SolanaAPIClient+Token2022.swift b/Packages/KeyAppKit/Sources/Send/SolanaAPIClient+Token2022.swift new file mode 100644 index 0000000000..5b1fbd0e51 --- /dev/null +++ b/Packages/KeyAppKit/Sources/Send/SolanaAPIClient+Token2022.swift @@ -0,0 +1,27 @@ +import Foundation +import SolanaSwift + +extension SolanaAPIClient { + func getTokenAccountsByOwnerWithToken2022( + pubkey: String, + params: OwnerInfoParams?, + configs: RequestConfiguration? + ) async throws -> [TokenAccount] + { // Temporarily convert all state into basic SPLTokenAccountState layout + async let classicTokenAccounts = getTokenAccountsByOwner( + pubkey: pubkey, + params: params, + configs: configs, + decodingTo: SPLTokenAccountState.self + ) + + async let token2022Accounts = getTokenAccountsByOwner( + pubkey: pubkey, + params: params, + configs: configs, + decodingTo: SPLTokenAccountState.self + ) + + return try await classicTokenAccounts + token2022Accounts + } +} diff --git a/p2p_wallet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/p2p_wallet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 75916bc683..42ef19bda2 100644 --- a/p2p_wallet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/p2p_wallet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -311,8 +311,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/p2p-org/solana-swift", "state" : { - "branch" : "main", - "revision" : "0751755b18d162f534b3e96eb09de37c51e444a0" + "branch" : "feature/token-2022", + "revision" : "2cbdb61ca2ec368b63a60d5c6436b0774932c2ec" } }, { diff --git a/p2p_wallet/Scenes/Main/ReceiveFundsViaLink/ViewModel/ReceiveFundsViaLinkViewModel.swift b/p2p_wallet/Scenes/Main/ReceiveFundsViaLink/ViewModel/ReceiveFundsViaLinkViewModel.swift index 8268697c64..a04543dc37 100644 --- a/p2p_wallet/Scenes/Main/ReceiveFundsViaLink/ViewModel/ReceiveFundsViaLinkViewModel.swift +++ b/p2p_wallet/Scenes/Main/ReceiveFundsViaLink/ViewModel/ReceiveFundsViaLinkViewModel.swift @@ -95,7 +95,11 @@ final class ReceiveFundsViaLinkViewModel: BaseViewModel, ObservableObject { let transaction = ClaimSentViaLinkTransaction( claimableTokenInfo: claimableToken, token: token, - destinationWallet: SolanaAccount(pubkey: claimableToken.account, token: token), + destinationWallet: .classicSPLTokenAccount( + address: claimableToken.account, + lamports: 0, + token: token + ), tokenAmount: cryptoAmount, isFakeTransaction: isFakeSendingTransaction, fakeTransactionErrorType: fakeTransactionErrorType diff --git a/p2p_wallet/Scenes/Main/Send/Input/Details/FeePrompt/SendInputFeePromptView.swift b/p2p_wallet/Scenes/Main/Send/Input/Details/FeePrompt/SendInputFeePromptView.swift index eccbbda684..9e02fb0795 100644 --- a/p2p_wallet/Scenes/Main/Send/Input/Details/FeePrompt/SendInputFeePromptView.swift +++ b/p2p_wallet/Scenes/Main/Send/Input/Details/FeePrompt/SendInputFeePromptView.swift @@ -93,7 +93,11 @@ struct SendInputFeePromptView_Previews: PreviewProvider { static var previews: some View { SendInputFeePromptView( viewModel: SendInputFeePromptViewModel( - feeToken: .init(token: .usdc), + feeToken: .classicSPLTokenAccount( + address: "", + lamports: 0, + token: .usdc + ), feeInToken: .zero, availableFeeTokens: [] ) diff --git a/p2p_wallet/Scenes/Main/Send/Input/Details/SendTransactionDetails/SendTransactionDetailViewModel.swift b/p2p_wallet/Scenes/Main/Send/Input/Details/SendTransactionDetails/SendTransactionDetailViewModel.swift index bd730a9f07..0ae8aa4360 100644 --- a/p2p_wallet/Scenes/Main/Send/Input/Details/SendTransactionDetails/SendTransactionDetailViewModel.swift +++ b/p2p_wallet/Scenes/Main/Send/Input/Details/SendTransactionDetails/SendTransactionDetailViewModel.swift @@ -179,7 +179,7 @@ final class SendTransactionDetailViewModel: BaseViewModel, ObservableObject { ) } - private func convert(_ input: Lamports, _ token: TokenMetadata, _ price: TokenPrice?) -> (String, String?) { + private func convert(_ input: Lamports, _ token: SolanaAccount, _ price: TokenPrice?) -> (String, String?) { let amountInToken: Double = input.convertToBalance(decimals: token.decimals) let amountInFiat: Double? diff --git a/p2p_wallet/Scenes/Main/Send/Input/SendInputViewModel.swift b/p2p_wallet/Scenes/Main/Send/Input/SendInputViewModel.swift index 29434b84b9..c681d5ee61 100644 --- a/p2p_wallet/Scenes/Main/Send/Input/SendInputViewModel.swift +++ b/p2p_wallet/Scenes/Main/Send/Input/SendInputViewModel.swift @@ -113,7 +113,7 @@ final class SendInputViewModel: BaseViewModel, ObservableObject { case let .solanaTokenAddress(_, token): tokenInWallet = wallets .first(where: { $0.token.mintAddress == token.mintAddress }) ?? - SolanaAccount(token: TokenMetadata.nativeSolana) + .nativeSolana(pubkey: nil, lamport: nil) default: if let preChosenWallet { tokenInWallet = preChosenWallet @@ -128,14 +128,14 @@ final class SendInputViewModel: BaseViewModel, ObservableObject { return lhs.amountInCurrentFiat > rhs.amountInCurrentFiat } } - tokenInWallet = sortedWallets.first ?? SolanaAccount(token: TokenMetadata.nativeSolana) + tokenInWallet = sortedWallets.first ?? .nativeSolana(pubkey: nil, lamport: nil) } } sourceWallet = tokenInWallet let feeTokenInWallet = wallets .first(where: { $0.token.mintAddress == TokenMetadata.usdc.mintAddress }) ?? - SolanaAccount(token: TokenMetadata.usdc) + .classicSPLTokenAccount(address: "", lamports: 0, token: .usdc) var exchangeRate = [String: TokenPrice]() var tokens = Set() @@ -153,8 +153,8 @@ final class SendInputViewModel: BaseViewModel, ObservableObject { let state = SendInputState.zero( recipient: recipient, - token: tokenInWallet.token, - feeToken: feeTokenInWallet.token, + token: tokenInWallet, + feeToken: feeTokenInWallet, userWalletState: env, sendViaLinkSeed: sendViaLinkSeed ) @@ -305,7 +305,7 @@ private extension SendInputViewModel { _ = await self.stateMachine.accept(action: .changeAmountInToken(0)) self.inputAmountViewModel.amountText = "0" } - _ = await self.stateMachine.accept(action: .changeUserToken(value.token)) + _ = await self.stateMachine.accept(action: .changeUserToken(value)) await MainActor.run { [weak self] in self?.inputAmountViewModel.token = value self?.isFeeLoading = false @@ -357,7 +357,7 @@ private extension SendInputViewModel { .sinkAsync { [weak self] newFeeToken in guard let self else { return } self.isFeeLoading = true - _ = await self.stateMachine.accept(action: .changeFeeToken(newFeeToken.token)) + _ = await self.stateMachine.accept(action: .changeFeeToken(newFeeToken)) self.isFeeLoading = false } .store(in: &subscriptions) diff --git a/p2p_wallet/Scenes/Main/Send/Input/Subviews/AmountView/SendInputAmountViewModel.swift b/p2p_wallet/Scenes/Main/Send/Input/Subviews/AmountView/SendInputAmountViewModel.swift index 43eeb1b477..e9aa08898c 100644 --- a/p2p_wallet/Scenes/Main/Send/Input/Subviews/AmountView/SendInputAmountViewModel.swift +++ b/p2p_wallet/Scenes/Main/Send/Input/Subviews/AmountView/SendInputAmountViewModel.swift @@ -60,7 +60,9 @@ final class SendInputAmountViewModel: BaseViewModel, ObservableObject { private let fiat: Fiat = Defaults.fiat private var currentText: String? - private var tokenChangedEvent = CurrentValueSubject(.init(token: .nativeSolana)) + private var tokenChangedEvent = CurrentValueSubject( + .nativeSolana(pubkey: nil, lamport: nil) + ) // MARK: - Dependencies diff --git a/p2p_wallet/Scenes/Main/Send/Input/Subviews/AmountView/SendInputAmountWrapperView.swift b/p2p_wallet/Scenes/Main/Send/Input/Subviews/AmountView/SendInputAmountWrapperView.swift index 0dc7f6f2e2..5617243eda 100644 --- a/p2p_wallet/Scenes/Main/Send/Input/Subviews/AmountView/SendInputAmountWrapperView.swift +++ b/p2p_wallet/Scenes/Main/Send/Input/Subviews/AmountView/SendInputAmountWrapperView.swift @@ -34,7 +34,9 @@ struct SendInputAmountWrapperView_Previews: PreviewProvider { ZStack { Color(.smoke) SendInputAmountWrapperView( - viewModel: SendInputAmountViewModel(initialToken: .init(token: .nativeSolana)) + viewModel: SendInputAmountViewModel( + initialToken: .nativeSolana(pubkey: nil, lamport: nil) + ) ) .padding(.horizontal, 16) } diff --git a/p2p_wallet/Scenes/Main/Swap/Swap/SwapViewModel.swift b/p2p_wallet/Scenes/Main/Swap/Swap/SwapViewModel.swift index f5a3b32dc9..3e5a2c027a 100644 --- a/p2p_wallet/Scenes/Main/Swap/Swap/SwapViewModel.swift +++ b/p2p_wallet/Scenes/Main/Swap/Swap/SwapViewModel.swift @@ -457,8 +457,9 @@ private extension SwapViewModel { } // form transaction - let destinationWallet = currentState.toToken.userWallet ?? SolanaAccount( - pubkey: nil, + let destinationWallet = currentState.toToken.userWallet ?? .classicSPLTokenAccount( + address: "", + lamports: 0, token: currentState.toToken.token ) diff --git a/p2p_wallet/Scenes/Main/WalletDetail/New/AccountDetailsViewModel.swift b/p2p_wallet/Scenes/Main/WalletDetail/New/AccountDetailsViewModel.swift index 970e425e81..db79498064 100644 --- a/p2p_wallet/Scenes/Main/WalletDetail/New/AccountDetailsViewModel.swift +++ b/p2p_wallet/Scenes/Main/WalletDetail/New/AccountDetailsViewModel.swift @@ -106,7 +106,11 @@ class AccountDetailsViewModel: BaseViewModel, ObservableObject { .send( .openSwapWithDestination( solanaAccount, - SolanaAccount(token: supportedWormholeToken) + .classicSPLTokenAccount( + address: "", + lamports: 0, + token: supportedWormholeToken + ) ) ) }, close: { [weak self] in diff --git a/p2p_wallet/Scenes/Main/Wormhole/Send/WormholeSendInputCoordinator.swift b/p2p_wallet/Scenes/Main/Wormhole/Send/WormholeSendInputCoordinator.swift index a7de3c6571..eeec0ff8fc 100644 --- a/p2p_wallet/Scenes/Main/Wormhole/Send/WormholeSendInputCoordinator.swift +++ b/p2p_wallet/Scenes/Main/Wormhole/Send/WormholeSendInputCoordinator.swift @@ -51,7 +51,11 @@ final class WormholeSendInputCoordinator: SmartCoordinator