Skip to content

Commit

Permalink
enable non-cctp deposit and wihtdrawals
Browse files Browse the repository at this point in the history
  • Loading branch information
yogurtandjam committed Jun 14, 2024
1 parent 934bf1a commit e364db6
Show file tree
Hide file tree
Showing 10 changed files with 442 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ interface IRouterProcessor {
transactionId: String?,
): Map<String, Any>?

fun getTokenByDenomAndChainId(tokenDenom: String?, chainId: String?): Map<String, Any>?
fun updateTokensDefaults(modified: MutableMap<String, Any>, selectedChainId: String?)
fun defaultChainId(): String?
fun selectedTokenSymbol(tokenAddress: String?, selectedChainId: String?): String?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,16 +151,21 @@ internal class SkipProcessor(
return parser.asString(selectedChain?.get("chain_id"))
}

override fun selectedTokenSymbol(tokenAddress: String?, selectedChainId: String?): String? {
val tokensList = filteredTokens(selectedChainId)
override fun getTokenByDenomAndChainId(tokenDenom: String?, chainId: String?): Map<String, Any>? {
val tokensList = filteredTokens(chainId)
tokensList?.find {
parser.asString(parser.asNativeMap(it)?.get("denom")) == tokenAddress
parser.asString(parser.asNativeMap(it)?.get("denom")) == tokenDenom
}?.let {
return parser.asString(parser.asNativeMap(it)?.get("symbol"))
return parser.asNativeMap(it)
}
return null
}

override fun selectedTokenSymbol(tokenAddress: String?, selectedChainId: String?): String? {
val token = getTokenByDenomAndChainId(tokenAddress, selectedChainId) ?: return null
return parser.asString(token.get("symbol"))
}

override fun selectedTokenDecimals(tokenAddress: String?, selectedChainId: String?): String? {
val tokensList = filteredTokens(selectedChainId)
tokensList?.find {
Expand All @@ -174,7 +179,31 @@ internal class SkipProcessor(
override fun filteredTokens(chainId: String?): List<Any>? {
val chainIdToUse = chainId ?: defaultChainId()
val assetsMapForChainId = parser.asNativeMap(this.skipTokens?.get(chainIdToUse))
return parser.asNativeList(assetsMapForChainId?.get("assets"))
val assetsForChainId = parser.asNativeList(assetsMapForChainId?.get("assets"))
// coinbase exchange chainId is noble-1. we only allow usdc withdrawals from it
if (chainId === "noble-1") {
return assetsForChainId?.filter {
parser.asString(parser.asNativeMap(it)?.get("denom")) == "uusdc"
}
}

val filteredTokens = mutableListOf<Map<String, Any>>()
// we have to replace skip's {chain-name}-native naming bc it doesn't play well with
// any of our SDKs.
// however, their {chain-name}-native denom naming is required for their API
// so we need to store both values
assetsForChainId?.forEach {
val token = parser.asNativeMap(it)?.toMutableMap()
if (token != null) {
val denom = parser.asString(token["denom"])
if (denom?.endsWith("native") == true) {
token["skipDenom"] = denom
token["denom"] = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"
}
filteredTokens.add(token.toMap())
}
}
return filteredTokens
}

override fun defaultTokenAddress(chainId: String?): String? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ internal class SkipRouteProcessor(internal val parser: ParserProtocol) {
"string" to mapOf(
"route.usd_amount_out" to "toAmountUSD",
"route.estimated_amount_out" to "toAmount",
"swap_price_impact_percent" to "aggregatePriceImpact",
"route.swap_price_impact_percent" to "aggregatePriceImpact",

// SQUID PARAMS THAT ARE NOW DEPRECATED:
// "route.estimate.gasCosts.0.amountUSD" to "gasFee",
Expand All @@ -23,6 +23,12 @@ internal class SkipRouteProcessor(internal val parser: ParserProtocol) {
),
)

private val transactionTypes = listOf(
"transfer",
"axelar_transfer",
"hyperlane_transfer",
)

private fun findFee(payload: Map<String, Any>, key: String): Double? {
val estimatedFees = parser.asList(parser.value(payload, "route.estimated_fees"))
val foundFeeObj = estimatedFees?.find {
Expand All @@ -32,26 +38,47 @@ internal class SkipRouteProcessor(internal val parser: ParserProtocol) {
return feeInUSD
}

private fun calculateFeesFromSwaps(payload: Map<String, Any>): Double {
val operations = parser.asList(parser.value(payload, "route.operations")) ?: return 0.0
var total = 0.0
operations.forEach { operation ->
val fee = getFeeFromOperation(parser.asNativeMap(operation))
total += fee
}
return total
}

private fun getFeeFromOperation(operationPayload: Map<String, Any>?): Double {
if (operationPayload == null) return 0.0
var fee = 0.0
transactionTypes.forEach { transactionType ->
val transaction = parser.asNativeMap(operationPayload.get(transactionType))
if (transaction != null) {
val usdFeeAmount = parser.asDouble(transaction.get("usd_fee_amount")) ?: 0.0
fee += usdFeeAmount
}
}
return fee
}

fun received(
existing: Map<String, Any>?,
payload: Map<String, Any>,
decimals: Double?
): Map<String, Any> {
val modified = BaseProcessor(parser).transform(existing, payload, keyMap)

var bridgeFees = findFee(payload, "BRIDGE")
var bridgeFees = findFee(payload, "BRIDGE") ?: 0.0
// TODO: update web UI to show smart relay fees
// For now we're just bundling it with the bridge fees
val smartRelayFees = findFee(payload, "SMART_RELAY")
if (bridgeFees == null) {
bridgeFees = smartRelayFees
} else if (smartRelayFees != null) {
bridgeFees += smartRelayFees
}
val smartRelayFees = findFee(payload, "SMART_RELAY") ?: 0.0
bridgeFees += smartRelayFees
bridgeFees += calculateFeesFromSwaps(payload)

val gasFees = findFee(payload, "GAS")

modified.safeSet("gasFees", gasFees)
modified.safeSet("bridgeFees", bridgeFees)
modified.safeSet("gasFee", gasFees)
modified.safeSet("bridgeFee", bridgeFees)

val toAmount = parser.asLong(parser.value(payload, "route.estimated_amount_out"))
if (toAmount != null && decimals != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,16 @@ internal class SquidProcessor(
return parser.asString(selectedChain?.get("chainId"))
}

override fun getTokenByDenomAndChainId(tokenDenom: String?, chainId: String?): Map<String, Any>? {
val tokensList = filteredTokens(chainId)
tokensList?.find {
parser.asString(parser.asNativeMap(it)?.get("address")) == tokenDenom
}?.let {
return parser.asNativeMap(it)
}
return null
}

override fun selectedTokenSymbol(tokenAddress: String?, selectedChainId: String?): String? {
this.tokens?.find {
parser.asString(parser.asNativeMap(it)?.get("address")) == tokenAddress
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ class V4StateManagerConfigs(
return if (environment.isMainNet) "noble-1" else "grand-1"
}

fun osmosisChainId(): String? {
return if (environment.isMainNet) "osmosis-1" else "osmosis-5"
}

fun skipV1Chains(): String {
return "$skipHost/v1/info/chains?include_evm=true"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,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.Logger
import exchange.dydx.abacus.utils.mutable
import exchange.dydx.abacus.utils.mutableMapOf
import exchange.dydx.abacus.utils.safeSet
Expand Down Expand Up @@ -245,7 +246,9 @@ private fun TradingStateMachine.updateTransferToChainType(transfer: MutableMap<S

private fun TradingStateMachine.updateTransferExchangeType(transfer: MutableMap<String, Any>, exchange: String) {
val exchangeDestinationChainId = routerProcessor.exchangeDestinationChainId
Logger.e({ "exchangedestinationchainid:$exchangeDestinationChainId" })
val tokenOptions = routerProcessor.tokenOptions(exchangeDestinationChainId)
Logger.e({ "tokenOptions:$tokenOptions" })
if (transfer["type"] != "TRANSFER_OUT") {
internalState.transfer.tokens = tokenOptions
transfer.safeSet("token", routerProcessor.defaultTokenAddress(exchangeDestinationChainId))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ import exchange.dydx.abacus.utils.isAddressValid
import exchange.dydx.abacus.utils.mutable
import exchange.dydx.abacus.utils.safeSet
import exchange.dydx.abacus.utils.toJsonPrettyPrint
import exchange.dydx.abacus.utils.toNeutronAddress
import exchange.dydx.abacus.utils.toNobleAddress
import exchange.dydx.abacus.utils.toOsmosisAddress
import io.ktor.util.encodeBase64
import kollections.iListOf
import kotlinx.serialization.encodeToString
Expand Down Expand Up @@ -210,48 +212,51 @@ internal class OnboardingSupervisor(
}
}

@Suppress("UnusedPrivateMember", "ForbiddenComment")
private fun retrieveSkipDepositRouteNonCCTP(
state: PerpetualState?,
accountAddress: String,
sourceAddress: String,
subaccountNumber: Int?,
) {
// We have a lot of duplicate code for these deposit/withdrawal route calls
// It's easier to dedupe now that he url is the same and only the args differ
// TODO: Consider creating generateArgs fun to reduce code duplication
val fromChain = state?.input?.transfer?.chain
val fromToken = state?.input?.transfer?.token
val fromAmount = helper.parser.asDecimal(state?.input?.transfer?.size?.size)?.let {
val fromChain = state?.input?.transfer?.chain ?: return
val fromTokenDenom = state.input.transfer.token ?: return
val fromTokenSkipDenom = stateMachine.routerProcessor.getTokenByDenomAndChainId(
tokenDenom = fromTokenDenom,
chainId = fromChain,
)?.get("skipDenom")
// Denoms for tokens on their native chains are returned from the skip API in an incompatible
// format for our frontend SDKs but are required by the skip API for other API calls.
// So we prefer the skimDenom and default to the regular denom for API calls.
val fromTokenDenomForAPIUse = fromTokenSkipDenom ?: fromTokenDenom
val fromAmount = helper.parser.asDecimal(state.input.transfer.size?.size)?.let {
val decimals =
helper.parser.asInt(stateMachine.routerProcessor.selectedTokenDecimals(tokenAddress = fromToken, selectedChainId = fromChain))
helper.parser.asInt(stateMachine.routerProcessor.selectedTokenDecimals(tokenAddress = fromTokenDenom, selectedChainId = fromChain))
if (decimals != null) {
(it * Numeric.decimal.TEN.pow(decimals)).toBigInteger()
} else {
null
}
}
val chainId = helper.environment.dydxChainId
val nativeChainUSDCDenom = helper.environment.tokens["usdc"]?.denom
val fromAmountString = helper.parser.asString(fromAmount)
val osmosisAddress = accountAddress.toOsmosisAddress() ?: return
val nobleAddress = accountAddress.toNobleAddress() ?: return
val osmosisChainId = helper.configs.osmosisChainId() ?: return
val nobleChainId = helper.configs.nobleChainId()
val chainId = helper.environment.dydxChainId ?: return
val nativeChainUSDCDenom = helper.environment.tokens["usdc"]?.denom ?: return
val fromAmountString = helper.parser.asString(fromAmount) ?: return
val url = helper.configs.skipV2MsgsDirect()
if (fromChain != null &&
fromToken != null &&
fromAmount != null && fromAmount > 0 &&
fromAmountString != null &&
chainId != null &&
nativeChainUSDCDenom != null &&
url != null
) {
if (fromAmount != null && fromAmount > 0) {
val body: Map<String, Any> = mapOf(
"amount_in" to fromAmountString,
"source_asset_denom" to fromToken,
"source_asset_denom" to fromTokenDenomForAPIUse,
"source_asset_chain_id" to fromChain,
"dest_asset_denom" to nativeChainUSDCDenom,
"dest_asset_chain_id" to chainId,
"chain_ids_to_addresses" to mapOf(
"fromChain" to sourceAddress,
"toChain" to accountAddress,
fromChain to sourceAddress,
osmosisChainId to osmosisAddress,
nobleChainId to nobleAddress,
chainId to accountAddress,
),
"slippage_tolerance_percent" to SLIPPAGE_PERCENT,
)
Expand Down Expand Up @@ -995,14 +1000,28 @@ internal class OnboardingSupervisor(
subaccountNumber: Int?,
) {
val toChain = state?.input?.transfer?.chain ?: return
val toToken = state?.input?.transfer?.token ?: return
val toAddress = state?.input?.transfer?.address
val toTokenDenom = state.input.transfer.token ?: return
val toTokenSkipDenom = stateMachine.routerProcessor.getTokenByDenomAndChainId(
tokenDenom = toTokenDenom,
chainId = toChain,
)?.get("skipDenom")
// Denoms for tokens on their native chains are returned from the skip API in an incompatible
// format for our frontend SDKs but are required by the skip API for other API calls.
// So we prefer the skimDenom and default to the regular denom for API calls.
val toTokenDenomForAPIUse = toTokenSkipDenom ?: toTokenDenom

val usdcSize = helper.parser.asDecimal(state?.input?.transfer?.size?.usdcSize) ?: return
val fromAmount = if (usdcSize > gas) {
((usdcSize - gas) * Numeric.decimal.TEN.pow(decimals)).toBigInteger()
} else {
return
}
if (fromAmount <= 0) return
val osmosisAddress = accountAddress.toOsmosisAddress() ?: return
val nobleAddress = accountAddress.toNobleAddress() ?: return
val osmosisChainId = helper.configs.osmosisChainId() ?: return
val nobleChainId = helper.configs.nobleChainId()
val fromChain = helper.environment.dydxChainId ?: return
val fromToken = helper.environment.tokens["usdc"]?.denom ?: return
val fromAmountString = helper.parser.asString(fromAmount) ?: return
Expand All @@ -1011,12 +1030,17 @@ internal class OnboardingSupervisor(
"amount_in" to fromAmountString,
"source_asset_denom" to fromToken,
"source_asset_chain_id" to fromChain,
"dest_asset_denom" to toToken,
"dest_asset_denom" to toTokenDenomForAPIUse,
"dest_asset_chain_id" to toChain,
"chain_ids_to_addresses" to mapOf(
fromChain to sourceAddress,
toChain to accountAddress,
fromChain to accountAddress,
osmosisChainId to osmosisAddress,
nobleChainId to nobleAddress,
"neutron-1" to accountAddress.toNeutronAddress(),
toChain to toAddress,
),
"allow_multi_tx" to true,
"allow_unsafe" to true,
"slippage_tolerance_percent" to SLIPPAGE_PERCENT,
)
val header = iMapOf(
Expand Down
23 changes: 23 additions & 0 deletions src/commonMain/kotlin/exchange.dydx.abacus/utils/String+Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,29 @@ fun String.toNobleAddress(): String? {
}
}

fun String.toOsmosisAddress(): String? {
try {
val (humanReadablePart, data) = Bech32.decode(this)
if (humanReadablePart != "dydx") {
return null
}
return Bech32.encode("osmo", data)
} catch (e: Exception) {
return null
}
}
fun String.toNeutronAddress(): String? {
try {
val (humanReadablePart, data) = Bech32.decode(this)
if (humanReadablePart != "dydx") {
return null
}
return Bech32.encode("neutron1", data)
} catch (e: Exception) {
return null
}
}

fun String.toDydxAddress(): String? {
try {
val (humanReadablePart, data) = Bech32.decode(this)
Expand Down
Loading

0 comments on commit e364db6

Please sign in to comment.