diff --git a/.github/workflows/bump_version.yml b/.github/workflows/bump_version.yml index f73020cf9..57647698d 100644 --- a/.github/workflows/bump_version.yml +++ b/.github/workflows/bump_version.yml @@ -1,16 +1,14 @@ -name: Bump version on PR merge +name: Bump version on: - pull_request: - branches: - - main - types: closed + pull_request_target: + types: [opened, synchronize, reopened] permissions: contents: write jobs: update_version: - if: github.event.pull_request.merged == true + #if: github.event.pull_request.merged == true runs-on: macos-latest steps: - name: checkout @@ -18,14 +16,102 @@ jobs: with: # Fetch full depth, otherwise the last step overwrites the last commit's parent, essentially removing the graph. fetch-depth: 0 + token: ${{ secrets.BOT_PAT }} + ref: ${{ github.head_ref }} - - name: Run bump_version_gh_action.sh + - name: Bump version if needed + id: bump-version run: | - bash ./bump_version_gh_action.sh - - name: Amend the last commit + set +e + vercomp () { + if [[ $1 == $2 ]] + then + return 0 + fi + local IFS=. + local i ver1=($1) ver2=($2) + # fill empty fields in ver1 with zeros + for ((i=${#ver1[@]}; i<${#ver2[@]}; i++)) + do + ver1[i]=0 + done + for ((i=0; i<${#ver1[@]}; i++)) + do + if [[ -z ${ver2[i]} ]] + then + # fill empty fields in ver2 with zeros + ver2[i]=0 + fi + if ((10#${ver1[i]} > 10#${ver2[i]})) + then + return 1 + fi + if ((10#${ver1[i]} < 10#${ver2[i]})) + then + return 2 + fi + done + return 0 + } + + # Defining a temporary directory for cloning + TMP_DIR=$(mktemp -d) + + curl https://raw.githubusercontent.com/dydxprotocol/v4-abacus/main/build.gradle.kts > $TMP_DIR/build.gradle.kts + + # search for the first line that starts with "version" in build.gradle.kts + # get the value in the quotes + VERSION=$(grep "^version = " build.gradle.kts | sed -n 's/version = "\(.*\)"/\1/p') + + REPO_VERSION=$(grep "^version = " $TMP_DIR/build.gradle.kts | sed -n 's/version = "\(.*\)"/\1/p') + + # call the version comparison function + + vercomp $REPO_VERSION $VERSION + case $? in + 0) SHOULD_BUMP=true ;; + 1) SHOULD_BUMP=true ;; + 2) SHOULD_BUMP=false ;; + esac + + if [ $SHOULD_BUMP == false ]; then + echo "Repo version < PR version... No need to bump." + echo "bump_version_ret=-1" >> $GITHUB_OUTPUT + exit 0 + fi + + # increment the version number + NEW_VERSION=$(echo $VERSION | awk -F. '{$NF = $NF + 1;} 1' | sed 's/ /./g') + + #if NEW_VERSION is not empty, replace the version in build.gradle.kts + if [ -n "$NEW_VERSION" ]; then + sed -i '' "s/version = \"$VERSION\"/version = \"$NEW_VERSION\"/" build.gradle.kts + echo "Version bumped to $NEW_VERSION" + fi + echo "bump_version_ret=0" >> $GITHUB_OUTPUT + + - name: Import bot's GPG key for signing commits + id: import-gpg + uses: crazy-max/ghaction-import-gpg@v6 + with: + gpg_private_key: ${{ secrets.BOT_GPG_PRIVATE_KEY }} + passphrase: ${{ secrets.BOT_GPG_PASSPHRASE }} + #git_config_global: true + git_user_signingkey: true + git_commit_gpgsign: true + + - name: Sign commit and push changes run: | - git config --global user.email "${GITHUB_ACTOR}@users.noreply.github.com" - git config --global user.name "${GITHUB_ACTOR}" - git commit -a --amend --no-edit - git push --force-with-lease - echo "Complete" + if [[ "${{ steps.bump-version.outputs.bump_version_ret }}" == "0" ]]; then + git config --global user.email ${{ steps.import-gpg.outputs.name }} + git config --global user.name ${{ steps.import-gpg.outputs.email }} + + git commit -S -m "Bump version" build.gradle.kts + git push + fi + env: + # GITHUB_TOKEN: ${{ secrets.BOT_PAT }} + GIT_AUTHOR_NAME: ${{ steps.import-gpg.outputs.name }} + GIT_AUTHOR_EMAIL: ${{ steps.import-gpg.outputs.email }} + GIT_COMMITTER_NAME: ${{ steps.import-gpg.outputs.name }} + GIT_COMMITTER_EMAIL: ${{ steps.import-gpg.outputs.email }} diff --git a/build.gradle.kts b/build.gradle.kts index 00084403b..c41a5ad02 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -51,7 +51,7 @@ allprojects { } group = "exchange.dydx.abacus" -version = "1.7.34" +version = "1.7.49" repositories { google() diff --git a/bump_version_gh_action.sh b/bump_version_gh_action.sh deleted file mode 100755 index 069819e32..000000000 --- a/bump_version_gh_action.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh - -# search for the first line that starts with "version" in build.gradle.kts -# get the value in the quotes -VERSION=$(grep "^version = " build.gradle.kts | sed -n 's/version = "\(.*\)"/\1/p') - -# increment the version number -NEW_VERSION=$(echo $VERSION | awk -F. '{$NF = $NF + 1;} 1' | sed 's/ /./g') - -#if NEW_VERSION is not empty, replace the version in build.gradle.kts -if [ -n "$NEW_VERSION" ]; then - sed -i '' "s/version = \"$VERSION\"/version = \"$NEW_VERSION\"/" build.gradle.kts - echo "Version bumped to $NEW_VERSION" -fi diff --git a/detekt.yml b/detekt.yml index 33960f594..e5142244b 100644 --- a/detekt.yml +++ b/detekt.yml @@ -20,7 +20,7 @@ complexity: LargeClass: threshold: 1000 # up from 600 LongMethod: - threshold: 100 # up from 60 + active: false TooManyFunctions: active: false NestedBlockDepth: diff --git a/docs/Account.md b/docs/Account.md index 6ed3fe13e..b5cd3f4bd 100644 --- a/docs/Account.md +++ b/docs/Account.md @@ -305,7 +305,7 @@ Order status ## timeInForce -Time in force, GTT (Good Til Time), IOC (Immediate or Cancel) or FOK (Fill or Kill) +Time in force, GTT (Good Til Time) or IOC (Immediate or Cancel) ## marketId diff --git a/docs/Input/TransferInput.md b/docs/Input/TransferInput.md index 416c53dcb..46729a4c8 100644 --- a/docs/Input/TransferInput.md +++ b/docs/Input/TransferInput.md @@ -7,7 +7,10 @@ data class TransferInput(  val fee: Double?,  val chain: String?,  val address: String?, + val memo: String?,  val depositOptions: DepositInputOptions?, + val withdrawalOptions: WithdrawalInputOptions?, + val transferOutOptions: TransferOutInputOptions?,  val summary: TransferInputSummary?,  val resources: TransferInputResources?,  val requestPayload: TransferInputRequestPayload? @@ -39,10 +42,22 @@ Selected chain to perform the transfer Selected token address of the chain to perform the transfer +## memo + +Memo for transfer + ## depositOptions structure of [DepositInputOptions](#DepositInputOptions) +## withdrawalOptions + +structure of [WithdrawalInputOptions](#WithdrawalInputOptions) + +## transferOutOptions + +structure of [TransferOutInputOptions](#TransferOutInputOptions) + ## summary structure of [TransferInputSummary](#TransferInputSummary) @@ -80,6 +95,66 @@ UX should let the user choose whether to use fast speed Option of assets to choose from +# WithdrawalInputOptions + +data class DepositInputOptions( + val needsSize: Boolean?, + val needsAddress: Boolean?, + val needsFastSpeed: Boolean?, + val exchanges: Array? + val chains: Array? + val assets: Array? +) + +## needsSize + +UX should let user enter the size + +## needsAddress + +UX should let user enter a wallet address + +## needsFastSpeed + +UX should let the user choose whether to use fast speed + +## exchanges + +Option of exchanges to choose from + +## chains + +Option of chains to choose from + +## assets + +Option of assets to choose from + +# TransferOutInputOptions + +data class TransferOutInputOptions( + val needsSize: Boolean?, + val needsAddress: Boolean?, + val chains: Array?, + val assets: Array? +) + +## needsSize + +UX should let user enter the size + +## needsAddress + +UX should let user enter a wallet address + +## chains + +Option of chains to choose from + +## assets + +Option of assets to choose from + # TransferInputSummary data class TransferInputSummary( @@ -102,7 +177,7 @@ Whether the transfer transaction can be filled # TransferInputResources -The chain and token resources of the selected chain and its associated tokens. Use the chainId +The chain and token resources of the selected chain and its associated tokens. Use the chainId and token address of the key to the maps, respectively, to get the resource. data class TransferInputResources( @@ -147,4 +222,4 @@ data class TransferInputRequestPayload(  val gasPrice: String?,  val maxFeePerGas: String?,  val maxPriorityFeePerGas: String? -) \ No newline at end of file +) diff --git a/integration/iOS/Pods/Pods.xcodeproj/project.pbxproj b/integration/iOS/Pods/Pods.xcodeproj/project.pbxproj index 56d884654..9da55c9ba 100644 --- a/integration/iOS/Pods/Pods.xcodeproj/project.pbxproj +++ b/integration/iOS/Pods/Pods.xcodeproj/project.pbxproj @@ -9,9 +9,10 @@ /* Begin PBXAggregateTarget section */ 4084846DAF1774840D25DF1BF2460325 /* abacus */ = { isa = PBXAggregateTarget; - buildConfigurationList = B36D4FC54F44832C64D6BFCFC7EF4665 /* Build configuration list for PBXAggregateTarget "abacus" */; + buildConfigurationList = 127F9C06E33E9DC2AB3876F089264EB1 /* Build configuration list for PBXAggregateTarget "abacus" */; buildPhases = ( - 1403E0AEBF8A2087DF8CACA188B609E8 /* [CP-User] Build abacus */, + DB36ACE8A857E9B93C25EA309FF42C2A /* [CP-User] Build abacus */, + 903ECA29140368245E4B69BE2D0DBCA4 /* [CP] Copy dSYMs */, ); dependencies = ( ); @@ -142,6 +143,13 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 174DE097054DE7A0410C57AF4AEF5A1A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 469F25E790D19440507BF938A40578A7; + remoteInfo = "Pods-abacus.ios"; + }; 8437FCB39ECA4A44D93FCDCBF6066E92 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; @@ -156,13 +164,6 @@ remoteGlobalIDString = 4084846DAF1774840D25DF1BF2460325; remoteInfo = abacus; }; - F99C8B5A9E9146040BECE7ABFD8E196E /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 469F25E790D19440507BF938A40578A7; - remoteInfo = "Pods-abacus.ios"; - }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ @@ -173,6 +174,7 @@ 05F962230C27EB8320B3F9DACAB26666 /* Random.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Random.swift; path = Sources/CryptoSwift/CS_BigInt/Random.swift; sourceTree = ""; }; 068F760AEB5D19F06497CBD5A01D4B17 /* PBKDF1.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = PBKDF1.swift; path = Sources/CryptoSwift/PKCS/PBKDF1.swift; sourceTree = ""; }; 0744F3E2DBB3A6893AC7B625FCBA2151 /* Generics.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Generics.swift; path = Sources/CryptoSwift/Generics.swift; sourceTree = ""; }; + 087E84600314FECE345137BBED3ABF4A /* abacus.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = abacus.debug.xcconfig; sourceTree = ""; }; 0A52523A80E0465BAEC42025DAD553B2 /* Pods-abacus.iosTests-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-abacus.iosTests-Info.plist"; sourceTree = ""; }; 0D4EF333478AFFA4893B29F877CE2E3B /* Pods-abacus.iosTests.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "Pods-abacus.iosTests.modulemap"; sourceTree = ""; }; 0ECEA0D8830DCE37C7297C5F1342B08E /* Pods-abacus.ios.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-abacus.ios.release.xcconfig"; sourceTree = ""; }; @@ -183,6 +185,7 @@ 15A46B32EBC892483620A52F24AFC350 /* ECB.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ECB.swift; path = Sources/CryptoSwift/BlockMode/ECB.swift; sourceTree = ""; }; 161A656240E3B4B64ECE6858B65DC977 /* Pods-abacus.ios-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-abacus.ios-dummy.m"; sourceTree = ""; }; 186DE576AF9B0EC2149708CD77D2BC1F /* Shifts.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Shifts.swift; path = Sources/CryptoSwift/CS_BigInt/Shifts.swift; sourceTree = ""; }; + 194377B2DFD1180A2D72988CCD50F152 /* Abacus.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Abacus.framework; path = build/cocoapods/framework/Abacus.framework; sourceTree = ""; }; 1B6D2B0A7ACC4ACD9BC9FA5D52199C08 /* Cipher.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Cipher.swift; path = Sources/CryptoSwift/Cipher.swift; sourceTree = ""; }; 1C5732A96CD4AA2A534F0887522F1C39 /* CryptoSwift.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = CryptoSwift.debug.xcconfig; sourceTree = ""; }; 1CF2318F38D1207A2F8BF92B7B8A294E /* Array+Extension.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Array+Extension.swift"; path = "Sources/CryptoSwift/Array+Extension.swift"; sourceTree = ""; }; @@ -190,7 +193,6 @@ 1E34A6EC8D702D85DFC8AB0BCA04D225 /* Pods-abacus.iosTests-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-abacus.iosTests-acknowledgements.markdown"; sourceTree = ""; }; 233CC871C3B5E5D1C643AA3F5362558A /* DigestType.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DigestType.swift; path = Sources/CryptoSwift/DigestType.swift; sourceTree = ""; }; 236DF73D92D4D71C82089CF882764244 /* NoPadding.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NoPadding.swift; path = Sources/CryptoSwift/NoPadding.swift; sourceTree = ""; }; - 2377C417BE96C2BABD3E1DBC9B1A9D8D /* abacus.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; lastKnownFileType = text; path = abacus.podspec; sourceTree = ""; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; 2653766C82503F31D9367F8E622F8258 /* PBKDF2.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = PBKDF2.swift; path = Sources/CryptoSwift/PKCS/PBKDF2.swift; sourceTree = ""; }; 289CF4084C8BCFCA379CCF7847F8D1F8 /* Blowfish.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Blowfish.swift; path = Sources/CryptoSwift/Blowfish.swift; sourceTree = ""; }; 295A5EFDE6FBDDF94AEC568618EF25FE /* Hashable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Hashable.swift; path = Sources/CryptoSwift/CS_BigInt/Hashable.swift; sourceTree = ""; }; @@ -206,16 +208,19 @@ 3E0AAD4392F812C1C216CCFB8F6C83F1 /* HMAC.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = HMAC.swift; path = Sources/CryptoSwift/HMAC.swift; sourceTree = ""; }; 3EA11A675218D8EC495F817C58CD7850 /* RSA+Cipher.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "RSA+Cipher.swift"; path = "Sources/CryptoSwift/RSA/RSA+Cipher.swift"; sourceTree = ""; }; 4020CF7A5E8D043073442B14C262B52D /* CBCMAC.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CBCMAC.swift; path = Sources/CryptoSwift/CBCMAC.swift; sourceTree = ""; }; + 41730F22ADD1AE7FC7AB8FCAD4AB2CDA /* abacus.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = abacus.release.xcconfig; sourceTree = ""; }; 4557C8EB08F67FA9FE5EF603C935E183 /* Cryptor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Cryptor.swift; path = Sources/CryptoSwift/Cryptor.swift; sourceTree = ""; }; 466CB280E8CD811A98491B57B02D1B46 /* AES+Foundation.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "AES+Foundation.swift"; path = "Sources/CryptoSwift/Foundation/AES+Foundation.swift"; sourceTree = ""; }; 47CAE619028B011ED98D7F74CF7215A1 /* Operators.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Operators.swift; path = Sources/CryptoSwift/Operators.swift; sourceTree = ""; }; 4B4AEBC4519953BDD492A3C4EB5E17A9 /* PKCS5.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = PKCS5.swift; path = Sources/CryptoSwift/PKCS/PKCS5.swift; sourceTree = ""; }; 4FDF19080B2A0EC139694E150432A773 /* CFB.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CFB.swift; path = Sources/CryptoSwift/BlockMode/CFB.swift; sourceTree = ""; }; + 5087F83FBB81F03CA21A553057E81A9F /* abacus-copy-dsyms.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "abacus-copy-dsyms.sh"; sourceTree = ""; }; 54D5FEEA2249C6778EA73EC5F3C75044 /* ASN1Decoder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ASN1Decoder.swift; path = Sources/CryptoSwift/ASN1/ASN1Decoder.swift; sourceTree = ""; }; 56CB7109CA84F00DCEB117080E3A0A6F /* Addition.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Addition.swift; path = Sources/CryptoSwift/CS_BigInt/Addition.swift; sourceTree = ""; }; 572CD7D8182D2BDB04D9B0AAB41FAAA2 /* Square Root.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Square Root.swift"; path = "Sources/CryptoSwift/CS_BigInt/Square Root.swift"; sourceTree = ""; }; 5825EF8E1C9D92AFC155A41AB00E7CDE /* CryptoSwift-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "CryptoSwift-umbrella.h"; sourceTree = ""; }; 58D9EEC98E8673B039471D0E403B05CD /* PCBC.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = PCBC.swift; path = Sources/CryptoSwift/BlockMode/PCBC.swift; sourceTree = ""; }; + 5ABBFD1A2A08C44870270007AE1EB5D4 /* abacus.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; lastKnownFileType = text; path = abacus.podspec; sourceTree = ""; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; 5B30BDDC9AD9C2380BF6791299AE1FBE /* Subtraction.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Subtraction.swift; path = Sources/CryptoSwift/CS_BigInt/Subtraction.swift; sourceTree = ""; }; 5E828917FF2AA3CC6B23ECF8A598B684 /* CryptoSwift.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = CryptoSwift.release.xcconfig; sourceTree = ""; }; 61300E9B71B8A176D5EAA3B605958848 /* AES.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AES.swift; path = Sources/CryptoSwift/AES.swift; sourceTree = ""; }; @@ -259,7 +264,6 @@ A7F75F1F8D19379B77CB9B5F20147724 /* Floating Point Conversion.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Floating Point Conversion.swift"; path = "Sources/CryptoSwift/CS_BigInt/Floating Point Conversion.swift"; sourceTree = ""; }; AD93A59528068EF859CFB769EA8A6B21 /* StreamEncryptor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = StreamEncryptor.swift; path = Sources/CryptoSwift/StreamEncryptor.swift; sourceTree = ""; }; AE9EA384CA84DA00859213076C156601 /* Rabbit+Foundation.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Rabbit+Foundation.swift"; path = "Sources/CryptoSwift/Foundation/Rabbit+Foundation.swift"; sourceTree = ""; }; - AF3276B68F8BA3E3862B4F55A9AB6170 /* abacus.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = abacus.release.xcconfig; sourceTree = ""; }; B008B6FE49BB00A27F5C3A8793251FA6 /* Comparable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Comparable.swift; path = Sources/CryptoSwift/CS_BigInt/Comparable.swift; sourceTree = ""; }; B08BBA0B72E8D4BE9811F9B7F68AA02C /* Blowfish+Foundation.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Blowfish+Foundation.swift"; path = "Sources/CryptoSwift/Foundation/Blowfish+Foundation.swift"; sourceTree = ""; }; B26CFCBE4B1911C8EC2A1C029CEB4E75 /* String+Extension.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "String+Extension.swift"; path = "Sources/CryptoSwift/String+Extension.swift"; sourceTree = ""; }; @@ -305,10 +309,8 @@ F270EDBD5E4E8490F69012237AFEC6CC /* BatchedCollection.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BatchedCollection.swift; path = Sources/CryptoSwift/BatchedCollection.swift; sourceTree = ""; }; F37859F4A09B4D8326D98D57FE5A1EE9 /* SecureBytes.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SecureBytes.swift; path = Sources/CryptoSwift/SecureBytes.swift; sourceTree = ""; }; F3BBCD0680550B4404763E23B515A31F /* OCB.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = OCB.swift; path = Sources/CryptoSwift/BlockMode/OCB.swift; sourceTree = ""; }; - F4B1F23DF610F8A9AD9305359993709D /* Abacus.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Abacus.framework; path = build/cocoapods/framework/Abacus.framework; sourceTree = ""; }; F702C9EC7CE0BC75E37AA56B71F7E614 /* AES.Cryptors.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AES.Cryptors.swift; path = Sources/CryptoSwift/AES.Cryptors.swift; sourceTree = ""; }; F81274EDB681F11E7CB05F7DCA2BB33C /* CryptoSwift */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = CryptoSwift; path = CryptoSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - F999223EA515513F8C78AB8A4893AAAD /* abacus.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = abacus.debug.xcconfig; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -339,33 +341,31 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 32C262F4B88173CFDD2E507FF6B0EBF1 /* Support Files */ = { + 208518205B4C2F51FC3700B9883D08D7 /* Support Files */ = { isa = PBXGroup; children = ( - F999223EA515513F8C78AB8A4893AAAD /* abacus.debug.xcconfig */, - AF3276B68F8BA3E3862B4F55A9AB6170 /* abacus.release.xcconfig */, + 5087F83FBB81F03CA21A553057E81A9F /* abacus-copy-dsyms.sh */, + 087E84600314FECE345137BBED3ABF4A /* abacus.debug.xcconfig */, + 41730F22ADD1AE7FC7AB8FCAD4AB2CDA /* abacus.release.xcconfig */, ); name = "Support Files"; path = "integration/iOS/Pods/Target Support Files/abacus"; sourceTree = ""; }; - 578452D2E740E91742655AC8F1636D1F /* iOS */ = { + 33D1C287C8CC4BF36926F2A16F33E291 /* Frameworks */ = { isa = PBXGroup; children = ( - 73010CC983E3809BECEE5348DA1BB8C6 /* Foundation.framework */, + 194377B2DFD1180A2D72988CCD50F152 /* Abacus.framework */, ); - name = iOS; + name = Frameworks; sourceTree = ""; }; - 6CE1150C1C9A0A71A18F30B35568DD8E /* abacus */ = { + 578452D2E740E91742655AC8F1636D1F /* iOS */ = { isa = PBXGroup; children = ( - C3735E810316ABDD783461EE462A964C /* Frameworks */, - 8750F5C2E47D320BFE4309AE82291B48 /* Pod */, - 32C262F4B88173CFDD2E507FF6B0EBF1 /* Support Files */, + 73010CC983E3809BECEE5348DA1BB8C6 /* Foundation.framework */, ); - name = abacus; - path = ../../..; + name = iOS; sourceTree = ""; }; 77BFA6027665EBACC42B94A7C2372871 /* Pods-abacus.iosTests */ = { @@ -384,22 +384,22 @@ path = "Target Support Files/Pods-abacus.iosTests"; sourceTree = ""; }; - 7D94CDF401128D689D2B11EDCC7ECD3A /* Products */ = { + 792A924F0CBCDCDB77BDB1651564E6F1 /* Pod */ = { isa = PBXGroup; children = ( - F81274EDB681F11E7CB05F7DCA2BB33C /* CryptoSwift */, - A1CCAE37318C6C914C8FE00E0C942B07 /* Pods-abacus.ios */, - E216D81D3656F56D4335CA097BDACFA9 /* Pods-abacus.iosTests */, + 5ABBFD1A2A08C44870270007AE1EB5D4 /* abacus.podspec */, ); - name = Products; + name = Pod; sourceTree = ""; }; - 8750F5C2E47D320BFE4309AE82291B48 /* Pod */ = { + 7D94CDF401128D689D2B11EDCC7ECD3A /* Products */ = { isa = PBXGroup; children = ( - 2377C417BE96C2BABD3E1DBC9B1A9D8D /* abacus.podspec */, + F81274EDB681F11E7CB05F7DCA2BB33C /* CryptoSwift */, + A1CCAE37318C6C914C8FE00E0C942B07 /* Pods-abacus.ios */, + E216D81D3656F56D4335CA097BDACFA9 /* Pods-abacus.iosTests */, ); - name = Pod; + name = Products; sourceTree = ""; }; 915C28C583640C124E56AE6BB9F0DAB2 /* CryptoSwift */ = { @@ -524,7 +524,7 @@ A5DC2D324221A781AC0E7A1B4908E26D /* Development Pods */ = { isa = PBXGroup; children = ( - 6CE1150C1C9A0A71A18F30B35568DD8E /* abacus */, + F896DEEFEA08BDAEEA48FBC9E29423FF /* abacus */, ); name = "Development Pods"; sourceTree = ""; @@ -555,14 +555,6 @@ name = "Targets Support Files"; sourceTree = ""; }; - C3735E810316ABDD783461EE462A964C /* Frameworks */ = { - isa = PBXGroup; - children = ( - F4B1F23DF610F8A9AD9305359993709D /* Abacus.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; CF1408CF629C7361332E53B88F7BD30C = { isa = PBXGroup; children = ( @@ -606,6 +598,17 @@ path = "../Target Support Files/CryptoSwift"; sourceTree = ""; }; + F896DEEFEA08BDAEEA48FBC9E29423FF /* abacus */ = { + isa = PBXGroup; + children = ( + 33D1C287C8CC4BF36926F2A16F33E291 /* Frameworks */, + 792A924F0CBCDCDB77BDB1651564E6F1 /* Pod */, + 208518205B4C2F51FC3700B9883D08D7 /* Support Files */, + ); + name = abacus; + path = ../../..; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -668,7 +671,7 @@ buildRules = ( ); dependencies = ( - AC2EBD83354400DAA47E7F9069901F4A /* PBXTargetDependency */, + 1F67D19EAC36A41088EB458D7432B293 /* PBXTargetDependency */, ); name = "Pods-abacus.iosTests"; productName = Pods_abacus_iosTests; @@ -748,7 +751,24 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 1403E0AEBF8A2087DF8CACA188B609E8 /* [CP-User] Build abacus */ = { + 903ECA29140368245E4B69BE2D0DBCA4 /* [CP] Copy dSYMs */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/abacus/abacus-copy-dsyms-input-files.xcfilelist", + ); + name = "[CP] Copy dSYMs"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/abacus/abacus-copy-dsyms-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/abacus/abacus-copy-dsyms.sh\"\n"; + showEnvVarsInLog = 0; + }; + DB36ACE8A857E9B93C25EA309FF42C2A /* [CP-User] Build abacus */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -898,11 +918,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - AC2EBD83354400DAA47E7F9069901F4A /* PBXTargetDependency */ = { + 1F67D19EAC36A41088EB458D7432B293 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = "Pods-abacus.ios"; target = 469F25E790D19440507BF938A40578A7 /* Pods-abacus.ios */; - targetProxy = F99C8B5A9E9146040BECE7ABFD8E196E /* PBXContainerItemProxy */; + targetProxy = 174DE097054DE7A0410C57AF4AEF5A1A /* PBXContainerItemProxy */; }; CF848ADCA9D462971E67CA041BBB55DD /* PBXTargetDependency */ = { isa = PBXTargetDependency; @@ -956,23 +976,6 @@ }; name = Debug; }; - 22B0388248EDA44270493DDECC58CF78 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = AF3276B68F8BA3E3862B4F55A9AB6170 /* abacus.release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; 4C06C857647A16E5CF368D22DFA55BAF /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 0ECEA0D8830DCE37C7297C5F1342B08E /* Pods-abacus.ios.release.xcconfig */; @@ -1046,22 +1049,6 @@ }; name = Debug; }; - 5B331F72A7F4402311EBC4A06282E60D /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = F999223EA515513F8C78AB8A4893AAAD /* abacus.debug.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; 5D1E4AA7093DAD0F254B24C6406C303C /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7996373D44C9A5554EA9135984BC01DB /* Pods-abacus.iosTests.release.xcconfig */; @@ -1228,6 +1215,39 @@ }; name = Debug; }; + 96B05028328C5704FCE6A41A515364C0 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 087E84600314FECE345137BBED3ABF4A /* abacus.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + B0264023D64B16BF2F6C8B4C076E862A /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 41730F22ADD1AE7FC7AB8FCAD4AB2CDA /* abacus.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; F3A0E769E09875E7B65318E499E93FAC /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 5E828917FF2AA3CC6B23ECF8A598B684 /* CryptoSwift.release.xcconfig */; @@ -1304,29 +1324,29 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 2ECD87CECC0DF0128DB6EE0BDC2D9E8A /* Build configuration list for PBXNativeTarget "CryptoSwift" */ = { + 127F9C06E33E9DC2AB3876F089264EB1 /* Build configuration list for PBXAggregateTarget "abacus" */ = { isa = XCConfigurationList; buildConfigurations = ( - 4F6792E687410AB2E5E862AE7B821068 /* Debug */, - F3A0E769E09875E7B65318E499E93FAC /* Release */, + 96B05028328C5704FCE6A41A515364C0 /* Debug */, + B0264023D64B16BF2F6C8B4C076E862A /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */ = { + 2ECD87CECC0DF0128DB6EE0BDC2D9E8A /* Build configuration list for PBXNativeTarget "CryptoSwift" */ = { isa = XCConfigurationList; buildConfigurations = ( - 934ED2B84836A780113D1F63484628B2 /* Debug */, - 92486E5E72E54FAF60E1A7D022C21B10 /* Release */, + 4F6792E687410AB2E5E862AE7B821068 /* Debug */, + F3A0E769E09875E7B65318E499E93FAC /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - B36D4FC54F44832C64D6BFCFC7EF4665 /* Build configuration list for PBXAggregateTarget "abacus" */ = { + 4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */ = { isa = XCConfigurationList; buildConfigurations = ( - 5B331F72A7F4402311EBC4A06282E60D /* Debug */, - 22B0388248EDA44270493DDECC58CF78 /* Release */, + 934ED2B84836A780113D1F63484628B2 /* Debug */, + 92486E5E72E54FAF60E1A7D022C21B10 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/calculator/AdjustIsolatedMarginInputCalculator.kt b/src/commonMain/kotlin/exchange.dydx.abacus/calculator/AdjustIsolatedMarginInputCalculator.kt index ac4ddd73a..fb38ded29 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/calculator/AdjustIsolatedMarginInputCalculator.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/calculator/AdjustIsolatedMarginInputCalculator.kt @@ -98,7 +98,7 @@ internal class AdjustIsolatedMarginInputCalculator(val parser: ParserProtocol) { val crossMarginUsage = parentSubaccount?.get("marginUsage") val openPositions = parser.asNativeMap(childSubaccount?.get("openPositions")) val marketId = openPositions?.keys?.firstOrNull() - val positionMargin = childSubaccount?.get("freeCollateral") + val positionMargin = childSubaccount?.get("equity") val positionLeverage = parser.value(childSubaccount, "openPositions.$marketId.leverage") val liquidationPrice = parser.value(childSubaccount, "openPositions.$marketId.liquidationPrice") diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/calculator/TradeInputCalculator.kt b/src/commonMain/kotlin/exchange.dydx.abacus/calculator/TradeInputCalculator.kt index b8fc25285..723f793ce 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/calculator/TradeInputCalculator.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/calculator/TradeInputCalculator.kt @@ -8,6 +8,7 @@ import exchange.dydx.abacus.calculator.SlippageConstants.STOP_MARKET_ORDER_SLIPP import exchange.dydx.abacus.calculator.SlippageConstants.STOP_MARKET_ORDER_SLIPPAGE_BUFFER_MAJOR_MARKET import exchange.dydx.abacus.calculator.SlippageConstants.TAKE_PROFIT_MARKET_ORDER_SLIPPAGE_BUFFER import exchange.dydx.abacus.calculator.SlippageConstants.TAKE_PROFIT_MARKET_ORDER_SLIPPAGE_BUFFER_MAJOR_MARKET +import exchange.dydx.abacus.output.input.MarginMode import exchange.dydx.abacus.protocols.ParserProtocol import exchange.dydx.abacus.state.manager.EnvironmentFeatureFlags import exchange.dydx.abacus.utils.Numeric @@ -415,12 +416,9 @@ internal class TradeInputCalculator( when (marginMode) { "ISOLATED" -> { // TODO: When the collateral of child subaccounts is implemented, return from here. The below code is the CROSS implementation. - val currentNotionalTotal = - parser.asDouble(parser.value(position, "notionalTotal.current")) - val postOrderNotionalTotal = - parser.asDouble(parser.value(position, "notionalTotal.postOrder")) - val mmf = - parser.asDouble(parser.value(market, "configs.maintenanceMarginFraction")) + val currentNotionalTotal = parser.asDouble(parser.value(position, "notionalTotal.current")) + val postOrderNotionalTotal = parser.asDouble(parser.value(position, "notionalTotal.postOrder")) + val mmf = parser.asDouble(parser.value(market, "configs.maintenanceMarginFraction")) if (currentNotionalTotal != null && mmf != null) { if (postOrderNotionalTotal != null) { return postOrderNotionalTotal.times(mmf) @@ -824,7 +822,7 @@ internal class TradeInputCalculator( @Suppress("LocalVariableName", "PropertyName") val X = ((LV * AE) - (SZ * OR)) / - (OR + (OS * LV * MP * FR) - (LV * (OR - MP))) + (OR + (OS * LV * MP * FR) - (LV * (OR - MP))) val desiredSize = X.abs() if (desiredSize < entrySize) { val rounded = this.rounded(sizeTotal, desiredSize, stepSize) @@ -926,14 +924,24 @@ internal class TradeInputCalculator( ): List? { val type = parser.asString(trade["type"]) return when (type) { - "MARKET" -> - listOf( - sizeField(), - leverageField(), - bracketsField(), - marginModeField(market, account, subaccount), - reduceOnlyField(), - ).filterNotNull() + "MARKET" -> { + val marginMode = parser.asString(trade["marginMode"]) + return when (MarginMode.invoke(marginMode)) { + MarginMode.isolated -> listOf( + sizeField(), + bracketsField(), + marginModeField(market, account, subaccount), + reduceOnlyField(), + ).filterNotNull() + else -> listOf( + sizeField(), + leverageField(), + bracketsField(), + marginModeField(market, account, subaccount), + reduceOnlyField(), + ).filterNotNull() + } + } "LIMIT" -> { val timeInForce = parser.asString(trade["timeInForce"]) @@ -969,7 +977,7 @@ internal class TradeInputCalculator( executionField(true), marginModeField(market, account, subaccount), when (execution) { - "FOK", "IOC" -> reduceOnlyField() + "IOC" -> reduceOnlyField() else -> null }, ).filterNotNull() @@ -1069,10 +1077,10 @@ internal class TradeInputCalculator( return mapOf( "field" to "stopLoss", "type" to - listOf( - priceField(), - reduceOnlyField(), - ).filterNotNull(), + listOf( + priceField(), + reduceOnlyField(), + ).filterNotNull(), ) } @@ -1080,10 +1088,10 @@ internal class TradeInputCalculator( return mapOf( "field" to "takeProfit", "type" to - listOf( - priceField(), - reduceOnlyField(), - ).filterNotNull(), + listOf( + priceField(), + reduceOnlyField(), + ).filterNotNull(), ) } @@ -1101,7 +1109,6 @@ internal class TradeInputCalculator( "options" to listOf( timeInForceOptionGTT, timeInForceOptionIOC, - timeInForceOptionFOK, ), ) } @@ -1141,19 +1148,17 @@ internal class TradeInputCalculator( "field" to "execution", "type" to "string", "options" to - if (includesDefaultAndPostOnly) { - listOf( - executionDefault, - executionIOC, - executionFOK, - executionPostOnly, - ) - } else { - listOf( - executionIOC, - executionFOK, - ) - }, + if (includesDefaultAndPostOnly) { + listOf( + executionDefault, + executionIOC, + executionPostOnly, + ) + } else { + listOf( + executionIOC, + ) + }, ) } @@ -1297,9 +1302,9 @@ internal class TradeInputCalculator( ): String? { return if (featureFlags.reduceOnlySupported) { when (parser.asString(trade["type"])) { - "LIMIT" -> "GENERAL.TRADE.REDUCE_ONLY_TIMEINFORCE_IOC_FOK" + "LIMIT" -> "GENERAL.TRADE.REDUCE_ONLY_TIMEINFORCE_IOC" - "STOP_LIMIT", "TAKE_PROFIT" -> "GENERAL.TRADE.REDUCE_ONLY_EXECUTION_IOC_FOK" + "STOP_LIMIT", "TAKE_PROFIT" -> "GENERAL.TRADE.REDUCE_ONLY_TIMEINFORCE_IOC" else -> return null } @@ -1462,10 +1467,10 @@ internal class TradeInputCalculator( ) { val feeMultiplier = feeMultiplierPpm / QUANTUM_MULTIPLIER return feeMultiplier * (fee - maxMakerRebate * notional) / ( - tokenPrice * 10.0.pow( - tokenPriceExponent, - ) - ) + tokenPrice * 10.0.pow( + tokenPriceExponent, + ) + ) } return null } @@ -1536,11 +1541,11 @@ internal class TradeInputCalculator( val total = if (usdcSize != null) { ( - usdcSize * multiplier + ( - fee - ?: Numeric.double.ZERO - ) * Numeric.double.NEGATIVE - ) + usdcSize * multiplier + ( + fee + ?: Numeric.double.ZERO + ) * Numeric.double.NEGATIVE + ) } else { null } @@ -1669,11 +1674,11 @@ internal class TradeInputCalculator( val total = if (usdcSize != null) { ( - usdcSize * multiplier + ( - fee - ?: Numeric.double.ZERO - ) * Numeric.double.NEGATIVE - ) + usdcSize * multiplier + ( + fee + ?: Numeric.double.ZERO + ) * Numeric.double.NEGATIVE + ) } else { null } @@ -1726,11 +1731,11 @@ internal class TradeInputCalculator( val total = if (usdcSize != null) { ( - usdcSize * multiplier + ( - fee - ?: Numeric.double.ZERO - ) * Numeric.double.NEGATIVE - ) + usdcSize * multiplier + ( + fee + ?: Numeric.double.ZERO + ) * Numeric.double.NEGATIVE + ) } else { null } @@ -1846,8 +1851,6 @@ internal class TradeInputCalculator( private val timeInForceOptionGTT: Map get() = mapOf("type" to "GTT", "stringKey" to "APP.TRADE.GOOD_TIL_TIME") - private val timeInForceOptionFOK: Map - get() = mapOf("type" to "FOK", "stringKey" to "APP.TRADE.FILL_OR_KILL") private val timeInForceOptionIOC: Map get() = mapOf("type" to "IOC", "stringKey" to "APP.TRADE.IMMEDIATE_OR_CANCEL") @@ -1876,11 +1879,8 @@ internal class TradeInputCalculator( get() = mapOf("type" to "DEFAULT", "stringKey" to "APP.TRADE.GOOD_TIL_DATE") private val executionPostOnly: Map get() = mapOf("type" to "POST_ONLY", "stringKey" to "APP.TRADE.POST_ONLY") - private val executionFOK: Map - get() = mapOf("type" to "FOK", "stringKey" to "APP.TRADE.FILL_OR_KILL") private val executionIOC: Map get() = mapOf("type" to "IOC", "stringKey" to "APP.TRADE.IMMEDIATE_OR_CANCEL") - private val marginModeCross: Map get() = mapOf("type" to "CROSS", "stringKey" to "APP.TRADE.CROSS_MARGIN") private val marginModeIsolated: Map diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/output/Account.kt b/src/commonMain/kotlin/exchange.dydx.abacus/output/Account.kt index f010bb6de..4e626fbc9 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/output/Account.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/output/Account.kt @@ -1,5 +1,6 @@ package exchange.dydx.abacus.output +import exchange.dydx.abacus.output.input.MarginMode import exchange.dydx.abacus.output.input.OrderSide import exchange.dydx.abacus.output.input.OrderStatus import exchange.dydx.abacus.output.input.OrderTimeInForce @@ -255,6 +256,7 @@ data class SubaccountPosition( val marginUsage: TradeStatesWithDoubleValues, val quoteBalance: TradeStatesWithDoubleValues, // available for isolated market position val equity: TradeStatesWithDoubleValues, // available for isolated market position + val marginMode: MarginMode? ) { companion object { internal fun create( @@ -383,6 +385,7 @@ data class SubaccountPosition( parser, parser.asMap(data["equity"]), ) + val marginMode = parser.asString(data["marginMode"])?.let { MarginMode.invoke(it) } return if (existing?.id != id || existing.assetId != assetId || @@ -410,7 +413,8 @@ data class SubaccountPosition( existing.freeCollateral !== freeCollateral || existing.marginUsage !== marginUsage || existing.quoteBalance !== quoteBalance || - existing.equity !== equity + existing.equity !== equity || + existing.marginMode != marginMode ) { val side = positionSide(size) SubaccountPosition( @@ -442,6 +446,7 @@ data class SubaccountPosition( marginUsage, quoteBalance, equity, + marginMode, ) } else { existing @@ -662,6 +667,7 @@ data class SubaccountOrder( val reduceOnly: Boolean, val cancelReason: String?, val resources: SubaccountOrderResources, + val marginMode: MarginMode? ) { companion object { internal fun create( @@ -695,6 +701,7 @@ data class SubaccountOrder( val resources = parser.asMap(data["resources"])?.let { SubaccountOrderResources.create(existing?.resources, parser, it, localizer) } + val marginMode = parser.asString(data["marginMode"])?.let { MarginMode.invoke(it) } if (id != null && marketId != null && type != null && side != null && status != null && price != null && size != null && resources != null ) { @@ -744,7 +751,9 @@ data class SubaccountOrder( existing.postOnly != postOnly || existing.reduceOnly != reduceOnly || existing.cancelReason != cancelReason || - existing.resources !== resources + existing.resources !== resources || + existing.subaccountNumber != subaccountNumber || + existing.marginMode != marginMode ) { SubaccountOrder( subaccountNumber, @@ -774,6 +783,7 @@ data class SubaccountOrder( reduceOnly, cancelReason, resources, + marginMode, ) } else { existing @@ -1567,6 +1577,38 @@ data class AccountBalance( } } +@JsExport +@Serializable +data class StakingDelegation( + var validator: String, + var amount: String, +) { + companion object { + internal fun create( + existing: StakingDelegation?, + parser: ParserProtocol, + data: Map, + decimals: Int, + ): StakingDelegation? { + Logger.d { "creating Staking Delegation\n" } + + val validator = parser.asString(data["validator"]) + val amount = parser.asDecimal(data["amount"]) + if (validator != null && amount != null) { + val decimalAmount = amount * Numeric.decimal.TEN.pow(-1 * decimals) + val decimalAmountString = parser.asString(decimalAmount)!! + return if (existing?.validator != validator || existing.amount != decimalAmountString) { + StakingDelegation(validator, decimalAmountString) + } else { + existing + } + } + Logger.d { "Staking Delegation not valid" } + return null + } + } +} + @JsExport @Serializable data class HistoricalTradingReward( @@ -2020,6 +2062,7 @@ data class TradingRewards( data class Account( var balances: IMap?, var stakingBalances: IMap?, + var stakingDelegations: IList?, var subaccounts: IMap?, var groupedSubaccounts: IMap?, var tradingRewards: TradingRewards?, @@ -2057,26 +2100,20 @@ data class Account( } val stakingBalances: IMutableMap = - iMutableMapOf() - val stakingBalancesData = parser.asMap(data["stakingBalances"]) - if (stakingBalancesData != null) { - for ((key, value) in stakingBalancesData) { - // key is the denom - // It should be chain token denom here - val tokenInfo = findTokenInfo(tokensInfo, key) - if (tokenInfo != null) { - val balanceData = parser.asMap(value) ?: iMapOf() - AccountBalance.create( - existing?.stakingBalances?.get(key), - parser, - balanceData, - tokenInfo.decimals, - )?.let { balance -> - stakingBalances[key] = balance - } - } - } - } + processStakingBalance( + existing, + parser, + data, + tokensInfo, + ) + + val stakingDelegations: IMutableList = + processStakingDelegations( + existing, + parser, + data, + tokensInfo, + ) val tradingRewardsData = parser.asMap(data["tradingRewards"]) val tradingRewards = if (tradingRewardsData != null) { @@ -2132,6 +2169,7 @@ data class Account( return Account( balances, stakingBalances, + stakingDelegations, subaccounts, groupedSubaccounts, tradingRewards, @@ -2142,5 +2180,61 @@ data class Account( private fun findTokenInfo(tokensInfo: Map, denom: String): TokenInfo? { return tokensInfo.firstNotNullOfOrNull { if (it.value.denom == denom) it.value else null } } + + private fun processStakingDelegations( + existing: Account?, + parser: ParserProtocol, + data: Map, + tokensInfo: Map, + ): IMutableList { + val stakingDelegations: IMutableList = + iMutableListOf() + val stakingDelegationsData = parser.asList(data["stakingDelegations"]) + stakingDelegationsData?.forEachIndexed { index, value -> + val stakingDelegationData = parser.asMap(value) ?: iMapOf() + val tokenInfo = findTokenInfo(tokensInfo, stakingDelegationData["denom"] as String) + if (tokenInfo != null) { + StakingDelegation.create( + existing?.stakingDelegations?.getOrNull(index), + parser, + stakingDelegationData, + tokenInfo.decimals, + )?.let { stakingDelegation -> + stakingDelegations.add(stakingDelegation) + } + } + } + return stakingDelegations + } + + private fun processStakingBalance( + existing: Account?, + parser: ParserProtocol, + data: Map, + tokensInfo: Map, + ): IMutableMap { + val stakingBalances: IMutableMap = + iMutableMapOf() + val stakingBalancesData = parser.asMap(data["stakingBalances"]) + if (stakingBalancesData != null) { + for ((key, value) in stakingBalancesData) { + // key is the denom + // It should be chain token denom here + val tokenInfo = findTokenInfo(tokensInfo, key) + if (tokenInfo != null) { + val balanceData = parser.asMap(value) ?: iMapOf() + AccountBalance.create( + existing?.stakingBalances?.get(key), + parser, + balanceData, + tokenInfo.decimals, + )?.let { balance -> + stakingBalances[key] = balance + } + } + } + } + return stakingBalances + } } } diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/output/Configs.kt b/src/commonMain/kotlin/exchange.dydx.abacus/output/Configs.kt index 3a5077234..bfcd7e2bd 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/output/Configs.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/output/Configs.kt @@ -6,6 +6,7 @@ import exchange.dydx.abacus.protocols.ParserProtocol import exchange.dydx.abacus.utils.IList import exchange.dydx.abacus.utils.Logger import kollections.JsExport +import kollections.iListOf import kollections.iMutableListOf import kotlinx.serialization.Serializable @@ -247,6 +248,103 @@ data class FeeTier( } } +@JsExport +@Serializable +data class EquityTiers( + val shortTermOrderEquityTiers: IList, + val statefulOrderEquityTiers: IList, +) { + companion object { + internal fun create( + existing: EquityTiers?, + parser: ParserProtocol, + data: Map<*, *>?, + ): EquityTiers? { + data?.let { + val shortTermOrderEquityTiers = parser.asNativeList(data["shortTermOrderEquityTiers"])?.let { tiers -> + create(existing?.shortTermOrderEquityTiers, parser, tiers) + } ?: iListOf() + + val statefulOrderEquityTiers = parser.asNativeList(data["statefulOrderEquityTiers"])?.let { tiers -> + create(existing?.statefulOrderEquityTiers, parser, tiers) + } ?: iListOf() + + return EquityTiers(shortTermOrderEquityTiers, statefulOrderEquityTiers) + } + + Logger.d { "Equity Tiers not valid" } + return null + } + + internal fun create( + existing: IList?, + parser: ParserProtocol, + data: List<*>? + ): IList? { + data?.let { + val equityTiers = iMutableListOf() + for (i in data.indices) { + val item = data[i] + val nextItem = data.getOrNull(i + 1) + parser.asMap(item)?.let { + val tier = existing?.getOrNull(i) + val nextTierData = parser.asMap(nextItem) + EquityTier.create(tier, parser, it, nextTierData)?.let { equityTier -> + equityTiers.add(equityTier) + } + } + } + return equityTiers + } + Logger.d { "Equity Tiers not valid" } + return null + } + } +} + +@JsExport +@Serializable +data class EquityTier( + val requiredTotalNetCollateralUSD: Double, + val nextLevelRequiredTotalNetCollateralUSD: Double?, + val maxOrders: Int, +) { + companion object { + internal fun create( + existing: EquityTier?, + parser: ParserProtocol, + data: Map<*, *>?, + nextTierData: Map<*, *>? + ): EquityTier? { + data?.let { + val requiredTotalNetCollateralUSD = parser.asDouble(data["requiredTotalNetCollateralUSD"]) + val nextLevelRequiredTotalNetCollateralUSD = nextTierData?.let { + parser.asDouble(nextTierData["requiredTotalNetCollateralUSD"]) + } + val maxOrders = parser.asInt(data["maxOrders"]) + + if (requiredTotalNetCollateralUSD != null && maxOrders != null) { + return if ( + existing?.requiredTotalNetCollateralUSD != requiredTotalNetCollateralUSD || + existing?.nextLevelRequiredTotalNetCollateralUSD != nextLevelRequiredTotalNetCollateralUSD || + existing?.maxOrders != maxOrders + ) { + EquityTier( + requiredTotalNetCollateralUSD, + nextLevelRequiredTotalNetCollateralUSD, + maxOrders, + ) + } else { + existing + } + } + } + Logger.d { "Equity Tier not valid" } + return null + } + } +} + @JsExport @Serializable data class WithdrawalGating( @@ -346,6 +444,7 @@ data class Configs( val network: NetworkConfigs?, val feeTiers: IList?, val feeDiscounts: IList?, + val equityTiers: EquityTiers?, val withdrawalGating: WithdrawalGating?, val withdrawalCapacity: WithdrawalCapacity?, ) { @@ -371,6 +470,11 @@ data class Configs( parser.asList(data["feeDiscounts"]), localizer, ) + val equityTiers = EquityTiers.create( + existing?.equityTiers, + parser, + parser.asMap(data["equityTiers"]), + ) var withdrawalGating = WithdrawalGating.create( existing?.withdrawalGating, parser, @@ -383,12 +487,14 @@ data class Configs( ) return if (existing?.network !== network || existing?.feeTiers != feeTiers || - existing?.feeDiscounts != feeDiscounts + existing?.feeDiscounts != feeDiscounts || + existing?.equityTiers != equityTiers ) { Configs( network, feeTiers, feeDiscounts, + equityTiers, withdrawalGating, withdrawalCapacity, ) @@ -399,6 +505,7 @@ data class Configs( null, null, null, + null, ) } } diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/output/input/Input.kt b/src/commonMain/kotlin/exchange.dydx.abacus/output/input/Input.kt index c3695b813..c66e75095 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/output/input/Input.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/output/input/Input.kt @@ -1,6 +1,7 @@ package exchange.dydx.abacus.output.input import exchange.dydx.abacus.protocols.ParserProtocol +import exchange.dydx.abacus.state.internalstate.InternalState import exchange.dydx.abacus.state.manager.V4Environment import exchange.dydx.abacus.utils.IList import exchange.dydx.abacus.utils.Logger @@ -39,7 +40,8 @@ data class Input( existing: Input?, parser: ParserProtocol, data: Map<*, *>?, - environment: V4Environment? + environment: V4Environment?, + internalState: InternalState? ): Input? { Logger.d { "creating Input\n" } @@ -50,7 +52,7 @@ data class Input( val closePosition = ClosePositionInput.create(existing?.closePosition, parser, parser.asMap(data["closePosition"])) val transfer = - TransferInput.create(existing?.transfer, parser, parser.asMap(data["transfer"]), environment) + TransferInput.create(existing?.transfer, parser, parser.asMap(data["transfer"]), environment, internalState?.transfer) val triggerOrders = TriggerOrdersInput.create(existing?.triggerOrders, parser, parser.asMap(data["triggerOrders"])) val adjustIsolatedMargin = 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 d558bdf88..568322a29 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/output/input/TradeInput.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/output/input/TradeInput.kt @@ -721,8 +721,7 @@ enum class OrderStatus(val rawValue: String) { @Serializable enum class OrderTimeInForce(val rawValue: String) { GTT("GTT"), - IOC("IOC"), - FOK("FOK"); + IOC("IOC"); companion object { operator fun invoke(rawValue: String) = diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/output/input/TransferInput.kt b/src/commonMain/kotlin/exchange.dydx.abacus/output/input/TransferInput.kt index 8e14b5cba..5c49f6dd4 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/output/input/TransferInput.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/output/input/TransferInput.kt @@ -1,6 +1,7 @@ package exchange.dydx.abacus.output.input import exchange.dydx.abacus.protocols.ParserProtocol +import exchange.dydx.abacus.state.internalstate.InternalTransferInputState import exchange.dydx.abacus.state.manager.CctpConfig.cctpChainIds import exchange.dydx.abacus.state.manager.ExchangeConfig.exchangeList import exchange.dydx.abacus.state.manager.V4Environment @@ -30,7 +31,8 @@ data class DepositInputOptions( internal fun create( existing: DepositInputOptions?, parser: ParserProtocol, - data: Map<*, *>? + data: Map<*, *>?, + internalState: InternalTransferInputState? ): DepositInputOptions? { Logger.d { "creating Deposit Input Options\n" } @@ -39,35 +41,9 @@ data class DepositInputOptions( val needsAddress = parser.asBool(data["needsAddress"]) val needsFastSpeed = parser.asBool(data["needsFastSpeed"]) - var chains: IMutableList? = null - parser.asList(data["chains"])?.let { data -> - chains = iMutableListOf() - for (i in data.indices) { - val item = data[i] - SelectionOption.create( - existing?.chains?.getOrNull(i), - parser, - parser.asMap(item), - )?.let { - chains?.add(it) - } - } - } + val chains: IList? = internalState?.chains?.toIList() ?: iListOf() - var assets: IMutableList? = null - parser.asList(data["assets"])?.let { data -> - assets = iMutableListOf() - for (i in data.indices) { - val item = data[i] - SelectionOption.create( - existing?.assets?.getOrNull(i), - parser, - parser.asMap(item), - )?.let { - assets?.add(it) - } - } - } + val assets: IList? = internalState?.tokens?.toIList() ?: iListOf() var exchanges: IMutableList? = null exchangeList?.let { data -> @@ -118,7 +94,8 @@ data class WithdrawalInputOptions( internal fun create( existing: WithdrawalInputOptions?, parser: ParserProtocol, - data: Map<*, *>? + data: Map<*, *>?, + internalState: InternalTransferInputState?, ): WithdrawalInputOptions? { Logger.d { "creating Withdrawal Input Options\n" } @@ -127,34 +104,9 @@ data class WithdrawalInputOptions( val needsAddress = parser.asBool(data["needsAddress"]) val needsFastSpeed = parser.asBool(data["needsFastSpeed"]) - var chains: IMutableList? = null - parser.asList(data["chains"])?.let { data -> - chains = iMutableListOf() - for (i in data.indices) { - val item = data[i] - SelectionOption.create( - existing?.chains?.getOrNull(i), - parser, - parser.asMap(item), - )?.let { - chains?.add(it) - } - } - } - var assets: IMutableList? = null - parser.asList(data["assets"])?.let { data -> - assets = iMutableListOf() - for (i in data.indices) { - val item = data[i] - SelectionOption.create( - existing?.assets?.getOrNull(i), - parser, - parser.asMap(item), - )?.let { - assets?.add(it) - } - } - } + val chains: IList? = internalState?.chains?.toIList() ?: iListOf() + + val assets: IList? = internalState?.tokens?.toIList() ?: iListOf() var exchanges: IMutableList? = null exchangeList?.let { data -> @@ -197,7 +149,7 @@ data class TransferOutInputOptions( val needsSize: Boolean?, val needsAddress: Boolean?, val chains: IList?, - val assets: IList? + val assets: IList?, ) { companion object { internal fun create( @@ -256,48 +208,11 @@ data class TransferOutInputOptions( @Serializable data class TransferInputChainResource( val chainName: String?, - val rpc: String?, - val networkName: String?, + val rpc: String? = null, + val networkName: String? = null, val chainId: Int?, val iconUrl: String? -) { - companion object { - internal fun create( - existing: TransferInputChainResource?, - parser: ParserProtocol, - data: Map<*, *>? - ): TransferInputChainResource? { - Logger.d { "creating Transfer Input Chain Resource\n" } - - data?.let { - val chainName = parser.asString(data["chainName"]) - val rpc = parser.asString(data["rpc"]) - val networkName = parser.asString(data["networkName"]) - val chainId = parser.asInt(data["chainId"]) - val iconUrl = parser.asString(data["iconUrl"]) - - return if (existing?.chainName != chainName || - existing?.rpc != rpc || - existing?.networkName != networkName || - existing?.chainId != chainId || - existing?.iconUrl != iconUrl - ) { - TransferInputChainResource( - chainName, - rpc, - networkName, - chainId, - iconUrl, - ) - } else { - existing - } - } - Logger.d { "Transfer Input Chain Resource not valid" } - return null - } - } -} +) @JsExport @Serializable @@ -307,44 +222,7 @@ data class TransferInputTokenResource( var symbol: String?, var decimals: Int?, var iconUrl: String? -) { - companion object { - internal fun create( - existing: TransferInputTokenResource?, - parser: ParserProtocol, - data: Map<*, *>? - ): TransferInputTokenResource? { - Logger.d { "creating Transfer Input Token Resource\n" } - - data?.let { - val name = parser.asString(data["name"]) - val address = parser.asString(data["address"]) - val symbol = parser.asString(data["symbol"]) - val decimals = parser.asInt(data["decimals"]) - val iconUrl = parser.asString(data["iconUrl"]) - - return if (existing?.name != name || - existing?.address != address || - existing?.symbol != symbol || - existing?.decimals != decimals || - existing?.iconUrl != iconUrl - ) { - TransferInputTokenResource( - name, - address, - symbol, - decimals, - iconUrl, - ) - } else { - existing - } - } - Logger.d { "Transfer Input Token Resource not valid" } - return null - } - } -} +) @JsExport @Serializable @@ -355,32 +233,13 @@ data class TransferInputResources( companion object { internal fun create( existing: TransferInputResources?, - parser: ParserProtocol, - data: Map<*, *>? + internalState: InternalTransferInputState?, ): TransferInputResources? { Logger.d { "creating Transfer Input Resources\n" } - data?.let { - val chainResourcesMap = parser.asMap(data["chainResources"]) - val chainResources: IMap = - chainResourcesMap?.mapValues { entry -> - TransferInputChainResource.create( - null, - parser, - parser.asMap(entry.value), - ) ?: TransferInputChainResource(null, null, null, null, null) - }?.toIMap() ?: iMapOf() - - val tokenResourcesMap = parser.asMap(data["tokenResources"]) - val tokenResources: IMap = - tokenResourcesMap?.mapValues { - TransferInputTokenResource.create( - null, - parser, - parser.asMap(it.value), - ) ?: TransferInputTokenResource(null, null, null, null, null) - }?.toIMap() ?: iMapOf() - + internalState?.let { + val chainResources: IMap = internalState.chainResources?.toIMap() ?: iMapOf() + val tokenResources: IMap = internalState.tokenResources?.toIMap() ?: iMapOf() return if ( existing?.chainResources != chainResources || existing.tokenResources != tokenResources @@ -615,6 +474,7 @@ data class TransferInput( val chain: String?, val token: String?, val address: String?, + val memo: String?, val depositOptions: DepositInputOptions?, val withdrawalOptions: WithdrawalInputOptions?, val transferOutOptions: TransferOutInputOptions?, @@ -632,7 +492,8 @@ data class TransferInput( existing: TransferInput?, parser: ParserProtocol, data: Map<*, *>?, - environment: V4Environment? + environment: V4Environment?, + internalState: InternalTransferInputState? ): TransferInput? { Logger.d { "creating Transfer Input\n" } @@ -649,6 +510,7 @@ data class TransferInput( val chain = parser.asString(data["chain"]) val token = parser.asString(data["token"]) val address = parser.asString(data["address"]) + val memo = parser.asString(data["memo"]) var depositOptions: DepositInputOptions? = null if (type == TransferType.deposit) { @@ -656,6 +518,7 @@ data class TransferInput( existing?.depositOptions, parser, parser.asMap(data["depositOptions"]), + internalState, ) } @@ -665,6 +528,7 @@ data class TransferInput( existing?.withdrawalOptions, parser, parser.asMap(data["withdrawalOptions"]), + internalState, ) } @@ -686,8 +550,7 @@ data class TransferInput( val resources = TransferInputResources.create( existing?.resources, - parser, - parser.asMap(data["resources"]), + internalState, ) val route = parser.asMap(data["route"]) @@ -716,6 +579,7 @@ data class TransferInput( existing.chain != chain || existing.token != token || existing.address != address || + existing.memo != memo || existing.depositOptions != depositOptions || existing.withdrawalOptions != withdrawalOptions || existing.transferOutOptions != transferOutOptions || @@ -734,6 +598,7 @@ data class TransferInput( chain, token, address, + memo, depositOptions, withdrawalOptions, transferOutOptions, diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/IRouterProcessor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/IRouterProcessor.kt new file mode 100644 index 000000000..8e8d48f5a --- /dev/null +++ b/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/IRouterProcessor.kt @@ -0,0 +1,51 @@ +package exchange.dydx.abacus.processor.router + +interface IRouterProcessor { + var tokens: List? + var chains: List? + var exchangeDestinationChainId: String? + fun receivedChains( + existing: Map?, + payload: Map + ): Map? + + fun receivedTokens( + existing: Map?, + payload: Map + ): Map? + + fun receivedV2SdkInfo( + existing: Map?, + payload: Map + ): Map? + + fun receivedRoute( + existing: Map?, + payload: Map, + requestId: String?, + ): Map? + + fun receivedRouteV2( + existing: Map?, + payload: Map, + requestId: String? + ): Map? + + fun usdcAmount(data: Map): Double? + fun receivedStatus( + existing: Map?, + payload: Map, + transactionId: String?, + ): Map? + + fun updateTokensDefaults(modified: MutableMap, selectedChainId: String?) + fun defaultChainId(): String? + fun selectedTokenSymbol(tokenAddress: String?, selectedChainId: String?): String? + fun selectedTokenDecimals(tokenAddress: String?, selectedChainId: String?): String? + fun filteredTokens(chainId: String?): List? + fun defaultTokenAddress(chainId: String?): String? + fun chainResources(chainId: String?): Map? + fun tokenResources(chainId: String?): Map? + fun chainOptions(): List + fun tokenOptions(chainId: String?): List +} diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/SharedRouterProcessor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/SharedRouterProcessor.kt new file mode 100644 index 000000000..55f5f3c70 --- /dev/null +++ b/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/SharedRouterProcessor.kt @@ -0,0 +1,5 @@ +package exchange.dydx.abacus.processor.router + +import exchange.dydx.abacus.protocols.ParserProtocol + +class SharedRouterProcessor(val parser: ParserProtocol) diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/skip/SkipChainProcessor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/skip/SkipChainProcessor.kt new file mode 100644 index 000000000..07ee41a16 --- /dev/null +++ b/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/skip/SkipChainProcessor.kt @@ -0,0 +1,17 @@ +package exchange.dydx.abacus.processor.router.skip + +import exchange.dydx.abacus.output.input.SelectionOption +import exchange.dydx.abacus.protocols.ParserProtocol + +internal class SkipChainProcessor(private val parser: ParserProtocol) { + fun received( + payload: Map + ): SelectionOption { + return SelectionOption( + stringKey = parser.asString(payload["network_identifier"]) ?: parser.asString(payload["chain_name"]), + string = parser.asString(payload["network_identifier"]) ?: parser.asString(payload["chain_name"]), + type = parser.asString(payload["chain_id"]) ?: "", + iconUrl = parser.asString(payload["logo_uri"]), + ) + } +} diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/skip/SkipChainResourceProcessor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/skip/SkipChainResourceProcessor.kt new file mode 100644 index 000000000..849a6b4cc --- /dev/null +++ b/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/skip/SkipChainResourceProcessor.kt @@ -0,0 +1,19 @@ +package exchange.dydx.abacus.processor.router.skip + +import exchange.dydx.abacus.output.input.TransferInputChainResource +import exchange.dydx.abacus.protocols.ParserProtocol + +internal class SkipChainResourceProcessor(private val parser: ParserProtocol) { + + fun received( + payload: Map + ): TransferInputChainResource { + return TransferInputChainResource( + chainName = parser.asString(payload["chain_name"]), + rpc = parser.asString(payload["rpc"]), + networkName = parser.asString(payload["networkName"]), + chainId = parser.asInt(payload["chain_id"]), + iconUrl = parser.asString(payload["logo_uri"]), + ) + } +} diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/skip/SkipProcessor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/skip/SkipProcessor.kt new file mode 100644 index 000000000..968f5911f --- /dev/null +++ b/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/skip/SkipProcessor.kt @@ -0,0 +1,225 @@ +package exchange.dydx.abacus.processor.router.skip + +import exchange.dydx.abacus.output.input.SelectionOption +import exchange.dydx.abacus.output.input.TransferInputChainResource +import exchange.dydx.abacus.output.input.TransferInputTokenResource +import exchange.dydx.abacus.processor.base.BaseProcessor +import exchange.dydx.abacus.processor.router.IRouterProcessor +import exchange.dydx.abacus.processor.router.SharedRouterProcessor +import exchange.dydx.abacus.protocols.ParserProtocol +import exchange.dydx.abacus.state.internalstate.InternalTransferInputState +import exchange.dydx.abacus.state.manager.CctpConfig.cctpChainIds +import exchange.dydx.abacus.utils.mutable +import exchange.dydx.abacus.utils.safeSet + +@Suppress("NotImplementedDeclaration") +internal class SkipProcessor( + parser: ParserProtocol, + private val internalState: InternalTransferInputState +) : BaseProcessor(parser), IRouterProcessor { + override var chains: List? = null + +// possibly want to use a different variable so we aren't stuck with this bad type +// actual type of the tokens payload is Map>>> + override var tokens: List? = null + + var skipTokens: Map>>>? = null + override var exchangeDestinationChainId: String? = null + val sharedRouterProcessor = SharedRouterProcessor(parser) + + override fun receivedV2SdkInfo( + existing: Map?, + payload: Map + ): Map? { + throw NotImplementedError("receivedV2SdkInfo is not implemented in SkipProcessor!") + } + override fun receivedChains( + existing: Map?, + payload: Map + ): Map? { + if (this.chains != null) { + return existing + } + this.chains = parser.asNativeList(payload["chains"]) + var modified = mutableMapOf() + existing?.let { + modified = it.mutable() + } + val chainOptions = chainOptions() + + internalState.chains = chainOptions + val selectedChainId = defaultChainId() + modified.safeSet("transfer.chain", selectedChainId) + selectedChainId?.let { + internalState.chainResources = chainResources(chainId = selectedChainId) + } + return modified + } + + override fun receivedTokens( + existing: Map?, + payload: Map + ): Map? { + if (this.chains != null && this.skipTokens != null) { + return existing + } + + val chainToAssetsMap = payload["chain_to_assets_map"] as Map>>>? + + var modified = mutableMapOf() + existing?.let { + modified = it.mutable() + } + if (chainToAssetsMap == null) { + return existing + } + val selectedChainId = defaultChainId() + this.skipTokens = chainToAssetsMap + updateTokensDefaults(modified, selectedChainId) + + return modified + } + + override fun receivedRoute( + existing: Map?, + payload: Map, + requestId: String?, + ): Map? { + throw NotImplementedError("receivedRoute is not implemented in SkipProcessor!") + } + + override fun receivedRouteV2( + existing: Map?, + payload: Map, + requestId: String? + ): Map? { + throw NotImplementedError("receivedRouteV2 is not implemented in SkipProcessor!") + } + + override fun usdcAmount(data: Map): Double? { + throw NotImplementedError("usdcAmount is not implemented in SkipProcessor!") + } + + override fun receivedStatus( + existing: Map?, + payload: Map, + transactionId: String?, + ): Map? { + throw NotImplementedError("receivedStatus is not implemented in SkipProcessor!") + } + + override fun updateTokensDefaults(modified: MutableMap, selectedChainId: String?) { + val tokenOptions = tokenOptions(selectedChainId) + internalState.tokens = tokenOptions + modified.safeSet("transfer.token", defaultTokenAddress(selectedChainId)) + internalState.tokenResources = tokenResources(selectedChainId) + } + + override fun defaultChainId(): String? { + val selectedChain = parser.asNativeMap(this.chains?.find { parser.asString(parser.asNativeMap(it)?.get("chain_id")) == "1" }) + + return parser.asString(selectedChain?.get("chain_id")) + } + + override fun selectedTokenSymbol(tokenAddress: String?, selectedChainId: String?): String? { + val tokensList = filteredTokens(selectedChainId) + tokensList?.find { + parser.asString(parser.asNativeMap(it)?.get("denom")) == tokenAddress + }?.let { + return parser.asString(parser.asNativeMap(it)?.get("symbol")) + } + return null + } + + override fun selectedTokenDecimals(tokenAddress: String?, selectedChainId: String?): String? { + val tokensList = filteredTokens(selectedChainId) + tokensList?.find { + parser.asString(parser.asNativeMap(it)?.get("denom")) == tokenAddress + }?.let { + return parser.asString(parser.asNativeMap(it)?.get("decimals")) + } + return null + } + + override fun filteredTokens(chainId: String?): List? { + val chainIdToUse = chainId ?: defaultChainId() + val assetsMapForChainId = parser.asNativeMap(this.skipTokens?.get(chainIdToUse)) + return parser.asNativeList(assetsMapForChainId?.get("assets")) + } + + override fun defaultTokenAddress(chainId: String?): String? { + return chainId?.let { cid -> + // Retrieve the list of filtered tokens for the given chainId + val filteredTokens = this.filteredTokens(cid)?.mapNotNull { + parser.asString(parser.asNativeMap(it)?.get("denom")) + }.orEmpty() + // Find a matching CctpChainTokenInfo and check if its tokenAddress is in the filtered tokens + cctpChainIds?.firstOrNull { it.chainId == cid && filteredTokens.contains(it.tokenAddress) }?.tokenAddress + ?: run { + // Fallback to the first token's address from the filtered list if no CctpChainTokenInfo match is found + filteredTokens.firstOrNull() + } + } + } + + override fun chainResources(chainId: String?): Map? { + val chainResources = mutableMapOf() + chainId?.let { + this.chains?.find { + parser.asString(parser.asNativeMap(it)?.get("chain_id")) == chainId + }?.let { + val processor = SkipChainResourceProcessor(parser) + parser.asNativeMap(it)?.let { payload -> + chainResources[chainId] = processor.received(payload) + } + } + } + return chainResources + } + + override fun tokenResources(chainId: String?): Map? { + val tokenResources = mutableMapOf() + filteredTokens(chainId)?.forEach { + parser.asString(parser.asNativeMap(it)?.get("denom"))?.let { key -> + val processor = SkipTokenResourceProcessor(parser) + parser.asNativeMap(it)?.let { payload -> + tokenResources[key] = processor.received(payload) + } + } + } + return tokenResources + } + + override fun chainOptions(): List { + val chainProcessor = SkipChainProcessor(parser) + val options = mutableListOf() + + this.chains?.let { + for (chain in it) { + parser.asNativeMap(chain)?.let { chain -> + if (parser.asString(chain.get("chainType")) != "cosmos") { + options.add(chainProcessor.received(chain)) + } + } + } + } + + options.sortBy { parser.asString(it.stringKey) } + return options + } + + override fun tokenOptions(chainId: String?): List { + val processor = SkipTokenProcessor(parser) + val options = mutableListOf() + val tokensForSelectedChain = filteredTokens(chainId) + tokensForSelectedChain?.let { + for (asset in it) { + parser.asNativeMap(asset)?.let { _asset -> + options.add(processor.received(_asset)) + } + } + } + options.sortBy { parser.asString(it.stringKey) } + return options + } +} diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/skip/SkipTokenProcessor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/skip/SkipTokenProcessor.kt new file mode 100644 index 000000000..fbfba34ff --- /dev/null +++ b/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/skip/SkipTokenProcessor.kt @@ -0,0 +1,17 @@ +package exchange.dydx.abacus.processor.router.skip + +import exchange.dydx.abacus.output.input.SelectionOption +import exchange.dydx.abacus.protocols.ParserProtocol + +internal class SkipTokenProcessor(private val parser: ParserProtocol) { + fun received( + payload: Map + ): SelectionOption { + return SelectionOption( + stringKey = parser.asString(payload["name"]), + string = parser.asString(payload["name"]), + type = parser.asString(payload["denom"]) ?: "", + iconUrl = parser.asString(payload["logo_uri"]), + ) + } +} diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/skip/SkipTokenResourceProcessor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/skip/SkipTokenResourceProcessor.kt new file mode 100644 index 000000000..76563a084 --- /dev/null +++ b/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/skip/SkipTokenResourceProcessor.kt @@ -0,0 +1,20 @@ +package exchange.dydx.abacus.processor.router.skip + +import exchange.dydx.abacus.output.input.TransferInputTokenResource +import exchange.dydx.abacus.protocols.ParserProtocol + +internal class SkipTokenResourceProcessor( + private val parser: ParserProtocol +) { + fun received( + payload: Map + ): TransferInputTokenResource { + return TransferInputTokenResource( + name = parser.asString(payload["name"]), + address = parser.asString(payload["denom"]), + symbol = parser.asString(payload["symbol"]), + decimals = parser.asInt(payload["decimals"]), + iconUrl = parser.asString(payload["logo_uri"]), + ) + } +} diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/squid/SquidChainProcessor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/squid/SquidChainProcessor.kt new file mode 100644 index 000000000..b8bc72577 --- /dev/null +++ b/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/squid/SquidChainProcessor.kt @@ -0,0 +1,19 @@ +package exchange.dydx.abacus.processor.router.squid + +import exchange.dydx.abacus.output.input.SelectionOption +import exchange.dydx.abacus.protocols.ParserProtocol + +internal class SquidChainProcessor( + private val parser: ParserProtocol +) { + fun received( + payload: Map + ): SelectionOption { + return SelectionOption( + stringKey = parser.asString(payload["networkIdentifier"]) ?: parser.asString(payload["chainName"]), + string = parser.asString(payload["networkIdentifier"]) ?: parser.asString(payload["chainName"]), + type = parser.asString(payload["chainId"]) ?: "", + iconUrl = parser.asString(payload["chainIconURI"]), + ) + } +} diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/squid/SquidChainResourceProcessor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/squid/SquidChainResourceProcessor.kt new file mode 100644 index 000000000..774260b2a --- /dev/null +++ b/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/squid/SquidChainResourceProcessor.kt @@ -0,0 +1,20 @@ +package exchange.dydx.abacus.processor.router.squid + +import exchange.dydx.abacus.output.input.TransferInputChainResource +import exchange.dydx.abacus.protocols.ParserProtocol + +internal class SquidChainResourceProcessor( + private val parser: ParserProtocol +) { + fun received( + payload: Map + ): TransferInputChainResource { + return TransferInputChainResource( + chainName = parser.asString(payload["chainName"]), + rpc = parser.asString(payload["rpc"]), + networkName = parser.asString(payload["networkName"]), + chainId = parser.asInt(payload["chainId"]), + iconUrl = parser.asString(payload["chainIconURI"]), + ) + } +} diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/processor/squid/SquidProcessor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/squid/SquidProcessor.kt similarity index 74% rename from src/commonMain/kotlin/exchange.dydx.abacus/processor/squid/SquidProcessor.kt rename to src/commonMain/kotlin/exchange.dydx.abacus/processor/router/squid/SquidProcessor.kt index 327d6dafe..c0362a6be 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/processor/squid/SquidProcessor.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/squid/SquidProcessor.kt @@ -1,17 +1,25 @@ -package exchange.dydx.abacus.processor.squid +package exchange.dydx.abacus.processor.router.squid +import exchange.dydx.abacus.output.input.SelectionOption +import exchange.dydx.abacus.output.input.TransferInputChainResource +import exchange.dydx.abacus.output.input.TransferInputTokenResource import exchange.dydx.abacus.processor.base.BaseProcessor +import exchange.dydx.abacus.processor.router.IRouterProcessor import exchange.dydx.abacus.protocols.ParserProtocol +import exchange.dydx.abacus.state.internalstate.InternalTransferInputState import exchange.dydx.abacus.state.manager.CctpConfig.cctpChainIds import exchange.dydx.abacus.utils.mutable import exchange.dydx.abacus.utils.safeSet -internal class SquidProcessor(parser: ParserProtocol) : BaseProcessor(parser) { - private var chains: List? = null - private var tokens: List? = null - var exchangeDestinationChainId: String? = null +internal class SquidProcessor( + parser: ParserProtocol, + private val internalState: InternalTransferInputState, +) : BaseProcessor(parser), IRouterProcessor { + override var chains: List? = null + override var tokens: List? = null + override var exchangeDestinationChainId: String? = null - internal fun receivedChains( + override fun receivedChains( existing: Map?, payload: Map ): Map? { @@ -26,12 +34,12 @@ internal class SquidProcessor(parser: ParserProtocol) : BaseProcessor(parser) { } val chainOptions = chainOptions() - modified.safeSet("transfer.depositOptions.chains", chainOptions) - modified.safeSet("transfer.withdrawalOptions.chains", chainOptions) + internalState.chains = chainOptions + val selectedChainId = defaultChainId() modified.safeSet("transfer.chain", selectedChainId) selectedChainId?.let { - modified.safeSet("transfer.resources.chainResources", chainResources(selectedChainId)) + internalState.chainResources = chainResources(selectedChainId) } updateTokensDefaults(modified, selectedChainId) @@ -39,7 +47,7 @@ internal class SquidProcessor(parser: ParserProtocol) : BaseProcessor(parser) { return modified } - internal fun receivedTokens( + override fun receivedTokens( existing: Map?, payload: Map ): Map? { @@ -59,7 +67,7 @@ internal class SquidProcessor(parser: ParserProtocol) : BaseProcessor(parser) { return modified } - internal fun receivedV2SdkInfo( + override fun receivedV2SdkInfo( existing: Map?, payload: Map ): Map? { @@ -75,12 +83,12 @@ internal class SquidProcessor(parser: ParserProtocol) : BaseProcessor(parser) { } val chainOptions = chainOptions() - modified.safeSet("transfer.depositOptions.chains", chainOptions) - modified.safeSet("transfer.withdrawalOptions.chains", chainOptions) + internalState.chains = chainOptions + val selectedChainId = defaultChainId() modified.safeSet("transfer.chain", selectedChainId) selectedChainId?.let { - modified.safeSet("transfer.resources.chainResources", chainResources(selectedChainId)) + internalState.chainResources = chainResources(selectedChainId) } updateTokensDefaults(modified, selectedChainId) @@ -88,7 +96,7 @@ internal class SquidProcessor(parser: ParserProtocol) : BaseProcessor(parser) { return modified } - internal fun receivedRoute( + override fun receivedRoute( existing: Map?, payload: Map, requestId: String?, @@ -114,7 +122,7 @@ internal class SquidProcessor(parser: ParserProtocol) : BaseProcessor(parser) { return modified } - internal fun receivedRouteV2( + override fun receivedRouteV2( existing: Map?, payload: Map, requestId: String? @@ -140,7 +148,7 @@ internal class SquidProcessor(parser: ParserProtocol) : BaseProcessor(parser) { return modified } - private fun usdcAmount(data: Map): Double? { + override fun usdcAmount(data: Map): Double? { var toAmountUSD = parser.asString(parser.value(data, "transfer.route.toAmountUSD")) toAmountUSD = toAmountUSD?.replace(",", "") var toAmount = parser.asString(parser.value(data, "transfer.route.toAmount")) @@ -148,7 +156,7 @@ internal class SquidProcessor(parser: ParserProtocol) : BaseProcessor(parser) { return parser.asDouble(toAmountUSD) ?: parser.asDouble(toAmount) } - internal fun receivedStatus( + override fun receivedStatus( existing: Map?, payload: Map, transactionId: String?, @@ -162,20 +170,21 @@ internal class SquidProcessor(parser: ParserProtocol) : BaseProcessor(parser) { return processor.received(existing, payload) } - private fun updateTokensDefaults(modified: MutableMap, selectedChainId: String?) { + override fun updateTokensDefaults(modified: MutableMap, selectedChainId: String?) { val tokenOptions = tokenOptions(selectedChainId) + internalState.tokens = tokenOptions modified.safeSet("transfer.depositOptions.assets", tokenOptions) modified.safeSet("transfer.withdrawalOptions.assets", tokenOptions) modified.safeSet("transfer.token", defaultTokenAddress(selectedChainId)) - modified.safeSet("transfer.resources.tokenResources", tokenResources(selectedChainId)) + internalState.tokenResources = tokenResources(selectedChainId) } - internal fun defaultChainId(): String? { + override fun defaultChainId(): String? { val selectedChain = parser.asNativeMap(this.chains?.firstOrNull()) return parser.asString(selectedChain?.get("chainId")) } - internal fun selectedTokenSymbol(tokenAddress: String?): String? { + override fun selectedTokenSymbol(tokenAddress: String?, selectedChainId: String?): String? { this.tokens?.find { parser.asString(parser.asNativeMap(it)?.get("address")) == tokenAddress }?.let { @@ -184,7 +193,7 @@ internal class SquidProcessor(parser: ParserProtocol) : BaseProcessor(parser) { return null } - internal fun selectedTokenDecimals(tokenAddress: String?): String? { + override fun selectedTokenDecimals(tokenAddress: String?, selectedChainId: String?): String? { this.tokens?.find { parser.asString(parser.asNativeMap(it)?.get("address")) == tokenAddress }?.let { @@ -193,7 +202,7 @@ internal class SquidProcessor(parser: ParserProtocol) : BaseProcessor(parser) { return null } - private fun filteredTokens(chainId: String?): List? { + override fun filteredTokens(chainId: String?): List? { chainId?.let { val filteredTokens = mutableListOf>() this.tokens?.let { @@ -210,7 +219,7 @@ internal class SquidProcessor(parser: ParserProtocol) : BaseProcessor(parser) { return tokens } - internal fun defaultTokenAddress(chainId: String?): String? { + override fun defaultTokenAddress(chainId: String?): String? { return chainId?.let { cid -> // Retrieve the list of filtered tokens for the given chainId val filteredTokens = this.filteredTokens(cid)?.mapNotNull { @@ -226,43 +235,43 @@ internal class SquidProcessor(parser: ParserProtocol) : BaseProcessor(parser) { } } - internal fun chainResources(chainId: String?): Map? { - val chainResources = mutableMapOf() + override fun chainResources(chainId: String?): Map? { + val chainResources = mutableMapOf() chainId?.let { this.chains?.find { parser.asString(parser.asNativeMap(it)?.get("chainId")) == chainId }?.let { val processor = SquidChainResourceProcessor(parser) parser.asNativeMap(it)?.let { payload -> - chainResources[chainId] = processor.received(null, payload) + chainResources[chainId] = processor.received(payload) } } } return chainResources } - internal fun tokenResources(chainId: String?): Map? { - val tokenResources = mutableMapOf() + override fun tokenResources(chainId: String?): Map? { + val tokenResources = mutableMapOf() filteredTokens(chainId)?.forEach { parser.asString(parser.asNativeMap(it)?.get("address"))?.let { key -> val processor = SquidTokenResourceProcessor(parser) parser.asNativeMap(it)?.let { payload -> - tokenResources[key] = processor.received(null, payload) + tokenResources[key] = processor.received(payload) } } } return tokenResources } - private fun chainOptions(): List { + override fun chainOptions(): List { val chainProcessor = SquidChainProcessor(parser) - val options = mutableListOf() + val options = mutableListOf() - this.chains?.let { it -> + this.chains?.let { for (chain in it) { parser.asNativeMap(chain)?.let { chain -> - if (parser.asString(chain.get("chainType")) != "cosmos") { - options.add(chainProcessor.received(null, chain)) + if (parser.asString(chain["chainType"]) != "cosmos") { + options.add(chainProcessor.received(chain)) } } } @@ -272,9 +281,9 @@ internal class SquidProcessor(parser: ParserProtocol) : BaseProcessor(parser) { return options } - internal fun tokenOptions(chainId: String?): List { + override fun tokenOptions(chainId: String?): List { val processor = SquidTokenProcessor(parser) - val options = mutableListOf() + val options = mutableListOf() val selectedChainId = chainId ?: defaultChainId() selectedChainId?.let { @@ -282,15 +291,15 @@ internal class SquidProcessor(parser: ParserProtocol) : BaseProcessor(parser) { this.tokens?.let { for (token in it) { parser.asNativeMap(token)?.let { token -> - if (parser.asString(token.get("chainId")) == selectedChainId) { - options.add(processor.received(null, token)) + if (parser.asString(token["chainId"]) == selectedChainId) { + options.add(processor.received(token)) } } } } } - options.sortBy { parser.asString(parser.asNativeMap(it)?.get("stringKey")) } + options.sortBy { it.stringKey } return options } } diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/processor/squid/SquidRoutePayloadProcessor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/squid/SquidRoutePayloadProcessor.kt similarity index 96% rename from src/commonMain/kotlin/exchange.dydx.abacus/processor/squid/SquidRoutePayloadProcessor.kt rename to src/commonMain/kotlin/exchange.dydx.abacus/processor/router/squid/SquidRoutePayloadProcessor.kt index 8c32ab806..1879d4b3b 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/processor/squid/SquidRoutePayloadProcessor.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/squid/SquidRoutePayloadProcessor.kt @@ -1,4 +1,4 @@ -package exchange.dydx.abacus.processor.squid +package exchange.dydx.abacus.processor.router.squid import exchange.dydx.abacus.processor.base.BaseProcessor import exchange.dydx.abacus.protocols.ParserProtocol diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/processor/squid/SquidRouteProcessor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/squid/SquidRouteProcessor.kt similarity index 97% rename from src/commonMain/kotlin/exchange.dydx.abacus/processor/squid/SquidRouteProcessor.kt rename to src/commonMain/kotlin/exchange.dydx.abacus/processor/router/squid/SquidRouteProcessor.kt index 9abef1715..4cec8b9f3 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/processor/squid/SquidRouteProcessor.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/squid/SquidRouteProcessor.kt @@ -1,4 +1,4 @@ -package exchange.dydx.abacus.processor.squid +package exchange.dydx.abacus.processor.router.squid import exchange.dydx.abacus.processor.base.BaseProcessor import exchange.dydx.abacus.protocols.ParserProtocol diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/processor/squid/SquidRouteV2PayloadProcessor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/squid/SquidRouteV2PayloadProcessor.kt similarity index 96% rename from src/commonMain/kotlin/exchange.dydx.abacus/processor/squid/SquidRouteV2PayloadProcessor.kt rename to src/commonMain/kotlin/exchange.dydx.abacus/processor/router/squid/SquidRouteV2PayloadProcessor.kt index 46949bfb2..438cc5a78 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/processor/squid/SquidRouteV2PayloadProcessor.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/squid/SquidRouteV2PayloadProcessor.kt @@ -1,4 +1,4 @@ -package exchange.dydx.abacus.processor.squid +package exchange.dydx.abacus.processor.router.squid import exchange.dydx.abacus.processor.base.BaseProcessor import exchange.dydx.abacus.protocols.ParserProtocol diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/processor/squid/SquidRouteV2Processor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/squid/SquidRouteV2Processor.kt similarity index 98% rename from src/commonMain/kotlin/exchange.dydx.abacus/processor/squid/SquidRouteV2Processor.kt rename to src/commonMain/kotlin/exchange.dydx.abacus/processor/router/squid/SquidRouteV2Processor.kt index a70f7e39e..025727d79 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/processor/squid/SquidRouteV2Processor.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/squid/SquidRouteV2Processor.kt @@ -1,4 +1,4 @@ -package exchange.dydx.abacus.processor.squid +package exchange.dydx.abacus.processor.router.squid import exchange.dydx.abacus.processor.base.BaseProcessor import exchange.dydx.abacus.protocols.ParserProtocol diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/processor/squid/SquidStatusProcessor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/squid/SquidStatusProcessor.kt similarity index 92% rename from src/commonMain/kotlin/exchange.dydx.abacus/processor/squid/SquidStatusProcessor.kt rename to src/commonMain/kotlin/exchange.dydx.abacus/processor/router/squid/SquidStatusProcessor.kt index 910998c9b..6e5c5215c 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/processor/squid/SquidStatusProcessor.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/squid/SquidStatusProcessor.kt @@ -1,4 +1,4 @@ -package exchange.dydx.abacus.processor.squid +package exchange.dydx.abacus.processor.router.squid import exchange.dydx.abacus.processor.base.BaseProcessor import exchange.dydx.abacus.protocols.ParserProtocol diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/squid/SquidTokenProcessor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/squid/SquidTokenProcessor.kt new file mode 100644 index 000000000..43ddd98d7 --- /dev/null +++ b/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/squid/SquidTokenProcessor.kt @@ -0,0 +1,19 @@ +package exchange.dydx.abacus.processor.router.squid + +import exchange.dydx.abacus.output.input.SelectionOption +import exchange.dydx.abacus.protocols.ParserProtocol + +internal class SquidTokenProcessor( + private val parser: ParserProtocol, +) { + fun received( + payload: Map + ): SelectionOption { + return SelectionOption( + stringKey = parser.asString(payload["name"]), + string = parser.asString(payload["name"]), + type = parser.asString(payload["address"]) ?: "", + iconUrl = parser.asString(payload["logoURI"]), + ) + } +} diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/squid/SquidTokenResourceProcessor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/squid/SquidTokenResourceProcessor.kt new file mode 100644 index 000000000..d8e6b1285 --- /dev/null +++ b/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/squid/SquidTokenResourceProcessor.kt @@ -0,0 +1,20 @@ +package exchange.dydx.abacus.processor.router.squid + +import exchange.dydx.abacus.output.input.TransferInputTokenResource +import exchange.dydx.abacus.protocols.ParserProtocol + +internal class SquidTokenResourceProcessor( + private val parser: ParserProtocol +) { + fun received( + payload: Map + ): TransferInputTokenResource { + return TransferInputTokenResource( + name = parser.asString(payload["name"]), + address = parser.asString(payload["address"]), + symbol = parser.asString(payload["symbol"]), + decimals = parser.asInt(payload["decimals"]), + iconUrl = parser.asString(payload["logoURI"]), + ) + } +} diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/processor/squid/SquidChainProcessor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/processor/squid/SquidChainProcessor.kt deleted file mode 100644 index 684b9b6a8..000000000 --- a/src/commonMain/kotlin/exchange.dydx.abacus/processor/squid/SquidChainProcessor.kt +++ /dev/null @@ -1,22 +0,0 @@ -package exchange.dydx.abacus.processor.squid - -import exchange.dydx.abacus.processor.base.BaseProcessor -import exchange.dydx.abacus.protocols.ParserProtocol - -internal class SquidChainProcessor(parser: ParserProtocol) : BaseProcessor(parser) { - private val keyMap = mapOf( - "string" to mapOf( - "chainName" to "stringKey", - "networkIdentifier" to "stringKey", - "chainId" to "type", - "chainIconURI" to "iconUrl", - ), - ) - - override fun received( - existing: Map?, - payload: Map - ): Map { - return transform(existing, payload, keyMap) - } -} diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/processor/squid/SquidChainResourceProcessor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/processor/squid/SquidChainResourceProcessor.kt deleted file mode 100644 index b090e0593..000000000 --- a/src/commonMain/kotlin/exchange.dydx.abacus/processor/squid/SquidChainResourceProcessor.kt +++ /dev/null @@ -1,23 +0,0 @@ -package exchange.dydx.abacus.processor.squid - -import exchange.dydx.abacus.processor.base.BaseProcessor -import exchange.dydx.abacus.protocols.ParserProtocol - -internal class SquidChainResourceProcessor(parser: ParserProtocol) : BaseProcessor(parser) { - private val keyMap = mapOf( - "string" to mapOf( - "chainName" to "chainName", - "rpc" to "rpc", - "networkName" to "networkName", - "chainId" to "chainId", - "chainIconURI" to "iconUrl", - ), - ) - - override fun received( - existing: Map?, - payload: Map - ): Map { - return transform(existing, payload, keyMap) - } -} diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/processor/squid/SquidTokenProcessor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/processor/squid/SquidTokenProcessor.kt deleted file mode 100644 index 217e769c0..000000000 --- a/src/commonMain/kotlin/exchange.dydx.abacus/processor/squid/SquidTokenProcessor.kt +++ /dev/null @@ -1,21 +0,0 @@ -package exchange.dydx.abacus.processor.squid - -import exchange.dydx.abacus.processor.base.BaseProcessor -import exchange.dydx.abacus.protocols.ParserProtocol - -internal class SquidTokenProcessor(parser: ParserProtocol) : BaseProcessor(parser) { - private val keyMap = mapOf( - "string" to mapOf( - "name" to "stringKey", - "address" to "type", - "logoURI" to "iconUrl", - ), - ) - - override fun received( - existing: Map?, - payload: Map - ): Map { - return transform(existing, payload, keyMap) - } -} diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/processor/squid/SquidTokenResourceProcessor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/processor/squid/SquidTokenResourceProcessor.kt deleted file mode 100644 index 26f389a1c..000000000 --- a/src/commonMain/kotlin/exchange.dydx.abacus/processor/squid/SquidTokenResourceProcessor.kt +++ /dev/null @@ -1,23 +0,0 @@ -package exchange.dydx.abacus.processor.squid - -import exchange.dydx.abacus.processor.base.BaseProcessor -import exchange.dydx.abacus.protocols.ParserProtocol - -internal class SquidTokenResourceProcessor(parser: ParserProtocol) : BaseProcessor(parser) { - private val keyMap = mapOf( - "string" to mapOf( - "name" to "name", - "address" to "address", - "symbol" to "symbol", - "decimals" to "decimals", - "logoURI" to "iconUrl", - ), - ) - - override fun received( - existing: Map?, - payload: Map - ): Map { - return transform(existing, payload, keyMap) - } -} diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/processor/wallet/account/AccountProcessor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/processor/wallet/account/AccountProcessor.kt index 8bd017a8e..14f0fc56a 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/processor/wallet/account/AccountProcessor.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/processor/wallet/account/AccountProcessor.kt @@ -299,13 +299,14 @@ internal open class SubaccountProcessor(parser: ParserProtocol) : BaseProcessor( modified = transform(modified, payload, "current", currentAccountKeyMap) if (firstTime) { + val subaccountNumber = parser.asInt(modified["subaccountNumber"]) val openPerpetualPositionsData = ( parser.asNativeMap(payload["openPositions"]) ?: parser.asNativeMap(payload["openPerpetualPositions"]) ) - val positions = perpetualPositionsProcessor.received(openPerpetualPositionsData) + val positions = perpetualPositionsProcessor.received(openPerpetualPositionsData, subaccountNumber) modified.safeSet( "positions", positions, @@ -371,12 +372,14 @@ internal open class SubaccountProcessor(parser: ParserProtocol) : BaseProcessor( payload: List?, height: BlockAndTime?, ): Map { + val subaccountNumber = parser.asInt(subaccount["subaccountNumber"]) return if (payload != null) { val modified = subaccount.mutable() val transformed = ordersProcessor.received( parser.asNativeMap(subaccount["orders"]), payload, height, + subaccountNumber, ) modified.safeSet("orders", transformed) modified @@ -809,8 +812,9 @@ private class V4AccountDelegationsProcessor(parser: ParserProtocol) : BaseProces return if (payload != null) { val modified = mutableMapOf() for (itemPayload in payload) { - val delegation = parser.asNativeMap(itemPayload) - val balance = parser.asNativeMap(delegation?.get("balance")) + val item = parser.asNativeMap(itemPayload) + val balance = parser.asNativeMap(item?.get("balance")) + if (balance != null) { val denom = parser.asString(balance["denom"]) if (denom != null) { @@ -842,6 +846,33 @@ private class V4AccountDelegationsProcessor(parser: ParserProtocol) : BaseProces null } } + + fun receivedDelegations( + existing: Map?, + payload: List?, + ): List? { + return if (payload != null) { + val modified = mutableListOf() + for (itemPayload in payload) { + val item = parser.asNativeMap(itemPayload) + val validator = parser.asString(parser.value(item, "delegation.validatorAddress")) + val amount = parser.asDecimal(parser.value(item, "balance.amount")) + val denom = parser.asString(parser.value(item, "balance.denom")) + if (validator != null && amount != null) { + modified.add( + mapOf( + "validator" to validator, + "amount" to amount, + "denom" to denom, + ), + ) + } + } + return modified + } else { + null + } + } } private class V4AccountTradingRewardsProcessor(parser: ParserProtocol) : BaseProcessor(parser) { @@ -914,8 +945,10 @@ internal class V4AccountProcessor(parser: ParserProtocol) : BaseProcessor(parser ): Map? { val modified = existing?.mutable() ?: mutableMapOf() val delegations = parser.asNativeMap(parser.value(existing, "stakingBalances")) - val modifiedDelegations = delegationsProcessor.received(delegations, payload) - modified.safeSet("stakingBalances", modifiedDelegations) + val modifiedStakingBalance = delegationsProcessor.received(delegations, payload) + modified.safeSet("stakingBalances", modifiedStakingBalance) + val modifiedDelegations = delegationsProcessor.receivedDelegations(delegations, payload) + modified.safeSet("stakingDelegations", modifiedDelegations) return modified } diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/processor/wallet/account/OrderProcessor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/processor/wallet/account/OrderProcessor.kt index 413dbd7d7..8d2113507 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/processor/wallet/account/OrderProcessor.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/processor/wallet/account/OrderProcessor.kt @@ -1,9 +1,11 @@ package exchange.dydx.abacus.processor.wallet.account +import exchange.dydx.abacus.output.input.MarginMode import exchange.dydx.abacus.processor.base.BaseProcessor import exchange.dydx.abacus.processor.utils.OrderTypeProcessor import exchange.dydx.abacus.protocols.ParserProtocol import exchange.dydx.abacus.state.manager.BlockAndTime +import exchange.dydx.abacus.utils.NUM_PARENT_SUBACCOUNTS import exchange.dydx.abacus.utils.Numeric import exchange.dydx.abacus.utils.mutable import exchange.dydx.abacus.utils.safeSet @@ -202,6 +204,11 @@ internal class OrderProcessor(parser: ParserProtocol) : BaseProcessor(parser) { if (modified["id"] == null) { modified.safeSet("id", payload["clientId"]) } + parser.asInt(modified["subaccountNumber"])?.run { + modified.safeSet("subaccountNumber", this) + // the v4_parent_subaccount message has subaccountNumber available but v4_orders does not + modified.safeSet("marginMode", if (this >= NUM_PARENT_SUBACCOUNTS) MarginMode.isolated.rawValue else MarginMode.cross.rawValue) + } val size = parser.asDouble(payload["size"]) if (size != null) { var totalFilled = parser.asDouble(payload["totalFilled"]) diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/processor/wallet/account/OrdersProcessor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/processor/wallet/account/OrdersProcessor.kt index 236507209..d8fbafcdd 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/processor/wallet/account/OrdersProcessor.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/processor/wallet/account/OrdersProcessor.kt @@ -4,6 +4,7 @@ import exchange.dydx.abacus.processor.base.BaseProcessor import exchange.dydx.abacus.protocols.ParserProtocol import exchange.dydx.abacus.state.manager.BlockAndTime import exchange.dydx.abacus.utils.mutable +import exchange.dydx.abacus.utils.safeSet import exchange.dydx.abacus.utils.typedSafeSet internal class OrdersProcessor(parser: ParserProtocol) : BaseProcessor(parser) { @@ -12,16 +13,24 @@ internal class OrdersProcessor(parser: ParserProtocol) : BaseProcessor(parser) { internal fun received( existing: Map?, payload: List?, - height: BlockAndTime? + height: BlockAndTime?, + subaccountNumber: Int?, ): Map? { return if (payload != null) { val orders = existing?.mutable() ?: mutableMapOf() for (data in payload) { parser.asNativeMap(data)?.let { data -> val orderId = parser.asString(data["id"] ?: data["clientId"]) + val modified = data.toMutableMap() + val orderSubaccountNumber = parser.asInt(data["subaccountNumber"]) + + if (orderSubaccountNumber == null) { + modified.safeSet("subaccountNumber", subaccountNumber) + } + if (orderId != null) { val existing = parser.asNativeMap(orders[orderId]) - val order = itemProcessor.received(existing, data, height) + val order = itemProcessor.received(existing, modified, height) orders.typedSafeSet(orderId, order) } } diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/processor/wallet/account/PerpetualPositionProcessor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/processor/wallet/account/PerpetualPositionProcessor.kt index e2ff1e9e8..62b2e619b 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/processor/wallet/account/PerpetualPositionProcessor.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/processor/wallet/account/PerpetualPositionProcessor.kt @@ -1,8 +1,10 @@ package exchange.dydx.abacus.processor.wallet.account import abs +import exchange.dydx.abacus.output.input.MarginMode import exchange.dydx.abacus.processor.base.BaseProcessor import exchange.dydx.abacus.protocols.ParserProtocol +import exchange.dydx.abacus.utils.NUM_PARENT_SUBACCOUNTS import exchange.dydx.abacus.utils.Numeric import exchange.dydx.abacus.utils.ParsingHelper import exchange.dydx.abacus.utils.safeSet @@ -106,7 +108,7 @@ internal class PerpetualPositionProcessor(parser: ParserProtocol) : BaseProcesso override fun received( existing: Map?, - payload: Map + payload: Map, ): Map { var modified = transform(existing, payload, positionKeyMap) modified = transform(modified, payload, "current", currentPositionKeyMap) @@ -115,6 +117,13 @@ internal class PerpetualPositionProcessor(parser: ParserProtocol) : BaseProcesso val sizeMap = size(parser.asNativeMap(payload["size"]), size, parser.asString(payload["side"])) modified.safeSet("size", sizeMap) + parser.asInt(payload["subaccountNumber"])?.run { + modified.safeSet("subaccountNumber", this) + + // the v4_parent_subaccount message has subaccountNumber available but v4_orders does not + modified.safeSet("marginMode", if (this >= NUM_PARENT_SUBACCOUNTS) MarginMode.isolated.rawValue else MarginMode.cross.rawValue) + } + ParsingHelper.asset(parser.asString(modified["id"]))?.let { modified["assetId"] = it } diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/processor/wallet/account/PerpetualPositionsProcessor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/processor/wallet/account/PerpetualPositionsProcessor.kt index e97dcb8ec..b4a04c4de 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/processor/wallet/account/PerpetualPositionsProcessor.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/processor/wallet/account/PerpetualPositionsProcessor.kt @@ -2,18 +2,28 @@ package exchange.dydx.abacus.processor.wallet.account import exchange.dydx.abacus.processor.base.BaseProcessor import exchange.dydx.abacus.protocols.ParserProtocol +import exchange.dydx.abacus.utils.modify import exchange.dydx.abacus.utils.mutable import exchange.dydx.abacus.utils.safeSet internal class PerpetualPositionsProcessor(parser: ParserProtocol) : BaseProcessor(parser) { private val itemProcessor = PerpetualPositionProcessor(parser = parser) - internal fun received(payload: Map?): Map? { + internal fun received( + payload: Map?, + subaccountNumber: Int?, + ): Map? { if (payload != null) { val result = mutableMapOf() for ((key, value) in payload) { - parser.asNativeMap(value)?.let { value -> - val item = itemProcessor.received(null, value) + parser.asNativeMap(value)?.let { data -> + + var modifiedData = data.toMutableMap() + subaccountNumber?.run { + modifiedData.modify("subaccountNumber", subaccountNumber) + } + + val item = itemProcessor.received(null, modifiedData) result.safeSet(key, item) } } diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/internalstate/InternalState.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/internalstate/InternalState.kt new file mode 100644 index 000000000..34214d499 --- /dev/null +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/internalstate/InternalState.kt @@ -0,0 +1,5 @@ +package exchange.dydx.abacus.state.internalstate + +internal data class InternalState( + val transfer: InternalTransferInputState = InternalTransferInputState(), +) diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/internalstate/InternalTransferInputState.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/internalstate/InternalTransferInputState.kt new file mode 100644 index 000000000..7dc5e6687 --- /dev/null +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/internalstate/InternalTransferInputState.kt @@ -0,0 +1,12 @@ +package exchange.dydx.abacus.state.internalstate + +import exchange.dydx.abacus.output.input.SelectionOption +import exchange.dydx.abacus.output.input.TransferInputChainResource +import exchange.dydx.abacus.output.input.TransferInputTokenResource + +internal data class InternalTransferInputState( + var chains: List? = null, + var tokens: List? = null, + var chainResources: Map? = null, + var tokenResources: Map? = null, +) diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/AsyncAbacusStateManager.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/AsyncAbacusStateManager.kt index 10153a944..bb27e6436 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/AsyncAbacusStateManager.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/AsyncAbacusStateManager.kt @@ -114,6 +114,7 @@ class AsyncAbacusStateManager( value?.historicalPnlPeriod = historicalPnlPeriod value?.candlesResolution = candlesResolution value?.readyToConnect = readyToConnect + value?.cosmosWalletConnected = cosmosWalletConnected field = value } } @@ -144,6 +145,14 @@ class AsyncAbacusStateManager( } } + override var cosmosWalletConnected: Boolean? = false + set(value) { + field = value + ioImplementations.threading?.async(ThreadingType.abacus) { + adaptor?.cosmosWalletConnected = field + } + } + override var sourceAddress: String? = null set(value) { field = value @@ -251,19 +260,27 @@ class AsyncAbacusStateManager( } private fun load(configFile: ConfigFile) { - val path = configFile.path - if (appConfigs.loadRemote) { - loadFromRemoteConfigFile(configFile) - val configFileUrl = "$deploymentUri$path" - ioImplementations.rest?.get(configFileUrl, null, callback = { response, httpCode, _ -> - if (success(httpCode) && response != null) { - if (parse(response, configFile)) { - writeToLocalFile(response, path) - } - } - }) - } else { - loadFromBundledLocalConfigFile(configFile) + ioImplementations.threading?.async(ThreadingType.network) { + val path = configFile.path + if (appConfigs.loadRemote) { + loadFromRemoteConfigFile(configFile) + val configFileUrl = "$deploymentUri$path" + ioImplementations.rest?.get( + configFileUrl, + null, + callback = { response, httpCode, _ -> + ioImplementations.threading?.async(ThreadingType.abacus) { + if (success(httpCode) && response != null) { + if (parse(response, configFile)) { + writeToLocalFile(response, path) + } + } + } + }, + ) + } else { + loadFromBundledLocalConfigFile(configFile) + } } } @@ -271,7 +288,9 @@ class AsyncAbacusStateManager( ioImplementations.fileSystem?.readCachedTextFile( configFile.path, )?.let { - parse(it, configFile) + ioImplementations.threading?.async(ThreadingType.abacus) { + parse(it, configFile) + } } } @@ -280,7 +299,9 @@ class AsyncAbacusStateManager( FileLocation.AppBundle, configFile.path, )?.let { - parse(it, configFile) + ioImplementations.threading?.async(ThreadingType.abacus) { + parse(it, configFile) + } } } @@ -300,10 +321,12 @@ class AsyncAbacusStateManager( } private fun writeToLocalFile(response: String, file: String) { - ioImplementations.fileSystem?.writeTextFile( - file, - response, - ) + ioImplementations.threading?.async(ThreadingType.network) { + ioImplementations.fileSystem?.writeTextFile( + file, + response, + ) + } } private fun parseDocumentation(response: String) { diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/AsyncAbacusStateManagerProtocol.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/AsyncAbacusStateManagerProtocol.kt index 75b656adc..433f8fa12 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/AsyncAbacusStateManagerProtocol.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/AsyncAbacusStateManagerProtocol.kt @@ -96,6 +96,7 @@ interface AsyncAbacusStateManagerSingletonProtocol { var sourceAddress: String? var subaccountNumber: Int var market: String? + var cosmosWalletConnected: Boolean? } @JsExport 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 3b13608fb..94c9490ec 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/StateManagerAdaptor.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/StateManagerAdaptor.kt @@ -195,6 +195,8 @@ open class StateManagerAdaptor( } } + var cosmosWalletConnected: Boolean? = false + private var accountAddressTimer: LocalTimerProtocol? = null set(value) { if (field !== value) { @@ -1877,7 +1879,7 @@ open class StateManagerAdaptor( val reduceOnly = true val postOnly = false - // TP/SL orders always have a null timeInForce. IOC/FOK/PostOnly/GTD is distinguished by the execution field. + // TP/SL orders always have a null timeInForce. IOC/PostOnly/GTD is distinguished by the execution field. val timeInForce = null; /** diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/V4StateManagerAdaptor+Transfer.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/V4StateManagerAdaptor+Transfer.kt index 3be6e2222..038fb6385 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/V4StateManagerAdaptor+Transfer.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/V4StateManagerAdaptor+Transfer.kt @@ -73,7 +73,7 @@ internal fun V4StateManagerAdaptor.retrieveDepositExchanges() { internal fun V4StateManagerAdaptor.retrieveDepositRoute(state: PerpetualState?) { val isCctp = state?.input?.transfer?.isCctp ?: false when (appConfigs.squidVersion) { - AppConfigs.SquidVersion.V1, AppConfigs.SquidVersion.V2WithdrawalOnly -> retrieveDepositRouteV1( + AppConfigs.SquidVersion.V2WithdrawalOnly -> retrieveDepositRouteV1( state, ) @@ -86,7 +86,7 @@ private fun V4StateManagerAdaptor.retrieveDepositRouteV1(state: PerpetualState?) val fromChain = state?.input?.transfer?.chain val fromToken = state?.input?.transfer?.token val fromAmount = parser.asDecimal(state?.input?.transfer?.size?.size)?.let { - val decimals = parser.asInt(stateMachine.squidProcessor.selectedTokenDecimals(fromToken)) + val decimals = parser.asInt(stateMachine.squidProcessor.selectedTokenDecimals(tokenAddress = fromToken, selectedChainId = fromChain)) if (decimals != null) { (it * Numeric.decimal.TEN.pow(decimals)).toBigInteger() } else { @@ -144,7 +144,7 @@ private fun V4StateManagerAdaptor.retrieveDepositRouteV2(state: PerpetualState?) val fromChain = state?.input?.transfer?.chain val fromToken = state?.input?.transfer?.token val fromAmount = parser.asDecimal(state?.input?.transfer?.size?.size)?.let { - val decimals = parser.asInt(stateMachine.squidProcessor.selectedTokenDecimals(fromToken)) + val decimals = parser.asInt(stateMachine.squidProcessor.selectedTokenDecimals(tokenAddress = fromToken, selectedChainId = fromChain)) if (decimals != null) { (it * Numeric.decimal.TEN.pow(decimals)).toBigInteger() } else { @@ -273,7 +273,7 @@ internal fun V4StateManagerAdaptor.retrieveWithdrawalRoute( val isCctp = cctpChainIds?.any { it.isCctpEnabled(state?.input?.transfer) } ?: false val isExchange = state?.input?.transfer?.exchange != null when (appConfigs.squidVersion) { - AppConfigs.SquidVersion.V1, AppConfigs.SquidVersion.V2DepositOnly -> retrieveWithdrawalRouteV1( + AppConfigs.SquidVersion.V2DepositOnly -> retrieveWithdrawalRouteV1( state, decimals, gas, diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/V4StateManagerAdaptor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/V4StateManagerAdaptor.kt index 38db5c706..0920ceaeb 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/V4StateManagerAdaptor.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/V4StateManagerAdaptor.kt @@ -27,8 +27,6 @@ import exchange.dydx.abacus.state.model.onChainRewardTokenPrice import exchange.dydx.abacus.state.model.onChainRewardsParams import exchange.dydx.abacus.state.model.onChainUserFeeTier import exchange.dydx.abacus.state.model.onChainUserStats -import exchange.dydx.abacus.state.model.squidChains -import exchange.dydx.abacus.state.model.squidTokens import exchange.dydx.abacus.state.model.squidV2SdkInfo import exchange.dydx.abacus.state.model.updateHeight import exchange.dydx.abacus.utils.CoroutineTimer @@ -294,20 +292,8 @@ class V4StateManagerAdaptor( override fun didSetReadyToConnect(readyToConnect: Boolean) { super.didSetReadyToConnect(readyToConnect) if (readyToConnect) { - when (appConfigs.squidVersion) { - AppConfigs.SquidVersion.V1 -> { - retrieveTransferChains() - retrieveTransferTokens() - } - - AppConfigs.SquidVersion.V2, - AppConfigs.SquidVersion.V2DepositOnly, - AppConfigs.SquidVersion.V2WithdrawalOnly -> { - retrieveTransferAssets() - retrieveCctpChainIds() - } - } - + retrieveTransferAssets() + retrieveCctpChainIds() retrieveDepositExchanges() bestEffortConnectChain() retrieveLaunchIncentiveSeasons() @@ -491,6 +477,9 @@ class V4StateManagerAdaptor( } private fun pollNobleBalance() { + if (cosmosWalletConnected == true) { + return + } val timer = ioImplementations.timer ?: CoroutineTimer.instance nobleBalancesTimer = timer.schedule(0.0, nobleBalancePollingDuration) { if (validatorConnected && accountAddress != null) { @@ -734,20 +723,6 @@ class V4StateManagerAdaptor( } } - private fun retrieveTransferChains() { - val oldState = stateMachine.state - val url = configs.squidChains() - val squidIntegratorId = environment.squidIntegratorId - if (url != null && squidIntegratorId != null) { - val header = iMapOf("x-integrator-id" to squidIntegratorId) - get(url, null, header) { _, response, httpCode, _ -> - if (success(httpCode) && response != null) { - update(stateMachine.squidChains(response), oldState) - } - } - } - } - private fun retrieveTransferAssets() { val oldState = stateMachine.state val url = configs.squidV2Assets() @@ -762,20 +737,6 @@ class V4StateManagerAdaptor( } } - private fun retrieveTransferTokens() { - val oldState = stateMachine.state - val url = configs.squidToken() - val squidIntegratorId = environment.squidIntegratorId - if (url != null && squidIntegratorId != null) { - val header = iMapOf("x-integrator-id" to squidIntegratorId) - get(url, null, header) { _, response, httpCode, _ -> - if (success(httpCode) && response != null) { - update(stateMachine.squidTokens(response), oldState) - } - } - } - } - private fun parseHeight(response: String) { val json = parser.decodeJsonObject(response) if (json != null && json["error"] != 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 79d3aaa45..71967078c 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 @@ -115,10 +115,23 @@ class V4StateManagerConfigs( return if (environment.isMainNet) "noble-1" else "grand-1" } + fun skipV1Chains(): String { + return "$skipHost/v1/info/chains?include_evm=true" + } + + fun skipV1Assets(): String { + return "$skipHost/v1/fungible/assets?include_evm_assets=true" + } + fun nobleDenom(): String? { return "uusdc" } + private val skipHost: String + get() { + return "https://api.skip.money" + } + private val squidV2Host: String get() { return if (environment.isMainNet) { diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/utils/Settings.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/utils/Settings.kt index 1744f02c9..8eed0e3ba 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/utils/Settings.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/utils/Settings.kt @@ -10,12 +10,11 @@ class AppConfigs( var enableLogger: Boolean = false, ) { enum class SquidVersion { - V1, V2, V2DepositOnly, V2WithdrawalOnly, } - var squidVersion: SquidVersion = SquidVersion.V1 + var squidVersion: SquidVersion = SquidVersion.V2 companion object { val forApp = AppConfigs(subscribeToCandles = true, loadRemote = true) diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/model/TradingStateMachine+Squid.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/model/TradingStateMachine+Squid.kt index ddb14d6a9..a65417958 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/model/TradingStateMachine+Squid.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/model/TradingStateMachine+Squid.kt @@ -6,7 +6,7 @@ import kollections.iListOf import kotlinx.serialization.json.Json import kotlinx.serialization.json.jsonObject -internal fun TradingStateMachine.squidChains(payload: String): StateChanges? { +internal fun TradingStateMachine.routerChains(payload: String): StateChanges? { val json = parser.decodeJsonObject(payload) return if (json != null) { input = squidProcessor.receivedChains(input, json) @@ -16,7 +16,7 @@ internal fun TradingStateMachine.squidChains(payload: String): StateChanges? { } } -internal fun TradingStateMachine.squidTokens(payload: String): StateChanges? { +internal fun TradingStateMachine.routerTokens(payload: String): StateChanges? { val json = parser.decodeJsonObject(payload) return if (json != null) { input = squidProcessor.receivedTokens(input, json) diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/model/TradingStateMachine+TransferInput.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/model/TradingStateMachine+TransferInput.kt index 2cc152517..89265d288 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/model/TradingStateMachine+TransferInput.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/model/TradingStateMachine+TransferInput.kt @@ -25,6 +25,7 @@ enum class TransferInputField(val rawValue: String) { chain("chain"), token("token"), address("address"), + MEMO("memo"), fastSpeed("fastSpeed"); companion object { @@ -69,6 +70,7 @@ fun TradingStateMachine.transfer( transfer.safeSet("size.usdcSize", null) transfer.safeSet("route", null) transfer.safeSet("requestPayload", null) + transfer.safeSet("memo", null) if (parser.asString(data) == "TRANSFER_OUT") { transfer.safeSet("chain", "chain") transfer.safeSet("token", "usdc") @@ -135,7 +137,6 @@ fun TradingStateMachine.transfer( iListOf(subaccountNumber), ) } - TransferInputField.fastSpeed.rawValue -> { transfer.safeSet(typeText, parser.asBool(data)) changes = StateChanges( @@ -166,6 +167,14 @@ fun TradingStateMachine.transfer( iListOf(subaccountNumber), ) } + TransferInputField.MEMO.rawValue -> { + transfer.safeSet(typeText, parser.asString(data)) + changes = StateChanges( + iListOf(Changes.input), + null, + iListOf(subaccountNumber), + ) + } else -> {} } } else { @@ -183,18 +192,19 @@ fun TradingStateMachine.transfer( return StateResponse(state, changes, if (error != null) iListOf(error) else null) } -private fun TradingStateMachine.updateTransferToTokenType(transfer: MutableMap, token: String) { +private fun TradingStateMachine.updateTransferToTokenType(transfer: MutableMap, tokenAddress: String) { + val selectedChainId = transfer["chain"] as? String if (transfer["type"] == "TRANSFER_OUT") { transfer.safeSet("size.usdcSize", null) transfer.safeSet("size.size", null) } else { transfer.safeSet( "resources.tokenSymbol", - squidProcessor.selectedTokenSymbol(token), + squidProcessor.selectedTokenSymbol(tokenAddress = tokenAddress, selectedChainId = selectedChainId), ) transfer.safeSet( "resources.tokenDecimals", - squidProcessor.selectedTokenDecimals(token), + squidProcessor.selectedTokenDecimals(tokenAddress = tokenAddress, selectedChainId = selectedChainId), ) } transfer.safeSet("route", null) @@ -204,35 +214,44 @@ private fun TradingStateMachine.updateTransferToTokenType(transfer: MutableMap, chainType: String) { val tokenOptions = squidProcessor.tokenOptions(chainType) if (transfer["type"] != "TRANSFER_OUT") { - transfer.safeSet( - "depositOptions.assets", - tokenOptions, - ) - transfer.safeSet( - "withdrawalOptions.assets", - tokenOptions, - ) + internalState.transfer.tokens = tokenOptions transfer.safeSet("chain", chainType) transfer.safeSet("token", squidProcessor.defaultTokenAddress(chainType)) - transfer.safeSet( - "resources.chainResources", - squidProcessor.chainResources(chainType), - ) - transfer.safeSet( - "resources.tokenResources", - squidProcessor.tokenResources(chainType), - ) + internalState.transfer.chainResources = squidProcessor.chainResources(chainType) + internalState.transfer.tokenResources = squidProcessor.tokenResources(chainType) } transfer.safeSet("exchange", null) transfer.safeSet("size.size", null) transfer.safeSet("route", null) transfer.safeSet("requestPayload", null) +// needed to pass tests, remove later + transfer.safeSet( + "depositOptions.assets", + tokenOptions, + ) + transfer.safeSet( + "withdrawalOptions.assets", + tokenOptions, + ) + transfer.safeSet( + "resources.chainResources", + squidProcessor.chainResources(chainType), + ) + transfer.safeSet( + "resources.tokenResources", + squidProcessor.tokenResources(chainType), + ) } private fun TradingStateMachine.updateTransferExchangeType(transfer: MutableMap, exchange: String) { val exchangeDestinationChainId = squidProcessor.exchangeDestinationChainId val tokenOptions = squidProcessor.tokenOptions(exchangeDestinationChainId) if (transfer["type"] != "TRANSFER_OUT") { + internalState.transfer.tokens = tokenOptions + transfer.safeSet("token", squidProcessor.defaultTokenAddress(exchangeDestinationChainId)) + internalState.transfer.tokenResources = squidProcessor.tokenResources(exchangeDestinationChainId) + +// needed to pass tests, remove later transfer.safeSet( "depositOptions.assets", tokenOptions, @@ -241,7 +260,6 @@ private fun TradingStateMachine.updateTransferExchangeType(transfer: MutableMap< "withdrawalOptions.assets", tokenOptions, ) - transfer.safeSet("token", squidProcessor.defaultTokenAddress(exchangeDestinationChainId)) transfer.safeSet( "resources.tokenResources", squidProcessor.tokenResources(exchangeDestinationChainId), diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/model/TradingStateMachine.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/model/TradingStateMachine.kt index e0cd84253..c08d384fa 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/model/TradingStateMachine.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/model/TradingStateMachine.kt @@ -32,7 +32,7 @@ import exchange.dydx.abacus.processor.assets.AssetsProcessor import exchange.dydx.abacus.processor.configs.ConfigsProcessor import exchange.dydx.abacus.processor.launchIncentive.LaunchIncentiveProcessor import exchange.dydx.abacus.processor.markets.MarketsSummaryProcessor -import exchange.dydx.abacus.processor.squid.SquidProcessor +import exchange.dydx.abacus.processor.router.squid.SquidProcessor import exchange.dydx.abacus.processor.wallet.WalletProcessor import exchange.dydx.abacus.protocols.LocalizerProtocol import exchange.dydx.abacus.protocols.ParserProtocol @@ -45,6 +45,7 @@ import exchange.dydx.abacus.state.app.adaptors.AbUrl import exchange.dydx.abacus.state.app.helper.Formatter import exchange.dydx.abacus.state.changes.Changes import exchange.dydx.abacus.state.changes.StateChanges +import exchange.dydx.abacus.state.internalstate.InternalState import exchange.dydx.abacus.state.manager.BlockAndTime import exchange.dydx.abacus.state.manager.EnvironmentFeatureFlags import exchange.dydx.abacus.state.manager.TokenInfo @@ -81,6 +82,8 @@ open class TradingStateMachine( private val maxSubaccountNumber: Int, private val useParentSubaccount: Boolean, ) { + internal val internalState: InternalState = InternalState() + internal val parser: ParserProtocol = Parser() internal val marketsProcessor = MarketsSummaryProcessor(parser) internal val assetsProcessor = run { @@ -90,7 +93,7 @@ open class TradingStateMachine( } internal val walletProcessor = WalletProcessor(parser) internal val configsProcessor = ConfigsProcessor(parser) - internal val squidProcessor = SquidProcessor(parser) + internal val squidProcessor = SquidProcessor(parser, internalState.transfer) internal val rewardsProcessor = RewardsProcessor(parser) internal val launchIncentiveProcessor = LaunchIncentiveProcessor(parser) @@ -909,24 +912,46 @@ open class TradingStateMachine( "trade" -> { val trade = parser.asNativeMap(input["trade"]) ?: return null val type = parser.asString(trade["type"]) ?: return null + val isolatedMargin = parser.asString(trade["marginMode"]) == "ISOLATED" return when (type) { "MARKET", "STOP_MARKET", "TAKE_PROFIT_MARKET", "TRAILING_STOP" -> { - listOf( - ReceiptLine.BuyingPower.rawValue, - ReceiptLine.MarginUsage.rawValue, - ReceiptLine.ExpectedPrice.rawValue, - ReceiptLine.Fee.rawValue, - ReceiptLine.Reward.rawValue, - ) + if (isolatedMargin) { + listOf( + ReceiptLine.ExpectedPrice.rawValue, + ReceiptLine.LiquidationPrice.rawValue, + ReceiptLine.PositionMargin.rawValue, + ReceiptLine.PositionLeverage.rawValue, + ReceiptLine.Fee.rawValue, + ReceiptLine.Reward.rawValue, + ) + } else { + listOf( + ReceiptLine.BuyingPower.rawValue, + ReceiptLine.MarginUsage.rawValue, + ReceiptLine.ExpectedPrice.rawValue, + ReceiptLine.Fee.rawValue, + ReceiptLine.Reward.rawValue, + ) + } } else -> { - listOf( - ReceiptLine.BuyingPower.rawValue, - ReceiptLine.MarginUsage.rawValue, - ReceiptLine.Fee.rawValue, - ReceiptLine.Reward.rawValue, - ) + if (isolatedMargin) { + listOf( + ReceiptLine.LiquidationPrice.rawValue, + ReceiptLine.PositionMargin.rawValue, + ReceiptLine.PositionLeverage.rawValue, + ReceiptLine.Fee.rawValue, + ReceiptLine.Reward.rawValue, + ) + } else { + listOf( + ReceiptLine.BuyingPower.rawValue, + ReceiptLine.MarginUsage.rawValue, + ReceiptLine.Fee.rawValue, + ReceiptLine.Reward.rawValue, + ) + } } } } @@ -1175,6 +1200,7 @@ open class TradingStateMachine( Account( account.balances, account.stakingBalances, + account.stakingDelegations, subaccounts, groupedSubaccounts, account.tradingRewards, @@ -1272,7 +1298,7 @@ open class TradingStateMachine( this.environment, ) this.input?.let { - input = Input.create(input, parser, it, environment) + input = Input.create(input, parser, it, environment, internalState) } } } diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/manager/AsyncAbacusStateManagerV2.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/manager/AsyncAbacusStateManagerV2.kt index 3c7f26d7e..8f21a5b0d 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/manager/AsyncAbacusStateManagerV2.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/manager/AsyncAbacusStateManagerV2.kt @@ -136,6 +136,7 @@ class AsyncAbacusStateManagerV2( value?.historicalPnlPeriod = historicalPnlPeriod value?.candlesResolution = candlesResolution value?.readyToConnect = readyToConnect + value?.cosmosWalletConnected = cosmosWalletConnected field = value } } @@ -182,6 +183,14 @@ class AsyncAbacusStateManagerV2( } } + override var cosmosWalletConnected: Boolean? = false + set(value) { + field = value + ioImplementations.threading?.async(ThreadingType.abacus) { + adaptor?.cosmosWalletConnected = field + } + } + override var sourceAddress: String? = null set(value) { field = value @@ -273,19 +282,27 @@ class AsyncAbacusStateManagerV2( } private fun load(configFile: ConfigFile) { - val path = configFile.path - if (appConfigs.loadRemote) { - loadFromRemoteConfigFile(configFile) - val configFileUrl = "$deploymentUri$path" - ioImplementations.rest?.get(configFileUrl, null, callback = { response, httpCode, headers -> - if (success(httpCode) && response != null) { - if (parse(response, configFile)) { - writeToLocalFile(response, path) - } - } - }) - } else { - loadFromBundledLocalConfigFile(configFile) + ioImplementations.threading?.async(ThreadingType.network) { + val path = configFile.path + if (appConfigs.loadRemote) { + loadFromRemoteConfigFile(configFile) + val configFileUrl = "$deploymentUri$path" + ioImplementations.rest?.get( + configFileUrl, + null, + callback = { response, httpCode, headers -> + ioImplementations.threading?.async(ThreadingType.abacus) { + if (success(httpCode) && response != null) { + if (parse(response, configFile)) { + writeToLocalFile(response, path) + } + } + } + }, + ) + } else { + loadFromBundledLocalConfigFile(configFile) + } } } @@ -293,7 +310,9 @@ class AsyncAbacusStateManagerV2( ioImplementations.fileSystem?.readCachedTextFile( configFile.path, )?.let { - parse(it, configFile) + ioImplementations.threading?.async(ThreadingType.abacus) { + parse(it, configFile) + } } } @@ -302,7 +321,9 @@ class AsyncAbacusStateManagerV2( FileLocation.AppBundle, configFile.path, )?.let { - parse(it, configFile) + ioImplementations.threading?.async(ThreadingType.abacus) { + parse(it, configFile) + } } } @@ -322,10 +343,12 @@ class AsyncAbacusStateManagerV2( } private fun writeToLocalFile(response: String, file: String) { - ioImplementations.fileSystem?.writeTextFile( - file, - response, - ) + ioImplementations.threading?.async(ThreadingType.network) { + ioImplementations.fileSystem?.writeTextFile( + file, + response, + ) + } } private fun parseDocumentation(response: String) { diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/manager/StateManagerAdaptorV2.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/manager/StateManagerAdaptorV2.kt index dd01e261e..1cbb0483b 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/manager/StateManagerAdaptorV2.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/manager/StateManagerAdaptorV2.kt @@ -64,6 +64,7 @@ import exchange.dydx.abacus.state.v2.supervisor.commitClosePosition import exchange.dydx.abacus.state.v2.supervisor.commitPlaceOrder import exchange.dydx.abacus.state.v2.supervisor.commitTriggerOrders import exchange.dydx.abacus.state.v2.supervisor.connectedSubaccountNumber +import exchange.dydx.abacus.state.v2.supervisor.cosmosWalletConnected import exchange.dydx.abacus.state.v2.supervisor.depositPayload import exchange.dydx.abacus.state.v2.supervisor.faucet import exchange.dydx.abacus.state.v2.supervisor.marketId @@ -259,6 +260,14 @@ internal class StateManagerAdaptorV2( accounts.accountAddress = value } + internal var cosmosWalletConnected: Boolean? + get() { + return accounts.cosmosWalletConnected + } + set(value) { + accounts.cosmosWalletConnected = value + } + internal var sourceAddress: String? get() { return accounts.sourceAddress diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/AccountSupervisor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/AccountSupervisor.kt index 76041ee67..42468d7f1 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/AccountSupervisor.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/AccountSupervisor.kt @@ -113,6 +113,8 @@ internal open class AccountSupervisor( } } + var cosmosWalletConnected: Boolean? = false + private var sourceAddressRestriction: Restriction? = null set(value) { if (field != value) { diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/AccountsSupervisor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/AccountsSupervisor.kt index 0f6244a18..7d94d9ab7 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/AccountsSupervisor.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/AccountsSupervisor.kt @@ -208,6 +208,14 @@ internal var AccountsSupervisor.accountAddress: String? } } +internal var AccountsSupervisor.cosmosWalletConnected: Boolean? + get() { + return account?.cosmosWalletConnected + } + set(value) { + account?.cosmosWalletConnected = value + } + internal var AccountsSupervisor.sourceAddress: String? get() { return account?.sourceAddress diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/Configs.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/Configs.kt index e1317f371..7fdabee38 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/Configs.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/Configs.kt @@ -164,13 +164,12 @@ data class OnboardingConfigs( val retrieveSquidRoutes: Boolean, ) { enum class SquidVersion { - V1, V2, V2DepositOnly, V2WithdrawalOnly, } - var squidVersion: SquidVersion = SquidVersion.V1 + var squidVersion: SquidVersion = SquidVersion.V2 companion object { val forApp = OnboardingConfigs( diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/OnboardingSupervisor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/OnboardingSupervisor.kt index 1ba83abd1..4214fb79b 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/OnboardingSupervisor.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/OnboardingSupervisor.kt @@ -24,11 +24,11 @@ import exchange.dydx.abacus.state.manager.HumanReadableWithdrawPayload import exchange.dydx.abacus.state.manager.pendingCctpWithdraw import exchange.dydx.abacus.state.model.TradingStateMachine import exchange.dydx.abacus.state.model.TransferInputField -import exchange.dydx.abacus.state.model.squidChains +import exchange.dydx.abacus.state.model.routerChains +import exchange.dydx.abacus.state.model.routerTokens import exchange.dydx.abacus.state.model.squidRoute import exchange.dydx.abacus.state.model.squidRouteV2 import exchange.dydx.abacus.state.model.squidStatus -import exchange.dydx.abacus.state.model.squidTokens import exchange.dydx.abacus.state.model.squidV2SdkInfo import exchange.dydx.abacus.state.model.transfer import exchange.dydx.abacus.utils.AnalyticsUtils @@ -67,31 +67,30 @@ internal class OnboardingSupervisor( } private fun retrieveSquidRoutes() { - when (configs.squidVersion) { - OnboardingConfigs.SquidVersion.V1 -> { - retrieveTransferChains() - retrieveTransferTokens() - } + retrieveTransferAssets() + retrieveCctpChainIds() + } - OnboardingConfigs.SquidVersion.V2, - OnboardingConfigs.SquidVersion.V2DepositOnly, - OnboardingConfigs.SquidVersion.V2WithdrawalOnly -> { - retrieveTransferAssets() - retrieveCctpChainIds() + @Suppress("UnusedPrivateMember") + private fun retrieveSkipTransferChains() { + val oldState = stateMachine.state + val chainsUrl = helper.configs.skipV1Chains() + helper.get(chainsUrl, null, null) { _, response, httpCode, _ -> + if (helper.success(httpCode) && response != null) { + update(stateMachine.routerChains(response), oldState) } } } - private fun retrieveTransferChains() { + @Suppress("UnusedPrivateMember") + private fun retrieveSkipTransferTokens() { val oldState = stateMachine.state - val url = helper.configs.squidChains() - val squidIntegratorId = helper.environment.squidIntegratorId - if (url != null && squidIntegratorId != null) { - val header = iMapOf("x-integrator-id" to squidIntegratorId) - helper.get(url, null, header) { _, response, httpCode, _ -> - if (helper.success(httpCode) && response != null) { - update(stateMachine.squidChains(response), oldState) - } + val tokensUrl = helper.configs.skipV1Assets() +// add API key injection +// val header = iMapOf("authorization" to skipAPIKey) + helper.get(tokensUrl, null, null) { _, response, httpCode, _ -> + if (helper.success(httpCode) && response != null) { + update(stateMachine.routerTokens(response), oldState) } } } @@ -110,20 +109,6 @@ internal class OnboardingSupervisor( } } - private fun retrieveTransferTokens() { - val oldState = stateMachine.state - val url = helper.configs.squidToken() - val squidIntegratorId = helper.environment.squidIntegratorId - if (url != null && squidIntegratorId != null) { - val header = iMapOf("x-integrator-id" to squidIntegratorId) - helper.get(url, null, header) { _, response, httpCode, _ -> - if (helper.success(httpCode) && response != null) { - update(stateMachine.squidTokens(response), oldState) - } - } - } - } - private fun retrieveCctpChainIds() { val url = "${helper.deploymentUri}/configs/cctp.json" helper.get(url) { _, response, _, _ -> @@ -176,7 +161,7 @@ internal class OnboardingSupervisor( ) { val isCctp = state?.input?.transfer?.isCctp ?: false when (configs.squidVersion) { - OnboardingConfigs.SquidVersion.V1, OnboardingConfigs.SquidVersion.V2WithdrawalOnly -> retrieveDepositRouteV1( + OnboardingConfigs.SquidVersion.V2WithdrawalOnly -> retrieveDepositRouteV1( state, accountAddress, sourceAddress, @@ -212,7 +197,7 @@ internal class OnboardingSupervisor( val fromToken = state?.input?.transfer?.token val fromAmount = helper.parser.asDecimal(state?.input?.transfer?.size?.size)?.let { val decimals = - helper.parser.asInt(stateMachine.squidProcessor.selectedTokenDecimals(fromToken)) + helper.parser.asInt(stateMachine.squidProcessor.selectedTokenDecimals(tokenAddress = fromToken, selectedChainId = fromChain)) if (decimals != null) { (it * Numeric.decimal.TEN.pow(decimals)).toBigInteger() } else { @@ -274,7 +259,7 @@ internal class OnboardingSupervisor( val fromToken = state?.input?.transfer?.token val fromAmount = helper.parser.asDecimal(state?.input?.transfer?.size?.size)?.let { val decimals = - helper.parser.asInt(stateMachine.squidProcessor.selectedTokenDecimals(fromToken)) + helper.parser.asInt(stateMachine.squidProcessor.selectedTokenDecimals(tokenAddress = fromToken, selectedChainId = fromChain)) if (decimals != null) { (it * Numeric.decimal.TEN.pow(decimals)).toBigInteger() } else { @@ -530,7 +515,7 @@ internal class OnboardingSupervisor( CctpConfig.cctpChainIds?.any { it.isCctpEnabled(state?.input?.transfer) } ?: false val isExchange = state?.input?.transfer?.exchange != null when (configs.squidVersion) { - OnboardingConfigs.SquidVersion.V1, OnboardingConfigs.SquidVersion.V2DepositOnly -> retrieveWithdrawalRouteV1( + OnboardingConfigs.SquidVersion.V2DepositOnly -> retrieveWithdrawalRouteV1( state, decimals, gas, 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 3acf36469..19b831a98 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 @@ -836,7 +836,7 @@ internal class SubaccountSupervisor( val isIsolatedMarginOrder = helper.parser.asInt(orderPayload.subaccountNumber) != subaccountNumber val transferPayload = - if (isIsolatedMarginOrder) getTransferPayloadForIsolatedMarginTrade(orderPayload) else null + if (isIsolatedMarginOrder && orderPayload.reduceOnly != true) getTransferPayloadForIsolatedMarginTrade(orderPayload) else null val uiClickTimeMs = trackOrderClick(analyticsPayload, AnalyticsEvent.TradePlaceOrderClick) return submitPlaceOrder(callback, orderPayload, analyticsPayload, uiClickTimeMs, false, transferPayload) @@ -1049,7 +1049,7 @@ internal class SubaccountSupervisor( val reduceOnly = true val postOnly = false - // TP/SL orders always have a null timeInForce. IOC/FOK/PostOnly/GTD is distinguished by the execution field. + // TP/SL orders always have a null timeInForce. IOC/PostOnly/GTD is distinguished by the execution field. val timeInForce = null; /** diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/validator/TriggerOrdersInputValidator.kt b/src/commonMain/kotlin/exchange.dydx.abacus/validator/TriggerOrdersInputValidator.kt index 32f7e2b17..9c5c65adf 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/validator/TriggerOrdersInputValidator.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/validator/TriggerOrdersInputValidator.kt @@ -1,8 +1,6 @@ package exchange.dydx.abacus.validator import abs import exchange.dydx.abacus.output.input.OrderSide -import exchange.dydx.abacus.output.input.OrderStatus -import exchange.dydx.abacus.output.input.OrderTimeInForce import exchange.dydx.abacus.output.input.OrderType import exchange.dydx.abacus.protocols.LocalizerProtocol import exchange.dydx.abacus.protocols.ParserProtocol @@ -11,8 +9,6 @@ import exchange.dydx.abacus.state.manager.BlockAndTime import exchange.dydx.abacus.state.manager.V4Environment import exchange.dydx.abacus.state.model.TriggerOrdersInputField import exchange.dydx.abacus.utils.Rounder -import exchange.dydx.abacus.validator.trade.EquityTier -import exchange.dydx.abacus.validator.trade.SubaccountLimitConstants.MAX_NUM_OPEN_UNTRIGGERED_ORDERS enum class RelativeToPrice(val rawValue: String) { ABOVE("ABOVE"), @@ -47,7 +43,8 @@ internal class TriggerOrdersInputValidator( val marketId = parser.asString(transaction["marketId"]) ?: return null val market = parser.asNativeMap(markets?.get(marketId)) - val position = parser.asNativeMap(parser.value(subaccount, "openPositions.$marketId")) ?: return null + val position = parser.asNativeMap(parser.value(subaccount, "openPositions.$marketId")) + ?: return null val tickSize = parser.asString(parser.value(market, "configs.tickSize")) ?: "0.01" val oraclePrice = parser.asDouble( parser.value( @@ -56,7 +53,7 @@ internal class TriggerOrdersInputValidator( ), ) ?: return null - validateTriggerOrders(transaction, market, subaccount, configs, environment)?.let { + validateTriggerOrders(transaction, market)?.let { errors.addAll(it) } @@ -103,17 +100,8 @@ internal class TriggerOrdersInputValidator( private fun validateTriggerOrders( triggerOrders: Map, market: Map?, - subaccount: Map?, - configs: Map?, - environment: V4Environment?, ): MutableList? { val triggerErrors = mutableListOf() - validateOrderCount(triggerOrders, subaccount, configs, environment)?.let { - /* - USER_MAX_ORDERS - */ - triggerErrors.addAll(it) - } validateSize(parser.asDouble(triggerOrders["size"]), market)?.let { /* AMOUNT_INPUT_STEP_SIZE @@ -206,6 +194,7 @@ internal class TriggerOrdersInputValidator( null } } + RelativeToPrice.BELOW -> { if (triggerPrice >= liquidationPrice) { liquidationPriceError( @@ -219,6 +208,7 @@ internal class TriggerOrdersInputValidator( null } } + else -> null } } @@ -249,137 +239,6 @@ internal class TriggerOrdersInputValidator( ) } - private fun validateOrderCount( - triggerOrders: Map, - subaccount: Map?, - configs: Map?, - environment: V4Environment?, - ): List? { - val equityTier = equityTier(subaccount, configs) - - val fallbackMaxNumOrders = MAX_NUM_OPEN_UNTRIGGERED_ORDERS - - val equityTierLimit = equityTier?.maxOrders ?: fallbackMaxNumOrders - val nextLevelRequiredTotalNetCollateralUSD = - equityTier?.nextLevelRequiredTotalNetCollateralUSD - val numOrders = orderCount(subaccount) - var numOrdersToCreate = 0 - var numOrdersToCancel = 0 - - if (parser.value(triggerOrders, "stopLossOrder.price.triggerPrice") != null && parser.value(triggerOrders, "stopLossOrder.orderId") == null) { - numOrdersToCreate += 1 - } else if (parser.value(triggerOrders, "stopLossOrder.price.triggerPrice") == null && parser.value(triggerOrders, "stopLossOrder.orderId") != null) { - numOrdersToCancel += 1 - } - if (parser.value(triggerOrders, "takeProfitOrder.price.triggerPrice") != null && parser.value(triggerOrders, "takeProfitOrder.orderId") == null) { - numOrdersToCreate += 1 - } else if (parser.value(triggerOrders, "takeProfitOrder.price.triggerPrice") == null && parser.value(triggerOrders, "takeProfitOrder.orderId") != null) { - numOrdersToCancel += 1 - } - - val documentation = environment?.links?.documentation - val link = if (documentation != null) "$documentation/trading/other_limits" else null - - return if ((numOrders + numOrdersToCreate - numOrdersToCancel) > equityTierLimit) { - listOf( - if (nextLevelRequiredTotalNetCollateralUSD != null) { - error( - "ERROR", - "USER_MAX_ORDERS", - null, // No input field since the error is not related to a specific field - null, - "ERRORS.TRADE_BOX_TITLE.USER_MAX_ORDERS", - "ERRORS.TRADE_BOX.USER_MAX_ORDERS_FOR_CURRENT_EQUITY_TIER", - mapOf( - "EQUITY" to mapOf( - "value" to nextLevelRequiredTotalNetCollateralUSD, - "format" to "price", - ), - "LIMIT" to mapOf( - "value" to equityTierLimit, - "format" to "string", - ), - ), - null, - link, - ) - } else { - error( - "ERROR", - "USER_MAX_ORDERS", - null, // No input field since the error is not related to a specific field - null, - "ERRORS.TRADE_BOX_TITLE.USER_MAX_ORDERS", - "ERRORS.TRADE_BOX.USER_MAX_ORDERS_FOR_TOP_EQUITY_TIER", - mapOf( - "LIMIT" to mapOf( - "value" to equityTierLimit, - "format" to "string", - ), - ), - null, - link, - ) - }, - ) - } else { - null - } - } - - private fun equityTier( - subaccount: Map?, - configs: Map? - ): EquityTier? { - var equityTier: EquityTier? = null - val equity: Double = parser.asDouble(parser.value(subaccount, "equity.current")) ?: 0.0 - parser.asNativeMap(parser.value(configs, "equityTiers"))?.let { equityTiers -> - parser.asNativeList(equityTiers["statefulOrderEquityTiers"])?.let { tiers -> - if (tiers.isEmpty()) return null - for (tier in tiers) { - parser.asNativeMap(tier)?.let { item -> - val requiredTotalNetCollateralUSD = - parser.asDouble(item["requiredTotalNetCollateralUSD"]) ?: 0.0 - if (requiredTotalNetCollateralUSD <= equity) { - val maxNumOrders = parser.asInt(item["maxOrders"]) ?: 0 - equityTier = EquityTier( - requiredTotalNetCollateralUSD, - maxNumOrders, - ) - } else if (equityTier?.nextLevelRequiredTotalNetCollateralUSD == null) { - equityTier?.nextLevelRequiredTotalNetCollateralUSD = - requiredTotalNetCollateralUSD - } - } - } - } - } ?: run { - return null - } - return equityTier - } - - private fun orderCount( - subaccount: Map?, - ): Int { - var count = 0 - parser.asNativeMap(subaccount?.get("orders"))?.let { orders -> - for ((_, item) in orders) { - parser.asNativeMap(item)?.let { order -> - val status = parser.asString(order["status"])?.let { OrderStatus.invoke(it) } - val orderType = parser.asString(order["type"])?.let { OrderType.invoke(it) } - val timeInForce = parser.asString(order["timeInForce"])?.let { OrderTimeInForce.invoke(it) } - if (orderType != null && timeInForce != null && status != null) { - if (isOrderIncludedInEquityTierLimit(orderType, timeInForce, status)) { - count += 1 - } - } - } - } - } - return count - } - private fun validateRequiredInput( triggerOrder: Map, ): List? { @@ -390,7 +249,11 @@ internal class TriggerOrdersInputValidator( if (triggerPrice == null && limitPrice != null) { errors.add( - required("REQUIRED_TRIGGER_PRICE", "price.triggerPrice", "APP.TRADE.ENTER_TRIGGER_PRICE"), + required( + "REQUIRED_TRIGGER_PRICE", + "price.triggerPrice", + "APP.TRADE.ENTER_TRIGGER_PRICE", + ), ) } @@ -553,6 +416,7 @@ internal class TriggerOrdersInputValidator( } } } + else -> null } } @@ -585,8 +449,7 @@ internal class TriggerOrdersInputValidator( parser.asNativeMap(market?.get("configs"))?.let { configs -> val errors = mutableListOf>() - parser.asDouble(configs["stepSize"])?.let { - stepSize -> + parser.asDouble(configs["stepSize"])?.let { stepSize -> if (Rounder.round(size, stepSize) != size) { errors.add( error( @@ -695,39 +558,6 @@ internal class TriggerOrdersInputValidator( } } -private fun isOrderIncludedInEquityTierLimit( - orderType: OrderType, - timeInForce: OrderTimeInForce, - status: OrderStatus -): Boolean { - val isCurrentOrderStateful = isStatefulOrder(orderType, timeInForce) - // Short term with IOC or FOK should not be counted - val isShortTermAndRequiresImmediateExecution = - !isCurrentOrderStateful && (timeInForce == OrderTimeInForce.IOC || timeInForce == OrderTimeInForce.FOK) - - return if (!isShortTermAndRequiresImmediateExecution) { - when (status) { - OrderStatus.open, OrderStatus.pending, OrderStatus.untriggered, OrderStatus.partiallyFilled -> true - else -> false - } - } else { - false - } -} - -private fun isStatefulOrder(orderType: OrderType, timeInForce: OrderTimeInForce): Boolean { - return when (orderType) { - OrderType.market -> false - OrderType.limit -> { - when (timeInForce) { - OrderTimeInForce.GTT -> true - else -> false - } - } - else -> true - } -} - private fun requiredTriggerToLiquidationPrice(type: OrderType?, side: OrderSide): RelativeToPrice? { return when (type) { OrderType.stopMarket -> diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeInputDataValidator.kt b/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeInputDataValidator.kt index 69ebfc2d7..a2e58bc24 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeInputDataValidator.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeInputDataValidator.kt @@ -10,7 +10,6 @@ import exchange.dydx.abacus.utils.Rounder import exchange.dydx.abacus.validator.BaseInputValidator import exchange.dydx.abacus.validator.PositionChange import exchange.dydx.abacus.validator.TradeValidatorProtocol -import exchange.dydx.abacus.validator.trade.SubaccountLimitConstants.MAX_NUM_OPEN_UNTRIGGERED_ORDERS import kotlin.time.Duration.Companion.days /* @@ -29,17 +28,6 @@ LIMIT_PRICE_TRIGGER_PRICE_SLIPPAGE_LOWER */ -internal data class EquityTier( - val requiredTotalNetCollateralUSD: Double, - val maxOrders: Int, -) { - var nextLevelRequiredTotalNetCollateralUSD: Double? = null -} - -internal object SubaccountLimitConstants { - const val MAX_NUM_OPEN_UNTRIGGERED_ORDERS = 20 -} - internal class TradeInputDataValidator( localizer: LocalizerProtocol?, formatter: Formatter?, @@ -56,23 +44,14 @@ internal class TradeInputDataValidator( restricted: Boolean, environment: V4Environment?, ): List? { - return validateTradeInput(subaccount, market, configs, trade, environment) + return validateTradeInput(market, trade) } private fun validateTradeInput( - subaccount: Map?, market: Map?, - configs: Map?, trade: Map, - environment: V4Environment?, ): List? { val errors = mutableListOf() - validateOrder(trade, subaccount, configs, environment)?.let { - /* - USER_MAX_ORDERS - */ - errors.addAll(it) - } validateSize(trade, market)?.let { /* AMOUNT_INPUT_STEP_SIZE @@ -103,150 +82,6 @@ internal class TradeInputDataValidator( return if (errors.size > 0) errors else null } - private fun validateOrder( - trade: Map, - subaccount: Map?, - configs: Map?, - environment: V4Environment?, - ): List? { - /* - USER_MAX_ORDERS - */ - val fallbackMaxNumOrders = MAX_NUM_OPEN_UNTRIGGERED_ORDERS - val orderType = parser.asString(trade["type"]) - val timeInForce = parser.asString(trade["timeInForce"]) - - if (orderType == null || timeInForce == null) return null - - val equityTier = - equityTier(isStatefulOrder(orderType, timeInForce), subaccount, configs) - val equityTierLimit = equityTier?.maxOrders ?: fallbackMaxNumOrders - val nextLevelRequiredTotalNetCollateralUSD = - equityTier?.nextLevelRequiredTotalNetCollateralUSD - val numOrders = orderCount(isStatefulOrder(orderType, timeInForce), subaccount) - - // Equity tier limit is not applicable for `MARKET` orders and `LIMIT` orders with FOK or IOC time in force - val isEquityTierLimitApplicable = orderType != "MARKET" && - !(orderType == "LIMIT" && (timeInForce == "FOK" || timeInForce == "IOC")) - - val documentation = environment?.links?.documentation - val link = if (documentation != null) "$documentation/trading/other_limits" else null - return if (numOrders >= equityTierLimit && isEquityTierLimitApplicable) { - listOf( - if (nextLevelRequiredTotalNetCollateralUSD != null) { - error( - "ERROR", - "USER_MAX_ORDERS", - null, - null, - "ERRORS.TRADE_BOX_TITLE.USER_MAX_ORDERS", - "ERRORS.TRADE_BOX.USER_MAX_ORDERS_FOR_CURRENT_EQUITY_TIER", - mapOf( - "EQUITY" to mapOf( - "value" to nextLevelRequiredTotalNetCollateralUSD, - "format" to "price", - ), - "LIMIT" to mapOf( - "value" to equityTierLimit, - "format" to "string", - ), - ), - null, - link, - ) - } else { - error( - "ERROR", - "USER_MAX_ORDERS", - null, - null, - "ERRORS.TRADE_BOX_TITLE.USER_MAX_ORDERS", - "ERRORS.TRADE_BOX.USER_MAX_ORDERS_FOR_TOP_EQUITY_TIER", - mapOf( - "LIMIT" to mapOf( - "value" to equityTierLimit, - "format" to "string", - ), - ), - null, - link, - ) - }, - ) - } else { - null - } - } - - private fun equityTier( - isStatefulOrder: Boolean, - subaccount: Map?, - configs: Map? - ): EquityTier? { - /* - USER_MAX_ORDERS according to Equity Tier - */ - var equityTier: EquityTier? = null - val equity: Double = parser.asDouble(parser.value(subaccount, "equity.current")) ?: 0.0 - val equityTierKey: String = - if (isStatefulOrder) "statefulOrderEquityTiers" else "shortTermOrderEquityTiers" - parser.asNativeMap(parser.value(configs, "equityTiers"))?.let { equityTiers -> - parser.asNativeList(equityTiers[equityTierKey])?.let { tiers -> - if (tiers.isEmpty()) return null - for (tier in tiers) { - parser.asNativeMap(tier)?.let { item -> - val requiredTotalNetCollateralUSD = - parser.asDouble(item["requiredTotalNetCollateralUSD"]) ?: 0.0 - if (requiredTotalNetCollateralUSD <= equity) { - val maxNumOrders = parser.asInt(item["maxOrders"]) ?: 0 - equityTier = EquityTier( - requiredTotalNetCollateralUSD, - maxNumOrders, - ) - } else if (equityTier?.nextLevelRequiredTotalNetCollateralUSD == null) { - equityTier?.nextLevelRequiredTotalNetCollateralUSD = - requiredTotalNetCollateralUSD - } - } - } - } - } ?: run { - return null - } - - return equityTier - } - - private fun orderCount( - shouldCountStatefulOrders: Boolean, - subaccount: Map?, - ): Int { - var count = 0 - parser.asNativeMap(subaccount?.get("orders"))?.let { orders -> - for ((_, item) in orders) { - parser.asNativeMap(item)?.let { order -> - val status = parser.asString(order["status"]) - val orderType = parser.asString(order["type"]) - val timeInForce = parser.asString(order["timeInForce"]) - if (orderType != null && timeInForce != null) { - val isCurrentOrderStateful = isStatefulOrder(orderType, timeInForce) - // Short term with IOC or FOK should not be counted - val isShortTermAndRequiresImmediateExecution = - !isCurrentOrderStateful && (timeInForce == "IOC" || timeInForce == "FOK") - if (!isShortTermAndRequiresImmediateExecution && - (status == "OPEN" || status == "PENDING" || status == "UNTRIGGERED" || status == "PARTIALLY_FILLED") && - (isCurrentOrderStateful == shouldCountStatefulOrders) - ) { - count += 1 - } - } - } - } - } - - return count - } - private fun validateSize( trade: Map, market: Map?, @@ -320,7 +155,7 @@ internal class TradeInputDataValidator( return when (parser.asString(trade["type"])) { "STOP_LIMIT", "TAKE_PROFIT" -> { val execution = parser.asString(trade["execution"]) - if (execution == "IOC" || execution == "FOK") { + if (execution == "IOC") { parser.asString(parser.value(trade, "side"))?.let { side -> parser.asDouble(parser.value(trade, "price.limitPrice")) ?.let { limitPrice -> @@ -402,19 +237,4 @@ internal class TradeInputDataValidator( null } } - - private fun isStatefulOrder(orderType: String, timeInForce: String): Boolean { - return when (orderType) { - "MARKET" -> false - - "LIMIT" -> { - when (parser.asString(timeInForce)) { - "GTT" -> true - else -> false - } - } - - else -> true - } - } } diff --git a/src/commonTest/kotlin/exchange.dydx.abacus/app/manager/V4ForegroundCycleTests.kt b/src/commonTest/kotlin/exchange.dydx.abacus/app/manager/V4ForegroundCycleTests.kt index 329cfa37a..c01fa1639 100644 --- a/src/commonTest/kotlin/exchange.dydx.abacus/app/manager/V4ForegroundCycleTests.kt +++ b/src/commonTest/kotlin/exchange.dydx.abacus/app/manager/V4ForegroundCycleTests.kt @@ -86,8 +86,8 @@ class V4ForegroundCycleTests : NetworkTests() { "https://indexer.v4staging.dydx.exchange/v4/time", "https://api.examples.com/configs/markets.json", "https://api.dydx.exchange/v4/geo", - "https://squid-api-git-main-cosmos-testnet-0xsquid.vercel.app/v1/chains", - "https://squid-api-git-main-cosmos-testnet-0xsquid.vercel.app/v1/tokens", + "https://testnet.v2.api.squidrouter.com/v2/sdk-info", + "https://api.examples.com/configs/cctp.json", "https://api.examples.com/configs/exchanges.json", "https://indexer.v4staging.dydx.exchange/v4/height", "https://dydx.exchange/v4-launch-incentive/query/ccar-perpetuals" @@ -169,8 +169,8 @@ class V4ForegroundCycleTests : NetworkTests() { "https://indexer.v4staging.dydx.exchange/v4/time", "https://api.examples.com/configs/markets.json", "https://api.dydx.exchange/v4/geo", - "https://squid-api-git-main-cosmos-testnet-0xsquid.vercel.app/v1/chains", - "https://squid-api-git-main-cosmos-testnet-0xsquid.vercel.app/v1/tokens", + "https://testnet.v2.api.squidrouter.com/v2/sdk-info", + "https://api.examples.com/configs/cctp.json", "https://api.examples.com/configs/exchanges.json", "https://indexer.v4staging.dydx.exchange/v4/height", "https://dydx.exchange/v4-launch-incentive/query/ccar-perpetuals", @@ -221,8 +221,8 @@ class V4ForegroundCycleTests : NetworkTests() { "https://indexer.v4staging.dydx.exchange/v4/time", "https://api.examples.com/configs/markets.json", "https://api.dydx.exchange/v4/geo", - "https://squid-api-git-main-cosmos-testnet-0xsquid.vercel.app/v1/chains", - "https://squid-api-git-main-cosmos-testnet-0xsquid.vercel.app/v1/tokens", + "https://testnet.v2.api.squidrouter.com/v2/sdk-info", + "https://api.examples.com/configs/cctp.json", "https://api.examples.com/configs/exchanges.json", "https://indexer.v4staging.dydx.exchange/v4/height", "https://dydx.exchange/v4-launch-incentive/query/ccar-perpetuals", @@ -261,8 +261,8 @@ class V4ForegroundCycleTests : NetworkTests() { "https://indexer.v4staging.dydx.exchange/v4/time", "https://api.examples.com/configs/markets.json", "https://api.dydx.exchange/v4/geo", - "https://squid-api-git-main-cosmos-testnet-0xsquid.vercel.app/v1/chains", - "https://squid-api-git-main-cosmos-testnet-0xsquid.vercel.app/v1/tokens", + "https://testnet.v2.api.squidrouter.com/v2/sdk-info", + "https://api.examples.com/configs/cctp.json", "https://api.examples.com/configs/exchanges.json", "https://indexer.v4staging.dydx.exchange/v4/height", "https://dydx.exchange/v4-launch-incentive/query/ccar-perpetuals", @@ -325,8 +325,8 @@ class V4ForegroundCycleTests : NetworkTests() { "https://indexer.v4staging.dydx.exchange/v4/time", "https://api.examples.com/configs/markets.json", "https://api.dydx.exchange/v4/geo", - "https://squid-api-git-main-cosmos-testnet-0xsquid.vercel.app/v1/chains", - "https://squid-api-git-main-cosmos-testnet-0xsquid.vercel.app/v1/tokens", + "https://testnet.v2.api.squidrouter.com/v2/sdk-info", + "https://api.examples.com/configs/cctp.json", "https://api.examples.com/configs/exchanges.json", "https://indexer.v4staging.dydx.exchange/v4/height", "https://dydx.exchange/v4-launch-incentive/query/ccar-perpetuals", @@ -378,8 +378,8 @@ class V4ForegroundCycleTests : NetworkTests() { "https://indexer.v4staging.dydx.exchange/v4/time", "https://api.examples.com/configs/markets.json", "https://api.dydx.exchange/v4/geo", - "https://squid-api-git-main-cosmos-testnet-0xsquid.vercel.app/v1/chains", - "https://squid-api-git-main-cosmos-testnet-0xsquid.vercel.app/v1/tokens", + "https://testnet.v2.api.squidrouter.com/v2/sdk-info", + "https://api.examples.com/configs/cctp.json", "https://api.examples.com/configs/exchanges.json", "https://indexer.v4staging.dydx.exchange/v4/height", "https://dydx.exchange/v4-launch-incentive/query/ccar-perpetuals", @@ -461,8 +461,8 @@ class V4ForegroundCycleTests : NetworkTests() { "https://indexer.v4staging.dydx.exchange/v4/time", "https://api.examples.com/configs/markets.json", "https://api.dydx.exchange/v4/geo", - "https://squid-api-git-main-cosmos-testnet-0xsquid.vercel.app/v1/chains", - "https://squid-api-git-main-cosmos-testnet-0xsquid.vercel.app/v1/tokens", + "https://testnet.v2.api.squidrouter.com/v2/sdk-info", + "https://api.examples.com/configs/cctp.json", "https://api.examples.com/configs/exchanges.json", "https://indexer.v4staging.dydx.exchange/v4/height", "https://dydx.exchange/v4-launch-incentive/query/ccar-perpetuals", @@ -509,8 +509,8 @@ class V4ForegroundCycleTests : NetworkTests() { "https://indexer.v4staging.dydx.exchange/v4/time", "https://api.examples.com/configs/markets.json", "https://api.dydx.exchange/v4/geo", - "https://squid-api-git-main-cosmos-testnet-0xsquid.vercel.app/v1/chains", - "https://squid-api-git-main-cosmos-testnet-0xsquid.vercel.app/v1/tokens", + "https://testnet.v2.api.squidrouter.com/v2/sdk-info", + "https://api.examples.com/configs/cctp.json", "https://api.examples.com/configs/exchanges.json", "https://indexer.v4staging.dydx.exchange/v4/height", "https://dydx.exchange/v4-launch-incentive/query/ccar-perpetuals", @@ -590,8 +590,8 @@ class V4ForegroundCycleTests : NetworkTests() { "https://indexer.v4staging.dydx.exchange/v4/time", "https://api.examples.com/configs/markets.json", "https://api.dydx.exchange/v4/geo", - "https://squid-api-git-main-cosmos-testnet-0xsquid.vercel.app/v1/chains", - "https://squid-api-git-main-cosmos-testnet-0xsquid.vercel.app/v1/tokens", + "https://testnet.v2.api.squidrouter.com/v2/sdk-info", + "https://api.examples.com/configs/cctp.json", "https://api.examples.com/configs/exchanges.json", "https://indexer.v4staging.dydx.exchange/v4/height", "https://dydx.exchange/v4-launch-incentive/query/ccar-perpetuals", diff --git a/src/commonTest/kotlin/exchange.dydx.abacus/app/manager/v2/V4ForegroundCycleTests.kt b/src/commonTest/kotlin/exchange.dydx.abacus/app/manager/v2/V4ForegroundCycleTests.kt index ea722cc71..19ca16e76 100644 --- a/src/commonTest/kotlin/exchange.dydx.abacus/app/manager/v2/V4ForegroundCycleTests.kt +++ b/src/commonTest/kotlin/exchange.dydx.abacus/app/manager/v2/V4ForegroundCycleTests.kt @@ -103,8 +103,8 @@ class V4ForegroundCycleTests : NetworkTests() { // "https://api.examples.com/configs/documentation.json", // "https://indexer.v4staging.dydx.exchange/v4/time", // "https://api.examples.com/configs/markets.json", -// "https://squid-api-git-main-cosmos-testnet-0xsquid.vercel.app/v1/chains", -// "https://squid-api-git-main-cosmos-testnet-0xsquid.vercel.app/v1/tokens", +// "https://testnet.v2.api.squidrouter.com/v2/sdk-info", +// "https://api.examples.com/configs/cctp.json", // "https://api.examples.com/configs/exchanges.json", // "https://indexer.v4staging.dydx.exchange/v4/height", // "https://dydx.exchange/v4-launch-incentive/query/ccar-perpetuals" @@ -117,8 +117,8 @@ class V4ForegroundCycleTests : NetworkTests() { "https://indexer.v4staging.dydx.exchange/v4/height", "https://api.examples.com/configs/markets.json", "https://dydx.exchange/v4-launch-incentive/query/ccar-perpetuals", - "https://squid-api-git-main-cosmos-testnet-0xsquid.vercel.app/v1/chains", - "https://squid-api-git-main-cosmos-testnet-0xsquid.vercel.app/v1/tokens", + "https://testnet.v2.api.squidrouter.com/v2/sdk-info", + "https://api.examples.com/configs/cctp.json", "https://api.examples.com/configs/exchanges.json", "https://api.dydx.exchange/v4/geo" ] @@ -193,21 +193,6 @@ class V4ForegroundCycleTests : NetworkTests() { ) compareExpectedRequests( -// """ -// [ -// "https://api.examples.com/configs/documentation.json", -// "https://indexer.v4staging.dydx.exchange/v4/time", -// "https://api.examples.com/configs/markets.json", -// "https://squid-api-git-main-cosmos-testnet-0xsquid.vercel.app/v1/chains", -// "https://squid-api-git-main-cosmos-testnet-0xsquid.vercel.app/v1/tokens", -// "https://api.examples.com/configs/exchanges.json", -// "https://indexer.v4staging.dydx.exchange/v4/height", -// "https://dydx.exchange/v4-launch-incentive/query/ccar-perpetuals", -// "https://indexer.v4staging.dydx.exchange/v4/sparklines?timePeriod=ONE_DAY", -// "https://indexer.v4staging.dydx.exchange/v4/historicalFunding/ETH-USD", -// "https://indexer.v4staging.dydx.exchange/v4/candles/perpetualMarkets/ETH-USD?resolution=1DAY" -// ] -// """.trimIndent(), """ [ "https://api.examples.com/configs/documentation.json", @@ -215,8 +200,8 @@ class V4ForegroundCycleTests : NetworkTests() { "https://indexer.v4staging.dydx.exchange/v4/height", "https://api.examples.com/configs/markets.json", "https://dydx.exchange/v4-launch-incentive/query/ccar-perpetuals", - "https://squid-api-git-main-cosmos-testnet-0xsquid.vercel.app/v1/chains", - "https://squid-api-git-main-cosmos-testnet-0xsquid.vercel.app/v1/tokens", + "https://testnet.v2.api.squidrouter.com/v2/sdk-info", + "https://api.examples.com/configs/cctp.json", "https://api.examples.com/configs/exchanges.json", "https://api.dydx.exchange/v4/geo", "https://indexer.v4staging.dydx.exchange/v4/sparklines?timePeriod=ONE_DAY", @@ -265,8 +250,8 @@ class V4ForegroundCycleTests : NetworkTests() { // "https://api.examples.com/configs/documentation.json", // "https://indexer.v4staging.dydx.exchange/v4/time", // "https://api.examples.com/configs/markets.json", -// "https://squid-api-git-main-cosmos-testnet-0xsquid.vercel.app/v1/chains", -// "https://squid-api-git-main-cosmos-testnet-0xsquid.vercel.app/v1/tokens", +// "https://testnet.v2.api.squidrouter.com/v2/sdk-info", +// "https://api.examples.com/configs/cctp.json", // "https://api.examples.com/configs/exchanges.json", // "https://indexer.v4staging.dydx.exchange/v4/height", // "https://dydx.exchange/v4-launch-incentive/query/ccar-perpetuals", @@ -285,8 +270,8 @@ class V4ForegroundCycleTests : NetworkTests() { "https://indexer.v4staging.dydx.exchange/v4/height", "https://api.examples.com/configs/markets.json", "https://dydx.exchange/v4-launch-incentive/query/ccar-perpetuals", - "https://squid-api-git-main-cosmos-testnet-0xsquid.vercel.app/v1/chains", - "https://squid-api-git-main-cosmos-testnet-0xsquid.vercel.app/v1/tokens", + "https://testnet.v2.api.squidrouter.com/v2/sdk-info", + "https://api.examples.com/configs/cctp.json", "https://api.examples.com/configs/exchanges.json", "https://api.dydx.exchange/v4/geo", "https://indexer.v4staging.dydx.exchange/v4/sparklines?timePeriod=ONE_DAY", @@ -323,8 +308,8 @@ class V4ForegroundCycleTests : NetworkTests() { // "https://api.examples.com/configs/documentation.json", // "https://indexer.v4staging.dydx.exchange/v4/time", // "https://api.examples.com/configs/markets.json", -// "https://squid-api-git-main-cosmos-testnet-0xsquid.vercel.app/v1/chains", -// "https://squid-api-git-main-cosmos-testnet-0xsquid.vercel.app/v1/tokens", +// "https://testnet.v2.api.squidrouter.com/v2/sdk-info", +// "https://api.examples.com/configs/cctp.json", // "https://api.examples.com/configs/exchanges.json", // "https://indexer.v4staging.dydx.exchange/v4/height", // "https://dydx.exchange/v4-launch-incentive/query/ccar-perpetuals", @@ -340,8 +325,8 @@ class V4ForegroundCycleTests : NetworkTests() { "https://indexer.v4staging.dydx.exchange/v4/height", "https://api.examples.com/configs/markets.json", "https://dydx.exchange/v4-launch-incentive/query/ccar-perpetuals", - "https://squid-api-git-main-cosmos-testnet-0xsquid.vercel.app/v1/chains", - "https://squid-api-git-main-cosmos-testnet-0xsquid.vercel.app/v1/tokens", + "https://testnet.v2.api.squidrouter.com/v2/sdk-info", + "https://api.examples.com/configs/cctp.json", "https://api.examples.com/configs/exchanges.json", "https://api.dydx.exchange/v4/geo", "https://indexer.v4staging.dydx.exchange/v4/sparklines?timePeriod=ONE_DAY", @@ -404,8 +389,8 @@ class V4ForegroundCycleTests : NetworkTests() { "https://indexer.v4staging.dydx.exchange/v4/height", "https://api.examples.com/configs/markets.json", "https://dydx.exchange/v4-launch-incentive/query/ccar-perpetuals", - "https://squid-api-git-main-cosmos-testnet-0xsquid.vercel.app/v1/chains", - "https://squid-api-git-main-cosmos-testnet-0xsquid.vercel.app/v1/tokens", + "https://testnet.v2.api.squidrouter.com/v2/sdk-info", + "https://api.examples.com/configs/cctp.json", "https://api.examples.com/configs/exchanges.json", "https://api.dydx.exchange/v4/geo", "https://indexer.v4staging.dydx.exchange/v4/screen?address=0xsecondaryFakeAddress", @@ -457,8 +442,8 @@ class V4ForegroundCycleTests : NetworkTests() { "https://indexer.v4staging.dydx.exchange/v4/height", "https://api.examples.com/configs/markets.json", "https://dydx.exchange/v4-launch-incentive/query/ccar-perpetuals", - "https://squid-api-git-main-cosmos-testnet-0xsquid.vercel.app/v1/chains", - "https://squid-api-git-main-cosmos-testnet-0xsquid.vercel.app/v1/tokens", + "https://testnet.v2.api.squidrouter.com/v2/sdk-info", + "https://api.examples.com/configs/cctp.json", "https://api.examples.com/configs/exchanges.json", "https://api.dydx.exchange/v4/geo", "https://indexer.v4staging.dydx.exchange/v4/screen?address=cosmos1fq8q55896ljfjj7v3x0qd0z3sr78wmes940uhm", @@ -566,8 +551,8 @@ class V4ForegroundCycleTests : NetworkTests() { "https://indexer.v4staging.dydx.exchange/v4/height", "https://api.examples.com/configs/markets.json", "https://dydx.exchange/v4-launch-incentive/query/ccar-perpetuals", - "https://squid-api-git-main-cosmos-testnet-0xsquid.vercel.app/v1/chains", - "https://squid-api-git-main-cosmos-testnet-0xsquid.vercel.app/v1/tokens", + "https://testnet.v2.api.squidrouter.com/v2/sdk-info", + "https://api.examples.com/configs/cctp.json", "https://api.examples.com/configs/exchanges.json", "https://api.dydx.exchange/v4/geo", "https://indexer.v4staging.dydx.exchange/v4/screen?address=cosmos1fq8q55896ljfjj7v3x0qd0z3sr78wmes940uhm", @@ -614,8 +599,8 @@ class V4ForegroundCycleTests : NetworkTests() { "https://indexer.v4staging.dydx.exchange/v4/height", "https://api.examples.com/configs/markets.json", "https://dydx.exchange/v4-launch-incentive/query/ccar-perpetuals", - "https://squid-api-git-main-cosmos-testnet-0xsquid.vercel.app/v1/chains", - "https://squid-api-git-main-cosmos-testnet-0xsquid.vercel.app/v1/tokens", + "https://testnet.v2.api.squidrouter.com/v2/sdk-info", + "https://api.examples.com/configs/cctp.json", "https://api.examples.com/configs/exchanges.json", "https://api.dydx.exchange/v4/geo", "https://indexer.v4staging.dydx.exchange/v4/screen?address=cosmos1fq8q55896ljfjj7v3x0qd0z3sr78wmes940uhm", @@ -695,8 +680,8 @@ class V4ForegroundCycleTests : NetworkTests() { "https://indexer.v4staging.dydx.exchange/v4/height", "https://api.examples.com/configs/markets.json", "https://dydx.exchange/v4-launch-incentive/query/ccar-perpetuals", - "https://squid-api-git-main-cosmos-testnet-0xsquid.vercel.app/v1/chains", - "https://squid-api-git-main-cosmos-testnet-0xsquid.vercel.app/v1/tokens", + "https://testnet.v2.api.squidrouter.com/v2/sdk-info", + "https://api.examples.com/configs/cctp.json", "https://api.examples.com/configs/exchanges.json", "https://api.dydx.exchange/v4/geo", "https://indexer.v4staging.dydx.exchange/v4/screen?address=cosmos1fq8q55896ljfjj7v3x0qd0z3sr78wmes940uhm", 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 6810cc8c6..82aaf209b 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,14 +350,15 @@ class V4TransactionTests : NetworkTests() { testWebSocket?.simulateConnected(true) testWebSocket?.simulateReceived(mock.connectionMock.connectedMessage) testWebSocket?.simulateReceived(mock.marketsChannel.v4_subscribed_r1) - + stateManager.market = "ETH-USD" + testWebSocket?.simulateReceived(mock.orderbookChannel.load_test_2_subscribed) stateManager.setAddresses(null, "dydx155va0m7wz5n8zcqscn9afswwt04n4usj46wvp5") + if (withPositions) { testWebSocket?.simulateReceived(mock.v4ParentSubaccountsMock.subscribed_with_positions) } else { testWebSocket?.simulateReceived(mock.v4ParentSubaccountsMock.subscribed) } - stateManager.market = "BTC-USD" } private fun prepareIsolatedMarginClosePosition() { @@ -372,7 +373,7 @@ class V4TransactionTests : NetworkTests() { stateManager.trade("2", TradeInputField.targetLeverage) if (isShortTerm) { - stateManager.trade("MARKET", TradeInputField.timeInForceType) + stateManager.trade("MARKET", TradeInputField.type) } else { stateManager.trade("LIMIT", TradeInputField.type) stateManager.trade("GTT", TradeInputField.timeInForceType) @@ -403,6 +404,39 @@ class V4TransactionTests : NetworkTests() { assertEquals(256, transferPayload.destinationSubaccountNumber, "Should have 2 transactions") } + @Test + fun testIsolatedMarginPlaceShortTermOrderTransactions() { + setStateMachineForIsolatedMarginTests(stateManager) + prepareIsolatedMarginTrade(true) + + val orderPayload = subaccountSupervisor?.placeOrderPayload(0) + assertNotNull(orderPayload, "Order payload should not be null") + assertEquals(256, orderPayload.subaccountNumber, "Should be 256 since 0 and 128 are unavailable") + + val transferPayload = subaccountSupervisor?.getTransferPayloadForIsolatedMarginTrade(orderPayload) + assertNotNull(transferPayload, "Transfer payload should not be null") + assertEquals(0, transferPayload.subaccountNumber, "The parent subaccount 0 should be the origin") + assertEquals(256, transferPayload.destinationSubaccountNumber, "Should have 2 transactions") + } + + @Test + fun testIsolatedMarginPlaceOrderWithReduceOnly() { + setStateMachineForIsolatedMarginTests(stateManager) + prepareIsolatedMarginTrade(true) + stateManager.trade("true", TradeInputField.reduceOnly) + var transactionData: Any? = null + + val transactionCallback: TransactionCallback = { _, _, data -> + transactionData = data + } + + val orderPayload = subaccountSupervisor?.commitPlaceOrder(0, transactionCallback) + assertNotNull(orderPayload, "Order payload should not be null") + + testChain?.simulateTransactionResponse(testChain!!.dummySuccess) + assertEquals(orderPayload, transactionData, "Transaction data should match order payload instead of transfer payload") + } + @Test fun testCancelOrderForChildSubaccount() { setStateMachineForIsolatedMarginTests(stateManager) diff --git a/src/commonTest/kotlin/exchange.dydx.abacus/payload/TradeInputOptionsTests.kt b/src/commonTest/kotlin/exchange.dydx.abacus/payload/TradeInputOptionsTests.kt index 005f091ca..8b5a24314 100644 --- a/src/commonTest/kotlin/exchange.dydx.abacus/payload/TradeInputOptionsTests.kt +++ b/src/commonTest/kotlin/exchange.dydx.abacus/payload/TradeInputOptionsTests.kt @@ -180,10 +180,6 @@ class TradeInputOptionsTests : V3BaseTests() { { "type": "IOC", "stringKey": "APP.TRADE.IMMEDIATE_OR_CANCEL" - }, - { - "type": "FOK", - "stringKey": "APP.TRADE.FILL_OR_KILL" } ] } @@ -192,32 +188,5 @@ class TradeInputOptionsTests : V3BaseTests() { } """.trimIndent(), ) - - test( - { - perp.trade("FOK", TradeInputField.timeInForceType, 0) - }, - """ - { - "input": { - "trade": { - "options": { - "needsSize": true, - "needsLeverage": false, - "needsTriggerPrice": false, - "needsLimitPrice": true, - "needsTrailingPercent": false, - "needsReduceOnly": true, - "needsPostOnly": false, - "needsBrackets": false, - "needsTimeInForce": true, - "needsGoodUntil": false, - "needsExecution": false - } - } - } - } - """.trimIndent(), - ) } } diff --git a/src/commonTest/kotlin/exchange.dydx.abacus/payload/TransferInputTests.kt b/src/commonTest/kotlin/exchange.dydx.abacus/payload/TransferInputTests.kt index d86937fbc..633528067 100644 --- a/src/commonTest/kotlin/exchange.dydx.abacus/payload/TransferInputTests.kt +++ b/src/commonTest/kotlin/exchange.dydx.abacus/payload/TransferInputTests.kt @@ -28,6 +28,8 @@ class TransferInputTests : V3BaseTests() { testTransferOutTransferInput() perp.log("Transfer Out", time) + + testTransferInputTypeChange() } private fun testDepositTransferInput() { @@ -414,6 +416,10 @@ class TransferInputTests : V3BaseTests() { perp.transfer("5000.0", TransferInputField.usdcSize) }, null) + test({ + perp.transfer("test memo", TransferInputField.MEMO) + }, null) + test( { perp.transfer("1000.0", TransferInputField.usdcSize) @@ -423,6 +429,7 @@ class TransferInputTests : V3BaseTests() { "input": { "transfer": { "type": "TRANSFER_OUT", + "memo": "test memo", "size": { "usdcSize": 1000.0 }, @@ -469,4 +476,70 @@ class TransferInputTests : V3BaseTests() { }, ) } + + private fun testTransferInputTypeChange() { + test( + { + perp.transfer("DEPOSIT", TransferInputField.type) + }, + """ + { + "input": { + "transfer": { + "type": "DEPOSIT", + "memo": null + } + } + } + """.trimIndent(), + ) + + test( + { + perp.transfer("TRANSFER_OUT", TransferInputField.type) + }, + """ + { + "input": { + "transfer": { + "type": "TRANSFER_OUT", + "memo": null + } + } + } + """.trimIndent(), + ) + + test( + { + perp.transfer("test memo", TransferInputField.MEMO) + }, + """ + { + "input": { + "transfer": { + "type": "TRANSFER_OUT", + "memo": "test memo" + } + } + } + """.trimIndent(), + ) + + test( + { + perp.transfer("WITHDRAWAL", TransferInputField.type) + }, + """ + { + "input": { + "transfer": { + "type": "WITHDRAWAL", + "memo": null + } + } + } + """.trimIndent(), + ) + } } diff --git a/src/commonTest/kotlin/exchange.dydx.abacus/payload/v3/V3PerpTests.kt b/src/commonTest/kotlin/exchange.dydx.abacus/payload/v3/V3PerpTests.kt index 4280f6269..7aef642af 100644 --- a/src/commonTest/kotlin/exchange.dydx.abacus/payload/v3/V3PerpTests.kt +++ b/src/commonTest/kotlin/exchange.dydx.abacus/payload/v3/V3PerpTests.kt @@ -329,7 +329,7 @@ class V3PerpTests : V3BaseTests() { "sideStringKey": "APP.GENERAL.BUY", "typeStringKey": "APP.TRADE.MARKET_ORDER_SHORT", "statusStringKey": "APP.TRADE.ORDER_FILLED", - "timeInForceStringKey": "APP.TRADE.FILL_OR_KILL" + "timeInForceStringKey": "APP.TRADE.IMMEDIATE_OR_CANCEL" } } }, diff --git a/src/commonTest/kotlin/exchange.dydx.abacus/payload/v4/V4ParentSubaccountTests.kt b/src/commonTest/kotlin/exchange.dydx.abacus/payload/v4/V4ParentSubaccountTests.kt index 62f65bb03..0f29a6531 100644 --- a/src/commonTest/kotlin/exchange.dydx.abacus/payload/v4/V4ParentSubaccountTests.kt +++ b/src/commonTest/kotlin/exchange.dydx.abacus/payload/v4/V4ParentSubaccountTests.kt @@ -82,7 +82,9 @@ class V4ParentSubaccountTests : V4BaseTests(true) { }, "leverage": { "current": -0.12 - } + }, + "subaccountNumber": 0, + "marginMode": "CROSS" } } }, @@ -128,7 +130,9 @@ class V4ParentSubaccountTests : V4BaseTests(true) { }, "buyingPower": { "current": 7962.44 - } + }, + "subaccountNumber": 128, + "marginMode": "ISOLATED" } }, "orders": { @@ -144,7 +148,9 @@ class V4ParentSubaccountTests : V4BaseTests(true) { "timeInForce": "GTT", "postOnly": false, "reduceOnly": false, - "goodTilBlock": "5837" + "goodTilBlock": "5837", + "subaccountNumber": 128, + "marginMode": "ISOLATED" } } } @@ -186,7 +192,9 @@ class V4ParentSubaccountTests : V4BaseTests(true) { }, "leverage": { "current": -0.12 - } + }, + "subaccountNumber": 0, + "marginMode": "CROSS" }, "RUNE-USD": { "id": "RUNE-USD", @@ -229,7 +237,9 @@ class V4ParentSubaccountTests : V4BaseTests(true) { }, "marginUsage": { "current": 0.0397 - } + }, + "subaccountNumber": 128, + "marginMode": "ISOLATED" } }, "pendingPositions": [ @@ -274,7 +284,14 @@ class V4ParentSubaccountTests : V4BaseTests(true) { "options": { "needsMarginMode": false } - } + }, + "receiptLines": [ + "LIQUIDATION_PRICE", + "POSITION_MARGIN", + "POSITION_LEVERAGE", + "FEE", + "REWARD" + ] } } """.trimIndent(), @@ -293,7 +310,13 @@ class V4ParentSubaccountTests : V4BaseTests(true) { "options": { "needsMarginMode": false } - } + }, + "receiptLines": [ + "BUYING_POWER", + "MARGIN_USAGE", + "FEE", + "REWARD" + ] } } """.trimIndent(), diff --git a/src/commonTest/kotlin/exchange.dydx.abacus/payload/v4/V4SquidTests.kt b/src/commonTest/kotlin/exchange.dydx.abacus/payload/v4/V4SquidTests.kt index a17f7c6f4..48280110a 100644 --- a/src/commonTest/kotlin/exchange.dydx.abacus/payload/v4/V4SquidTests.kt +++ b/src/commonTest/kotlin/exchange.dydx.abacus/payload/v4/V4SquidTests.kt @@ -1,11 +1,11 @@ package exchange.dydx.abacus.payload.v4 import exchange.dydx.abacus.state.model.TransferInputField -import exchange.dydx.abacus.state.model.squidChains +import exchange.dydx.abacus.state.model.routerChains +import exchange.dydx.abacus.state.model.routerTokens import exchange.dydx.abacus.state.model.squidRoute import exchange.dydx.abacus.state.model.squidRouteV2 import exchange.dydx.abacus.state.model.squidStatus -import exchange.dydx.abacus.state.model.squidTokens import exchange.dydx.abacus.state.model.squidV2SdkInfo import exchange.dydx.abacus.state.model.transfer import kotlin.test.Test @@ -20,7 +20,7 @@ class V4SquidTests : V4BaseTests() { // Due to the JIT compiler nature for JVM (and Kotlin) and JS, Android/web would ran slow the first round. Second round give more accurate result setup() - val stateChange = perp.squidChains(mock.squidChainsMock.payload) + val stateChange = perp.routerChains(mock.squidChainsMock.payload) assertNotNull(stateChange) test({ @@ -47,10 +47,10 @@ class V4SquidTests : V4BaseTests() { // Due to the JIT compiler nature for JVM (and Kotlin) and JS, Android/web would ran slow the first round. Second round give more accurate result setup() - var stateChange = perp.squidChains(mock.squidChainsMock.payload) + var stateChange = perp.routerChains(mock.squidChainsMock.payload) assertNotNull(stateChange) - stateChange = perp.squidTokens(mock.squidTokensMock.payload) + stateChange = perp.routerTokens(mock.squidTokensMock.payload) assertNotNull(stateChange) test({ @@ -112,10 +112,10 @@ class V4SquidTests : V4BaseTests() { perp.transfer("DEPOSIT", TransferInputField.type, 0) - var stateChange = perp.squidChains(mock.squidChainsMock.payload) + var stateChange = perp.routerChains(mock.squidChainsMock.payload) assertNotNull(stateChange) - stateChange = perp.squidTokens(mock.squidTokensMock.payload) + stateChange = perp.routerTokens(mock.squidTokensMock.payload) assertNotNull(stateChange) stateChange = perp.squidRoute(mock.squidRouteMock.payload, 0, null) @@ -148,10 +148,10 @@ class V4SquidTests : V4BaseTests() { perp.transfer("DEPOSIT", TransferInputField.type, 0) - var stateChange = perp.squidChains(mock.squidChainsMock.payload) + var stateChange = perp.routerChains(mock.squidChainsMock.payload) assertNotNull(stateChange) - stateChange = perp.squidTokens(mock.squidTokensMock.payload) + stateChange = perp.routerTokens(mock.squidTokensMock.payload) assertNotNull(stateChange) stateChange = perp.squidRoute(mock.squidRouteMock.errors_payload, 0, null) @@ -199,30 +199,30 @@ class V4SquidTests : V4BaseTests() { fun testSelectedTokenSymbol() { setup() - val stateChange = perp.squidTokens(mock.squidTokensMock.payload) + val stateChange = perp.routerTokens(mock.squidTokensMock.payload) assertNotNull(stateChange) - assertTrue(perp.squidProcessor.selectedTokenSymbol("0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE") == "ETH") + assertTrue(perp.squidProcessor.selectedTokenSymbol("0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", "should_not_matter") == "ETH") } @Test fun testSelectedTokenDecimals() { setup() - val stateChange = perp.squidTokens(mock.squidTokensMock.payload) + val stateChange = perp.routerTokens(mock.squidTokensMock.payload) assertNotNull(stateChange) - assertTrue(perp.squidProcessor.selectedTokenDecimals("0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE") == "18") + assertTrue(perp.squidProcessor.selectedTokenDecimals("0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", "should_not_matter") == "18") } @Test fun testDefaultTokenAddress() { setup() - var stateChange = perp.squidChains(mock.squidChainsMock.payload) + var stateChange = perp.routerChains(mock.squidChainsMock.payload) assertNotNull(stateChange) - stateChange = perp.squidTokens(mock.squidTokensMock.payload) + stateChange = perp.routerTokens(mock.squidTokensMock.payload) assertNotNull(stateChange) assertTrue(perp.squidProcessor.defaultTokenAddress("1") == "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE") @@ -232,24 +232,23 @@ class V4SquidTests : V4BaseTests() { fun testChainResources() { setup() - val stateChange = perp.squidChains(mock.squidChainsMock.payload) + val stateChange = perp.routerChains(mock.squidChainsMock.payload) assertNotNull(stateChange) - val result = parser.asMap(perp.squidProcessor.chainResources("1"))!! - val resource = parser.asMap(result["1"])!! - assertTrue(resource.keys.size == 5) - assertTrue(resource["chainId"] == "1") // Ethereum - assertTrue(resource["chainName"] == "Ethereum") // Ethereum + val result = perp.squidProcessor.chainResources("1") + val resource = result?.get("1") + assertTrue(resource?.chainId == 1) // Ethereum + assertTrue(resource?.chainName == "Ethereum") // Ethereum } @Test fun testTokenResources() { setup() - var stateChange = perp.squidChains(mock.squidChainsMock.payload) + var stateChange = perp.routerChains(mock.squidChainsMock.payload) assertNotNull(stateChange) - stateChange = perp.squidTokens(mock.squidTokensMock.payload) + stateChange = perp.routerTokens(mock.squidTokensMock.payload) assertNotNull(stateChange) val result = perp.squidProcessor.tokenResources("1") @@ -260,10 +259,10 @@ class V4SquidTests : V4BaseTests() { fun testTokenOptions() { setup() - var stateChange = perp.squidChains(mock.squidChainsMock.payload) + var stateChange = perp.routerChains(mock.squidChainsMock.payload) assertNotNull(stateChange) - stateChange = perp.squidTokens(mock.squidTokensMock.payload) + stateChange = perp.routerTokens(mock.squidTokensMock.payload) assertNotNull(stateChange) val result = perp.squidProcessor.tokenOptions("1") diff --git a/src/commonTest/kotlin/exchange.dydx.abacus/payload/v4/V4TradeInputTests.kt b/src/commonTest/kotlin/exchange.dydx.abacus/payload/v4/V4TradeInputTests.kt index d67f31d11..b3eca41fe 100644 --- a/src/commonTest/kotlin/exchange.dydx.abacus/payload/v4/V4TradeInputTests.kt +++ b/src/commonTest/kotlin/exchange.dydx.abacus/payload/v4/V4TradeInputTests.kt @@ -919,28 +919,6 @@ open class V4TradeInputTests : V4BaseTests() { """.trimIndent(), ) - test( - { - perp.trade("FOK", TradeInputField.timeInForceType, 0) - }, - """ - { - "input": { - "current": "trade", - "trade": { - "type": "LIMIT", - "options": { - "needsReduceOnly": true, - "needsPostOnly": false, - "reduceOnlyPromptStringKey": null, - "postOnlyPromptStringKey": "GENERAL.TRADE.POST_ONLY_TIMEINFORCE_GTT" - } - } - } - } - """.trimIndent(), - ) - perp.trade("STOP_LIMIT", TradeInputField.type, 0) test( @@ -986,28 +964,6 @@ open class V4TradeInputTests : V4BaseTests() { """.trimIndent(), ) - test( - { - perp.trade("FOK", TradeInputField.execution, 0) - }, - """ - { - "input": { - "current": "trade", - "trade": { - "type": "STOP_LIMIT", - "options": { - "needsReduceOnly": true, - "needsPostOnly": false, - "reduceOnlyPromptStringKey": null, - "postOnlyPromptStringKey": null - } - } - } - } - """.trimIndent(), - ) - test( { perp.trade("POST_ONLY", TradeInputField.execution, 0) @@ -1074,28 +1030,6 @@ open class V4TradeInputTests : V4BaseTests() { """.trimIndent(), ) - test( - { - perp.trade("FOK", TradeInputField.execution, 0) - }, - """ - { - "input": { - "current": "trade", - "trade": { - "type": "TAKE_PROFIT", - "options": { - "needsReduceOnly": true, - "needsPostOnly": false, - "reduceOnlyPromptStringKey": null, - "postOnlyPromptStringKey": null - } - } - } - } - """.trimIndent(), - ) - test( { perp.trade("POST_ONLY", TradeInputField.execution, 0) @@ -1141,28 +1075,6 @@ open class V4TradeInputTests : V4BaseTests() { """.trimIndent(), ) - test( - { - perp.trade("FOK", TradeInputField.execution, 0) - }, - """ - { - "input": { - "current": "trade", - "trade": { - "type": "STOP_MARKET", - "options": { - "needsReduceOnly": true, - "needsPostOnly": false, - "reduceOnlyPromptStringKey": null, - "postOnlyPromptStringKey": null - } - } - } - } - """.trimIndent(), - ) - perp.trade("TAKE_PROFIT_MARKET", TradeInputField.type, 0) test( @@ -1186,28 +1098,6 @@ open class V4TradeInputTests : V4BaseTests() { } """.trimIndent(), ) - - test( - { - perp.trade("FOK", TradeInputField.execution, 0) - }, - """ - { - "input": { - "current": "trade", - "trade": { - "type": "TAKE_PROFIT_MARKET", - "options": { - "needsReduceOnly": true, - "needsPostOnly": false, - "reduceOnlyPromptStringKey": null, - "postOnlyPromptStringKey": null - } - } - } - } - """.trimIndent(), - ) } private fun testExecution() { @@ -1230,7 +1120,6 @@ open class V4TradeInputTests : V4BaseTests() { test( { perp.trade("STOP_MARKET", TradeInputField.type, 0) - // should change to IOC }, """ { @@ -1250,7 +1139,6 @@ open class V4TradeInputTests : V4BaseTests() { test( { perp.trade("STOP_MARKET", TradeInputField.type, 0) - // should change to IOC }, """ { @@ -1265,33 +1153,12 @@ open class V4TradeInputTests : V4BaseTests() { """.trimIndent(), ) - perp.trade("STOP_LIMIT", TradeInputField.type, 0) - perp.trade("FOK", TradeInputField.execution, 0) - test( - { - perp.trade("STOP_MARKET", TradeInputField.type, 0) - // should change to IOC - }, - """ - { - "input": { - "current": "trade", - "trade": { - "type": "STOP_MARKET", - "execution": "FOK" - } - } - } - """.trimIndent(), - ) - perp.trade("STOP_LIMIT", TradeInputField.type, 0) perp.trade("POST_ONLY", TradeInputField.execution, 0) test( { perp.trade("STOP_MARKET", TradeInputField.type, 0) - // should change to IOC }, """ { @@ -1314,7 +1181,6 @@ open class V4TradeInputTests : V4BaseTests() { test( { perp.trade("TAKE_PROFIT", TradeInputField.type, 0) - // should change to IOC }, """ { @@ -1334,7 +1200,6 @@ open class V4TradeInputTests : V4BaseTests() { test( { perp.trade("TAKE_PROFIT", TradeInputField.type, 0) - // should change to IOC }, """ { @@ -1349,33 +1214,12 @@ open class V4TradeInputTests : V4BaseTests() { """.trimIndent(), ) - perp.trade("STOP_LIMIT", TradeInputField.type, 0) - perp.trade("FOK", TradeInputField.execution, 0) - test( - { - perp.trade("TAKE_PROFIT", TradeInputField.type, 0) - // should change to IOC - }, - """ - { - "input": { - "current": "trade", - "trade": { - "type": "TAKE_PROFIT", - "execution": "FOK" - } - } - } - """.trimIndent(), - ) - perp.trade("STOP_LIMIT", TradeInputField.type, 0) perp.trade("POST_ONLY", TradeInputField.execution, 0) test( { perp.trade("TAKE_PROFIT", TradeInputField.type, 0) - // should change to IOC }, """ { @@ -1398,7 +1242,6 @@ open class V4TradeInputTests : V4BaseTests() { test( { perp.trade("TAKE_PROFIT_MARKET", TradeInputField.type, 0) - // should change to IOC }, """ { @@ -1433,33 +1276,12 @@ open class V4TradeInputTests : V4BaseTests() { """.trimIndent(), ) - perp.trade("STOP_LIMIT", TradeInputField.type, 0) - perp.trade("FOK", TradeInputField.execution, 0) - test( - { - perp.trade("TAKE_PROFIT_MARKET", TradeInputField.type, 0) - // should change to IOC - }, - """ - { - "input": { - "current": "trade", - "trade": { - "type": "TAKE_PROFIT_MARKET", - "execution": "FOK" - } - } - } - """.trimIndent(), - ) - perp.trade("STOP_LIMIT", TradeInputField.type, 0) perp.trade("POST_ONLY", TradeInputField.execution, 0) test( { perp.trade("TAKE_PROFIT_MARKET", TradeInputField.type, 0) - // should change to IOC }, """ { @@ -1488,7 +1310,6 @@ open class V4TradeInputTests : V4BaseTests() { test( { perp.trade("STOP_LIMIT", TradeInputField.type, 0) - // should change to IOC }, """ { @@ -1502,26 +1323,6 @@ open class V4TradeInputTests : V4BaseTests() { } """.trimIndent(), ) - - perp.trade("STOP_MARKET", TradeInputField.type, 0) - perp.trade("FOK", TradeInputField.execution, 0) - test( - { - perp.trade("STOP_LIMIT", TradeInputField.type, 0) - // should change to IOC - }, - """ - { - "input": { - "current": "trade", - "trade": { - "type": "STOP_LIMIT", - "execution": "FOK" - } - } - } - """.trimIndent(), - ) } private fun testExecutionStopMarketToTakeProfit() { @@ -1530,7 +1331,6 @@ open class V4TradeInputTests : V4BaseTests() { test( { perp.trade("TAKE_PROFIT", TradeInputField.type, 0) - // should change to IOC }, """ { @@ -1544,26 +1344,6 @@ open class V4TradeInputTests : V4BaseTests() { } """.trimIndent(), ) - - perp.trade("STOP_MARKET", TradeInputField.type, 0) - perp.trade("FOK", TradeInputField.execution, 0) - test( - { - perp.trade("TAKE_PROFIT", TradeInputField.type, 0) - // should change to IOC - }, - """ - { - "input": { - "current": "trade", - "trade": { - "type": "TAKE_PROFIT", - "execution": "FOK" - } - } - } - """.trimIndent(), - ) } private fun testExecutionStopMarketToTakeProfitMarket() { @@ -1572,7 +1352,6 @@ open class V4TradeInputTests : V4BaseTests() { test( { perp.trade("TAKE_PROFIT_MARKET", TradeInputField.type, 0) - // should change to IOC }, """ { @@ -1586,26 +1365,6 @@ open class V4TradeInputTests : V4BaseTests() { } """.trimIndent(), ) - - perp.trade("STOP_MARKET", TradeInputField.type, 0) - perp.trade("FOK", TradeInputField.execution, 0) - test( - { - perp.trade("TAKE_PROFIT_MARKET", TradeInputField.type, 0) - // should change to IOC - }, - """ - { - "input": { - "current": "trade", - "trade": { - "type": "TAKE_PROFIT_MARKET", - "execution": "FOK" - } - } - } - """.trimIndent(), - ) } private fun testExecutionTakeProfit() { @@ -1621,7 +1380,6 @@ open class V4TradeInputTests : V4BaseTests() { test( { perp.trade("STOP_MARKET", TradeInputField.type, 0) - // should change to IOC }, """ { @@ -1656,33 +1414,12 @@ open class V4TradeInputTests : V4BaseTests() { """.trimIndent(), ) - perp.trade("TAKE_PROFIT", TradeInputField.type, 0) - perp.trade("FOK", TradeInputField.execution, 0) - test( - { - perp.trade("STOP_MARKET", TradeInputField.type, 0) - // should change to IOC - }, - """ - { - "input": { - "current": "trade", - "trade": { - "type": "STOP_MARKET", - "execution": "FOK" - } - } - } - """.trimIndent(), - ) - perp.trade("TAKE_PROFIT", TradeInputField.type, 0) perp.trade("POST_ONLY", TradeInputField.execution, 0) test( { perp.trade("STOP_MARKET", TradeInputField.type, 0) - // should change to IOC }, """ { @@ -1724,7 +1461,6 @@ open class V4TradeInputTests : V4BaseTests() { test( { perp.trade("STOP_LIMIT", TradeInputField.type, 0) - // should change to IOC }, """ { @@ -1739,26 +1475,6 @@ open class V4TradeInputTests : V4BaseTests() { """.trimIndent(), ) - perp.trade("TAKE_PROFIT", TradeInputField.type, 0) - perp.trade("FOK", TradeInputField.execution, 0) - test( - { - perp.trade("STOP_LIMIT", TradeInputField.type, 0) - // should change to IOC - }, - """ - { - "input": { - "current": "trade", - "trade": { - "type": "STOP_LIMIT", - "execution": "FOK" - } - } - } - """.trimIndent(), - ) - perp.trade("TAKE_PROFIT", TradeInputField.type, 0) perp.trade("POST_ONLY", TradeInputField.execution, 0) @@ -1787,7 +1503,6 @@ open class V4TradeInputTests : V4BaseTests() { test( { perp.trade("TAKE_PROFIT_MARKET", TradeInputField.type, 0) - // should change to IOC }, """ { @@ -1807,7 +1522,6 @@ open class V4TradeInputTests : V4BaseTests() { test( { perp.trade("TAKE_PROFIT_MARKET", TradeInputField.type, 0) - // should change to IOC }, """ { @@ -1822,33 +1536,12 @@ open class V4TradeInputTests : V4BaseTests() { """.trimIndent(), ) - perp.trade("TAKE_PROFIT", TradeInputField.type, 0) - perp.trade("FOK", TradeInputField.execution, 0) - test( - { - perp.trade("TAKE_PROFIT_MARKET", TradeInputField.type, 0) - // should change to IOC - }, - """ - { - "input": { - "current": "trade", - "trade": { - "type": "TAKE_PROFIT_MARKET", - "execution": "FOK" - } - } - } - """.trimIndent(), - ) - perp.trade("TAKE_PROFIT", TradeInputField.type, 0) perp.trade("POST_ONLY", TradeInputField.execution, 0) test( { perp.trade("TAKE_PROFIT_MARKET", TradeInputField.type, 0) - // should change to IOC }, """ { @@ -1877,7 +1570,6 @@ open class V4TradeInputTests : V4BaseTests() { test( { perp.trade("STOP_LIMIT", TradeInputField.type, 0) - // should change to IOC }, """ { @@ -1891,26 +1583,6 @@ open class V4TradeInputTests : V4BaseTests() { } """.trimIndent(), ) - - perp.trade("TAKE_PROFIT_MARKET", TradeInputField.type, 0) - perp.trade("FOK", TradeInputField.execution, 0) - test( - { - perp.trade("STOP_LIMIT", TradeInputField.type, 0) - // should change to IOC - }, - """ - { - "input": { - "current": "trade", - "trade": { - "type": "STOP_LIMIT", - "execution": "FOK" - } - } - } - """.trimIndent(), - ) } private fun testExecutionTakeProfitMarketToTakeProfit() { @@ -1919,7 +1591,6 @@ open class V4TradeInputTests : V4BaseTests() { test( { perp.trade("TAKE_PROFIT", TradeInputField.type, 0) - // should change to IOC }, """ { @@ -1933,26 +1604,6 @@ open class V4TradeInputTests : V4BaseTests() { } """.trimIndent(), ) - - perp.trade("TAKE_PROFIT_MARKET", TradeInputField.type, 0) - perp.trade("FOK", TradeInputField.execution, 0) - test( - { - perp.trade("TAKE_PROFIT", TradeInputField.type, 0) - // should change to IOC - }, - """ - { - "input": { - "current": "trade", - "trade": { - "type": "TAKE_PROFIT", - "execution": "FOK" - } - } - } - """.trimIndent(), - ) } private fun testExecutionTakeProfitMarketToStopMarket() { @@ -1961,7 +1612,6 @@ open class V4TradeInputTests : V4BaseTests() { test( { perp.trade("STOP_MARKET", TradeInputField.type, 0) - // should change to IOC }, """ { @@ -1975,25 +1625,5 @@ open class V4TradeInputTests : V4BaseTests() { } """.trimIndent(), ) - - perp.trade("TAKE_PROFIT_MARKET", TradeInputField.type, 0) - perp.trade("FOK", TradeInputField.execution, 0) - test( - { - perp.trade("STOP_MARKET", TradeInputField.type, 0) - // should change to IOC - }, - """ - { - "input": { - "current": "trade", - "trade": { - "type": "STOP_MARKET", - "execution": "FOK" - } - } - } - """.trimIndent(), - ) } } diff --git a/src/commonTest/kotlin/exchange.dydx.abacus/processor/router/skip/SkipProcessorTests.kt b/src/commonTest/kotlin/exchange.dydx.abacus/processor/router/skip/SkipProcessorTests.kt new file mode 100644 index 000000000..b4db78f6f --- /dev/null +++ b/src/commonTest/kotlin/exchange.dydx.abacus/processor/router/skip/SkipProcessorTests.kt @@ -0,0 +1,305 @@ +package exchange.dydx.abacus.processor.router.skip +import exchange.dydx.abacus.output.input.SelectionOption +import exchange.dydx.abacus.output.input.TransferInputChainResource +import exchange.dydx.abacus.output.input.TransferInputTokenResource +import exchange.dydx.abacus.state.internalstate.InternalTransferInputState +import exchange.dydx.abacus.tests.payloads.SkipChainsMock +import exchange.dydx.abacus.tests.payloads.SkipTokensMock +import exchange.dydx.abacus.utils.Parser +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.jsonObject +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals + +internal fun templateToJson(template: String): Map { + return Json.parseToJsonElement(template.trimIndent()).jsonObject.toMap() +} + +class SkipProcessorTests { + + internal val internalState = InternalTransferInputState() + internal val parser = Parser() + internal val skipProcessor = SkipProcessor(parser = parser, internalState = internalState) + internal val skipChainsMock = SkipChainsMock() + internal val skipTokensMock = SkipTokensMock() + internal val selectedChainId = "osmosis-1" + internal val selectedTokenAddress = "selectedTokenDenom" + internal val selectedTokenSymbol = "selectedTokenSymbol" + internal val selectedTokenDecimals = "15" + internal val selectedChainAssets = listOf( + mapOf( + "denom" to selectedTokenAddress, + "symbol" to selectedTokenSymbol, + "decimals" to selectedTokenDecimals, + "name" to "some-name", + "logo_uri" to "some-logo-uri", + ), + mapOf( + "denom" to "testTokenKeyValue2", + "symbol" to "ARB", + "decimals" to 8, + "name" to "some-name-2", + "logo_uri" to "some-logo-uri-2", + ), + mapOf( + "denom" to "testTokenKeyValue3", + "symbol" to "ETH", + "decimals" to 5, + "name" to "some-name-3", + "logo_uri" to "some-logo-uri-3", + ), + ) + + /** + * Adds tokens to the skipProcessor instance + * This is a reduced scope mock that is to be used for UNIT TESTS ONLY. + * Integration tests should use the skipChainsMock or skipTokensMock structures. + * The test tokens fixture looks like this: + * { + * "osmosis-1": { + * "assets": [ + * { + * "denom": "selectedTokenDenom", + * "symbol": "selectedTokenSymbol", + * "decimals": 15 + * }, + * { + * "denom": "testTokenKeyValue2", + * "symbol": "ARB", + * "decimals": 8 + * }, + * { + * "denom": "testTokenKeyValue3" + * "symbol": "ETH", + * "decimals": 5 + * } + * ] + * }, + * "dont-select": { + * "assets": [ + * {"denom": "shouldNotBeSelectedValue1"}, + * {"denom": "shouldNotBeSelectedValue2"}, + * {"denom": "shouldNotBeSelectedValue3"} + * ] + * } + * } + * + * This makes it easy to know what the filteredTokens output should be + * which in turn helps us know the results of the funs that depend on it. + */ + internal fun addTokens() { + skipProcessor.skipTokens = mapOf( + selectedChainId to mapOf("assets" to selectedChainAssets), + "dont-select" to mapOf( + "assets" to listOf( + mapOf("shouldNotBeSelected1" to "shouldNotBeSelectedValue1"), + mapOf("shouldNotBeSelected2" to "shouldNotBeSelectedValue2"), + mapOf("shouldNotBeSelected3" to "shouldNotBeSelectedValue3"), + ), + ), + ) + } + + @BeforeTest + internal fun setUp() { + addTokens() + } + +// ////////////////// UNIT TESTS ////////////////////// + @Test + fun testFilteredTokens() { + val result = skipProcessor.filteredTokens(chainId = selectedChainId) + val expected = selectedChainAssets + assertEquals(expected, result) + } + + @Test + fun testSelectedTokenSymbol() { + val result = skipProcessor.selectedTokenSymbol(tokenAddress = selectedTokenAddress, selectedChainId = selectedChainId) + val expected = selectedTokenSymbol + assertEquals(expected, result) + } + + @Test + fun testSelectedTokenDecimals() { + val result = skipProcessor.selectedTokenDecimals(tokenAddress = selectedTokenAddress, selectedChainId = selectedChainId) + val expected = selectedTokenDecimals + assertEquals(expected, result) + } + + @Test + fun testDefaultTokenAddress() { + val result = skipProcessor.defaultTokenAddress(selectedChainId) + val expected = selectedTokenAddress + assertEquals(expected, result) + } + + @Test + fun testTokenResources() { + val result = skipProcessor.tokenResources(selectedChainId) + val expected = mapOf( + selectedTokenAddress to TransferInputTokenResource( + address = selectedTokenAddress, + symbol = selectedTokenSymbol, + decimals = parser.asInt(selectedTokenDecimals), + name = "some-name", + iconUrl = "some-logo-uri", + ), + "testTokenKeyValue2" to TransferInputTokenResource( + address = "testTokenKeyValue2", + symbol = "ARB", + decimals = 8, + name = "some-name-2", + iconUrl = "some-logo-uri-2", + ), + "testTokenKeyValue3" to TransferInputTokenResource( + address = "testTokenKeyValue3", + symbol = "ETH", + decimals = 5, + name = "some-name-3", + iconUrl = "some-logo-uri-3", + ), + ) + assertEquals(expected, result) + } + + @Test + fun testTokenOptions() { + val result = skipProcessor.tokenOptions(selectedChainId) + val expected = listOf( + SelectionOption( + stringKey = "some-name", + string = "some-name", + type = selectedTokenAddress, + iconUrl = "some-logo-uri", + ), + SelectionOption( + stringKey = "some-name-2", + string = "some-name-2", + type = "testTokenKeyValue2", + iconUrl = "some-logo-uri-2", + ), + SelectionOption( + stringKey = "some-name-3", + string = "some-name-3", + type = "testTokenKeyValue3", + iconUrl = "some-logo-uri-3", + ), + ) + assertEquals(expected, result) + } + + // /////////////// INTEGRATION TESTS //////////////////// + + @Test + fun testReceivedChains() { + val payload = templateToJson( + skipChainsMock.payload, + ) + val modified = skipProcessor.receivedChains( + existing = mapOf(), + payload = payload, + ) + val expectedChains = listOf( + SelectionOption(stringKey = "Ethereum", string = "Ethereum", type = "1", iconUrl = "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/info/logo.png"), + SelectionOption(stringKey = "aura", string = "aura", type = "xstaxy-1", iconUrl = "https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/xstaxy/chain.png"), + SelectionOption(stringKey = "cheqd", string = "cheqd", type = "cheqd-mainnet-1", iconUrl = "https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/cheqd-mainnet/chain.png"), + SelectionOption(stringKey = "kujira", string = "kujira", type = "kaiyo-1", iconUrl = "https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/kaiyo/chain.png"), + SelectionOption(stringKey = "osmosis", string = "osmosis", type = "osmosis-1", iconUrl = "https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/osmosis/chain.png"), + SelectionOption(stringKey = "stride", string = "stride", type = "stride-1", iconUrl = "https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/stride/chain.png"), + ) + val expectedChainResources = mapOf( + "1" to TransferInputChainResource( + chainName = "Ethereum", + chainId = 1, + iconUrl = "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/info/logo.png", + ), + ) + val expectedModified = mapOf( + "transfer" to mapOf( + "chain" to "1", + ), + ) + + assertEquals(expectedChains, internalState.chains) + assertEquals(payload["chains"], skipProcessor.chains) + assertEquals(expectedChainResources, internalState.chainResources) + assertEquals(expectedModified, modified) + } + + @Test + fun testReceivedTokens() { + val payload = templateToJson(skipTokensMock.payload) + skipProcessor.skipTokens = null + skipProcessor.chains = listOf( + mapOf( + "chain_name" to "aura", + "chain_id" to "1", + "pfm_enabled" to false, + "supports_memo" to true, + "logo_uri" to "https ://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/xstaxy/chain.png", + "bech32_prefix" to "aura", + "chain_type" to "cosmos", + "is_testnet" to false, + ), + ) + val modified = skipProcessor.receivedTokens( + existing = mapOf(), + payload = payload, + ) + val expectedModified = mapOf( + "transfer" to mapOf( + "token" to "0x97e6E0a40a3D02F12d1cEC30ebfbAE04e37C119E", + ), + ) + val expectedTokens = listOf( + SelectionOption( + stringKey = "Euro Coin", + string = "Euro Coin", + type = "0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c", + iconUrl = "https://raw.githubusercontent.com/axelarnetwork/axelar-configs/main/images/tokens/euroc.svg", + ), + SelectionOption( + stringKey = "Real Yield USD", + string = "Real Yield USD", + type = "0x97e6E0a40a3D02F12d1cEC30ebfbAE04e37C119E", + iconUrl = "https://raw.githubusercontent.com/axelarnetwork/axelar-configs/main/images/tokens/yieldusd.svg", + ), + SelectionOption( + stringKey = "Umee native token", + string = "Umee native token", + type = "0x923e030f951A2401426a3407a9bcc7EB715d9a0b", + iconUrl = "https://raw.githubusercontent.com/axelarnetwork/axelar-configs/main/images/tokens/umee.svg", + ), + ) + val expectedTokenResources = mapOf( + "0x97e6E0a40a3D02F12d1cEC30ebfbAE04e37C119E" to TransferInputTokenResource( + name = "Real Yield USD", + address = "0x97e6E0a40a3D02F12d1cEC30ebfbAE04e37C119E", + symbol = "YieldUSD", + decimals = 18, + iconUrl = "https://raw.githubusercontent.com/axelarnetwork/axelar-configs/main/images/tokens/yieldusd.svg", + ), + "0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c" to TransferInputTokenResource( + name = "Euro Coin", + address = "0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c", + symbol = "EUROC", + decimals = 6, + iconUrl = "https://raw.githubusercontent.com/axelarnetwork/axelar-configs/main/images/tokens/euroc.svg", + ), + "0x923e030f951A2401426a3407a9bcc7EB715d9a0b" to TransferInputTokenResource( + name = "Umee native token", + address = "0x923e030f951A2401426a3407a9bcc7EB715d9a0b", + symbol = "UMEE", + decimals = 6, + iconUrl = "https://raw.githubusercontent.com/axelarnetwork/axelar-configs/main/images/tokens/umee.svg", + ), + ) + + assertEquals(expectedModified, modified) + assertEquals(payload["chain_to_assets_map"], skipProcessor.skipTokens) + assertEquals(expectedTokens, internalState.tokens) + assertEquals(expectedTokenResources, internalState.tokenResources) + } +} diff --git a/src/commonTest/kotlin/exchange.dydx.abacus/tests/payloads/AccountsChannelMock.kt b/src/commonTest/kotlin/exchange.dydx.abacus/tests/payloads/AccountsChannelMock.kt index f717d1dca..d6d0bf8a2 100644 --- a/src/commonTest/kotlin/exchange.dydx.abacus/tests/payloads/AccountsChannelMock.kt +++ b/src/commonTest/kotlin/exchange.dydx.abacus/tests/payloads/AccountsChannelMock.kt @@ -1049,7 +1049,7 @@ internal class AccountsChannelMock { "type": "MARKET", "status": "FILLED", "signature": "07c17d2804cbe11203a200c496e0fb72af4d4e824367f4a60227261b7f459ebe06f5fe893596b66a6bc7b23d05b83a99dc118fe7f967dee52792c01b8dab88bd", - "timeInForce": "FOK", + "timeInForce": "IOC", "postOnly": false, "cancelReason": null, "expiresAt": "2022-08-01T20:13:29.361Z", @@ -1939,7 +1939,7 @@ internal class AccountsChannelMock { "price": "3406.4", "status": "BEST_EFFORT_OPENED", "type": "LIMIT", - "timeInForce": "FOK", + "timeInForce": "IOC", "postOnly": false, "reduceOnly": false, "orderFlags": "0", @@ -2597,7 +2597,7 @@ internal class AccountsChannelMock { "price": "3514.6", "status": "BEST_EFFORT_OPENED", "type": "LIMIT", - "timeInForce": "FOK", + "timeInForce": "IOC", "postOnly": false, "reduceOnly": false, "orderFlags": "0", diff --git a/src/commonTest/kotlin/exchange.dydx.abacus/tests/payloads/SkipChainsMock.kt b/src/commonTest/kotlin/exchange.dydx.abacus/tests/payloads/SkipChainsMock.kt new file mode 100644 index 000000000..a25cf8b92 --- /dev/null +++ b/src/commonTest/kotlin/exchange.dydx.abacus/tests/payloads/SkipChainsMock.kt @@ -0,0 +1,187 @@ +package exchange.dydx.abacus.tests.payloads + +internal class SkipChainsMock { + internal val payload = """{ + "chains": [ + { + "chain_name": "kujira", + "chain_id": "kaiyo-1", + "pfm_enabled": false, + "cosmos_module_support": { + "authz": true, + "feegrant": true + }, + "supports_memo": true, + "logo_uri": "https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/kaiyo/chain.png", + "bech32_prefix": "kujira", + "fee_assets": [ + { + "denom": "ibc/47BD209179859CDE4A2806763D7189B6E6FE13A17880FE2B42DE1E6C1E329E23", + "gas_price": null + }, + { + "denom": "ibc/EFF323CC632EC4F747C61BCE238A758EFDB7699C3226565F7C20DA06509D59A5", + "gas_price": null + } + ], + "chain_type": "cosmos", + "ibc_capabilities": { + "cosmos_pfm": false, + "cosmos_ibc_hooks": false, + "cosmos_memo": true, + "cosmos_autopilot": false + }, + "is_testnet": false + }, + { + "chain_name": "cheqd", + "chain_id": "cheqd-mainnet-1", + "pfm_enabled": false, + "cosmos_module_support": { + "authz": true, + "feegrant": true + }, + "supports_memo": true, + "logo_uri": "https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/cheqd-mainnet/chain.png", + "bech32_prefix": "cheqd", + "fee_assets": [ + { + "denom": "ncheq", + "gas_price": { + "low": "25", + "average": "50", + "high": "100" + } + } + ], + "chain_type": "cosmos", + "ibc_capabilities": { + "cosmos_pfm": false, + "cosmos_ibc_hooks": false, + "cosmos_memo": true, + "cosmos_autopilot": false + }, + "is_testnet": false + }, + { + "chain_name": "osmosis", + "chain_id": "osmosis-1", + "pfm_enabled": true, + "cosmos_module_support": { + "authz": true, + "feegrant": false + }, + "supports_memo": true, + "logo_uri": "https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/osmosis/chain.png", + "bech32_prefix": "osmo", + "fee_assets": [ + { + "denom": "uosmo", + "gas_price": { + "low": "0.0025", + "average": "0.025", + "high": "0.04" + } + } + ], + "chain_type": "cosmos", + "ibc_capabilities": { + "cosmos_pfm": true, + "cosmos_ibc_hooks": true, + "cosmos_memo": true, + "cosmos_autopilot": false + }, + "is_testnet": false + }, + { + "chain_name": "stride", + "chain_id": "stride-1", + "pfm_enabled": true, + "cosmos_module_support": { + "authz": false, + "feegrant": true + }, + "supports_memo": true, + "logo_uri": "https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/stride/chain.png", + "bech32_prefix": "stride", + "fee_assets": [ + { + "denom": "stusaga", + "gas_price": { + "low": "0.01", + "average": "0.01", + "high": "0.01" + } + }, + { + "denom": "stuatom", + "gas_price": { + "low": "0.0001", + "average": "0.001", + "high": "0.01" + } + } + ], + "chain_type": "cosmos", + "ibc_capabilities": { + "cosmos_pfm": true, + "cosmos_ibc_hooks": false, + "cosmos_memo": true, + "cosmos_autopilot": true + }, + "is_testnet": false + }, + { + "chain_name": "aura", + "chain_id": "xstaxy-1", + "pfm_enabled": false, + "cosmos_module_support": { + "authz": true, + "feegrant": true + }, + "supports_memo": true, + "logo_uri": "https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/xstaxy/chain.png", + "bech32_prefix": "aura", + "fee_assets": [ + { + "denom": "uaura", + "gas_price": { + "low": "0.001", + "average": "0.0025", + "high": "0.004" + } + } + ], + "chain_type": "cosmos", + "ibc_capabilities": { + "cosmos_pfm": false, + "cosmos_ibc_hooks": false, + "cosmos_memo": true, + "cosmos_autopilot": false + }, + "is_testnet": false + }, + { + "chain_name": "Ethereum", + "chain_id": "1", + "pfm_enabled": false, + "cosmos_module_support": { + "authz": false, + "feegrant": false + }, + "supports_memo": false, + "logo_uri": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/info/logo.png", + "bech32_prefix": "", + "fee_assets": [], + "chain_type": "evm", + "ibc_capabilities": { + "cosmos_pfm": false, + "cosmos_ibc_hooks": false, + "cosmos_memo": false, + "cosmos_autopilot": false + }, + "is_testnet": false + } + ] +}""" +} diff --git a/src/commonTest/kotlin/exchange.dydx.abacus/tests/payloads/SkipTokensMock.kt b/src/commonTest/kotlin/exchange.dydx.abacus/tests/payloads/SkipTokensMock.kt new file mode 100644 index 000000000..3d3b432eb --- /dev/null +++ b/src/commonTest/kotlin/exchange.dydx.abacus/tests/payloads/SkipTokensMock.kt @@ -0,0 +1,463 @@ +package exchange.dydx.abacus.tests.payloads + +class SkipTokensMock { + internal val defaultChainIdAssets = """{ + "assets": [ + { + "denom": "0x97e6E0a40a3D02F12d1cEC30ebfbAE04e37C119E", + "chain_id": "1", + "origin_denom": "0x97e6E0a40a3D02F12d1cEC30ebfbAE04e37C119E", + "origin_chain_id": "1", + "trace": "", + "is_cw20": false, + "is_evm": true, + "is_svm": false, + "symbol": "YieldUSD", + "name": "Real Yield USD", + "logo_uri": "https://raw.githubusercontent.com/axelarnetwork/axelar-configs/main/images/tokens/yieldusd.svg", + "decimals": 18, + "token_contract": "0x97e6E0a40a3D02F12d1cEC30ebfbAE04e37C119E", + "recommended_symbol": "YieldUSD" + }, + { + "denom": "0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c", + "chain_id": "1", + "origin_denom": "0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c", + "origin_chain_id": "1", + "trace": "", + "is_cw20": false, + "is_evm": true, + "is_svm": false, + "symbol": "EUROC", + "name": "Euro Coin", + "logo_uri": "https://raw.githubusercontent.com/axelarnetwork/axelar-configs/main/images/tokens/euroc.svg", + "decimals": 6, + "token_contract": "0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c", + "coingecko_id": "euro-coin", + "recommended_symbol": "EUROC" + }, + { + "denom": "0x923e030f951A2401426a3407a9bcc7EB715d9a0b", + "chain_id": "1", + "origin_denom": "0x923e030f951A2401426a3407a9bcc7EB715d9a0b", + "origin_chain_id": "1", + "trace": "", + "is_cw20": false, + "is_evm": true, + "is_svm": false, + "symbol": "UMEE", + "name": "Umee native token", + "logo_uri": "https://raw.githubusercontent.com/axelarnetwork/axelar-configs/main/images/tokens/umee.svg", + "decimals": 6, + "token_contract": "0x923e030f951A2401426a3407a9bcc7EB715d9a0b", + "coingecko_id": "umee", + "recommended_symbol": "UMEE" + } + ] + }""" + internal val payload = """{ + "chain_to_assets_map": { + "1": $defaultChainIdAssets, + "5": { + "assets": [ + { + "denom": "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6", + "chain_id": "5", + "origin_denom": "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6", + "origin_chain_id": "5", + "trace": "", + "is_cw20": false, + "is_evm": true, + "is_svm": false, + "symbol": "WETH", + "name": "weth", + "logo_uri": "https://raw.githubusercontent.com/cosmostation/chainlist/main/chain/ethereum/asset/weth.png", + "decimals": 18, + "token_contract": "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6", + "coingecko_id": "weth", + "recommended_symbol": "WETH" + }, + { + "denom": "0x9c3C9283D3e44854697Cd22D3Faa240Cfb032889", + "chain_id": "5", + "origin_denom": "0x9c3C9283D3e44854697Cd22D3Faa240Cfb032889", + "origin_chain_id": "5", + "trace": "", + "is_cw20": false, + "is_evm": true, + "is_svm": false, + "symbol": "WMATIC", + "name": "wmatic-wei", + "logo_uri": "https://raw.githubusercontent.com/cosmostation/chainlist/main/chain/polygon/asset/wmatic.png", + "decimals": 18, + "token_contract": "0x9c3C9283D3e44854697Cd22D3Faa240Cfb032889", + "coingecko_id": "matic-network", + "recommended_symbol": "WMATIC" + }, + { + "denom": "", + "chain_id": "5", + "origin_denom": "", + "origin_chain_id": "5", + "trace": "", + "is_cw20": false, + "is_evm": true, + "is_svm": false, + "symbol": "ETH", + "name": "eth", + "logo_uri": "https://raw.githubusercontent.com/cosmostation/chainlist/main/chain/ethereum/asset/eth.png", + "decimals": 18, + "coingecko_id": "ethereum", + "recommended_symbol": "ETH" + } + ] + }, + "10": { + "assets": [ + { + "denom": "0x789CbBE5d19f04F38Ec9790b28Ecb07ba5617f61", + "chain_id": "10", + "origin_denom": "0x789CbBE5d19f04F38Ec9790b28Ecb07ba5617f61", + "origin_chain_id": "10", + "trace": "", + "is_cw20": false, + "is_evm": true, + "is_svm": false, + "symbol": "stTIA.axl", + "name": "Stride Staked Tia", + "logo_uri": "https://raw.githubusercontent.com/axelarnetwork/axelar-configs/main/images/tokens/sttia.svg", + "decimals": 6, + "token_contract": "0x789CbBE5d19f04F38Ec9790b28Ecb07ba5617f61", + "coingecko_id": "stride-staked-tia", + "recommended_symbol": "stTIA.axl" + }, + { + "denom": "0xb829b68f57CC546dA7E5806A929e53bE32a4625D", + "chain_id": "10", + "origin_denom": "0xb829b68f57CC546dA7E5806A929e53bE32a4625D", + "origin_chain_id": "10", + "trace": "", + "is_cw20": false, + "is_evm": true, + "is_svm": false, + "symbol": "axlETH", + "name": "Axelar Wrapped ETH", + "logo_uri": "https://raw.githubusercontent.com/axelarnetwork/axelar-configs/main/images/tokens/weth.svg", + "decimals": 18, + "token_contract": "0xb829b68f57CC546dA7E5806A929e53bE32a4625D", + "coingecko_id": "weth", + "recommended_symbol": "ETH.axl" + }, + { + "denom": "0x4200000000000000000000000000000000000042", + "chain_id": "10", + "origin_denom": "0x4200000000000000000000000000000000000042", + "origin_chain_id": "10", + "trace": "", + "is_cw20": false, + "is_evm": true, + "is_svm": false, + "symbol": "OP", + "name": "Optimism", + "logo_uri": "https://raw.githubusercontent.com/axelarnetwork/axelar-configs/main/images/tokens/op.svg", + "decimals": 18, + "token_contract": "0x4200000000000000000000000000000000000042", + "coingecko_id": "optimism", + "recommended_symbol": "OP" + } + ] + }, + "56": { + "assets": [ + { + "denom": "0x43a8cab15D06d3a5fE5854D714C37E7E9246F170", + "chain_id": "56", + "origin_denom": "0x43a8cab15D06d3a5fE5854D714C37E7E9246F170", + "origin_chain_id": "56", + "trace": "", + "is_cw20": false, + "is_evm": true, + "is_svm": false, + "symbol": "ORBS", + "name": "Orbs", + "logo_uri": "https://raw.githubusercontent.com/axelarnetwork/axelar-configs/main/images/tokens/orbs.svg", + "decimals": 18, + "token_contract": "0x43a8cab15D06d3a5fE5854D714C37E7E9246F170", + "coingecko_id": "orbs", + "recommended_symbol": "ORBS" + }, + { + "denom": "0x7C8DbFdB185C088E73999770C93b885295805739", + "chain_id": "56", + "origin_denom": "0x7C8DbFdB185C088E73999770C93b885295805739", + "origin_chain_id": "56", + "trace": "", + "is_cw20": false, + "is_evm": true, + "is_svm": false, + "symbol": "MOON", + "name": "Moonflow", + "logo_uri": "https://raw.githubusercontent.com/axelarnetwork/axelar-configs/main/images/tokens/moon.svg", + "decimals": 18, + "token_contract": "0x7C8DbFdB185C088E73999770C93b885295805739", + "recommended_symbol": "MOON" + }, + { + "denom": "0xF700D4c708C2be1463E355F337603183D20E0808", + "chain_id": "56", + "origin_denom": "0xF700D4c708C2be1463E355F337603183D20E0808", + "origin_chain_id": "56", + "trace": "", + "is_cw20": false, + "is_evm": true, + "is_svm": false, + "symbol": "GQ", + "name": "Galactic Quadrant", + "logo_uri": "https://raw.githubusercontent.com/axelarnetwork/axelar-configs/main/images/tokens/gq.svg", + "decimals": 18, + "token_contract": "0xF700D4c708C2be1463E355F337603183D20E0808", + "recommended_symbol": "GQ" + } + ] + }, + "137": { + "assets": [ + { + "denom": "0x1ED2B2b097E92B2Fe95a172dd29840c71294F1d6", + "chain_id": "137", + "origin_denom": "0x1ED2B2b097E92B2Fe95a172dd29840c71294F1d6", + "origin_chain_id": "137", + "trace": "", + "is_cw20": false, + "is_evm": true, + "is_svm": false, + "symbol": "sFRAX", + "name": "Staked FRAX", + "logo_uri": "https://raw.githubusercontent.com/axelarnetwork/axelar-configs/main/images/tokens/sfrax.svg", + "decimals": 18, + "token_contract": "0x1ED2B2b097E92B2Fe95a172dd29840c71294F1d6", + "recommended_symbol": "sFRAX" + }, + { + "denom": "0x779661872e9C891027099C9E3fd101DCc8B96433", + "chain_id": "137", + "origin_denom": "0x779661872e9C891027099C9E3fd101DCc8B96433", + "origin_chain_id": "137", + "trace": "", + "is_cw20": false, + "is_evm": true, + "is_svm": false, + "symbol": "axlWBTC", + "name": "Axelar Wrapped WBTC", + "logo_uri": "https://raw.githubusercontent.com/axelarnetwork/axelar-configs/main/images/tokens/wbtc.svg", + "decimals": 8, + "token_contract": "0x779661872e9C891027099C9E3fd101DCc8B96433", + "coingecko_id": "wrapped-bitcoin", + "recommended_symbol": "WBTC.axl" + }, + { + "denom": "0x0294D8eB7857D43FEb1210Db72456d41481f9Ede", + "chain_id": "137", + "origin_denom": "0x0294D8eB7857D43FEb1210Db72456d41481f9Ede", + "origin_chain_id": "137", + "trace": "", + "is_cw20": false, + "is_evm": true, + "is_svm": false, + "symbol": "axlLqdr", + "name": "Axelar Wrapped Lqdr", + "logo_uri": "https://raw.githubusercontent.com/axelarnetwork/axelar-configs/main/images/tokens/lqdr.svg", + "decimals": 18, + "token_contract": "0x0294D8eB7857D43FEb1210Db72456d41481f9Ede", + "coingecko_id": "liquiddriver", + "recommended_symbol": "Lqdr.axl" + } + ] + }, + "169": { + "assets": [ + { + "denom": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", + "chain_id": "169", + "origin_denom": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", + "origin_chain_id": "169", + "trace": "", + "is_cw20": false, + "is_evm": true, + "is_svm": false, + "symbol": "TIA.n", + "name": "TIA.n", + "logo_uri": "https://raw.githubusercontent.com/cosmos/chain-registry/master/celestia/images/celestia.png", + "decimals": 6, + "token_contract": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", + "coingecko_id": "bridged-tia-hyperlane", + "recommended_symbol": "TIA.n" + } + ] + }, + "250": { + "assets": [ + { + "denom": "0x3bB68cb55Fc9C22511467c18E42D14E8c959c4dA", + "chain_id": "250", + "origin_denom": "0x3bB68cb55Fc9C22511467c18E42D14E8c959c4dA", + "origin_chain_id": "250", + "trace": "", + "is_cw20": false, + "is_evm": true, + "is_svm": false, + "symbol": "axlATOM", + "name": "Axelar Wrapped ATOM", + "logo_uri": "https://raw.githubusercontent.com/axelarnetwork/axelar-configs/main/images/tokens/atom.svg", + "decimals": 6, + "token_contract": "0x3bB68cb55Fc9C22511467c18E42D14E8c959c4dA", + "coingecko_id": "cosmos", + "recommended_symbol": "ATOM.axl" + }, + { + "denom": "0x11eDFA12d70e8AC9e94DE019eBa278430873f8C3", + "chain_id": "250", + "origin_denom": "0x11eDFA12d70e8AC9e94DE019eBa278430873f8C3", + "origin_chain_id": "250", + "trace": "", + "is_cw20": false, + "is_evm": true, + "is_svm": false, + "symbol": "TORI", + "name": "Teritori", + "logo_uri": "https://raw.githubusercontent.com/axelarnetwork/axelar-configs/main/images/tokens/tori.svg", + "decimals": 6, + "token_contract": "0x11eDFA12d70e8AC9e94DE019eBa278430873f8C3", + "recommended_symbol": "TORI" + }, + { + "denom": "0x05E7857Cb748F0018C0CBCe3dfd575B0d8677aeF", + "chain_id": "250", + "origin_denom": "0x05E7857Cb748F0018C0CBCe3dfd575B0d8677aeF", + "origin_chain_id": "250", + "trace": "", + "is_cw20": false, + "is_evm": true, + "is_svm": false, + "symbol": "FXS", + "name": "Frax Share", + "logo_uri": "https://raw.githubusercontent.com/axelarnetwork/axelar-configs/main/images/tokens/fxs.svg", + "decimals": 18, + "token_contract": "0x05E7857Cb748F0018C0CBCe3dfd575B0d8677aeF", + "recommended_symbol": "FXS" + } + ] + }, + "314": { + "assets": [ + { + "denom": "0xEB466342C4d449BC9f53A865D5Cb90586f405215", + "chain_id": "314", + "origin_denom": "0xEB466342C4d449BC9f53A865D5Cb90586f405215", + "origin_chain_id": "314", + "trace": "", + "is_cw20": false, + "is_evm": true, + "is_svm": false, + "symbol": "axlUSDC", + "name": "Axelar Wrapped USDC", + "logo_uri": "https://raw.githubusercontent.com/axelarnetwork/axelar-configs/main/images/tokens/usdc.svg", + "decimals": 6, + "token_contract": "0xEB466342C4d449BC9f53A865D5Cb90586f405215", + "coingecko_id": "usd-coin", + "recommended_symbol": "USDC.axl" + }, + { + "denom": "0x4AA81D7AB59C775fe6F9F45E6941A0FB8cD692a6", + "chain_id": "314", + "origin_denom": "0x4AA81D7AB59C775fe6F9F45E6941A0FB8cD692a6", + "origin_chain_id": "314", + "trace": "", + "is_cw20": false, + "is_evm": true, + "is_svm": false, + "symbol": "milkTIA", + "name": "milkTIA", + "logo_uri": "https://raw.githubusercontent.com/axelarnetwork/axelar-configs/main/images/tokens/milktia.svg", + "decimals": 6, + "token_contract": "0x4AA81D7AB59C775fe6F9F45E6941A0FB8cD692a6", + "coingecko_id": "milkyway-staked-tia", + "recommended_symbol": "milkTIA" + }, + { + "denom": "filecoin-native", + "chain_id": "314", + "origin_denom": "filecoin-native", + "origin_chain_id": "314", + "trace": "", + "is_cw20": false, + "is_evm": true, + "is_svm": false, + "symbol": "FIL", + "name": "FIL", + "logo_uri": "https://assets.coingecko.com/coins/images/12817/standard/filecoin.png?1696512609", + "decimals": 18, + "coingecko_id": "filecoin", + "recommended_symbol": "FIL" + } + ] + }, + "1284": { + "assets": [ + { + "denom": "0x151904806a266EEe52700E195D2937891fb8eD59", + "chain_id": "1284", + "origin_denom": "0x151904806a266EEe52700E195D2937891fb8eD59", + "origin_chain_id": "1284", + "trace": "", + "is_cw20": false, + "is_evm": true, + "is_svm": false, + "symbol": "FXS", + "name": "Frax Share", + "logo_uri": "https://raw.githubusercontent.com/axelarnetwork/axelar-configs/main/images/tokens/fxs.svg", + "decimals": 18, + "token_contract": "0x151904806a266EEe52700E195D2937891fb8eD59", + "recommended_symbol": "FXS" + }, + { + "denom": "0x5Ac3aD1acC0A3EFd6fB89791967656128e86d8C5", + "chain_id": "1284", + "origin_denom": "0x5Ac3aD1acC0A3EFd6fB89791967656128e86d8C5", + "origin_chain_id": "1284", + "trace": "", + "is_cw20": false, + "is_evm": true, + "is_svm": false, + "symbol": "axlKNC", + "name": "Axelar Wrapped KNC", + "logo_uri": "https://raw.githubusercontent.com/axelarnetwork/axelar-configs/main/images/tokens/knc.svg", + "decimals": 18, + "token_contract": "0x5Ac3aD1acC0A3EFd6fB89791967656128e86d8C5", + "coingecko_id": "kyber-network-crystal", + "recommended_symbol": "KNC.axl" + }, + { + "denom": "0xF2605EaB29c67d06E71372CA9dfA8aDfd2d34BbF", + "chain_id": "1284", + "origin_denom": "0xF2605EaB29c67d06E71372CA9dfA8aDfd2d34BbF", + "origin_chain_id": "1284", + "trace": "", + "is_cw20": false, + "is_evm": true, + "is_svm": false, + "symbol": "axlSTARS", + "name": "Axelar Wrapped STARS", + "logo_uri": "https://raw.githubusercontent.com/axelarnetwork/axelar-configs/main/images/tokens/stars.svg", + "decimals": 6, + "token_contract": "0xF2605EaB29c67d06E71372CA9dfA8aDfd2d34BbF", + "coingecko_id": "stargaze", + "recommended_symbol": "STARS.axl" + } + ] + } + } +} + + """.trimMargin() +} diff --git a/src/commonTest/kotlin/exchange.dydx.abacus/tests/payloads/TransactionsMock.kt b/src/commonTest/kotlin/exchange.dydx.abacus/tests/payloads/TransactionsMock.kt index c4ae1c096..d52e66aef 100644 --- a/src/commonTest/kotlin/exchange.dydx.abacus/tests/payloads/TransactionsMock.kt +++ b/src/commonTest/kotlin/exchange.dydx.abacus/tests/payloads/TransactionsMock.kt @@ -15,18 +15,4 @@ internal class TransactionsMock { } } """.trimIndent() - - internal val place_order_fok_failed_transaction = """ - { - "jsonrpc": "2.0", - "id": 106799053660, - "result": { - "code": 2000, - "data": "", - "log": "FillOrKill order could not be fully filled", - "codespace": "clob", - "hash": "E8E355275220AEF2C38E51B9B901FA448F42C8CBECAB2C25886E0D682D843BCA" - } - } - """.trimIndent() } diff --git a/v4_abacus.podspec b/v4_abacus.podspec index 2e668c1f2..e6044e131 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.34' + spec.version = '1.7.49' spec.homepage = 'https://github.com/dydxprotocol/v4-abacus' spec.source = { :http=> ''} spec.authors = ''