diff --git a/build.gradle.kts b/build.gradle.kts index d89f62ead..1cf90fe99 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -51,7 +51,7 @@ allprojects { } group = "exchange.dydx.abacus" -version = "1.7.19" +version = "1.7.23" repositories { google() diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/calculator/AccountCalculator.kt b/src/commonMain/kotlin/exchange.dydx.abacus/calculator/AccountCalculator.kt index 0d6d5401a..d9c128523 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/calculator/AccountCalculator.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/calculator/AccountCalculator.kt @@ -180,6 +180,7 @@ class AccountCalculator(val parser: ParserProtocol, private val useParentSubacco val modifiedPendingPosition = mutableMapOf() modifiedPendingPosition.safeSet("assetId", assetId) + modifiedPendingPosition.safeSet("marketId", marketId) modifiedPendingPosition.safeSet( "firstOrderId", parser.value(pending, "firstOrderId"), diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/calculator/AdjustIsolatedMarginInputCalculator.kt b/src/commonMain/kotlin/exchange.dydx.abacus/calculator/AdjustIsolatedMarginInputCalculator.kt index 113feec6a..ac4ddd73a 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/calculator/AdjustIsolatedMarginInputCalculator.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/calculator/AdjustIsolatedMarginInputCalculator.kt @@ -21,7 +21,7 @@ internal class AdjustIsolatedMarginInputCalculator(val parser: ParserProtocol) { IsolatedMarginAdjustmentType.valueOf(it) } ?: IsolatedMarginAdjustmentType.Add - return if (wallet != null && isolatedMarginAdjustment != null && type != null) { + return if (wallet != null && isolatedMarginAdjustment != null) { val modified = state.mutable() val parentTransferDelta = getModifiedTransferDelta(isolatedMarginAdjustment, true) val childTransferDelta = getModifiedTransferDelta(isolatedMarginAdjustment, false) @@ -44,8 +44,8 @@ internal class AdjustIsolatedMarginInputCalculator(val parser: ParserProtocol) { "postOrder", ) - val modifiedParentSubaccount = parser.asNativeMap(parser.value(walletPostChildSubaccountTransfer, "accounts.subaccounts.$parentSubaccountNumber")) - val modifiedChildSubaccount = parser.asNativeMap(parser.value(walletPostChildSubaccountTransfer, "accounts.subaccounts.$childSubaccountNumber")) + val modifiedParentSubaccount = parser.asNativeMap(parser.value(walletPostChildSubaccountTransfer, "account.subaccounts.$parentSubaccountNumber")) + val modifiedChildSubaccount = parser.asNativeMap(parser.value(walletPostChildSubaccountTransfer, "account.subaccounts.$childSubaccountNumber")) val modifiedIsolatedMarginAdjustment = finalize(isolatedMarginAdjustment, modifiedParentSubaccount, modifiedChildSubaccount, type) modified["adjustIsolatedMargin"] = modifiedIsolatedMarginAdjustment @@ -94,13 +94,13 @@ internal class AdjustIsolatedMarginInputCalculator(val parser: ParserProtocol) { type: IsolatedMarginAdjustmentType, ): Map { val summary = mutableMapOf() - val crossCollateral = parser.asDouble(parser.value(parentSubaccount, "freeCollateral.postOrder")) - val crossMarginUsage = parser.asDouble(parser.value(parentSubaccount, "marginUsage.postOrder")) + val crossCollateral = parentSubaccount?.get("freeCollateral") + val crossMarginUsage = parentSubaccount?.get("marginUsage") val openPositions = parser.asNativeMap(childSubaccount?.get("openPositions")) val marketId = openPositions?.keys?.firstOrNull() - val positionMargin = parser.asDouble(parser.value(childSubaccount, "freeCollateral.postOrder")) - val positionLeverage = parser.asDouble(parser.value(childSubaccount, "openPositions.$marketId.leverage.postOrder")) - val liquidationPrice = parser.asDouble(parser.value(childSubaccount, "openPositions.$marketId.liquidationPrice.postOrder")) + val positionMargin = childSubaccount?.get("freeCollateral") + val positionLeverage = parser.value(childSubaccount, "openPositions.$marketId.leverage") + val liquidationPrice = parser.value(childSubaccount, "openPositions.$marketId.liquidationPrice") when (type) { IsolatedMarginAdjustmentType.Add -> { @@ -123,6 +123,41 @@ internal class AdjustIsolatedMarginInputCalculator(val parser: ParserProtocol) { return summary } + private fun amountField(): Map { + return mapOf( + "field" to "amount", + "type" to "double", + ) + } + + private fun requiredFields(): List { + return listOf( + amountField(), + ) + } + + private fun calculatedOptionsFromField(fields: List?): Map? { + fields?.let { + val options = mutableMapOf( + "needsSize" to false, + ) + + for (item in fields) { + parser.asNativeMap(item)?.let { field -> + when (parser.asString(field["field"])) { + "amount" -> { + options["needsSize"] = true + } + } + } + } + + return options + } + + return null + } + private fun finalize( isolatedMarginAdjustment: Map, parentSubaccount: Map?, @@ -130,6 +165,9 @@ internal class AdjustIsolatedMarginInputCalculator(val parser: ParserProtocol) { type: IsolatedMarginAdjustmentType, ): Map { val modified = isolatedMarginAdjustment.mutable() + val fields = requiredFields() + modified.safeSet("fields", fields) + modified.safeSet("options", calculatedOptionsFromField(fields)) modified.safeSet("summary", summaryForType(parentSubaccount, childSubaccount, type)) return modified } diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/output/Account.kt b/src/commonMain/kotlin/exchange.dydx.abacus/output/Account.kt index 4b2a450d7..b2b14c481 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/output/Account.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/output/Account.kt @@ -477,6 +477,7 @@ data class SubaccountPosition( @Serializable data class SubaccountPendingPosition( val assetId: String, + val marketId: String, val firstOrderId: String, val orderCount: Int, val freeCollateral: TradeStatesWithDoubleValues?, @@ -492,6 +493,7 @@ data class SubaccountPendingPosition( Logger.d { "creating Account Pending Position\n" } data?.let { val assetId = parser.asString(data["assetId"]) ?: return null + val marketId = parser.asString(data["marketId"]) ?: return null val firstOrderId = parser.asString(data["firstOrderId"]) ?: return null val orderCount = parser.asInt(data["orderCount"]) ?: return null val freeCollateral = TradeStatesWithDoubleValues.create( @@ -511,6 +513,7 @@ data class SubaccountPendingPosition( ) return if (existing?.assetId != assetId || + existing.marketId != marketId || existing.firstOrderId != firstOrderId || existing.orderCount != orderCount || existing.freeCollateral !== freeCollateral || @@ -519,6 +522,7 @@ data class SubaccountPendingPosition( ) { SubaccountPendingPosition( assetId, + marketId, firstOrderId, orderCount, freeCollateral, diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/output/input/AdjustIsolatedMarginInput.kt b/src/commonMain/kotlin/exchange.dydx.abacus/output/input/AdjustIsolatedMarginInput.kt index c6dd2941a..6b3b7f7c9 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/output/input/AdjustIsolatedMarginInput.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/output/input/AdjustIsolatedMarginInput.kt @@ -38,10 +38,15 @@ data class AdjustIsolatedMarginInputOptions( @Serializable data class AdjustIsolatedMarginInputSummary( val crossFreeCollateral: Double?, + val crossFreeCollateralUpdated: Double?, val crossMarginUsage: Double?, + val crossMarginUsageUpdated: Double?, val positionMargin: Double?, + val positionMarginUpdated: Double?, val positionLeverage: Double?, + val positionLeverageUpdated: Double?, val liquidationPrice: Double?, + val liquidationPriceUpdated: Double?, ) { companion object { internal fun create( @@ -52,25 +57,40 @@ data class AdjustIsolatedMarginInputSummary( Logger.d { "creating Adjust Isolated Margin Input Summary\n" } data?.let { - val crossFreeCollateral = parser.asDouble(data["crossFreeCollateral"]) - val crossMarginUsage = parser.asDouble(data["crossMarginUsage"]) - val positionMargin = parser.asDouble(data["positionMargin"]) - val positionLeverage = parser.asDouble(data["positionLeverage"]) - val liquidationPrice = parser.asDouble(data["liquidationPrice"]) + val crossFreeCollateral = parser.asDouble(parser.value(data, "crossFreeCollateral.current")) + val crossFreeCollateralUpdated = parser.asDouble(parser.value(data, "crossFreeCollateral.postOrder")) + val crossMarginUsage = parser.asDouble(parser.value(data, "crossMarginUsage.current")) + val crossMarginUsageUpdated = parser.asDouble(parser.value(data, "crossMarginUsage.postOrder")) + val positionMargin = parser.asDouble(parser.value(data, "positionMargin.current")) + val positionMarginUpdated = parser.asDouble(parser.value(data, "positionMargin.postOrder")) + val positionLeverage = parser.asDouble(parser.value(data, "positionLeverage.current")) + val positionLeverageUpdated = parser.asDouble(parser.value(data, "positionLeverage.postOrder")) + val liquidationPrice = parser.asDouble(parser.value(data, "liquidationPrice.current")) + val liquidationPriceUpdated = parser.asDouble(parser.value(data, "liquidationPrice.postOrder")) return if ( existing?.crossFreeCollateral != crossFreeCollateral || + existing?.crossFreeCollateralUpdated != crossFreeCollateralUpdated || existing?.crossMarginUsage != crossMarginUsage || + existing?.crossMarginUsageUpdated != crossMarginUsageUpdated || existing?.positionMargin != positionMargin || + existing?.positionMarginUpdated != positionMarginUpdated || existing?.positionLeverage != positionLeverage || - existing?.liquidationPrice != liquidationPrice + existing?.positionLeverageUpdated != positionLeverageUpdated || + existing?.liquidationPrice != liquidationPrice || + existing?.liquidationPriceUpdated != liquidationPriceUpdated ) { AdjustIsolatedMarginInputSummary( crossFreeCollateral, + crossFreeCollateralUpdated, crossMarginUsage, + crossMarginUsageUpdated, positionMargin, + positionMarginUpdated, positionLeverage, + positionLeverageUpdated, liquidationPrice, + liquidationPriceUpdated, ) } else { existing @@ -94,12 +114,10 @@ enum class IsolatedMarginAdjustmentType { data class AdjustIsolatedMarginInput( val type: IsolatedMarginAdjustmentType, val amount: String?, + val amountPercent: String?, val childSubaccountNumber: Int?, val adjustIsolatedMarginInputOptions: AdjustIsolatedMarginInputOptions?, - val summary: AdjustIsolatedMarginInputSummary?, - val errors: String?, - val errorMessage: String?, - val fee: Double?, + val summary: AdjustIsolatedMarginInputSummary? ) { companion object { internal fun create( @@ -116,11 +134,12 @@ data class AdjustIsolatedMarginInput( val childSubaccountNumber = parser.asInt(data["ChildSubaccountNumber"]) val amount = parser.asString(data["Amount"]) - val fee = parser.asDouble(data["fee"]) + val amountPercent = parser.asString(data["AmountPercent"]) + val adjustIsolatedMarginInputOptions = AdjustIsolatedMarginInputOptions.create( existing?.adjustIsolatedMarginInputOptions, parser, - parser.asMap(data["adjustIsolatedMarginInputOptions"]), + parser.asMap(data["options"]), ) val summary = AdjustIsolatedMarginInputSummary.create( existing?.summary, @@ -128,36 +147,21 @@ data class AdjustIsolatedMarginInput( parser.asMap(data["summary"]), ) - val errors = parser.asString(data["errors"]) - - val errorMessage: String? = - if (errors != null) { - val errorArray = parser.decodeJsonArray(errors) - val firstError = parser.asMap(errorArray?.first()) - parser.asString(firstError?.get("message")) - } else { - null - } - return if ( existing?.type != type || existing.amount != amount || + existing.amountPercent != amountPercent || existing.childSubaccountNumber != childSubaccountNumber || existing.adjustIsolatedMarginInputOptions != adjustIsolatedMarginInputOptions || - existing.summary !== summary || - existing.errors !== errors || - existing.errorMessage != errorMessage || - existing.fee != fee + existing.summary !== summary ) { AdjustIsolatedMarginInput( type, amount, + amountPercent, childSubaccountNumber, adjustIsolatedMarginInputOptions, summary, - errors, - errorMessage, - fee, ) } else { existing diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/output/input/TradeInput.kt b/src/commonMain/kotlin/exchange.dydx.abacus/output/input/TradeInput.kt index abdc8cafc..b27cd27d4 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/output/input/TradeInput.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/output/input/TradeInput.kt @@ -779,7 +779,7 @@ data class TradeInput( val postOnly: Boolean, val fee: Double?, val marginMode: MarginMode, - val targetLeverage: Double?, + val targetLeverage: Double, val bracket: TradeInputBracket?, val marketOrder: TradeInputMarketOrder?, val options: TradeInputOptions?, @@ -818,7 +818,7 @@ data class TradeInput( MarginMode.invoke(it) } ?: MarginMode.cross - val targetLeverage = parser.asDouble(data["targetLeverage"]) + val targetLeverage = parser.asDouble(data["targetLeverage"]) ?: 1.0 val goodTil = TradeInputGoodUntil.create( existing?.goodTil, @@ -859,7 +859,7 @@ data class TradeInput( existing.postOnly != postOnly || existing.fee != fee || existing.marginMode != marginMode || - existing?.targetLeverage != targetLeverage || + existing.targetLeverage != targetLeverage || existing.bracket != bracket || existing.marketOrder != marketOrder || existing.options != options || diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/StateManagerAdaptor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/StateManagerAdaptor.kt index ef78ed483..f8b8f934d 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/StateManagerAdaptor.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/StateManagerAdaptor.kt @@ -2244,7 +2244,7 @@ open class StateManagerAdaptor( val walletAddress = wallet.walletAddress ?: error("walletAddress is null") val isolatedMarginAdjustment = stateMachine.state?.input?.adjustIsolatedMargin ?: error("isolatedMarginAdjustment is null") - val amount = isolatedMarginAdjustment.amount ?: error("amount is null") + val amount = parser.asString(isolatedMarginAdjustment.amount) ?: error("amount is null") val childSubaccountNumber = isolatedMarginAdjustment.childSubaccountNumber ?: error("childSubaccountNumber is null") diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/configs/V4StateManagerConfigs.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/configs/V4StateManagerConfigs.kt index 07d101b55..79d3aaa45 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/configs/V4StateManagerConfigs.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/configs/V4StateManagerConfigs.kt @@ -36,8 +36,8 @@ class V4StateManagerConfigs( "historical-pnl":"/v4/historical-pnl", "transfers":"/v4/transfers", "historicalTradingRewardAggregations":"/v4/historicalTradingRewardAggregations", - "parent-fills":"/v4/fills/parentSubaccount", - "parent-transfers": "/v4/transfers/parentSubaccount" + "parent-fills":"/v4/fills/parentSubaccountNumber", + "parent-transfers": "/v4/transfers/parentSubaccountNumber" }, "faucet":{ "faucet":"/faucet/tokens" diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/model/TradingStateMachine+AdjustIsolatedMarginInput.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/model/TradingStateMachine+AdjustIsolatedMarginInput.kt index a902e6358..780d26763 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/model/TradingStateMachine+AdjustIsolatedMarginInput.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/model/TradingStateMachine+AdjustIsolatedMarginInput.kt @@ -6,6 +6,7 @@ import exchange.dydx.abacus.responses.ParsingError import exchange.dydx.abacus.responses.StateResponse import exchange.dydx.abacus.state.changes.Changes import exchange.dydx.abacus.state.changes.StateChanges +import exchange.dydx.abacus.utils.IList import exchange.dydx.abacus.utils.NUM_PARENT_SUBACCOUNTS import exchange.dydx.abacus.utils.mutable import exchange.dydx.abacus.utils.mutableMapOf @@ -19,6 +20,7 @@ import kotlinx.serialization.Serializable enum class AdjustIsolatedMarginInputField { Type, Amount, + AmountPercent, ChildSubaccountNumber, } @@ -64,37 +66,59 @@ fun TradingStateMachine.adjustIsolatedMargin( if (adjustIsolatedMargin["Type"] != parser.asString(data)) { adjustIsolatedMargin.safeSet(type.name, parser.asString(data)) adjustIsolatedMargin.safeSet("Amount", null) + adjustIsolatedMargin.safeSet("AmountPercent", null) } - changes = StateChanges( - iListOf(Changes.wallet, Changes.subaccount, Changes.input), - null, - subaccountNumbers, - ) + changes = getStateChanges(subaccountNumbers) } + AdjustIsolatedMarginInputField.AmountPercent, AdjustIsolatedMarginInputField.Amount -> { - val amount = parser.asString(data) - adjustIsolatedMargin.safeSet(type.name, amount) - changes = StateChanges( - iListOf(Changes.wallet, Changes.subaccount, Changes.input), - null, - subaccountNumbers, + val isolatedMarginAdjustmentType = adjustIsolatedMargin["Type"] ?: IsolatedMarginAdjustmentType.Add.name + val subaccountNumber = if (isolatedMarginAdjustmentType == IsolatedMarginAdjustmentType.Add.name) { + parentSubaccountNumber + } else { + childSubaccountNumber + } + val subaccount = parser.asNativeMap( + parser.value(this.account, "subaccounts.$subaccountNumber"), ) + + val freeCollateral = parser.asDouble(parser.value(subaccount, "freeCollateral.current")) + val amountValue = parser.asDouble(data) + + if (amountValue == null) { + adjustIsolatedMargin.safeSet("Amount", null) + adjustIsolatedMargin.safeSet("AmountPercent", null) + } else if (type == AdjustIsolatedMarginInputField.Amount) { + adjustIsolatedMargin.safeSet(type.name, amountValue.toString()) + + if (freeCollateral != null) { + val amountPercent = amountValue / freeCollateral + adjustIsolatedMargin.safeSet("AmountPercent", amountPercent.toString()) + } else { + adjustIsolatedMargin.safeSet("AmountPercent", null) + } + } else if (type == AdjustIsolatedMarginInputField.AmountPercent) { + adjustIsolatedMargin.safeSet(type.name, amountValue.toString()) + + if (freeCollateral != null) { + val amount = amountValue * freeCollateral + adjustIsolatedMargin.safeSet("Amount", amount.toString()) + } else { + adjustIsolatedMargin.safeSet("Amount", null) + } + } + + changes = getStateChanges(subaccountNumbers) } AdjustIsolatedMarginInputField.ChildSubaccountNumber -> { - val childSubaccountNumber = parser.asInt(data) - adjustIsolatedMargin.safeSet(type.name, childSubaccountNumber) - val subaccountNumbers = if (childSubaccountNumber != null) { - iListOf(parentSubaccountNumber, childSubaccountNumber) - } else { - iListOf(parentSubaccountNumber) + var updatedSubaccountNumbers = iListOf(parentSubaccountNumber) + val updatedChildSubaccountNumber = parser.asInt(data) + adjustIsolatedMargin.safeSet(type.name, updatedChildSubaccountNumber) + if (updatedChildSubaccountNumber != null) { + updatedSubaccountNumbers = iListOf(parentSubaccountNumber, updatedChildSubaccountNumber) } - changes = StateChanges( - iListOf(Changes.wallet, Changes.subaccount, Changes.input), - null, - subaccountNumbers, - ) + changes = getStateChanges(updatedSubaccountNumbers) } - else -> {} } } else { error = cannotModify(type.name) @@ -113,6 +137,16 @@ fun TradingStateMachine.adjustIsolatedMargin( return StateResponse(state, changes, if (error != null) iListOf(error) else null) } +fun getStateChanges( + subaccountNumbers: IList, +): StateChanges { + return StateChanges( + iListOf(Changes.wallet, Changes.subaccount, Changes.input), + null, + subaccountNumbers, + ) +} + fun TradingStateMachine.validAdjustIsolatedMarginInput( adjustIsolatedMargin: Map, parentSubaccountNumber: Int?, @@ -130,6 +164,10 @@ fun TradingStateMachine.validAdjustIsolatedMarginInput( val amount = parser.asDouble(adjustIsolatedMargin["Amount"]) return amount == null || amount > 0 } + AdjustIsolatedMarginInputField.AmountPercent.name -> { + val amountPercent = parser.asDouble(adjustIsolatedMargin["AmountPercent"]) + return amountPercent == null || amountPercent > 0 + } AdjustIsolatedMarginInputField.ChildSubaccountNumber.name -> { val childSubaccountNumber = parser.asInt(adjustIsolatedMargin["ChildSubaccountNumber"]) return childSubaccountNumber == null || childSubaccountNumber % NUM_PARENT_SUBACCOUNTS == parentSubaccountNumber diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/SubaccountSupervisor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/SubaccountSupervisor.kt index 5e285568f..27ca53d76 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/SubaccountSupervisor.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/SubaccountSupervisor.kt @@ -4,6 +4,7 @@ import exchange.dydx.abacus.calculator.TriggerOrdersConstants.TRIGGER_ORDER_DEFA import exchange.dydx.abacus.output.Notification import exchange.dydx.abacus.output.SubaccountOrder import exchange.dydx.abacus.output.TransferRecordType +import exchange.dydx.abacus.output.input.IsolatedMarginAdjustmentType import exchange.dydx.abacus.output.input.OrderStatus import exchange.dydx.abacus.output.input.OrderType import exchange.dydx.abacus.output.input.TradeInputGoodUntil @@ -193,7 +194,7 @@ internal class SubaccountSupervisor( val oldState = stateMachine.state val url = helper.configs.privateApiUrl(if (configs.useParentSubaccount) "parent-fills" else "fills") - val params = subaccountParams() + val params = if (configs.useParentSubaccount) parentSubaccountParams() else subaccountParams() if (url != null) { helper.get(url, params, null, callback = { _, response, httpCode, _ -> if (helper.success(httpCode) && response != null) { @@ -206,6 +207,15 @@ internal class SubaccountSupervisor( } } + private fun parentSubaccountParams(): IMap { + val accountAddress = accountAddress + val subaccountNumber = subaccountNumber + return iMapOf( + "address" to accountAddress, + "parentSubaccountNumber" to "$subaccountNumber", + ) + } + private fun subaccountParams(): IMap { val accountAddress = accountAddress val subaccountNumber = subaccountNumber @@ -218,7 +228,7 @@ internal class SubaccountSupervisor( private fun retrieveTransfers() { val oldState = stateMachine.state val url = helper.configs.privateApiUrl(if (configs.useParentSubaccount) "parent-transfers" else "transfers") - val params = subaccountParams() + val params = if (configs.useParentSubaccount) parentSubaccountParams() else subaccountParams() if (url != null) { helper.get(url, params, null, callback = { _, response, httpCode, _ -> if (helper.success(httpCode) && response != null) { @@ -505,7 +515,7 @@ internal class SubaccountSupervisor( val openPositions = it.value.openPositions val openOrders = it.value.orders?.filter { order -> val status = helper.parser.asString(order.status) - status == "OPEN" + status == "open" || status == "pending" || status == "untriggered" || status == "partiallyFilled" } val postionMarketIds = openPositions?.map { position -> @@ -535,7 +545,7 @@ internal class SubaccountSupervisor( } // Find new childSubaccount number available for Isolated Margin Trade - val existingSubaccountNumbers = utilizedSubaccountsMarketIdMap?.keys ?: iListOf(subaccountNumber) + val existingSubaccountNumbers = utilizedSubaccountsMarketIdMap?.keys ?: iListOf(subaccountNumber.toString()) for (offset in NUM_PARENT_SUBACCOUNTS..MAX_SUBACCOUNT_NUMBER step NUM_PARENT_SUBACCOUNTS) { val tentativeSubaccountNumber = offset + subaccountNumber if (!existingSubaccountNumbers.contains(tentativeSubaccountNumber.toString())) { @@ -1271,12 +1281,25 @@ internal class SubaccountSupervisor( val isolatedMarginAdjustment = stateMachine.state?.input?.adjustIsolatedMargin ?: error("AdjustIsolatedMarginInput is null") val amount = isolatedMarginAdjustment.amount ?: error("amount is null") val childSubaccountNumber = isolatedMarginAdjustment.childSubaccountNumber ?: error("childSubaccountNumber is null") + val type = isolatedMarginAdjustment.type + + val recipientSubaccountNumber = if (type == IsolatedMarginAdjustmentType.Add) { + childSubaccountNumber + } else { + subaccountNumber + } + + val sourceSubaccountNumber = if (type == IsolatedMarginAdjustmentType.Add) { + subaccountNumber + } else { + childSubaccountNumber + } return HumanReadableSubaccountTransferPayload( - subaccountNumber, + sourceSubaccountNumber, amount, accountAddress, - childSubaccountNumber, + recipientSubaccountNumber, ) } diff --git a/src/commonTest/kotlin/exchange.dydx.abacus/app/manager/v2/V4TransactionTests.kt b/src/commonTest/kotlin/exchange.dydx.abacus/app/manager/v2/V4TransactionTests.kt index d62006520..91f76b079 100644 --- a/src/commonTest/kotlin/exchange.dydx.abacus/app/manager/v2/V4TransactionTests.kt +++ b/src/commonTest/kotlin/exchange.dydx.abacus/app/manager/v2/V4TransactionTests.kt @@ -350,9 +350,9 @@ class V4TransactionTests : NetworkTests() { testWebSocket?.simulateReceived(mock.connectionMock.connectedMessage) testWebSocket?.simulateReceived(mock.marketsChannel.v4_subscribed_r1) - stateManager.setAddresses(null, "dydx199tqg4wdlnu4qjlxchpd7seg454937hjrknju4") - testWebSocket?.simulateReceived(mock.parentSubaccountsChannel.subscribed) - testWebSocket?.simulateReceived(mock.parentSubaccountsChannel.channel_data) + stateManager.setAddresses(null, "dydx155va0m7wz5n8zcqscn9afswwt04n4usj46wvp5") + testWebSocket?.simulateReceived(mock.v4ParentSubaccountsMock.subscribed) + testWebSocket?.simulateReceived(mock.v4ParentSubaccountsMock.channel_batch_data) stateManager.market = "ETH-USD" } @@ -391,7 +391,7 @@ class V4TransactionTests : NetworkTests() { setStateMachineForIsolatedMarginTests(stateManager) val transactionCallback: TransactionCallback = { _, _, _ -> } - val cancelPayload = subaccountSupervisor?.cancelOrder("b812bea8-29d3-5841-9549-caa072f6f8a9", transactionCallback) + val cancelPayload = subaccountSupervisor?.cancelOrder("24b68694-d6ae-5df4-baf5-55b0716296e9", transactionCallback) assertNotNull(cancelPayload, "Cancel payload should not be null") assertEquals(128, cancelPayload.subaccountNumber) } diff --git a/src/commonTest/kotlin/exchange.dydx.abacus/payload/AdjustIsolatedMarginInputTests.kt b/src/commonTest/kotlin/exchange.dydx.abacus/payload/AdjustIsolatedMarginInputTests.kt index e60d945bc..6a328a935 100644 --- a/src/commonTest/kotlin/exchange.dydx.abacus/payload/AdjustIsolatedMarginInputTests.kt +++ b/src/commonTest/kotlin/exchange.dydx.abacus/payload/AdjustIsolatedMarginInputTests.kt @@ -45,6 +45,7 @@ class AdjustIsolatedMarginInputTests : V4BaseTests() { testMarginAddition() testMarginRemoval() testZeroAmount() + testMarginAmountPercent() } private fun testChildSubaccountNumberInput() { @@ -203,4 +204,82 @@ class AdjustIsolatedMarginInputTests : V4BaseTests() { """.trimIndent(), ) } + + private fun testMarginAmountPercent() { + test( + { + perp.adjustIsolatedMargin(IsolatedMarginAdjustmentType.Add.name, AdjustIsolatedMarginInputField.Type, 0) + }, + """ + { + "input": { + "current": "adjustIsolatedMargin", + "adjustIsolatedMargin": { + "Type": "Add", + "Amount": null, + "AmountPercent": null + } + } + } + """.trimIndent(), + ) + + test( + { + perp.adjustIsolatedMargin("0.1", AdjustIsolatedMarginInputField.AmountPercent, 0) + }, + """ + { + "input": { + "current": "adjustIsolatedMargin", + "adjustIsolatedMargin": { + "Type": "Add", + "AmountPercent": "0.1", + "Amount": "8882.656169898173" + } + } + } + """.trimIndent(), + ) + + test( + { + perp.adjustIsolatedMargin( + IsolatedMarginAdjustmentType.Remove.name, + AdjustIsolatedMarginInputField.Type, + 0, + ) + }, + """ + { + "input": { + "current": "adjustIsolatedMargin", + "adjustIsolatedMargin": { + "Type": "Remove", + "Amount": null, + "AmountPercent": null + } + } + } + """.trimIndent(), + ) + + test( + { + perp.adjustIsolatedMargin("0.1", AdjustIsolatedMarginInputField.AmountPercent, 0) + }, + """ + { + "input": { + "current": "adjustIsolatedMargin", + "adjustIsolatedMargin": { + "Type": "Remove", + "AmountPercent": "0.1", + "Amount": "79.62439999999999" + } + } + } + """.trimIndent(), + ) + } } diff --git a/src/commonTest/kotlin/exchange.dydx.abacus/tests/payloads/AbacusMockData.kt b/src/commonTest/kotlin/exchange.dydx.abacus/tests/payloads/AbacusMockData.kt index 490e82e68..58212dc12 100644 --- a/src/commonTest/kotlin/exchange.dydx.abacus/tests/payloads/AbacusMockData.kt +++ b/src/commonTest/kotlin/exchange.dydx.abacus/tests/payloads/AbacusMockData.kt @@ -48,6 +48,7 @@ class AbacusMockData { internal val localizationMock = LocalizationMock() internal val launchIncentiveMock = LaunchIncentiveMock() internal val v4OnChainMock = V4OnChainMock() + internal val v4ParentSubaccountsMock = V4ParentSubaccountsMock() internal val v4WithdrawalSafetyChecksMock = V4WithdrawalSafetyChecksMock() internal val v4Environment = V4Environment( "test", diff --git a/src/commonTest/kotlin/exchange.dydx.abacus/tests/payloads/V4ParentSubaccountsMock.kt b/src/commonTest/kotlin/exchange.dydx.abacus/tests/payloads/V4ParentSubaccountsMock.kt new file mode 100644 index 000000000..cb2ac7522 --- /dev/null +++ b/src/commonTest/kotlin/exchange.dydx.abacus/tests/payloads/V4ParentSubaccountsMock.kt @@ -0,0 +1,158 @@ +package exchange.dydx.abacus.tests.payloads + +import kollections.JsExport +import kotlinx.serialization.Serializable + +@Suppress("PropertyName") +@JsExport +@Serializable +internal class V4ParentSubaccountsMock { + internal val subscribed = """ + { + "type": "subscribed", + "connection_id": "83f3d084-f1bb-4506-8f56-96f2f2f69017", + "message_id": 2, + "channel": "v4_parent_subaccounts", + "id": "dydx155va0m7wz5n8zcqscn9afswwt04n4usj46wvp5/0", + "contents": { + "subaccount": { + "address": "dydx155va0m7wz5n8zcqscn9afswwt04n4usj46wvp5", + "parentSubaccountNumber": 0, + "equity": "200", + "freeCollateral": "200", + "childSubaccounts": [ + { + "address": "dydx155va0m7wz5n8zcqscn9afswwt04n4usj46wvp5", + "subaccountNumber": 0, + "equity": "50.5", + "freeCollateral": "50.5", + "openPerpetualPositions": {}, + "assetPositions": { + "USDC": { + "size": "50.5", + "symbol": "USDC", + "side": "LONG", + "assetId": "0", + "subaccountNumber": 0 + } + }, + "marginEnabled": true, + "updatedAtHeight": "17689" + }, + { + "address": "dydx155va0m7wz5n8zcqscn9afswwt04n4usj46wvp5", + "subaccountNumber": 128, + "equity": "149.5", + "freeCollateral": "149.5", + "openPerpetualPositions": {}, + "assetPositions": { + "USDC": { + "size": "149.5", + "symbol": "USDC", + "side": "LONG", + "assetId": "0", + "subaccountNumber": 128 + } + }, + "marginEnabled": true, + "updatedAtHeight": "17689" + } + ] + }, + "orders": [ + { + "id": "683bc8c4-ed8c-5c3c-86c0-4d0a2d74aa31", + "subaccountId": "b456e984-b4bc-5ad2-8662-6b8378c7e0ad", + "clientId": "6681712", + "clobPairId": "22", + "side": "BUY", + "size": "10", + "totalFilled": "0", + "price": "1", + "type": "LIMIT", + "status": "OPEN", + "timeInForce": "GTT", + "reduceOnly": false, + "orderFlags": "64", + "goodTilBlockTime": "2024-06-13T23:35:58.000Z", + "createdAtHeight": "17691", + "clientMetadata": "0", + "updatedAt": "2024-05-16T23:35:57.872Z", + "updatedAtHeight": "17691", + "postOnly": false, + "ticker": "APE-USD", + "subaccountNumber": 0 + }, + { + "id": "24b68694-d6ae-5df4-baf5-55b0716296e9", + "subaccountId": "b456e984-b4bc-5ad2-8662-6b8378c7e0ad", + "clientId": "1616027290", + "clobPairId": "6", + "side": "BUY", + "size": "40", + "totalFilled": "0", + "price": "0.44", + "type": "LIMIT", + "status": "OPEN", + "timeInForce": "GTT", + "reduceOnly": false, + "orderFlags": "64", + "goodTilBlockTime": "2024-06-13T20:20:15.000Z", + "createdAtHeight": "6603", + "clientMetadata": "0", + "updatedAt": "2024-05-16T20:20:15.399Z", + "updatedAtHeight": "6603", + "postOnly": false, + "ticker": "ADA-USD", + "subaccountNumber": 128 + } + ] + } + } + """.trimIndent() + + internal val channel_batch_data = """ + { + "type": "channel_batch_data", + "connection_id": "83f3d084-f1bb-4506-8f56-96f2f2f69017", + "message_id": 29, + "id": "dydx155va0m7wz5n8zcqscn9afswwt04n4usj46wvp5/0", + "channel": "v4_parent_subaccounts", + "version": "2.4.0", + "contents": [ + { + "assetPositions": [ + { + "address": "dydx155va0m7wz5n8zcqscn9afswwt04n4usj46wvp5", + "subaccountNumber": 0, + "positionId": "2314ffd5-6723-54c7-ab87-7b292ae14ee1", + "assetId": "0", + "symbol": "USDC", + "side": "LONG", + "size": "46" + } + ] + }, + { + "transfers": { + "sender": { + "address": "dydx155va0m7wz5n8zcqscn9afswwt04n4usj46wvp5", + "subaccountNumber": 0 + }, + "recipient": { + "address": "dydx155va0m7wz5n8zcqscn9afswwt04n4usj46wvp5", + "subaccountNumber": 128 + }, + "symbol": "USDC", + "size": "4.5", + "type": "TRANSFER_OUT", + "createdAt": "2024-05-17T04:02:07.208Z", + "createdAtHeight": "32866", + "transactionHash": "72E80E605FAD46FF82C8C7AE2260519ACB73F5900A8F340ABBF190E84C2C4DFC" + } + } + ], + "subaccountNumber": 0 + } + """.trimIndent() +} diff --git a/v4_abacus.podspec b/v4_abacus.podspec index 8a843c550..ad6a0a643 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.7.19' + spec.version = '1.7.23' spec.homepage = 'https://github.com/dydxprotocol/v4-abacus' spec.source = { :http=> ''} spec.authors = ''