Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ETH-946 - save send fee payer #2194

Merged
merged 7 commits into from
Feb 14, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,40 @@ import kotlinx.parcelize.Parcelize
import org.p2p.solanaj.core.FeeAmount

@Parcelize
data class FeeRelayerFee constructor(
data class FeeRelayerFee(
val transactionFeeInSol: BigInteger,
val accountCreationFeeInSol: BigInteger,

val transactionFeeInSpl: BigInteger,
val accountCreationFeeInSpl: BigInteger,
val transactionFeeInFeePayerToken: BigInteger,
val accountCreationFeeInFeePayerToken: BigInteger,

val transactionFeeInSourceToken: BigInteger,
val accountCreateFeeInSourceToken: BigInteger,

val expectedFee: FeeAmount
) : Parcelable {

constructor(feeInSol: FeeAmount, feeInSpl: FeeAmount, expectedFee: FeeAmount) : this(
transactionFeeInSol = feeInSol.transactionFee,
accountCreationFeeInSol = feeInSol.accountCreationFee,
transactionFeeInSpl = feeInSpl.transactionFee,
accountCreationFeeInSpl = feeInSpl.accountCreationFee,
constructor(
feesInSol: FeeAmount,
feesInFeePayerToken: FeeAmount,
feesInSourceToken: FeeAmount,
expectedFee: FeeAmount
) : this(
transactionFeeInSol = feesInSol.transactionFee,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

god damn that's a lot of fees

Copy link
Collaborator Author

@eduardmaximovich eduardmaximovich Feb 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

accountCreationFeeInSol = feesInSol.accountCreationFee,
transactionFeeInFeePayerToken = feesInFeePayerToken.transactionFee,
accountCreationFeeInFeePayerToken = feesInFeePayerToken.accountCreationFee,
transactionFeeInSourceToken = feesInSourceToken.transactionFee,
accountCreateFeeInSourceToken = feesInSourceToken.accountCreationFee,
expectedFee = expectedFee
)

val totalInSol: BigInteger
get() = transactionFeeInSol + accountCreationFeeInSol

val totalInSpl: BigInteger
get() = transactionFeeInSpl + accountCreationFeeInSpl
val totalInFeePayerToken: BigInteger
get() = transactionFeeInFeePayerToken + accountCreationFeeInFeePayerToken

val totalInSourceToken: BigInteger
get() = transactionFeeInSourceToken + accountCreateFeeInSourceToken
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ private const val PREFS_SWAP = "swap_prefs"
private const val PREFS_STRIGA = "striga_prefs"

object StorageModule {
const val PREFS_PNL = "pnl_prefs"
const val PREFS_SEND = "send_prefs"

private fun Scope.androidPreferences(prefsName: String): SharedPreferences {
return with(androidContext()) {
Expand Down Expand Up @@ -70,7 +70,7 @@ object StorageModule {
SecureStorageContract::class
factory { AccountStorage(get(named(PREFS_ACCOUNT))) } bind
AccountStorageContract::class
single { JupiterSwapStorage(androidPreferences(PREFS_SWAP),) } bind
single { JupiterSwapStorage(androidPreferences(PREFS_SWAP)) } bind
JupiterSwapStorageContract::class
factory { StrigaStorage(get(named(PREFS_STRIGA))) } bind
StrigaStorageContract::class
Expand All @@ -94,8 +94,8 @@ object StorageModule {
single(named(PREFS_STRIGA)) {
androidEncryptedPreferences(PREFS_STRIGA)
}
single(named(PREFS_PNL)) {
androidPreferences(PREFS_PNL)
single(named(PREFS_SEND)) {
androidPreferences(PREFS_SEND)
}
}

Expand Down
134 changes: 73 additions & 61 deletions app/src/main/java/org/p2p/wallet/send/SendFeeRelayerManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import org.p2p.core.utils.Constants
import org.p2p.core.utils.formatTokenWithSymbol
import org.p2p.core.utils.fromLamports
import org.p2p.core.utils.isZero
import org.p2p.core.utils.orZero
import org.p2p.core.utils.scaleLong
import org.p2p.core.utils.toLamports
import org.p2p.core.utils.toUsd
import org.p2p.solanaj.core.FeeAmount
Expand Down Expand Up @@ -84,7 +82,7 @@ class SendFeeRelayerManager(
private val alternativeTokensMap: HashMap<String, List<Token.Active>> = HashMap()

suspend fun initialize(
initialToken: Token.Active,
feePayerToken: Token.Active,
solToken: Token.Active,
recipientAddress: SearchResult
) {
Expand All @@ -94,7 +92,7 @@ class SendFeeRelayerManager(

onFeeLoading?.invoke(FeeLoadingState.Instant(isLoading = true))
try {
initializeWithToken(initialToken)
initializeWithToken(feePayerToken)

initializeCompleted = true
} catch (e: Throwable) {
Expand All @@ -106,12 +104,12 @@ class SendFeeRelayerManager(
}
}

private suspend fun initializeWithToken(initialToken: Token.Active) {
Timber.tag(TAG).i("initialize for SendFeeRelayerManager with token ${initialToken.mintAddress}")
private suspend fun initializeWithToken(feePayerToken: Token.Active) {
Timber.tag(TAG).i("initialize for SendFeeRelayerManager with token ${feePayerToken.mintAddress}")
minRentExemption = sendInteractor.getMinRelayRentExemption()
feeLimitInfo = sendInteractor.getFreeTransactionsInfo()
currentSolanaEpoch = solanaRepository.getEpochInfo(useCache = true).epoch
sendInteractor.initialize(initialToken)
sendInteractor.initialize(feePayerToken)
}

fun getMinRentExemption(): BigInteger = minRentExemption
Expand Down Expand Up @@ -171,7 +169,7 @@ class SendFeeRelayerManager(
try {
onFeeLoading?.invoke(FeeLoadingState(isLoading = true, isDelayed = useCache))
if (!initializeCompleted) {
initializeWithToken(sourceToken)
initializeWithToken(feePayer)
initializeCompleted = true
}

Expand All @@ -189,7 +187,7 @@ class SendFeeRelayerManager(
feeLimitInfo = feeLimitInfo,
tokenExtensions = tokenExtensions
)
sendInteractor.setFeePayerToken(sourceToken)
sendInteractor.setFeePayerToken(feePayer)
}
is FeeCalculationState.PoolsNotFound -> {
val solanaFee = buildSolanaFee(
Expand Down Expand Up @@ -233,20 +231,8 @@ class SendFeeRelayerManager(
}
}

suspend fun buildDebugInfo(solanaFee: SendSolanaFee?): String {
val relayAccount = sendInteractor.getUserRelayAccount()
val relayInfo = sendInteractor.getRelayInfo()
fun buildDebugInfo(solanaFee: SendSolanaFee?): String {
return buildString {
append("Relay account is created: ${relayAccount.isCreated}, balance: ${relayAccount.balance} (A)")
appendLine()
append("Min relay account balance required: ${relayInfo.minimumRelayAccountRent} (B)")
appendLine()
if (relayAccount.balance != null) {
val diff = relayAccount.balance - relayInfo.minimumRelayAccountRent
append("Remainder (A - B): $diff (R)")
appendLine()
}

if (solanaFee == null) {
append("Expected total fee in SOL: 0 (E)")
appendLine()
Expand All @@ -255,26 +241,42 @@ class SendFeeRelayerManager(
append("Expected total fee in Token: 0 (T)")
} else {
val accountBalances = solanaFee.feeRelayerFee.expectedFee.accountCreationFee
val expectedFee = if (!relayAccount.isCreated) {
accountBalances + relayInfo.minimumRelayAccountRent
} else {
accountBalances
}
append("Expected total fee in SOL: $expectedFee (E)")
append("Expected total fee in SOL: $accountBalances (E)")
appendLine()

val neededTopUpAmount = solanaFee.feeRelayerFee.totalInSol
append("Needed top up amount (E - R): $neededTopUpAmount (S)")

appendLine()

val feePayerToken = solanaFee.feePayerToken
val expectedFeeInSpl = solanaFee.feeRelayerFee.totalInSpl.orZero()

val feePayerTokenValue = solanaFee
.feeRelayerFee
.totalInFeePayerToken
.fromLamports(feePayerToken.decimals)
.scaleLong()
append("Expected total fee in Token: $expectedFeeInSpl ${feePayerToken.tokenSymbol} (T)")
.formatTokenWithSymbol(
token = feePayerToken,
exactDecimals = true,
keepInitialDecimals = true
)

val sourceToken = solanaFee.sourceToken
val sourceTokenValue = solanaFee
.feeRelayerFee
.totalInSourceToken
.fromLamports(sourceToken.decimals)
.formatTokenWithSymbol(
token = sourceToken,
exactDecimals = true,
keepInitialDecimals = true
)

append("Expected total fee in Fee Payer Token: $feePayerTokenValue (T)")
appendLine()

append("Expected total fee in Source Token: $sourceTokenValue (T)")
appendLine()

append("[Token2022] Transfer Fee: ${solanaFee.token2022TransferFee}")
}
}
Expand All @@ -297,57 +299,67 @@ class SendFeeRelayerManager(

Timber.i("Requesting minRentExemption for spl_token_program: $minRentExemption")

var transactionFee = BigInteger.ZERO
var transactionFeeInSol = BigInteger.ZERO

// owner's signature
transactionFee += lamportsPerSignature
transactionFeeInSol += lamportsPerSignature

// feePayer's signature
if (!feePayerToken.isSOL) {
Timber.i("Fee payer is not sol, adding $lamportsPerSignature for fee")
transactionFee += lamportsPerSignature
transactionFeeInSol += lamportsPerSignature
}

val shouldCreateAccount = checkAccountCreationIsRequired(sourceToken, result.address)
Timber.i("Should create account = $shouldCreateAccount")

val accountCreationFee = if (shouldCreateAccount) minRentExemption else BigInteger.ZERO
val accountCreationFeeInSol = if (shouldCreateAccount) minRentExemption else BigInteger.ZERO

val expectedFee = FeeAmount(
transactionFee = transactionFee,
accountCreationFee = accountCreationFee,
val expectedFeesInSol = FeeAmount(
transactionFee = transactionFeeInSol,
accountCreationFee = accountCreationFeeInSol,
)

val fees = feeRelayerTopUpInteractor.calculateNeededTopUpAmount(expectedFee)
// todo: since we use send service it covers all network fees, no necessity to use fee relayer
// keep it here for a while, just in case
// val feesInSol = feeRelayerTopUpInteractor.calculateNeededTopUpAmount(expectedFeesInSol)

val feesInSol = expectedFeesInSol.copy(transactionFee = BigInteger.ZERO)

if (fees.totalFeeLamports.isZero()) {
if (feesInSol.totalFeeLamports.isZero()) {
Timber.i("Total fees are zero!")
return FeeCalculationState.NoFees
}

val poolsStateFees = getFeesInPayingTokenUseCase.execute(
feePayerToken = feePayerToken,
transactionFeeInSol = fees.transactionFee,
accountCreationFeeInSol = fees.accountCreationFee
val feesInFeePayerToken = getFeesInPayingTokenUseCase.execute(
targetToken = feePayerToken,
transactionFeeInSol = feesInSol.transactionFee,
accountCreationFeeInSol = feesInSol.accountCreationFee
)

if (poolsStateFees != null) {
FeeCalculationState.Success(
fee = FeeRelayerFee(
feeInSol = fees,
feeInSpl = poolsStateFees,
expectedFee = expectedFee
)
)
} else {
FeeCalculationState.PoolsNotFound(
feeInSol = FeeRelayerFee(
feeInSol = fees,
feeInSpl = FeeAmount(fees.transactionFee, fees.accountCreationFee),
expectedFee = expectedFee
)
)
val feesInSourceToken = getFeesInPayingTokenUseCase.execute(
targetToken = sourceToken,
transactionFeeInSol = feesInSol.transactionFee,
accountCreationFeeInSol = feesInSol.accountCreationFee
)

// it is incorrect to return fees in sol if there is some error happened
// because we would add apples to oranges when choosing fee payer token
require(feesInFeePayerToken != null && feesInSourceToken != null) {
buildString {
append("Cannot calculate transaction fees in source (${sourceToken.tokenSymbol})")
append("and fee payer token ${feePayerToken.tokenSymbol}")
}
}

FeeCalculationState.Success(
fee = FeeRelayerFee(
feesInSol = feesInSol,
feesInFeePayerToken = feesInFeePayerToken,
feesInSourceToken = feesInSourceToken,
expectedFee = expectedFeesInSol
)
)
} catch (e: CancellationException) {
Timber.i("Fee calculation cancelled")
return FeeCalculationState.Cancelled
Expand Down
11 changes: 10 additions & 1 deletion app/src/main/java/org/p2p/wallet/send/SendModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import org.p2p.core.token.Token
import org.p2p.wallet.feerelayer.interactor.FeeRelayerViaLinkInteractor
import org.p2p.wallet.home.ui.new.NewSelectTokenContract
import org.p2p.wallet.home.ui.new.NewSelectTokenPresenter
import org.p2p.wallet.infrastructure.StorageModule
import org.p2p.wallet.infrastructure.network.provider.SendModeProvider
import org.p2p.wallet.infrastructure.sendvialink.UserSendLinksDatabaseRepository
import org.p2p.wallet.infrastructure.sendvialink.UserSendLinksLocalRepository
Expand All @@ -27,6 +28,8 @@ import org.p2p.wallet.send.repository.RecipientsLocalRepository
import org.p2p.wallet.send.repository.SendServiceInMemoryRepository
import org.p2p.wallet.send.repository.SendServiceRemoteRepository
import org.p2p.wallet.send.repository.SendServiceRepository
import org.p2p.wallet.send.repository.SendStorage
import org.p2p.wallet.send.repository.SendStorageContract
import org.p2p.wallet.send.ui.NewSendContract
import org.p2p.wallet.send.ui.NewSendPresenter
import org.p2p.wallet.send.ui.SendOpenedFrom
Expand Down Expand Up @@ -93,7 +96,8 @@ object SendModule : InjectionModule {
userTokensInteractor = get(),
tokenServiceCoordinator = get(),
sendFeeRelayerManager = get(),
maximumAmountCalculator = get()
maximumAmountCalculator = get(),
sendStorage = get()
)
} bind NewSendContract.Presenter::class
factoryOf(::NewSendDetailsPresenter) bind NewSendDetailsContract.Presenter::class
Expand All @@ -116,6 +120,11 @@ object SendModule : InjectionModule {
factoryOf(::FeeRelayerViaLinkInteractor)
factoryOf(::SendViaLinkInteractor)
factoryOf(::UserSendLinksDatabaseRepository) bind UserSendLinksLocalRepository::class
factory {
SendStorage(
prefs = get(named(StorageModule.PREFS_SEND))
)
} bind SendStorageContract::class
}

private fun Module.initSendService() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class FeePayersRepository(
val feePayerTokensMints = sendServiceRepository.getCompensationTokens()
val tokenToExcludeSymbol = feePayerToExclude.tokenSymbol

val fees: Map<Base58String, BigInteger> = getFeesInPayingTokenUseCase.execute(
val feesInGivenTokens: Map<Base58String, BigInteger> = getFeesInPayingTokenUseCase.execute(
findFeesIn = feePayerTokensMints,
transactionFeeInSol = transactionFeeInSol,
accountCreationFeeInSol = accountCreationFeeInSol
Expand All @@ -61,9 +61,9 @@ class FeePayersRepository(
}

// assuming that all other tokens are SPL
val feesInSpl = fees[token.mintAddress.toBase58Instance()]
val feesInSpl = feesInGivenTokens[token.mintAddress.toBase58Instance()]
if (feesInSpl == null) {
Timber.i("Fee in SPL not found for ${token.tokenSymbol} in ${fees.keys}")
Timber.i("Fee in SPL not found for ${token.tokenSymbol} in ${feesInGivenTokens.keys}")
return@filter false
}
val isTokenCoversTheFee = token.totalInLamports >= feesInSpl
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ class SendInteractor(
/*
* Initialize fee payer token
* */
suspend fun initialize(token: Token.Active) {
feePayerToken = token
suspend fun initialize(feePayer: Token.Active) {
feePayerToken = feePayer
feeRelayerInteractor.load()
orcaInfoInteractor.load()
}
Expand Down
Loading
Loading