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

v1.7.24: Add function to reclaim funds #334

Merged
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ allprojects {
}

group = "exchange.dydx.abacus"
version = "1.6.54"
version = "1.6.55"

repositories {
google()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,8 @@ enum class TransactionType(val rawValue: String) {
Withdraw("withdraw"),
SubaccountTransfer("subaccountTransfer"),
Faucet("faucet"),
simulateWithdraw("simulateWithdraw"),
simulateTransferNativeToken("simulateTransferNativeToken"),
SimulateWithdraw("simulateWithdraw"),
SimulateTransferNativeToken("simulateTransferNativeToken"),
SendNobleIBC("sendNobleIBC"),
WithdrawToNobleIBC("withdrawToNobleIBC"),
CctpWithdraw("cctpWithdraw"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ internal fun V4StateManagerAdaptor.simulateWithdrawal(
val payload = withdrawPayloadJson()

transaction(
TransactionType.simulateWithdraw,
TransactionType.SimulateWithdraw,
payload,
) { response ->
val error = parseTransactionResponse(response)
Expand Down Expand Up @@ -243,7 +243,7 @@ internal fun V4StateManagerAdaptor.simulateTransferNativeToken(
val payload = transferNativeTokenPayloadJson()

transaction(
TransactionType.simulateTransferNativeToken,
TransactionType.SimulateTransferNativeToken,
payload,
) { response ->
val error = parseTransactionResponse(response)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,7 @@ internal class OnboardingSupervisor(
val payload = withdrawPayloadJson(subaccountNumber)

helper.transaction(
TransactionType.simulateWithdraw,
TransactionType.SimulateWithdraw,
payload,
) { response ->
val error = helper.parseTransactionResponse(response)
Expand Down Expand Up @@ -495,7 +495,7 @@ internal class OnboardingSupervisor(
val payload = transferNativeTokenPayloadJson(subaccountNumber)

helper.transaction(
TransactionType.simulateTransferNativeToken,
TransactionType.SimulateTransferNativeToken,
payload,
) { response ->
val error = helper.parseTransactionResponse(response)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import exchange.dydx.abacus.output.input.OrderType
import exchange.dydx.abacus.output.input.TradeInputGoodUntil
import exchange.dydx.abacus.output.input.TriggerOrder
import exchange.dydx.abacus.protocols.AnalyticsEvent
import exchange.dydx.abacus.protocols.LocalTimerProtocol
import exchange.dydx.abacus.protocols.ThreadingType
import exchange.dydx.abacus.protocols.TransactionCallback
import exchange.dydx.abacus.protocols.TransactionType
Expand Down Expand Up @@ -280,6 +281,8 @@ internal class SubaccountSupervisor(
channel,
subaccountChannelParams(accountAddress, subaccountNumber),
)

pollReclaimUnutilizedFunds()
}

private fun subaccountChannelParams(
Expand Down Expand Up @@ -1296,6 +1299,91 @@ internal class SubaccountSupervisor(
}
}

/**
* @description Loop through all subaccounts to find childSubaccounts that have funds but no open positions or orders. Initiate a transfer to parentSubaccount.
*/
private fun reclaimUnutilizedFundsFromChildSubaccounts() {
val subaccounts = stateMachine.state?.account?.subaccounts ?: return

val subaccountQuoteBalanceMap = subaccounts.mapValues { subaccount ->
// If the subaccount is the parentSubaccount, skip
if (subaccount.value.subaccountNumber == subaccountNumber) {
return@mapValues 0.0
}

val openPositions = subaccount.value.openPositions
val openOrders = subaccount.value.orders?.filter { order ->
val status = helper.parser.asString(order.status)
status == "OPEN"
jaredvu marked this conversation as resolved.
Show resolved Hide resolved
}
val quoteBalance = subaccount.value.quoteBalance?.current ?: 0.0

// Only return a quoteBalance if the subaccount has no open positions or orders
if (openPositions.isNullOrEmpty() && openOrders.isNullOrEmpty() && quoteBalance > 0.0) {
quoteBalance
} else {
0.0
}
}.filter {
it.value > 0.0
}

val transferPayloadStrings = iMutableListOf<String>()

subaccountQuoteBalanceMap.forEach {
val childSubaccountNumber = it.key.toInt()
val amountToTransfer = it.value.toString()

val transferPayload = HumanReadableSubaccountTransferPayload(
childSubaccountNumber,
amountToTransfer,
accountAddress,
subaccountNumber,
jaredvu marked this conversation as resolved.
Show resolved Hide resolved
)

val transferPayloadString = Json.encodeToString(transferPayload)
transferPayloadStrings.add(transferPayloadString)
}

recursivelyReclaimChildSubaccountFunds(transferPayloadStrings)
}

private fun recursivelyReclaimChildSubaccountFunds(transferPayloadStrings: MutableList<String>) {
if (transferPayloadStrings.isNotEmpty()) {
val transferPayloadString = transferPayloadStrings.removeAt(0)
helper.transaction(TransactionType.SubaccountTransfer, transferPayloadString) { response ->
val error = parseTransactionResponse(response)
if (error != null) {
emitError(error)
} else {
recursivelyReclaimChildSubaccountFunds(transferPayloadStrings)
}
}
}
}

private var reclaimUnutilizedFundsTimer: LocalTimerProtocol? = null
set(value) {
if (field !== value) {
field?.cancel()
field = value
}
}

private fun pollReclaimUnutilizedFunds() {
reclaimUnutilizedFundsTimer = null
helper.ioImplementations.threading?.async(ThreadingType.main) {
jaredvu marked this conversation as resolved.
Show resolved Hide resolved
this.reclaimUnutilizedFundsTimer = helper.ioImplementations.timer?.schedule(
(10.seconds).inWholeSeconds.toDouble(),
null,
Copy link
Contributor

Choose a reason for hiding this comment

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

can we use this repeat duration instead of recursively calling this function?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Current recursive implementation waits for a successful callback reponse for each transfer and goes through the entire list of childSubaccounts then creates a fresh 10 sec timer before repeating. I don't think I can achieve the same effect with repeat.

) {
reclaimUnutilizedFundsFromChildSubaccounts()
pollReclaimUnutilizedFunds()
false
}
}
}

override fun updateNotifications() {
val notifications = notificationsProvider.buildNotifications(stateMachine, subaccountNumber)
consolidateNotifications(notifications)
Expand Down
2 changes: 1 addition & 1 deletion v4_abacus.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = 'v4_abacus'
spec.version = '1.6.54'
spec.version = '1.6.55'
spec.homepage = 'https://github.com/dydxprotocol/v4-abacus'
spec.source = { :http=> ''}
spec.authors = ''
Expand Down
Loading