diff --git a/build.gradle.kts b/build.gradle.kts index 3bf2ec3ba..ec9cf1691 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -53,7 +53,7 @@ allprojects { } group = "exchange.dydx.abacus" -version = "1.14.14" +version = "1.14.15" repositories { google() diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/output/Leaderboard.kt b/src/commonMain/kotlin/exchange.dydx.abacus/output/Leaderboard.kt new file mode 100644 index 000000000..a8a1d17b5 --- /dev/null +++ b/src/commonMain/kotlin/exchange.dydx.abacus/output/Leaderboard.kt @@ -0,0 +1,34 @@ +package exchange.dydx.abacus.output + +import exchange.dydx.abacus.utils.IList +import kollections.JsExport +import kotlinx.serialization.Serializable + +@JsExport +@Serializable +data class FeeLeaderboardEntry( + val address: String, + val rank: Int, + val totalFees: Double +) + +@JsExport +@Serializable +data class FeeLeaderboard( + val leaderboard: IList +) + +@JsExport +@Serializable +data class RebateLeaderboardEntry( + val address: String, + val pnl: Double, + val rank: Int, + val dollarReward: Double +) + +@JsExport +@Serializable +data class RebateLeaderboard( + val leaderboard: IList +) diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/output/PerpetualState.kt b/src/commonMain/kotlin/exchange.dydx.abacus/output/PerpetualState.kt index b80523b08..81b5bb0f5 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/output/PerpetualState.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/output/PerpetualState.kt @@ -44,7 +44,9 @@ data class PerpetualState( val restriction: UsageRestriction?, val launchIncentive: LaunchIncentive?, val compliance: Compliance?, - val vault: Vault? + val vault: Vault?, + val feeLeaderboard: FeeLeaderboard?, + val rebateLeaderboard: RebateLeaderboard? ) { internal companion object { fun newState(): PerpetualState { @@ -70,6 +72,8 @@ data class PerpetualState( launchIncentive = null, compliance = null, vault = null, + feeLeaderboard = null, + rebateLeaderboard = null ) } } diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/processor/leaderboards/FeeLeaderboardProcessor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/processor/leaderboards/FeeLeaderboardProcessor.kt new file mode 100644 index 000000000..bdca464a3 --- /dev/null +++ b/src/commonMain/kotlin/exchange.dydx.abacus/processor/leaderboards/FeeLeaderboardProcessor.kt @@ -0,0 +1,16 @@ +package exchange.dydx.abacus.processor.leaderboards + +import exchange.dydx.abacus.output.FeeLeaderboardEntry +import exchange.dydx.abacus.processor.base.BaseProcessor +import exchange.dydx.abacus.protocols.ParserProtocol +import exchange.dydx.abacus.state.InternalFeeLeaderboardState +import exchange.dydx.abacus.state.machine.FeeLeaderboardResponse + +internal class FeeLeaderboardProcessor( + parser: ParserProtocol +): BaseProcessor(parser) { + fun processLeaderboard(existing: InternalFeeLeaderboardState, payload: FeeLeaderboardResponse?): InternalFeeLeaderboardState { + existing.leaderboard = payload?.data?.map { FeeLeaderboardEntry(it.address, it.rank, it.total_fees) } + return existing + } +} diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/processor/leaderboards/RebateLeaderboardProcessor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/processor/leaderboards/RebateLeaderboardProcessor.kt new file mode 100644 index 000000000..eaf5d42f3 --- /dev/null +++ b/src/commonMain/kotlin/exchange.dydx.abacus/processor/leaderboards/RebateLeaderboardProcessor.kt @@ -0,0 +1,16 @@ +package exchange.dydx.abacus.processor.leaderboards + +import exchange.dydx.abacus.output.RebateLeaderboardEntry +import exchange.dydx.abacus.processor.base.BaseProcessor +import exchange.dydx.abacus.protocols.ParserProtocol +import exchange.dydx.abacus.state.InternalRebateLeaderboardState +import exchange.dydx.abacus.state.machine.RebateLeaderboardResponse + +internal class RebateLeaderboardProcessor( + parser: ParserProtocol +): BaseProcessor(parser) { + fun processLeaderboard(existing: InternalRebateLeaderboardState, payload: RebateLeaderboardResponse?): InternalRebateLeaderboardState? { + existing.leaderboard = payload?.data?.map { RebateLeaderboardEntry(address = it.address, pnl = it.pnl, rank = it.position, dollarReward = it.dollarReward) } + return existing + } +} diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/InternalState.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/InternalState.kt index a47b1d4cf..5885da193 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/InternalState.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/InternalState.kt @@ -14,6 +14,8 @@ import exchange.dydx.abacus.output.MarketHistoricalFunding import exchange.dydx.abacus.output.MarketOrderbook import exchange.dydx.abacus.output.MarketTrade import exchange.dydx.abacus.output.PerpetualMarket +import exchange.dydx.abacus.output.FeeLeaderboardEntry +import exchange.dydx.abacus.output.RebateLeaderboardEntry import exchange.dydx.abacus.output.WithdrawalGating import exchange.dydx.abacus.output.account.PositionSide import exchange.dydx.abacus.output.account.StakingRewards @@ -75,6 +77,8 @@ internal data class InternalState( val marketsSummary: InternalMarketSummaryState = InternalMarketSummaryState(), val input: InternalInputState = InternalInputState(), var vault: InternalVaultState? = null, + val feeLeaderboard: InternalFeeLeaderboardState = InternalFeeLeaderboardState(), + val rebateLeaderboard: InternalRebateLeaderboardState = InternalRebateLeaderboardState() ) internal data class InternalInputState( @@ -488,6 +492,14 @@ internal data class InternalLaunchIncentiveState( var seasons: List? = null, ) +internal data class InternalFeeLeaderboardState( + var leaderboard: List? = null +) + +internal data class InternalRebateLeaderboardState( + var leaderboard: List? = null +) + internal fun TradeInputSize.Companion.safeCreate(existing: TradeInputSize?): TradeInputSize { return existing ?: TradeInputSize( size = null, diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/StateChanges.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/StateChanges.kt index 58f941801..02ae382e0 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/StateChanges.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/StateChanges.kt @@ -38,6 +38,8 @@ enum class Changes(val rawValue: String) { launchIncentive("launchIncentive"), vault("vault"), + feeLeaderboard("feeLeaderboard"), + rebateLeaderboard("rebateLeaderboard") ; companion object { diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/machine/TradingStateMachine+FeeLeaderboard.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/machine/TradingStateMachine+FeeLeaderboard.kt new file mode 100644 index 000000000..3bc2b15e2 --- /dev/null +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/machine/TradingStateMachine+FeeLeaderboard.kt @@ -0,0 +1,31 @@ +package exchange.dydx.abacus.state.machine + +import exchange.dydx.abacus.protocols.asTypedObject +import exchange.dydx.abacus.state.Changes +import exchange.dydx.abacus.state.StateChanges +import kollections.iListOf + +@Suppress("ConstructorParameterNaming") +data class FeeLeaderboardEntryResponse( + val address: String, + val rank: Int, + val total_fees: Double +) + +data class FeeLeaderboardResponse( + val data: List +) + +internal fun TradingStateMachine.feeLeaderboard(payload: String): StateChanges { + val leaderboard = parser.asTypedObject(payload) + val oldState = internalState.feeLeaderboard.copy() + feeLeaderboardProcessor.processLeaderboard( + internalState.feeLeaderboard, + leaderboard + ) + return if (oldState != internalState.feeLeaderboard) { + StateChanges(iListOf(Changes.feeLeaderboard)) + } else { + StateChanges.noChange + } +} diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/machine/TradingStateMachine+RebateLeaderboard.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/machine/TradingStateMachine+RebateLeaderboard.kt new file mode 100644 index 000000000..7e0fabe4e --- /dev/null +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/machine/TradingStateMachine+RebateLeaderboard.kt @@ -0,0 +1,32 @@ +package exchange.dydx.abacus.state.machine + +import exchange.dydx.abacus.protocols.asTypedObject +import exchange.dydx.abacus.state.Changes +import exchange.dydx.abacus.state.StateChanges +import kollections.iListOf + +@Suppress("ConstructorParameterNaming") +data class RebateLeaderboardEntryResponse( + val address: String, + val pnl: Double, + val position: Int, + val dollarReward: Double +) + +data class RebateLeaderboardResponse( + val data: List +) + +internal fun TradingStateMachine.rebateLeaderboard(payload: String): StateChanges { + val leaderboard = parser.asTypedObject(payload) + val oldState = internalState.rebateLeaderboard.copy() + rebateLeaderboardProcessor.processLeaderboard( + internalState.rebateLeaderboard, + leaderboard + ) + return if (oldState != internalState.rebateLeaderboard) { + StateChanges(iListOf(Changes.rebateLeaderboard)) + } else { + StateChanges.noChange + } +} diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/machine/TradingStateMachine.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/machine/TradingStateMachine.kt index 080bb29b0..48ccb15ca 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/machine/TradingStateMachine.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/machine/TradingStateMachine.kt @@ -18,6 +18,8 @@ import exchange.dydx.abacus.output.MarketCandle import exchange.dydx.abacus.output.MarketCandles import exchange.dydx.abacus.output.PerpetualMarketSummary import exchange.dydx.abacus.output.PerpetualState +import exchange.dydx.abacus.output.FeeLeaderboard +import exchange.dydx.abacus.output.RebateLeaderboard import exchange.dydx.abacus.output.TransferStatus import exchange.dydx.abacus.output.Vault import exchange.dydx.abacus.output.Wallet @@ -35,6 +37,8 @@ import exchange.dydx.abacus.processor.configs.RewardsParamsProcessor import exchange.dydx.abacus.processor.input.ClosePositionInputProcessor import exchange.dydx.abacus.processor.input.TradeInputProcessor import exchange.dydx.abacus.processor.launchIncentive.LaunchIncentiveProcessor +import exchange.dydx.abacus.processor.leaderboards.FeeLeaderboardProcessor +import exchange.dydx.abacus.processor.leaderboards.RebateLeaderboardProcessor import exchange.dydx.abacus.processor.markets.MarketsSummaryProcessor import exchange.dydx.abacus.processor.router.skip.SkipProcessor import exchange.dydx.abacus.processor.vault.VaultProcessor @@ -118,6 +122,8 @@ open class TradingStateMachine( internal val launchIncentiveProcessor = LaunchIncentiveProcessor(parser) internal val tradeInputProcessor = TradeInputProcessor(parser) internal val closePositionInputProcessor = ClosePositionInputProcessor(parser) + internal val feeLeaderboardProcessor = FeeLeaderboardProcessor(parser) + internal val rebateLeaderboardProcessor = RebateLeaderboardProcessor(parser) internal val marketsCalculator = MarketCalculator(parser) internal val accountCalculator = AccountCalculator(parser, useParentSubaccount) @@ -384,7 +390,9 @@ open class TradingStateMachine( Changes.trackStatuses, Changes.orderbook, Changes.launchIncentive, - Changes.vault + Changes.vault, + Changes.feeLeaderboard, + Changes.rebateLeaderboard -> true Changes.wallet -> state?.wallet != wallet @@ -599,6 +607,8 @@ open class TradingStateMachine( var launchIncentive = state?.launchIncentive val geo = state?.compliance var vault = state?.vault + var surgeLeaderboard = state?.feeLeaderboard + var rebateLeaderboard = state?.rebateLeaderboard if (changes.changes.contains(Changes.markets)) { marketsSummary = @@ -856,6 +866,16 @@ open class TradingStateMachine( ), ) } + if (changes.changes.contains(Changes.feeLeaderboard)) { + surgeLeaderboard = FeeLeaderboard( + leaderboard = internalState.feeLeaderboard.leaderboard?.toIList() ?: iListOf() + ) + } + if (changes.changes.contains(Changes.rebateLeaderboard)) { + rebateLeaderboard = RebateLeaderboard( + leaderboard = internalState.rebateLeaderboard.leaderboard?.toIList() ?: iListOf() + ) + } if (changes.changes.contains(Changes.vault) || changes.changes.contains(Changes.markets)) { if (internalState.vault != null) { val positions = VaultCalculator.calculateVaultPositionsInternal( @@ -903,6 +923,8 @@ open class TradingStateMachine( launchIncentive = launchIncentive, compliance = geo, vault = vault, + feeLeaderboard = surgeLeaderboard, + rebateLeaderboard = rebateLeaderboard ) } diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/supervisor/AccountSupervisor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/supervisor/AccountSupervisor.kt index a4a1ccf58..9630f38f3 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/supervisor/AccountSupervisor.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/supervisor/AccountSupervisor.kt @@ -25,6 +25,7 @@ import exchange.dydx.abacus.state.machine.onChainUnbonding import exchange.dydx.abacus.state.machine.onChainUserFeeTier import exchange.dydx.abacus.state.machine.onChainUserStakingTier import exchange.dydx.abacus.state.machine.onChainUserStats +import exchange.dydx.abacus.state.machine.feeLeaderboard import exchange.dydx.abacus.state.manager.ApiData import exchange.dydx.abacus.state.manager.AutoSweepConfig import exchange.dydx.abacus.state.manager.BlockAndTime @@ -220,6 +221,9 @@ internal open class AccountSupervisor( if (configs.retrieveLaunchIncentivePoints) { retrieveLaunchIncentivePoints() } + if (configs.retrieveFeeLeaderboard) { + retrieveFeeLeaderboard() + } } } @@ -552,6 +556,19 @@ internal open class AccountSupervisor( } } + private fun retrieveFeeLeaderboard() { + val url = "https://pp-external-api-ffb2ad95ef03.herokuapp.com/api/dydx-fee-leaderboard" + helper.get( + url = url, + params = iMapOf("perPage" to "100", "address" to accountAddress) + ) { _, response, httpCode, _ -> + if (helper.success(httpCode) && response != null) { + val oldState = stateMachine.state + update(stateMachine.feeLeaderboard(response), oldState) + } + } + } + private fun canConnectTo(subaccountNumber: Int): Boolean { return stateMachine.state?.subaccount(subaccountNumber) != null } diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/supervisor/Configs.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/supervisor/Configs.kt index 27f8b9b9a..3c1dc2f09 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/supervisor/Configs.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/supervisor/Configs.kt @@ -12,6 +12,7 @@ data class SystemConfigs( val retrieveRewardsParams: Boolean, val retrieveLaunchIncentiveSeasons: Boolean, val retrieveWithdrawSafetyChecks: Boolean, + val retrieveRebateLeaderboard: Boolean ) { companion object { val forApp = SystemConfigs( @@ -23,6 +24,7 @@ data class SystemConfigs( retrieveRewardsParams = true, retrieveLaunchIncentiveSeasons = true, retrieveWithdrawSafetyChecks = true, + retrieveRebateLeaderboard = true ) val forProgrammaticTraders = SystemConfigs( retrieveServerTime = true, @@ -33,6 +35,7 @@ data class SystemConfigs( retrieveRewardsParams = false, retrieveLaunchIncentiveSeasons = false, retrieveWithdrawSafetyChecks = false, + retrieveRebateLeaderboard = false ) } } @@ -137,6 +140,7 @@ data class AccountConfigs( val retrieveUserStakingTier: Boolean, val transferNobleBalances: Boolean, val subaccountConfigs: SubaccountConfigs, + val retrieveFeeLeaderboard: Boolean ) { companion object { @@ -150,6 +154,7 @@ data class AccountConfigs( retrieveUserStakingTier = true, transferNobleBalances = true, subaccountConfigs = SubaccountConfigs.forApp, + retrieveFeeLeaderboard = true ) val forAppWithIsolatedMargins = AccountConfigs( retrieveUserFeeTier = true, @@ -161,6 +166,7 @@ data class AccountConfigs( retrieveUserStakingTier = true, transferNobleBalances = true, subaccountConfigs = SubaccountConfigs.forAppWithIsolatedMargins, + retrieveFeeLeaderboard = true ) val forProgrammaticTraders = AccountConfigs( retrieveUserFeeTier = true, @@ -172,6 +178,7 @@ data class AccountConfigs( retrieveUserStakingTier = true, transferNobleBalances = true, subaccountConfigs = SubaccountConfigs.forProgrammaticTraders, + retrieveFeeLeaderboard = false ) } } diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/supervisor/SystemSupervisor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/supervisor/SystemSupervisor.kt index 8fc79f525..ed9cc0ead 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/supervisor/SystemSupervisor.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/supervisor/SystemSupervisor.kt @@ -11,6 +11,8 @@ import exchange.dydx.abacus.state.machine.onChainRewardTokenPrice import exchange.dydx.abacus.state.machine.onChainRewardsParams import exchange.dydx.abacus.state.machine.onChainWithdrawalCapacity import exchange.dydx.abacus.state.machine.onChainWithdrawalGating +import exchange.dydx.abacus.state.machine.feeLeaderboard +import exchange.dydx.abacus.state.machine.rebateLeaderboard import exchange.dydx.abacus.utils.AnalyticsUtils import exchange.dydx.abacus.utils.Logger import exchange.dydx.abacus.utils.ServerTime @@ -38,6 +40,9 @@ internal class SystemSupervisor( // get from launch incentive endpoints retrieveLaunchIncentiveSeasons() } + if (configs.retrieveRebateLeaderboard) { + retrieveRebateLeaderboard() + } } } @@ -193,6 +198,16 @@ internal class SystemSupervisor( } } + private fun retrieveRebateLeaderboard() { + val url = "https://pp-external-api-ffb2ad95ef03.herokuapp.com/api/dydx-weekly-clc" + helper.get(url = url, params = iMapOf("perPage" to "100")) { _, response, httpCode, _ -> + if (helper.success(httpCode) && response != null) { + val oldState = stateMachine.state + update(stateMachine.rebateLeaderboard(response), oldState) + } + } + } + fun retrieveWithdrawSafetyChecks(transferType: TransferType) { when (transferType) { TransferType.withdrawal -> { diff --git a/v4_abacus.podspec b/v4_abacus.podspec index 9c796ad65..383bf285c 100644 --- a/v4_abacus.podspec +++ b/v4_abacus.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = 'v4_abacus' - spec.version = '1.14.14' + spec.version = '1.14.15' spec.homepage = 'https://github.com/dydxprotocol/v4-abacus' spec.source = { :http=> ''} spec.authors = ''