diff --git a/.github/workflows/android-ci.yml b/.github/workflows/android-ci.yml index 992b97778e..0c3c0467b1 100644 --- a/.github/workflows/android-ci.yml +++ b/.github/workflows/android-ci.yml @@ -3,20 +3,8 @@ name: Android Tests on: push: branches: ["main"] - paths: - - "android/**" - - "core" - - "core/**" - - ".gitmodules" - - ".github/workflows/android-ci.yml" pull_request: branches: ["main"] - paths: - - "android/**" - - "core" - - "core/**" - - ".gitmodules" - - ".github/workflows/android-ci.yml" concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -24,6 +12,10 @@ concurrency: env: UNIT_TESTS: "true" + ANDROID_COMPILE_SDK: "36" + ANDROID_API: "35" + ANDROID_AVD_NAME: "ci-emulator" + ANDROID_EMULATOR_DEVICE: "pixel_6" jobs: unit_test: @@ -32,79 +24,65 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: recursive - # Re-enable these setup steps if the self-hosted runner stops providing - # Java, Android SDK/NDK, and `just` out of the box. - # - name: Set up JDK 17 - # uses: actions/setup-java@v3 - # with: - # java-version: "17" - # distribution: "temurin" - # - # - name: Setup Android SDK - # uses: android-actions/setup-android@v3 - # - # - name: Install just - # uses: extractions/setup-just@v2 - # - # - name: Cache NDK - # uses: actions/cache@v4 - # with: - # path: ${{ env.ANDROID_HOME }}/ndk/28.1.13356709 - # key: ndk-28.1.13356709-${{ runner.os }} - # - # - name: Setup NDK - # working-directory: android - # run: just install-ndk - - # Re-enable this on ephemeral GitHub-hosted runners where ~/.android/avd - # does not persist between jobs and emulator provisioning cost matters. - # - name: Cache AVD - # uses: actions/cache@v4 - # id: avd-cache - # with: - # path: | - # ~/.android/avd/* - # ~/.android/adb* - # key: avd-35-google-apis-arm64-v8a-${{ runner.os }}-${{ runner.arch }} - - # Re-enable Gradle cache if CI time becomes an issue and the self-hosted - # runner is no longer reusing local Gradle state effectively. - # - name: Gradle cache - # uses: gradle/actions/setup-gradle@v3 - # with: - # cache-read-only: ${{ github.ref != 'refs/heads/main' }} - - - name: Build tests + - name: Resolve Runner Toolchain + run: | + JAVA_HOME="$(/usr/libexec/java_home -v 17)" + ANDROID_HOME="/opt/homebrew/share/android-commandlinetools" + + [ -x "${JAVA_HOME}/bin/java" ] || { echo "JAVA_HOME is invalid: ${JAVA_HOME}"; exit 1; } + [ -x "${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager" ] || { echo "sdkmanager is missing under ${ANDROID_HOME}"; exit 1; } + + { + echo "JAVA_HOME=${JAVA_HOME}" + echo "ANDROID_HOME=${ANDROID_HOME}" + echo "ANDROID_SDK_ROOT=${ANDROID_HOME}" + } >> "${GITHUB_ENV}" + + - name: Bootstrap + working-directory: android + run: just bootstrap + + - name: Provision Android SDK + working-directory: android + run: just provision-emulator + + - name: Build + working-directory: android + run: just build + env: + GPR_USERNAME: ${{ github.actor }} + GPR_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Run unit tests + working-directory: android + run: just unit-test + env: + GPR_USERNAME: ${{ github.actor }} + GPR_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Start emulator + working-directory: android + run: just start-emulator + + - name: Build instrumented tests working-directory: android run: just build-test env: GPR_USERNAME: ${{ github.actor }} GPR_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Run tests + - name: Run instrumented tests working-directory: android run: just test env: GPR_USERNAME: ${{ github.actor }} GPR_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # Re-enable this on GitHub-hosted runners, or when the self-hosted runner - # does not keep a booted emulator/device available for connected tests. - # - name: Run tests - # uses: reactivecircus/android-emulator-runner@v2 - # with: - # api-level: 35 - # target: google_apis - # arch: arm64-v8a - # force-avd-creation: false - # disk-size: 2048M - # emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - # disable-animations: true - # script: cd android && just test - # env: - # GPR_USERNAME: ${{ github.actor }} - # GPR_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Stop emulator + if: always() + working-directory: android + run: just stop-emulator diff --git a/.github/workflows/android-docker.yml b/.github/workflows/android-docker.yml index 79f8a2789e..63c9bdf473 100644 --- a/.github/workflows/android-docker.yml +++ b/.github/workflows/android-docker.yml @@ -38,7 +38,7 @@ jobs: packages: write steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: recursive diff --git a/.github/workflows/android-publish-base-image.yml b/.github/workflows/android-publish-base-image.yml index cbe7156e02..60b024ab4e 100644 --- a/.github/workflows/android-publish-base-image.yml +++ b/.github/workflows/android-publish-base-image.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Read base image tag id: base_tag diff --git a/.github/workflows/android-verify-apk.yml b/.github/workflows/android-verify-apk.yml index e432c42312..a1af1cc3e3 100644 --- a/.github/workflows/android-verify-apk.yml +++ b/.github/workflows/android-verify-apk.yml @@ -45,7 +45,7 @@ jobs: VERIFY_REPO_URL: https://github.com/${{ github.repository }}.git steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: recursive diff --git a/.github/workflows/ios-ci.yml b/.github/workflows/ios-ci.yml index 4f5c1ab99b..1ac5b3a8e8 100644 --- a/.github/workflows/ios-ci.yml +++ b/.github/workflows/ios-ci.yml @@ -3,20 +3,8 @@ name: iOS Unit Tests on: push: branches: ["main"] - paths: - - "ios/**" - - "core" - - "core/**" - - ".gitmodules" - - ".github/workflows/ios-ci.yml" pull_request: branches: ["main"] - paths: - - "ios/**" - - "core" - - "core/**" - - ".gitmodules" - - ".github/workflows/ios-ci.yml" jobs: unit_test: @@ -25,7 +13,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: recursive diff --git a/.github/workflows/ios-ui-tests.yml b/.github/workflows/ios-ui-tests.yml index eb42b11963..aca08048a1 100644 --- a/.github/workflows/ios-ui-tests.yml +++ b/.github/workflows/ios-ui-tests.yml @@ -43,7 +43,7 @@ jobs: echo "RUSTC_WRAPPER=sccache" >> $GITHUB_ENV - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: recursive diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0414e704a6..71b147f99a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,7 +30,4 @@ This repository is a monorepo: * iOS application code lives under `ios/` * Android application code lives under `android/` -* Shared Rust core is the `core/` submodule - -### Commit Convention -We use [Conventional Commits](https://www.conventionalcommits.org/) for commit messages. Please read the guide through if you aren't familiar with it already. +* Shared Rust core is the `core/` submodule \ No newline at end of file diff --git a/android/blockchain/src/main/kotlin/com/gemwallet/android/blockchain/services/PerpetualService.kt b/android/blockchain/src/main/kotlin/com/gemwallet/android/blockchain/services/PerpetualService.kt index 7998e42e5e..d9f7f63392 100644 --- a/android/blockchain/src/main/kotlin/com/gemwallet/android/blockchain/services/PerpetualService.kt +++ b/android/blockchain/src/main/kotlin/com/gemwallet/android/blockchain/services/PerpetualService.kt @@ -69,6 +69,7 @@ fun GemPerpetualData.toDTO(): PerpetualData? { volume24h = perpetual.volume24h, funding = perpetual.funding, maxLeverage = perpetual.maxLeverage, + isIsolatedOnly = perpetual.isIsolatedOnly, ), asset = asset.toDTO(), metadata = PerpetualMetadata( @@ -145,4 +146,4 @@ fun GemChartCandleStick.toDTO(): ChartCandleStick { fun uniffi.gemstone.PerpetualProvider.toDTO(): PerpetualProvider = when (this) { uniffi.gemstone.PerpetualProvider.HYPERCORE -> PerpetualProvider.Hypercore -} \ No newline at end of file +} diff --git a/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinates/perpetuals/GetPerpetualImpl.kt b/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinates/perpetuals/GetPerpetualImpl.kt index 191ab7860b..e6f784b11f 100644 --- a/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinates/perpetuals/GetPerpetualImpl.kt +++ b/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinates/perpetuals/GetPerpetualImpl.kt @@ -24,7 +24,7 @@ class GetPerpetualImpl @Inject constructor( } class PerpetualDetailsDataAggregateImpl( - val data: PerpetualData + private val data: PerpetualData ) : PerpetualDetailsDataAggregate { override val id: String = data.perpetual.id @@ -45,4 +45,6 @@ class PerpetualDetailsDataAggregateImpl( override val price: Double = data.perpetual.price override val identifier: String = data.perpetual.identifier -} \ No newline at end of file + + override val isIsolatedOnly: Boolean = data.perpetual.isIsolatedOnly +} diff --git a/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositoreis/assets/AssetsRepository.kt b/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositoreis/assets/AssetsRepository.kt index a6bfbdeaab..9eabc0bbba 100644 --- a/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositoreis/assets/AssetsRepository.kt +++ b/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositoreis/assets/AssetsRepository.kt @@ -242,35 +242,32 @@ class AssetsRepository @Inject constructor( searchTokensCase.search(widgetAssetIds, currency) } - val prices = widgetAssetIds.map { assetId -> + val marketsByAssetId = widgetAssetIds.map { assetId -> async { - try { - gemApi.getAsset(assetId.toIdentifier()) + val market = try { + gemApi.getMarket(assetId.toIdentifier(), currency.string) } catch (_: Throwable) { null } + assetId to market } } .awaitAll() - .filterNotNull() + .toMap() (getTokensInfo(widgetAssetIds.map { it.toIdentifier() }).firstOrNull() ?: emptyList()) .map { assetInfo -> - val price = prices.firstOrNull { it.asset.id == assetInfo.asset.id } - if (price == null) { - assetInfo - } else { - assetInfo.copy( - price = AssetPriceInfo( - currency = currency, - price = AssetPrice( - assetId = assetInfo.asset.id, - price = price.price.price, - priceChangePercentage24h = price.price.priceChangePercentage24h, - updatedAt = System.currentTimeMillis() - ) + val marketPrice = marketsByAssetId[assetInfo.asset.id]?.price ?: return@map assetInfo + assetInfo.copy( + price = AssetPriceInfo( + currency = currency, + price = AssetPrice( + assetId = assetInfo.asset.id, + price = marketPrice.price, + priceChangePercentage24h = marketPrice.priceChangePercentage24h, + updatedAt = System.currentTimeMillis() ) ) - } + ) } } @@ -525,4 +522,4 @@ class AssetsRepository @Inject constructor( ): Flow> { return assetsDao.getRecentByType(type).toAssetInfoModel() } -} \ No newline at end of file +} diff --git a/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositoreis/perpetual/FakePerpetualRepository.kt b/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositoreis/perpetual/FakePerpetualRepository.kt index b8b205ed6a..e220245af1 100644 --- a/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositoreis/perpetual/FakePerpetualRepository.kt +++ b/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositoreis/perpetual/FakePerpetualRepository.kt @@ -278,7 +278,8 @@ class FakePerpetualRepository @Inject constructor() : PerpetualRepository { openInterest = 2500000000.0, volume24h = 15000000000.0, funding = 0.0001, - maxLeverage = 100u + maxLeverage = 100u, + isIsolatedOnly = false ), asset = btcAsset, metadata = PerpetualMetadata(isPinned = true) @@ -295,7 +296,8 @@ class FakePerpetualRepository @Inject constructor() : PerpetualRepository { openInterest = 1200000000.0, volume24h = 8000000000.0, funding = 0.00008, - maxLeverage = 50u + maxLeverage = 50u, + isIsolatedOnly = false ), asset = ethAsset, metadata = PerpetualMetadata(isPinned = true) @@ -312,7 +314,8 @@ class FakePerpetualRepository @Inject constructor() : PerpetualRepository { openInterest = 500000000.0, volume24h = 3000000000.0, funding = 0.00005, - maxLeverage = 20u + maxLeverage = 20u, + isIsolatedOnly = false ), asset = solAsset, metadata = PerpetualMetadata(isPinned = true) @@ -329,7 +332,8 @@ class FakePerpetualRepository @Inject constructor() : PerpetualRepository { openInterest = 350000000.0, volume24h = 2000000000.0, funding = 0.00012, - maxLeverage = 50u + maxLeverage = 50u, + isIsolatedOnly = false ), asset = bnbAsset, metadata = PerpetualMetadata(isPinned = false) @@ -346,7 +350,8 @@ class FakePerpetualRepository @Inject constructor() : PerpetualRepository { openInterest = 120000000.0, volume24h = 800000000.0, funding = 0.00006, - maxLeverage = 25u + maxLeverage = 25u, + isIsolatedOnly = false ), asset = maticAsset, metadata = PerpetualMetadata(isPinned = false) @@ -363,7 +368,8 @@ class FakePerpetualRepository @Inject constructor() : PerpetualRepository { openInterest = 200000000.0, volume24h = 1200000000.0, funding = 0.00009, - maxLeverage = 30u + maxLeverage = 30u, + isIsolatedOnly = false ), asset = avaxAsset, metadata = PerpetualMetadata(isPinned = false) @@ -380,7 +386,8 @@ class FakePerpetualRepository @Inject constructor() : PerpetualRepository { openInterest = 180000000.0, volume24h = 950000000.0, funding = 0.00007, - maxLeverage = 20u + maxLeverage = 20u, + isIsolatedOnly = false ), asset = adaAsset, metadata = PerpetualMetadata(isPinned = false) @@ -397,7 +404,8 @@ class FakePerpetualRepository @Inject constructor() : PerpetualRepository { openInterest = 500000000.0, volume24h = 3000000000.0, funding = 0.00001, - maxLeverage = 10u + maxLeverage = 10u, + isIsolatedOnly = false ), asset = usdtAsset, metadata = PerpetualMetadata(isPinned = false) @@ -414,7 +422,8 @@ class FakePerpetualRepository @Inject constructor() : PerpetualRepository { openInterest = 450000000.0, volume24h = 2800000000.0, funding = 0.00001, - maxLeverage = 10u + maxLeverage = 10u, + isIsolatedOnly = false ), asset = usdcAsset, metadata = PerpetualMetadata(isPinned = false) @@ -431,7 +440,8 @@ class FakePerpetualRepository @Inject constructor() : PerpetualRepository { openInterest = 150000000.0, volume24h = 900000000.0, funding = 0.00008, - maxLeverage = 25u + maxLeverage = 25u, + isIsolatedOnly = false ), asset = linkAsset, metadata = PerpetualMetadata(isPinned = false) @@ -448,7 +458,8 @@ class FakePerpetualRepository @Inject constructor() : PerpetualRepository { openInterest = 100000000.0, volume24h = 600000000.0, funding = 0.00007, - maxLeverage = 20u + maxLeverage = 20u, + isIsolatedOnly = false ), asset = uniAsset, metadata = PerpetualMetadata(isPinned = false) @@ -465,7 +476,8 @@ class FakePerpetualRepository @Inject constructor() : PerpetualRepository { openInterest = 80000000.0, volume24h = 450000000.0, funding = 0.00009, - maxLeverage = 20u + maxLeverage = 20u, + isIsolatedOnly = false ), asset = aaveAsset, metadata = PerpetualMetadata(isPinned = false) @@ -482,7 +494,8 @@ class FakePerpetualRepository @Inject constructor() : PerpetualRepository { openInterest = 50000000.0, volume24h = 300000000.0, funding = 0.00005, - maxLeverage = 15u + maxLeverage = 15u, + isIsolatedOnly = false ), asset = cakeAsset, metadata = PerpetualMetadata(isPinned = false) @@ -499,7 +512,8 @@ class FakePerpetualRepository @Inject constructor() : PerpetualRepository { openInterest = 40000000.0, volume24h = 250000000.0, funding = 0.00006, - maxLeverage = 15u + maxLeverage = 15u, + isIsolatedOnly = false ), asset = rayAsset, metadata = PerpetualMetadata(isPinned = false) @@ -516,7 +530,8 @@ class FakePerpetualRepository @Inject constructor() : PerpetualRepository { openInterest = 35000000.0, volume24h = 200000000.0, funding = 0.00004, - maxLeverage = 15u + maxLeverage = 15u, + isIsolatedOnly = false ), asset = orcaAsset, metadata = PerpetualMetadata(isPinned = false) diff --git a/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/GemDatabase.kt b/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/GemDatabase.kt index ae44fa33fe..47c1844719 100644 --- a/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/GemDatabase.kt +++ b/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/GemDatabase.kt @@ -110,4 +110,4 @@ abstract class GemDatabase : RoomDatabase() { abstract fun perpetualPositionDao(): PerpetualPositionDao abstract fun perpetualBalanceDao(): PerpetualBalanceDao -} \ No newline at end of file +} diff --git a/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/di/DatabaseModule.kt b/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/di/DatabaseModule.kt index a68be0ee85..7ed8dcf5be 100644 --- a/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/di/DatabaseModule.kt +++ b/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/di/DatabaseModule.kt @@ -133,4 +133,4 @@ object DatabaseModule { @Singleton @Provides fun providePerpetualBalanceDao(db: GemDatabase): PerpetualBalanceDao = db.perpetualBalanceDao() -} \ No newline at end of file +} diff --git a/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/entities/DbPerpetual.kt b/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/entities/DbPerpetual.kt index 1f251586b3..42c41f70ea 100644 --- a/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/entities/DbPerpetual.kt +++ b/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/entities/DbPerpetual.kt @@ -67,6 +67,7 @@ fun DbPerpetual.toDTO(): Perpetual? { volume24h = volume24h, funding = funding, maxLeverage = maxLeverage.toUByte(), + isIsolatedOnly = false, ) } @@ -100,4 +101,4 @@ fun DbPerpetualData.toDTO(): PerpetualData? { asset = asset?.toDTO() ?: return null, metadata = metadata?.toDTO() ?: PerpetualMetadata(false), ) -} \ No newline at end of file +} diff --git a/android/features/perpetual/presents/src/main/kotlin/com/gemwallet/features/perpetual/views/position/PerpetualPositionScene.kt b/android/features/perpetual/presents/src/main/kotlin/com/gemwallet/features/perpetual/views/position/PerpetualPositionScene.kt index de33d19671..32168629d0 100644 --- a/android/features/perpetual/presents/src/main/kotlin/com/gemwallet/features/perpetual/views/position/PerpetualPositionScene.kt +++ b/android/features/perpetual/presents/src/main/kotlin/com/gemwallet/features/perpetual/views/position/PerpetualPositionScene.kt @@ -85,6 +85,7 @@ private fun PerpetualPositionScenePreview() { override val maxLeverage: Int = 40 override val price: Double = 0.0 override val identifier: String = "BTC-PERP" + override val isIsolatedOnly: Boolean = false } val samplePosition = object : PerpetualPositionDetailsDataAggregate { @@ -136,4 +137,4 @@ private fun PerpetualPositionScenePreview() { onClose = {} ) } -} \ No newline at end of file +} diff --git a/android/features/transfer_amount/viewmodels/src/main/kotlin/com/gemwallet/features/transfer_amount/viewmodels/PerpetualAmountViewModel.kt b/android/features/transfer_amount/viewmodels/src/main/kotlin/com/gemwallet/features/transfer_amount/viewmodels/PerpetualAmountViewModel.kt index 9a4c113d66..c1e001b022 100644 --- a/android/features/transfer_amount/viewmodels/src/main/kotlin/com/gemwallet/features/transfer_amount/viewmodels/PerpetualAmountViewModel.kt +++ b/android/features/transfer_amount/viewmodels/src/main/kotlin/com/gemwallet/features/transfer_amount/viewmodels/PerpetualAmountViewModel.kt @@ -134,6 +134,7 @@ class PerpetualAmountViewModel @Inject constructor( leverage = leverage.value, baseAsset = asset, // USD direction = params.perpetualDirection ?: PerpetualDirection.Long, + marginType = perpetual.marginType, ) else -> throw IllegalArgumentException() } diff --git a/android/gemcore/src/main/kotlin/com/gemwallet/android/domains/perpetual/aggregates/PerpetualDetailsDataAggregate.kt b/android/gemcore/src/main/kotlin/com/gemwallet/android/domains/perpetual/aggregates/PerpetualDetailsDataAggregate.kt index 87209f0391..4b2ec19d3f 100644 --- a/android/gemcore/src/main/kotlin/com/gemwallet/android/domains/perpetual/aggregates/PerpetualDetailsDataAggregate.kt +++ b/android/gemcore/src/main/kotlin/com/gemwallet/android/domains/perpetual/aggregates/PerpetualDetailsDataAggregate.kt @@ -1,6 +1,7 @@ package com.gemwallet.android.domains.perpetual.aggregates import com.wallet.core.primitives.Asset +import com.wallet.core.primitives.PerpetualMarginType import com.wallet.core.primitives.PerpetualProvider interface PerpetualDetailsDataAggregate { @@ -14,4 +15,7 @@ interface PerpetualDetailsDataAggregate { val maxLeverage: Int val price: Double val identifier: String -} \ No newline at end of file + val isIsolatedOnly: Boolean + val marginType: PerpetualMarginType + get() = if (isIsolatedOnly) PerpetualMarginType.Isolated else PerpetualMarginType.Cross +} diff --git a/android/gemcore/src/main/kotlin/com/gemwallet/android/model/ConfirmParams.kt b/android/gemcore/src/main/kotlin/com/gemwallet/android/model/ConfirmParams.kt index 2f158ab7b1..7cdb46cfed 100644 --- a/android/gemcore/src/main/kotlin/com/gemwallet/android/model/ConfirmParams.kt +++ b/android/gemcore/src/main/kotlin/com/gemwallet/android/model/ConfirmParams.kt @@ -21,6 +21,7 @@ import com.wallet.core.primitives.Delegation import com.wallet.core.primitives.DelegationValidator import com.wallet.core.primitives.NFTAsset import com.wallet.core.primitives.PerpetualDirection +import com.wallet.core.primitives.PerpetualMarginType import com.wallet.core.primitives.PerpetualProvider import com.wallet.core.primitives.Resource import com.wallet.core.primitives.TransactionType @@ -155,6 +156,7 @@ sealed class ConfirmParams() { leverage: Int, baseAsset: Asset, // USD direction: PerpetualDirection, + marginType: PerpetualMarginType, slippage: Double = 2.0, ): PerpetualParams { val marginAmount = Crypto(amount).value(baseAsset.decimals).toDouble() @@ -162,20 +164,21 @@ sealed class ConfirmParams() { val assetSize = fiatValue / perpetualPrice val slippageFraction = slippage / 100.0 - val slippage = when (direction) { - PerpetualDirection.Short -> 1.0 - slippageFraction // TODO: Why we need slippage on Close = 1.0 + slippageFraction? - PerpetualDirection.Long -> 1.0 + slippageFraction // Close = 1.0 - slippageFraction + val slippageMultiplier = when (direction) { + PerpetualDirection.Short -> 1.0 - slippageFraction + PerpetualDirection.Long -> 1.0 + slippageFraction } val order = PerpetualParams.Order( perpetualId = perpetualId, provider = perpetualProvider, direction = direction, + marginType = marginType, baseAsset = baseAsset, assetIndex = perpetualIdentifier.toInt(), fiatValue = fiatValue, size = assetSize, - slippage = slippage, + slippage = slippageMultiplier, leverage = leverage, marketPrice = perpetualPrice, marginAmount = marginAmount, @@ -600,6 +603,7 @@ sealed class ConfirmParams() { val perpetualId: String, val provider: PerpetualProvider, val direction: PerpetualDirection, + val marginType: PerpetualMarginType, val baseAsset: Asset, val assetIndex: Int, // val price: String, @@ -753,6 +757,10 @@ fun ConfirmParams.PerpetualParams.Order.toGem() = PerpetualConfirmData( PerpetualDirection.Long -> uniffi.gemstone.PerpetualDirection.LONG PerpetualDirection.Short -> uniffi.gemstone.PerpetualDirection.SHORT }, + marginType = when (marginType) { + PerpetualMarginType.Cross -> uniffi.gemstone.GemPerpetualMarginType.CROSS + PerpetualMarginType.Isolated -> uniffi.gemstone.GemPerpetualMarginType.ISOLATED + }, baseAsset = baseAsset.toGem(), assetIndex = assetIndex, price = marketPrice.toString(), diff --git a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/ApprovalData.kt b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/ApprovalData.kt new file mode 100644 index 0000000000..de541d0a84 --- /dev/null +++ b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/ApprovalData.kt @@ -0,0 +1,3 @@ +package com.wallet.core.primitives + +typealias ApprovalData = com.wallet.core.primitives.swap.ApprovalData diff --git a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/AssetDetails.kt b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/AssetDetails.kt index b6600f5b1b..1041563ecc 100644 --- a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/AssetDetails.kt +++ b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/AssetDetails.kt @@ -41,8 +41,7 @@ data class AssetFull ( val score: AssetScore, val tags: List, val links: List, - val perpetuals: List, - val price: Price, + val perpetuals: List ) @Serializable diff --git a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/Auth.kt b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/Auth.kt index 46d2dae9af..d1f1e0c88a 100644 --- a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/Auth.kt +++ b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/Auth.kt @@ -13,13 +13,6 @@ data class AuthNonce ( val timestamp: UInt ) -@Serializable -data class AuthMessage ( - val chain: Chain, - val address: String, - val authNonce: AuthNonce -) - @Serializable data class AuthPayload ( val deviceId: String, diff --git a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/ContractCallData.kt b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/ContractCallData.kt index f0f4bdef9c..d723cffd7c 100644 --- a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/ContractCallData.kt +++ b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/ContractCallData.kt @@ -4,7 +4,6 @@ package com.wallet.core.primitives -import com.wallet.core.primitives.swap.ApprovalData import kotlinx.serialization.Serializable import kotlinx.serialization.SerialName diff --git a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/FiatQuote.kt b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/FiatQuote.kt index d028ced0b2..d6a66d832e 100644 --- a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/FiatQuote.kt +++ b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/FiatQuote.kt @@ -18,12 +18,6 @@ data class FiatQuote ( val paymentMethods: List ) -@Serializable -data class FiatQuoteError ( - val provider: String? = null, - val error: String -) - @Serializable data class FiatQuoteUrl ( val redirectUrl: String diff --git a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/FiatTransaction.kt b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/FiatTransaction.kt index e1fdd8d807..9ea04a4caa 100644 --- a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/FiatTransaction.kt +++ b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/FiatTransaction.kt @@ -15,3 +15,35 @@ enum class FiatQuoteType(val string: String) { Sell("sell"), } +@Serializable +enum class FiatTransactionStatus(val string: String) { + @SerialName("complete") + Complete("complete"), + @SerialName("pending") + Pending("pending"), + @SerialName("failed") + Failed("failed"), + @SerialName("unknown") + Unknown("unknown"), +} + +@Serializable +data class FiatTransaction ( + val id: String, + val assetId: AssetId, + val transactionType: FiatQuoteType, + val provider: FiatProviderName, + val status: FiatTransactionStatus, + val fiatAmount: Double, + val fiatCurrency: String, + val value: String, + val createdAt: SerializedDate +) + +@Serializable +data class FiatTransactionInfo ( + val transaction: FiatTransaction, + val asset: Asset, + val detailsUrl: String? = null +) + diff --git a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/Nft.kt b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/Nft.kt index 879c4cebed..1e446e6efe 100644 --- a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/Nft.kt +++ b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/Nft.kt @@ -70,13 +70,6 @@ data class NFTAssetData ( val asset: NFTAsset ) -@Serializable -data class NFTAssetId ( - val chain: Chain, - val contractAddress: String, - val tokenId: String -) - @Serializable data class NFTData ( val collection: NFTCollection, diff --git a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/Node.kt b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/Node.kt index aacb7e87dc..50fd638e60 100644 --- a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/Node.kt +++ b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/Node.kt @@ -34,9 +34,3 @@ data class ChainNodes ( val nodes: List ) -@Serializable -data class NodesResponse ( - val version: Int, - val nodes: List -) - diff --git a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/Perpetual.kt b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/Perpetual.kt index e84d1a0b55..0dfb1713cf 100644 --- a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/Perpetual.kt +++ b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/Perpetual.kt @@ -46,7 +46,8 @@ data class Perpetual ( val openInterest: Double, val volume24h: Double, val funding: Double, - val maxLeverage: UByte + val maxLeverage: UByte, + val isIsolatedOnly: Boolean ) @Serializable @@ -66,6 +67,7 @@ data class PerpetualBasic ( @Serializable data class PerpetualConfirmData ( val direction: PerpetualDirection, + val marginType: PerpetualMarginType, val baseAsset: Asset, val assetIndex: Int, val price: String, diff --git a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/Scan.kt b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/Scan.kt index bc177f3cfe..df7bbe407d 100644 --- a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/Scan.kt +++ b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/Scan.kt @@ -7,29 +7,6 @@ package com.wallet.core.primitives import kotlinx.serialization.Serializable import kotlinx.serialization.SerialName -@Serializable -enum class AddressType(val string: String) { - @SerialName("address") - Address("address"), - @SerialName("contract") - Contract("contract"), - @SerialName("validator") - Validator("validator"), - @SerialName("contact") - Contact("contact"), -} - -@Serializable -data class ScanAddress ( - val chain: Chain, - val address: String, - val name: String? = null, - val type: AddressType? = null, - val isMalicious: Boolean? = null, - val isMemoRequired: Boolean? = null, - val isVerified: Boolean? = null -) - @Serializable data class ScanAddressTarget ( val assetId: AssetId, @@ -50,3 +27,17 @@ data class ScanTransactionPayload ( val type: TransactionType ) +@Serializable +enum class AddressType(val string: String) { + @SerialName("address") + Address("address"), + @SerialName("contract") + Contract("contract"), + @SerialName("validator") + Validator("validator"), + @SerialName("contact") + Contact("contact"), + @SerialName("internalWallet") + InternalWallet("internalWallet"), +} + diff --git a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/Simulation.kt b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/Simulation.kt index b89b786eb8..4414569c6e 100644 --- a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/Simulation.kt +++ b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/Simulation.kt @@ -72,8 +72,6 @@ enum class SimulationSeverity(val string: String) { Critical("critical"), } -enum class SimulationWarningType - @Serializable data class SimulationWarning ( val severity: SimulationSeverity, diff --git a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/SimulationWarningType.kt b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/SimulationWarningType.kt new file mode 100644 index 0000000000..7982c125da --- /dev/null +++ b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/SimulationWarningType.kt @@ -0,0 +1,5 @@ +package com.wallet.core.primitives + +import kotlinx.serialization.json.JsonElement + +typealias SimulationWarningType = JsonElement diff --git a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/StakeType.kt b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/StakeType.kt index 47984eab29..6980df8511 100644 --- a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/StakeType.kt +++ b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/StakeType.kt @@ -8,12 +8,10 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.SerialName @Serializable -enum class FreezeType(val string: String) { - @SerialName("freeze") - Freeze("freeze"), - @SerialName("unfreeze") - Unfreeze("unfreeze"), -} +data class RedelegateData ( + val delegation: Delegation, + val toValidator: DelegationValidator +) @Serializable enum class Resource(val string: String) { @@ -23,18 +21,6 @@ enum class Resource(val string: String) { Energy("energy"), } -@Serializable -data class FreezeData ( - val freezeType: FreezeType, - val resource: Resource -) - -@Serializable -data class RedelegateData ( - val delegation: Delegation, - val toValidator: DelegationValidator -) - @Serializable data class TronUnfreeze ( val resource: Resource, @@ -66,7 +52,10 @@ sealed class StakeType { data class Withdraw(val content: Delegation): StakeType() @Serializable @SerialName("Freeze") - data class Freeze(val content: FreezeData): StakeType() + data class Freeze(val content: Resource): StakeType() + @Serializable + @SerialName("Unfreeze") + data class Unfreeze(val content: Resource): StakeType() } @Serializable diff --git a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/Stream.kt b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/Stream.kt index e9cb3267ba..4f2443abcb 100644 --- a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/Stream.kt +++ b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/Stream.kt @@ -42,7 +42,7 @@ data class StreamPriceAlertUpdate ( @Serializable data class StreamTransactionsUpdate ( val walletId: WalletId, - val transactions: List + val transactions: List ) @Serializable diff --git a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/SwapProvider.kt b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/SwapProvider.kt index 050358d366..fcf58f0ac5 100644 --- a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/SwapProvider.kt +++ b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/SwapProvider.kt @@ -47,5 +47,7 @@ enum class SwapProvider(val string: String) { Hyperliquid("hyperliquid"), @SerialName("orca") Orca("orca"), + @SerialName("squid") + Squid("squid"), } diff --git a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/Transaction.kt b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/Transaction.kt index e1198a1a07..551ea5dfcc 100644 --- a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/Transaction.kt +++ b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/Transaction.kt @@ -9,7 +9,7 @@ import kotlinx.serialization.SerialName @Serializable data class Transaction ( - val id: String, + val id: TransactionId, val assetId: AssetId, val from: String, val to: String, diff --git a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/TransactionId.kt b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/TransactionId.kt new file mode 100644 index 0000000000..1c4f7f43d6 --- /dev/null +++ b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/TransactionId.kt @@ -0,0 +1,3 @@ +package com.wallet.core.primitives + +typealias TransactionId = String diff --git a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/TransactionMetadataTypes.kt b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/TransactionMetadataTypes.kt index fd4add043c..c075383b09 100644 --- a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/TransactionMetadataTypes.kt +++ b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/TransactionMetadataTypes.kt @@ -18,6 +18,7 @@ data class TransactionPerpetualMetadata ( val pnl: Double, val price: Double, val direction: PerpetualDirection, + val isLiquidation: Boolean? = null, val provider: PerpetualProvider? = null ) diff --git a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/swap/Approval.kt b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/swap/Approval.kt index 64fb0fb733..653c30c629 100644 --- a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/swap/Approval.kt +++ b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/swap/Approval.kt @@ -4,7 +4,6 @@ package com.wallet.core.primitives.swap -import com.wallet.core.primitives.SwapProvider import kotlinx.serialization.Serializable import kotlinx.serialization.SerialName @@ -15,13 +14,6 @@ data class ApprovalData ( val value: String ) -@Serializable -data class QuoteAsset ( - val id: String, - val symbol: String, - val decimals: UInt -) - @Serializable data class SwapProviderData ( val provider: SwapProvider, diff --git a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/swap/Result.kt b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/swap/Result.kt index 831e79b6fe..8ed53e9757 100644 --- a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/swap/Result.kt +++ b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/swap/Result.kt @@ -4,7 +4,6 @@ package com.wallet.core.primitives.swap -import com.wallet.core.primitives.TransactionSwapMetadata import kotlinx.serialization.Serializable import kotlinx.serialization.SerialName diff --git a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/swap/SwapProvider.kt b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/swap/SwapProvider.kt new file mode 100644 index 0000000000..f014712890 --- /dev/null +++ b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/swap/SwapProvider.kt @@ -0,0 +1,3 @@ +package com.wallet.core.primitives.swap + +typealias SwapProvider = com.wallet.core.primitives.SwapProvider diff --git a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/swap/TransactionSwapMetadata.kt b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/swap/TransactionSwapMetadata.kt new file mode 100644 index 0000000000..a1b6766550 --- /dev/null +++ b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/swap/TransactionSwapMetadata.kt @@ -0,0 +1,3 @@ +package com.wallet.core.primitives.swap + +typealias TransactionSwapMetadata = com.wallet.core.primitives.TransactionSwapMetadata diff --git a/android/justfile b/android/justfile index 19405e53b9..fe140dd2af 100644 --- a/android/justfile +++ b/android/justfile @@ -27,6 +27,18 @@ build-test: test: @./gradlew connectedGoogleDebugAndroidTest +unit-test: + @./gradlew testGoogleDebugUnitTest + +provision-emulator: + @bash ./scripts/android_emulator.sh provision + +start-emulator: + @bash ./scripts/android_emulator.sh setup + +stop-emulator: + @bash ./scripts/android_emulator.sh shutdown + mobsfscan: @command -v uv >/dev/null || { \ echo "uv is not installed. Install it via 'curl -LsSf https://astral.sh/uv/install.sh | sh'."; \ diff --git a/android/scripts/android_emulator.sh b/android/scripts/android_emulator.sh new file mode 100644 index 0000000000..a65ac8da0e --- /dev/null +++ b/android/scripts/android_emulator.sh @@ -0,0 +1,210 @@ +#!/usr/bin/env bash +set -euo pipefail + +SDK_ROOT="${ANDROID_SDK_ROOT:-${ANDROID_HOME:-/opt/homebrew/share/android-commandlinetools}}" +if [[ ! -d "${SDK_ROOT}" ]] && [[ -f local.properties ]]; then + SDK_ROOT="$(sed -n 's/^sdk\.dir=//p' local.properties | head -n 1)" +fi +[[ -n "${SDK_ROOT}" ]] || { echo "ANDROID_HOME is not set"; exit 1; } + +SDKMANAGER="${SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager" +AVDMANAGER="${SDK_ROOT}/cmdline-tools/latest/bin/avdmanager" +EMULATOR="${SDK_ROOT}/emulator/emulator" +ADB="${SDK_ROOT}/platform-tools/adb" +DEVICES_FILE="${ANDROID_EMULATOR_DEVICES_FILE:-}" + +BOOT_TIMEOUT_SECONDS="${ANDROID_EMULATOR_BOOT_TIMEOUT:-120}" +ARCH="arm64-v8a" + +require_tool() { + [[ -x "${1}" ]] || { + echo "${2} is missing under ${SDK_ROOT}" >&2 + exit 1 + } +} + +list_devices() { + [[ -f "${DEVICES_FILE}" ]] || { + echo "ANDROID_EMULATOR_DEVICES_FILE is not set or does not exist" >&2 + exit 1 + } + awk -F: '!/^#/ && NF { print $1 }' "${DEVICES_FILE}" +} + +default_avd_name() { + [[ -f "${DEVICES_FILE}" ]] || return 1 + awk -F: '!/^#/ && NF { print $1; exit }' "${DEVICES_FILE}" +} + +api_for_avd_name() { + local avd_name="$1" + [[ -f "${DEVICES_FILE}" ]] || return 1 + awk -F: -v avd_name="${avd_name}" '$1 == avd_name { print $2; exit }' "${DEVICES_FILE}" +} + +resolve_avd_name() { + if [[ -n "${ANDROID_AVD_NAME:-}" ]]; then + echo "${ANDROID_AVD_NAME}" + else + default_avd_name + fi +} + +resolve_api() { + local avd_name="$1" + if [[ -n "${ANDROID_API:-}" ]]; then + echo "${ANDROID_API}" + else + api_for_avd_name "${avd_name}" + fi +} + +install_sdk_packages() { + local image="$1" + local compile_sdk="${2:-}" + local packages=("platform-tools" "emulator" "${image}") + + if [[ -n "${compile_sdk}" ]]; then + packages+=("platforms;android-${compile_sdk}" "build-tools;${compile_sdk}.0.0") + fi + + set +o pipefail + yes | "${SDKMANAGER}" --sdk_root="${SDK_ROOT}" --install "${packages[@]}" + install_status=$? + set -o pipefail + [[ ${install_status} -eq 0 ]] || exit "${install_status}" +} + +create_avd_if_missing() { + local avd_name="$1" + local image="$2" + local device="${3:-}" + + if ! "${EMULATOR}" -list-avds | grep -Fxq "${avd_name}"; then + local create_args=(-n "${avd_name}" -k "${image}" --force) + if [[ -n "${device}" ]]; then + create_args+=(--device "${device}") + fi + echo "no" | "${AVDMANAGER}" create avd "${create_args[@]}" + fi +} + +provision_one() { + local avd_name="$1" + local api="$2" + local compile_sdk="${ANDROID_COMPILE_SDK:-}" + local device="${ANDROID_EMULATOR_DEVICE:-}" + local image="system-images;android-${api};google_apis;${ARCH}" + + require_tool "${SDKMANAGER}" "Android sdkmanager" + require_tool "${AVDMANAGER}" "Android avdmanager" + install_sdk_packages "${image}" "${compile_sdk}" + require_tool "${EMULATOR}" "Android emulator" + require_tool "${ADB}" "Android adb" + create_avd_if_missing "${avd_name}" "${image}" "${device}" +} + +provision() { + if [[ -f "${DEVICES_FILE}" && -z "${ANDROID_AVD_NAME:-}" && -z "${ANDROID_API:-}" ]]; then + while IFS=: read -r avd_name api; do + [[ "${avd_name}" =~ ^#.*$ || -z "${avd_name}" ]] && continue + provision_one "${avd_name}" "${api}" + done < "${DEVICES_FILE}" + return + fi + + local avd_name + local api + + avd_name="$(resolve_avd_name)" + [[ -n "${avd_name}" ]] || { echo "ANDROID_AVD_NAME is not set" >&2; exit 1; } + + api="$(resolve_api "${avd_name}")" + [[ -n "${api}" ]] || { echo "ANDROID_API is not set for ${avd_name}" >&2; exit 1; } + + provision_one "${avd_name}" "${api}" +} + +setup() { + local avd_name + local api + local image + local image_dir + local boot_checks=$((BOOT_TIMEOUT_SECONDS / 2)) + + avd_name="$(resolve_avd_name)" + [[ -n "${avd_name}" ]] || { echo "ANDROID_AVD_NAME is not set" >&2; exit 1; } + + api="$(resolve_api "${avd_name}")" + [[ -n "${api}" ]] || { echo "ANDROID_API is not set for ${avd_name}" >&2; exit 1; } + + image="system-images;android-${api};google_apis;${ARCH}" + image_dir="${SDK_ROOT}/system-images/android-${api}/google_apis/${ARCH}" + + require_tool "${EMULATOR}" "Android emulator" + require_tool "${ADB}" "Android adb" + [[ -d "${image_dir}" ]] || { + echo "System image ${image} is missing under ${SDK_ROOT}. Run '$0 provision' first." >&2 + exit 1 + } + "${EMULATOR}" -list-avds | grep -Fxq "${avd_name}" || { + echo "AVD ${avd_name} is missing under ${SDK_ROOT}. Run '$0 provision' first." >&2 + exit 1 + } + + "${ADB}" emu kill 2>/dev/null || true + sleep 2 + + "${EMULATOR}" -avd "${avd_name}" -no-window -no-audio & + EMU_PID=$! + sleep 5 + if ! kill -0 "${EMU_PID}" 2>/dev/null; then + echo "Emulator failed to start" >&2 + exit 1 + fi + + "${ADB}" wait-for-device + for _ in $(seq 1 "${boot_checks}"); do + if [[ "$("${ADB}" shell getprop sys.boot_completed 2>/dev/null)" == "1" ]]; then + break + fi + if ! kill -0 "${EMU_PID}" 2>/dev/null; then + echo "Emulator failed during boot" >&2 + exit 1 + fi + sleep 2 + done + if [[ "$("${ADB}" shell getprop sys.boot_completed 2>/dev/null)" != "1" ]]; then + echo "Emulator failed to boot within ${BOOT_TIMEOUT_SECONDS}s" >&2 + exit 1 + fi + if ! "${ADB}" devices | grep -q '^emulator-'; then + echo "Emulator is not visible to adb" >&2 + exit 1 + fi + echo "Emulator ${avd_name} ready" +} + +install() { + local version="$1" + local apk="/tmp/gem_wallet_${version}.apk" + + require_tool "${ADB}" "Android adb" + curl -L -o "${apk}" "https://apk.gemwallet.com/gem_wallet_universal_${version}.apk" + "${ADB}" uninstall com.gemwallet.android 2>/dev/null || true + "${ADB}" install "${apk}" +} + +shutdown() { + [[ -x "${ADB}" ]] || exit 0 + "${ADB}" emu kill >/dev/null 2>&1 || true +} + +case "${1:-}" in + list) list_devices ;; + provision) provision ;; + setup) setup ;; + shutdown) shutdown ;; + install) install "$2" ;; + *) echo "Usage: $0 {list|provision|setup|shutdown|install VERSION}" >&2; exit 1 ;; +esac diff --git a/core b/core index d78a32194b..f2f5fe36d5 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit d78a32194b2a3f7a42fb898440b355e09cb37640 +Subproject commit f2f5fe36d530ed32c2d5e0a1301e290dece930d9 diff --git a/ios/Features/Perpetuals/Sources/ViewModels/PerpetualPositionViewModel.swift b/ios/Features/Perpetuals/Sources/ViewModels/PerpetualPositionViewModel.swift index a9ccd5b8b3..9b37cc1ecb 100644 --- a/ios/Features/Perpetuals/Sources/ViewModels/PerpetualPositionViewModel.swift +++ b/ios/Features/Perpetuals/Sources/ViewModels/PerpetualPositionViewModel.swift @@ -1,13 +1,13 @@ // Copyright (c). Gem Wallet. All rights reserved. +import Components +import Formatters import Foundation +import Localization import Primitives -import Formatters -import SwiftUI -import Style -import Components import PrimitivesComponents -import Localization +import Style +import SwiftUI public struct PerpetualPositionViewModel { public let data: PerpetualPositionData @@ -20,39 +20,39 @@ public struct PerpetualPositionViewModel { currencyStyle: CurrencyFormatterType = .currency ) { self.data = data - self.currencyFormatter = CurrencyFormatter(type: currencyStyle, currencyCode: Currency.usd.rawValue) - self.percentFormatter = CurrencyFormatter(type: .percent, currencyCode: Currency.usd.rawValue) - self.autocloseFormatter = AutocloseFormatter(currencyFormatter: currencyFormatter) + currencyFormatter = CurrencyFormatter(type: currencyStyle, currencyCode: Currency.usd.rawValue) + percentFormatter = CurrencyFormatter(type: .percent, currencyCode: Currency.usd.rawValue) + autocloseFormatter = AutocloseFormatter(currencyFormatter: currencyFormatter) } - + public var assetImage: AssetImage { AssetIdViewModel(assetId: data.perpetual.assetId).assetImage } - + public var nameText: String { - data.asset.name + data.perpetual.name } - + public var symbolText: String { data.asset.symbol } - + public var leverageText: String { "\(Int(data.position.leverage))x" } - + public var directionText: String { PerpetualDirectionViewModel(direction: data.position.direction).title } - + public var positionTypeText: String { "\(directionText.uppercased()) \(leverageText)" } - + public var positionTypeColor: Color { PerpetualDirectionViewModel(direction: data.position.direction).color } - + public var pnlViewModel: PnLViewModel { PnLViewModel( pnl: data.position.pnl, @@ -61,12 +61,14 @@ public struct PerpetualPositionViewModel { percentFormatter: percentFormatter ) } + public var pnlField: ListItemField { ListItemField( title: TextValue(text: pnlViewModel.title, style: .body), value: TextValue(text: pnlViewModel.text ?? "", style: pnlViewModel.textStyle) ) } + public var pnlColor: Color { pnlViewModel.color } public var pnlPercent: Double { pnlViewModel.percent } public var pnlWithPercentText: String { pnlViewModel.text ?? "" } @@ -85,7 +87,7 @@ public struct PerpetualPositionViewModel { public var marginField: ListItemField { let marginAmount = currencyFormatter.string(data.position.marginAmount) - return ListItemField(title: Localized.Perpetual.margin, value: "\(marginAmount) (\(data.position.marginType.displayText))") + return ListItemField(title: Localized.Perpetual.margin, value: "\(marginAmount) (\(data.position.marginType.title))") } public var fundingPaymentsField: ListItemField { @@ -94,6 +96,7 @@ public struct PerpetualPositionViewModel { value: TextValue(text: fundingPaymentsModel.text ?? "-", style: fundingPaymentsModel.textStyle) ) } + public var fundingPaymentsColor: Color { fundingPaymentsModel.color } public var sizeField: ListItemField { @@ -128,14 +131,3 @@ extension PerpetualPositionViewModel { extension PerpetualPositionViewModel: Identifiable { public var id: String { data.position.id } } - -extension PerpetualMarginType { - var displayText: String { - switch self { - case .cross: - return "cross" - case .isolated: - return "isolated" - } - } -} diff --git a/ios/Features/Perpetuals/Sources/ViewModels/PerpetualSceneViewModel.swift b/ios/Features/Perpetuals/Sources/ViewModels/PerpetualSceneViewModel.swift index a95a001547..99894d3fe0 100644 --- a/ios/Features/Perpetuals/Sources/ViewModels/PerpetualSceneViewModel.swift +++ b/ios/Features/Perpetuals/Sources/ViewModels/PerpetualSceneViewModel.swift @@ -122,7 +122,7 @@ public final class PerpetualSceneViewModel { } } - private var currentChartSubscription: ChartSubscription { ChartSubscription(coin: perpetual.name, period: currentPeriod) } + private var currentChartSubscription: ChartSubscription { ChartSubscription(coin: perpetual.coin, period: currentPeriod) } } // MARK: - Actions @@ -130,7 +130,7 @@ public final class PerpetualSceneViewModel { public extension PerpetualSceneViewModel { func fetch() { Task { await observerService.update(for: wallet) } - Task { try await perpetualService.updateMarket(symbol: perpetual.name) } + Task { try await perpetualService.updateMarket(symbol: perpetual.coin) } Task { await fetchTransactions() } Task { await updateCandlesticks() } } @@ -152,7 +152,7 @@ public extension PerpetualSceneViewModel { func onScenePhaseChange(_ oldPhase: ScenePhase, _ newPhase: ScenePhase) { switch newPhase { case .active: - Task { try? await perpetualService.updateMarket(symbol: perpetual.name) } + Task { try? await perpetualService.updateMarket(symbol: perpetual.coin) } Task { await fetchTransactions() } Task { await updateCandlesticks() } case .inactive, .background: break @@ -162,9 +162,9 @@ public extension PerpetualSceneViewModel { func onPeriodChange(_ oldPeriod: ChartPeriod, _ newPeriod: ChartPeriod) { Task { - await unsubscribeCandles(ChartSubscription(coin: perpetual.name, period: oldPeriod)) + await unsubscribeCandles(ChartSubscription(coin: perpetual.coin, period: oldPeriod)) await updateCandlesticks() - await subscribeCandles(ChartSubscription(coin: perpetual.name, period: newPeriod)) + await subscribeCandles(ChartSubscription(coin: perpetual.coin, period: newPeriod)) } } @@ -223,7 +223,8 @@ public extension PerpetualSceneViewModel { func onOpenLongPosition() { guard let transferData = createTransferData( direction: .long, - leverage: perpetual.maxLeverage + leverage: perpetual.maxLeverage, + marginType: perpetual.marginType ) else { return } @@ -233,7 +234,8 @@ public extension PerpetualSceneViewModel { func onOpenShortPosition() { guard let transferData = createTransferData( direction: .short, - leverage: perpetual.maxLeverage + leverage: perpetual.maxLeverage, + marginType: perpetual.marginType ) else { return } @@ -244,7 +246,7 @@ public extension PerpetualSceneViewModel { isPresentingModifyAlert = false guard let position = positions.first?.position, - let transferData = createTransferData(direction: position.direction, leverage: position.leverage) + let transferData = createTransferData(direction: position.direction, leverage: position.leverage, marginType: position.marginType) else { return } onPositionAction(.increase(transferData)) @@ -264,7 +266,7 @@ public extension PerpetualSceneViewModel { } }() - guard let transferData = createTransferData(direction: direction, leverage: position.leverage) else { + guard let transferData = createTransferData(direction: direction, leverage: position.leverage, marginType: position.marginType) else { return } @@ -289,7 +291,7 @@ private extension PerpetualSceneViewModel { state = .loading do { let candlesticks = try await perpetualService.candlesticks( - symbol: perpetual.name, + symbol: perpetual.coin, period: currentPeriod ) state = .data(candlesticks) @@ -341,7 +343,7 @@ private extension PerpetualSceneViewModel { state = .data(candlesticks) } - func createTransferData(direction: PerpetualDirection, leverage: UInt8) -> PerpetualTransferData? { + func createTransferData(direction: PerpetualDirection, leverage: UInt8, marginType: PerpetualMarginType) -> PerpetualTransferData? { guard let assetIndex = Int(perpetual.identifier) else { return nil } @@ -353,7 +355,8 @@ private extension PerpetualSceneViewModel { baseAsset: .hypercoreUSDC(), assetIndex: assetIndex, price: perpetual.price, - leverage: leverage + leverage: leverage, + marginType: marginType ) } diff --git a/ios/Features/Perpetuals/TestKit/Perpetual+TestKit.swift b/ios/Features/Perpetuals/TestKit/Perpetual+TestKit.swift index 5326e687fa..df4fe7aa0b 100644 --- a/ios/Features/Perpetuals/TestKit/Perpetual+TestKit.swift +++ b/ios/Features/Perpetuals/TestKit/Perpetual+TestKit.swift @@ -15,7 +15,8 @@ public extension Perpetual { maxLeverage: UInt8 = 25, openInterest: Double = 1000000, volume24h: Double = 5000000, - funding: Double = 0.01 + funding: Double = 0.01, + isIsolatedOnly: Bool = false ) -> Perpetual { Perpetual( id: id, @@ -28,7 +29,8 @@ public extension Perpetual { openInterest: openInterest, volume24h: volume24h, funding: funding, - maxLeverage: maxLeverage + maxLeverage: maxLeverage, + isIsolatedOnly: isIsolatedOnly ) } } diff --git a/ios/Features/Perpetuals/Tests/PerpetualPositionViewModelTests.swift b/ios/Features/Perpetuals/Tests/PerpetualPositionViewModelTests.swift index 6f8a051217..21cb3b3c91 100644 --- a/ios/Features/Perpetuals/Tests/PerpetualPositionViewModelTests.swift +++ b/ios/Features/Perpetuals/Tests/PerpetualPositionViewModelTests.swift @@ -1,33 +1,32 @@ // Copyright (c). Gem Wallet. All rights reserved. -import Testing -import Primitives -import Style import Components -import PerpetualsTestKit @testable import Perpetuals +import PerpetualsTestKit +import Primitives +import Style +import Testing struct PerpetualPositionViewModelTests { - @Test func leverageText() { #expect(createPositionViewModel(.mock(leverage: 10)).leverageText == "10x") } - + @Test func directionText() { #expect(createPositionViewModel(.mock(size: 100)).directionText == "Long") #expect(createPositionViewModel(.mock(size: -100)).directionText == "Short") } - + @Test func positionTypeText() { #expect(createPositionViewModel(.mock(size: 100, leverage: 5)).positionTypeText == "LONG 5x") } - + @Test func marginField() { - #expect(createPositionViewModel(.mock(marginAmount: 1000)).marginField.value.text == "$1,000.00 (isolated)") + #expect(createPositionViewModel(.mock(marginAmount: 1000)).marginField.value.text == "$1,000.00 (Isolated)") } @Test @@ -52,20 +51,20 @@ struct PerpetualPositionViewModelTests { #expect(createPositionViewModel(.mock(liquidationPrice: 0)).liquidationPriceField == nil) #expect(createPositionViewModel(.mock(liquidationPrice: nil)).liquidationPriceField == nil) } - + @Test func positionTypeColor() { #expect(createPositionViewModel(.mock(direction: .short)).positionTypeColor == Colors.red) #expect(createPositionViewModel(.mock(direction: .long)).positionTypeColor == Colors.green) } - + // @Test // func liquidationPriceColor() { // // Long position: entry $2.00, liquidation $1.50 // #expect(createPositionViewModel(.mock(entryPrice: 2.00, currencyPrice: 2.00, liquidationPrice: 1.50)).liquidationPriceColor == Colors.secondaryText) // #expect(createPositionViewModel(.mock(entryPrice: 2.00, currencyPrice: 1.75, liquidationPrice: 1.50)).liquidationPriceColor == Colors.orange) // #expect(createPositionViewModel(.mock(entryPrice: 2.00, currencyPrice: 1.599, liquidationPrice: 1.50)).liquidationPriceColor == Colors.red) -// +// // // Short position: entry $1.27, liquidation $1.91 // #expect(createPositionViewModel(.mock(entryPrice: 1.27, currencyPrice: 1.27, liquidationPrice: 1.91)).liquidationPriceColor == Colors.secondaryText) // #expect(createPositionViewModel(.mock(entryPrice: 1.27, currencyPrice: 1.59, liquidationPrice: 1.91)).liquidationPriceColor == Colors.orange) diff --git a/ios/Features/Swap/Sources/ViewModels/SwapSceneViewModel.swift b/ios/Features/Swap/Sources/ViewModels/SwapSceneViewModel.swift index 2adbb5710c..2c7a9f8922 100644 --- a/ios/Features/Swap/Sources/ViewModels/SwapSceneViewModel.swift +++ b/ios/Features/Swap/Sources/ViewModels/SwapSceneViewModel.swift @@ -212,7 +212,6 @@ extension SwapSceneViewModel { isPresentingInfoSheet = .info(.priceImpact) } - func onSelectAssetPay() { isPresentingInfoSheet = .selectAsset(.pay) } diff --git a/ios/Features/Transfer/Sources/ViewModels/ConfirmRecipientViewModel.swift b/ios/Features/Transfer/Sources/ViewModels/ConfirmRecipientViewModel.swift index 9b4b6f27ba..d81f48bffa 100644 --- a/ios/Features/Transfer/Sources/ViewModels/ConfirmRecipientViewModel.swift +++ b/ios/Features/Transfer/Sources/ViewModels/ConfirmRecipientViewModel.swift @@ -46,7 +46,7 @@ extension ConfirmRecipientViewModel { private var addressNameImage: AssetImage? { switch addressName?.type { case .contact: .image(Images.System.person) - case .address, .contract, .validator, .none: nil + case .address, .contract, .validator, .internalWallet, .none: nil } } diff --git a/ios/Features/Transfer/Sources/ViewModels/ConfirmTransferSceneViewModel.swift b/ios/Features/Transfer/Sources/ViewModels/ConfirmTransferSceneViewModel.swift index 0b0ccb7d38..15a8aee322 100644 --- a/ios/Features/Transfer/Sources/ViewModels/ConfirmTransferSceneViewModel.swift +++ b/ios/Features/Transfer/Sources/ViewModels/ConfirmTransferSceneViewModel.swift @@ -133,7 +133,6 @@ public final class ConfirmTransferSceneViewModel { ConfirmDetailsViewModel(type: transferData.type, metadata: metadata) } - private var headerType: TransactionHeaderType { if let headerData = simulationState.headerData { return .assetValue(headerData) diff --git a/ios/Features/Transfer/Sources/ViewModels/ReceiveViewModel.swift b/ios/Features/Transfer/Sources/ViewModels/ReceiveViewModel.swift index 5b07cb8ee9..b97f0abbec 100644 --- a/ios/Features/Transfer/Sources/ViewModels/ReceiveViewModel.swift +++ b/ios/Features/Transfer/Sources/ViewModels/ReceiveViewModel.swift @@ -52,7 +52,6 @@ public final class ReceiveViewModel: Sendable { Localized.Common.copy } - var warningMessage: String { [Localized.Receive.warning(assetModel.symbol.boldMarkdown(), assetModel.networkFullName.boldMarkdown()), memoWarningText] .compactMap { $0 } diff --git a/ios/Features/WalletConnector/Sources/WalletConnector/ViewModels/SignMessageSceneViewModel.swift b/ios/Features/WalletConnector/Sources/WalletConnector/ViewModels/SignMessageSceneViewModel.swift index e8d138bf82..90f6913c16 100644 --- a/ios/Features/WalletConnector/Sources/WalletConnector/ViewModels/SignMessageSceneViewModel.swift +++ b/ios/Features/WalletConnector/Sources/WalletConnector/ViewModels/SignMessageSceneViewModel.swift @@ -112,7 +112,6 @@ public final class SignMessageSceneViewModel { TextMessageViewModel(message: plainMessage) } - public var simulationWarnings: [SimulationWarning] { payload.simulation.warnings } diff --git a/ios/Features/WalletConnector/Tests/WalletConnectorTests/WalletConnectorPresenterTests.swift b/ios/Features/WalletConnector/Tests/WalletConnectorTests/WalletConnectorPresenterTests.swift index 4c61254298..c2560564ed 100644 --- a/ios/Features/WalletConnector/Tests/WalletConnectorTests/WalletConnectorPresenterTests.swift +++ b/ios/Features/WalletConnector/Tests/WalletConnectorTests/WalletConnectorPresenterTests.swift @@ -67,7 +67,6 @@ struct WalletConnectorPresenterTests { } } - private func presentedSheet(from presenter: WalletConnectorPresenter) async -> WalletConnectorSheetType? { for _ in 0..<10 { if let sheet = await MainActor.run(body: { presenter.isPresentingSheet }) { diff --git a/ios/Gem/Services/ServicesFactory.swift b/ios/Gem/Services/ServicesFactory.swift index d5160e2a24..2d11f5c21a 100644 --- a/ios/Gem/Services/ServicesFactory.swift +++ b/ios/Gem/Services/ServicesFactory.swift @@ -48,7 +48,6 @@ import ContactService import FiatService import WebSocketClient - struct ServicesFactory { func makeServices(storages: AppResolver.Storages, navigation: NavigationStateManager) -> AppResolver.Services { let storeManager = StoreManager(db: storages.db) diff --git a/ios/Packages/FeatureServices/AssetsService/AssetsService.swift b/ios/Packages/FeatureServices/AssetsService/AssetsService.swift index 0691150318..afb9b610b5 100644 --- a/ios/Packages/FeatureServices/AssetsService/AssetsService.swift +++ b/ios/Packages/FeatureServices/AssetsService/AssetsService.swift @@ -168,9 +168,6 @@ public final class AssetsService: Sendable { } } - - - public func setSwappableAssets(for chains: [Chain]) throws { try assetStore.setAssetIsSwappable(for: chains.map { $0.id }, value: true) } diff --git a/ios/Packages/FeatureServices/PerpetualService/PerpetualOrderFactory.swift b/ios/Packages/FeatureServices/PerpetualService/PerpetualOrderFactory.swift index 8922d3bd2e..075e3d0370 100644 --- a/ios/Packages/FeatureServices/PerpetualService/PerpetualOrderFactory.swift +++ b/ios/Packages/FeatureServices/PerpetualService/PerpetualOrderFactory.swift @@ -40,6 +40,7 @@ public struct PerpetualOrderFactory { let data = makePerpetualConfirmData( direction: perpetual.direction, + marginType: perpetual.marginType, baseAsset: perpetual.baseAsset, fiatValue: fiatValue, assetIndex: Int32(perpetual.assetIndex), @@ -81,6 +82,7 @@ public struct PerpetualOrderFactory { return makePerpetualConfirmData( direction: position.direction, + marginType: position.marginType, baseAsset: baseAsset, fiatValue: abs(position.size) * positionPrice, assetIndex: assetIndex, @@ -115,6 +117,7 @@ public struct PerpetualOrderFactory { private func makePerpetualConfirmData( direction: PerpetualDirection, + marginType: PerpetualMarginType, baseAsset: Asset, fiatValue: Double, assetIndex: Int32, @@ -143,6 +146,7 @@ public struct PerpetualOrderFactory { return PerpetualConfirmData( direction: direction, + marginType: marginType, baseAsset: baseAsset, assetIndex: assetIndex, price: price, diff --git a/ios/Packages/Formatters/Tests/FormattersTests/CurrencyFormatterTests.swift b/ios/Packages/Formatters/Tests/FormattersTests/CurrencyFormatterTests.swift index 6065f2bd8b..4fbf0b5f10 100644 --- a/ios/Packages/Formatters/Tests/FormattersTests/CurrencyFormatterTests.swift +++ b/ios/Packages/Formatters/Tests/FormattersTests/CurrencyFormatterTests.swift @@ -1,12 +1,11 @@ // Copyright (c). Gem Wallet. All rights reserved. -import Testing +import Formatters import Foundation @testable import Primitives -import Formatters +import Testing final class CurrencyFormatterTests { - let currencyFormatterUS = CurrencyFormatter(locale: .US, currencyCode: Currency.usd.rawValue) let percentFormatterUS = CurrencyFormatter(type: .percent, locale: .US, currencyCode: Currency.usd.rawValue) @@ -25,13 +24,13 @@ final class CurrencyFormatterTests { #expect(currencyFormatterUS.string(0) == "$0.00") #expect(currencyFormatterUS.string(11.12) == "$11.12") #expect(currencyFormatterUS.string(11) == "$11.00") - #expect(currencyFormatterUS.string(12000123) == "$12,000,123.00") + #expect(currencyFormatterUS.string(12_000_123) == "$12,000,123.00") #expect(currencyFormatterUS.string(0.0000000002) == "$0.0000000002") #expect(currencyFormatterUS.string(0.0000000000001) == "$0.00") } @Test - func testSmallValue() { + func smallValue() { #expect(currencyFormatterUS.string(0.10) == "$0.1") #expect(currencyFormatterUS.string(0.11) == "$0.11") #expect(currencyFormatterUS.string(0.2) == "$0.2") @@ -49,17 +48,17 @@ final class CurrencyFormatterTests { } @Test - func testCurrencyIncludeSign() { + func currencyIncludeSign() { #expect(currencyFormatterUS.string(0) == "$0.00") #expect(currencyFormatterUS.string(-1.2) == "-$1.20") } @Test - func testCurrencyGBPLocale() { + func currencyGBPLocale() { #expect(currencyFormatterUK.string(0.0002) == "£0.0002") #expect(currencyFormatterUK.string(11.12) == "£11.12") #expect(currencyFormatterUK.string(11) == "£11.00") - #expect(currencyFormatterUK.string(12000123) == "£12,000,123.00") + #expect(currencyFormatterUK.string(12_000_123) == "£12,000,123.00") } @Test @@ -67,12 +66,12 @@ final class CurrencyFormatterTests { #expect(percentFormatterUS.string(-1.23) == "-1.23%") #expect(percentFormatterUS.string(11.12) == "+11.12%") #expect(percentFormatterUS.string(11) == "+11.00%") - #expect(percentFormatterUS.string(12000123) == "+12,000,123.00%") + #expect(percentFormatterUS.string(12_000_123) == "+12,000,123.00%") #expect(percentFormatterUK.string(-1.23) == "-1.23%") #expect(percentFormatterUK.string(11.12) == "+11.12%") #expect(percentFormatterUK.string(11) == "+11.00%") - #expect(percentFormatterUK.string(12000123) == "+12,000,123.00%") + #expect(percentFormatterUK.string(12_000_123) == "+12,000,123.00%") } @Test @@ -80,17 +79,17 @@ final class CurrencyFormatterTests { #expect(percentSignLess.string(-1.23) == "1.23%") #expect(percentSignLess.string(11.12) == "11.12%") #expect(percentSignLess.string(11) == "11.00%") - #expect(percentSignLess.string(12000123) == "12,000,123.00%") + #expect(percentSignLess.string(12_000_123) == "12,000,123.00%") } @Test - func testDecimal() { + func decimal() { #expect(cryptoFormatter.string(double: 0.12) == "0.12") #expect(cryptoFormatter.string(double: 0.00012) == "0.00012") #expect(cryptoFormatter.string(double: 11.12) == "11.12") #expect(cryptoFormatter.string(double: 11) == "11.00") - #expect(cryptoFormatter.string(double: 12000123) == "12,000,123.00") + #expect(cryptoFormatter.string(double: 12_000_123) == "12,000,123.00") #expect(cryptoFormatterUA.string(double: 0.003011) == cryptoFormatterUA.string(double: 0.003011).trimmingCharacters(in: .whitespaces)) #expect(cryptoFormatterUA.string(double: 92500) == "92 500,00") @@ -98,7 +97,7 @@ final class CurrencyFormatterTests { } @Test - func testStringDecimalWithSymbolVariants() { + func stringDecimalWithSymbolVariants() { #expect(cryptoFormatter.string(double: 1234.56, symbol: "BTC") == "1,234.56 BTC") #expect(cryptoFormatter.string(double: 0.0001234, symbol: "BTC") == "0.0001234 BTC") } @@ -107,30 +106,30 @@ final class CurrencyFormatterTests { func testAbbreviated() { #expect(abbreviatedFormatterUS.string(0) == "$0.00") #expect(abbreviatedFormatterUS.string(12) == "$12.00") - #expect(abbreviatedFormatterUS.string(1_234) == "$1,234.00") + #expect(abbreviatedFormatterUS.string(1234) == "$1,234.00") #expect(abbreviatedFormatterUS.string(100_000) == "$100K") #expect(abbreviatedFormatterUS.string(-1234) == "-$1,234.00") #expect(abbreviatedFormatterUS.string(-5_600_000) == "-$5.6M") - #expect(abbreviatedFormatterUK.string(123_456) == "£123.46K") - #expect(abbreviatedFormatterUK.string(5_000_000) == "£5M") - #expect(abbreviatedFormatterUK.string(7_890_000_000) == "£7.89B") - #expect(abbreviatedFormatterUK.string(1_200_000_000_000) == "£1.2T") - #expect(abbreviatedFormatterUK.string(-9_999_999_999) == "-£10B") + #expect(abbreviatedFormatterUK.string(123_456) == "£123.46k") + #expect(abbreviatedFormatterUK.string(5_000_000) == "£5m") + #expect(abbreviatedFormatterUK.string(7_890_000_000) == "£7.89bn") + #expect(abbreviatedFormatterUK.string(1_200_000_000_000) == "£1.2tn") + #expect(abbreviatedFormatterUK.string(-9_999_999_999) == "-£10bn") } - + @Test func abbreviatedStringSymbol() { #expect(abbreviatedFormatterUS.string(double: 0, symbol: "BTC") == "0.00 BTC") #expect(abbreviatedFormatterUS.string(double: 12, symbol: "BTC") == "12.00 BTC") - #expect(abbreviatedFormatterUS.string(double: 1_234, symbol: "BTC") == "1,234.00 BTC") + #expect(abbreviatedFormatterUS.string(double: 1234, symbol: "BTC") == "1,234.00 BTC") #expect(abbreviatedFormatterUS.string(double: 5_000_000, symbol: "BTC") == "5M BTC") #expect(abbreviatedFormatterUS.string(double: 7_890_000_000, symbol: "BTC") == "7.89B BTC") - - #expect(abbreviatedFormatterUS.string(double: 1_234) == "1,234.00") + + #expect(abbreviatedFormatterUS.string(double: 1234) == "1,234.00") #expect(abbreviatedFormatterUS.string(double: 5_000_000) == "5M") } - + @Test func testNormalizedDouble() { // Basic formatting diff --git a/ios/Packages/GemstonePrimitives/Sources/Extensions/GemPerpetual+GemstonePrimitives.swift b/ios/Packages/GemstonePrimitives/Sources/Extensions/GemPerpetual+GemstonePrimitives.swift index 2f51b5a8f0..8f206ba902 100644 --- a/ios/Packages/GemstonePrimitives/Sources/Extensions/GemPerpetual+GemstonePrimitives.swift +++ b/ios/Packages/GemstonePrimitives/Sources/Extensions/GemPerpetual+GemstonePrimitives.swift @@ -17,7 +17,8 @@ extension GemPerpetual { openInterest: openInterest, volume24h: volume24h, funding: funding, - maxLeverage: maxLeverage + maxLeverage: maxLeverage, + isIsolatedOnly: isIsolatedOnly ) } } diff --git a/ios/Packages/GemstonePrimitives/Sources/Extensions/GemPerpetualConfirmData+GemstonePrimitives.swift b/ios/Packages/GemstonePrimitives/Sources/Extensions/GemPerpetualConfirmData+GemstonePrimitives.swift index f7a2cf593a..586f4fe55e 100644 --- a/ios/Packages/GemstonePrimitives/Sources/Extensions/GemPerpetualConfirmData+GemstonePrimitives.swift +++ b/ios/Packages/GemstonePrimitives/Sources/Extensions/GemPerpetualConfirmData+GemstonePrimitives.swift @@ -8,6 +8,7 @@ extension Gemstone.PerpetualConfirmData { public func map() throws -> Primitives.PerpetualConfirmData { Primitives.PerpetualConfirmData( direction: direction.map(), + marginType: marginType.map(), baseAsset: try baseAsset.map(), assetIndex: assetIndex, price: price, @@ -29,6 +30,7 @@ extension Primitives.PerpetualConfirmData { public func map() -> Gemstone.PerpetualConfirmData { Gemstone.PerpetualConfirmData( direction: direction.map(), + marginType: marginType.map(), baseAsset: baseAsset.map(), assetIndex: assetIndex, price: price, diff --git a/ios/Packages/Keystore/Tests/LocalKeystoreTests.swift b/ios/Packages/Keystore/Tests/LocalKeystoreTests.swift index 5455c4945b..ab2cb5e3c2 100644 --- a/ios/Packages/Keystore/Tests/LocalKeystoreTests.swift +++ b/ios/Packages/Keystore/Tests/LocalKeystoreTests.swift @@ -134,7 +134,6 @@ struct LocalKeystoreTests { #expect(encoded == "5ZRaXVuDePowJjZmKaMjfcuqBVZet6e8QiCjTkGXBn7xhCvoEswUKXiGs2wmPxcqTfJUH28eCC91J1vLSjANNM9v") } - @Test func deriveAddress() async { await #expect(throws: Never.self) { diff --git a/ios/Packages/Keystore/Tests/WalletKeyStoreTests.swift b/ios/Packages/Keystore/Tests/WalletKeyStoreTests.swift index 68f3c61311..86c0d5a738 100644 --- a/ios/Packages/Keystore/Tests/WalletKeyStoreTests.swift +++ b/ios/Packages/Keystore/Tests/WalletKeyStoreTests.swift @@ -81,7 +81,6 @@ final class WalletKeyStoreTests { #expect(address == "GADB4BDKTOE36L6QN2JLIPNNJ7EZPSY5BIVKWXLWYZLIPXNQWIRQQZKT") } - @Test func addImportWallet() async throws { let store = WalletKeyStore.mock() let newWallet = try store.importWallet( diff --git a/ios/Packages/Primitives/Sources/Extensions/Perpetual+Primitives.swift b/ios/Packages/Primitives/Sources/Extensions/Perpetual+Primitives.swift index de30ac4084..7c91473fd3 100644 --- a/ios/Packages/Primitives/Sources/Extensions/Perpetual+Primitives.swift +++ b/ios/Packages/Primitives/Sources/Extensions/Perpetual+Primitives.swift @@ -22,6 +22,14 @@ extension PerpetualDirection { } } +extension Perpetual { + public var coin: String { + assetId.tokenId?.components(separatedBy: AssetId.subTokenSeparator).last ?? name + } + + public var marginType: PerpetualMarginType { isIsolatedOnly ? .isolated : .cross } +} + extension PerpetualSearchData { public var assetBasic: AssetBasic { AssetBasic( diff --git a/ios/Packages/Primitives/Sources/Perpetual.swift b/ios/Packages/Primitives/Sources/Perpetual.swift index 65fee99f7d..67c4827d45 100644 --- a/ios/Packages/Primitives/Sources/Perpetual.swift +++ b/ios/Packages/Primitives/Sources/Perpetual.swift @@ -55,8 +55,9 @@ public struct Perpetual: Codable, Equatable, Hashable, Sendable { public let volume24h: Double public let funding: Double public let maxLeverage: UInt8 + public let isIsolatedOnly: Bool - public init(id: String, name: String, provider: PerpetualProvider, assetId: AssetId, identifier: String, price: Double, pricePercentChange24h: Double, openInterest: Double, volume24h: Double, funding: Double, maxLeverage: UInt8) { + public init(id: String, name: String, provider: PerpetualProvider, assetId: AssetId, identifier: String, price: Double, pricePercentChange24h: Double, openInterest: Double, volume24h: Double, funding: Double, maxLeverage: UInt8, isIsolatedOnly: Bool) { self.id = id self.name = name self.provider = provider @@ -68,6 +69,7 @@ public struct Perpetual: Codable, Equatable, Hashable, Sendable { self.volume24h = volume24h self.funding = funding self.maxLeverage = maxLeverage + self.isIsolatedOnly = isIsolatedOnly } } @@ -97,6 +99,7 @@ public struct PerpetualBasic: Codable, Equatable, Hashable, Sendable { public struct PerpetualConfirmData: Codable, Equatable, Hashable, Sendable { public let direction: PerpetualDirection + public let marginType: PerpetualMarginType public let baseAsset: Asset public let assetIndex: Int32 public let price: String @@ -111,8 +114,9 @@ public struct PerpetualConfirmData: Codable, Equatable, Hashable, Sendable { public let takeProfit: String? public let stopLoss: String? - public init(direction: PerpetualDirection, baseAsset: Asset, assetIndex: Int32, price: String, fiatValue: Double, size: String, slippage: Double, leverage: UInt8, pnl: Double?, entryPrice: Double?, marketPrice: Double, marginAmount: Double, takeProfit: String?, stopLoss: String?) { + public init(direction: PerpetualDirection, marginType: PerpetualMarginType, baseAsset: Asset, assetIndex: Int32, price: String, fiatValue: Double, size: String, slippage: Double, leverage: UInt8, pnl: Double?, entryPrice: Double?, marketPrice: Double, marginAmount: Double, takeProfit: String?, stopLoss: String?) { self.direction = direction + self.marginType = marginType self.baseAsset = baseAsset self.assetIndex = assetIndex self.price = price diff --git a/ios/Packages/Primitives/Sources/PerpetualTransferData.swift b/ios/Packages/Primitives/Sources/PerpetualTransferData.swift index 3d5b96b5a5..285bbd51d6 100644 --- a/ios/Packages/Primitives/Sources/PerpetualTransferData.swift +++ b/ios/Packages/Primitives/Sources/PerpetualTransferData.swift @@ -39,6 +39,7 @@ public struct PerpetualTransferData: Codable, Equatable, Hashable, Sendable { public let assetIndex: Int public let price: Double public let leverage: UInt8 + public let marginType: PerpetualMarginType public init( provider: PerpetualProvider, @@ -47,7 +48,8 @@ public struct PerpetualTransferData: Codable, Equatable, Hashable, Sendable { baseAsset: Asset, assetIndex: Int, price: Double, - leverage: UInt8 + leverage: UInt8, + marginType: PerpetualMarginType ) { self.provider = provider self.direction = direction @@ -56,6 +58,7 @@ public struct PerpetualTransferData: Codable, Equatable, Hashable, Sendable { self.assetIndex = assetIndex self.price = price self.leverage = leverage + self.marginType = marginType } } diff --git a/ios/Packages/Primitives/Sources/Scan.swift b/ios/Packages/Primitives/Sources/Scan.swift index 2dce527e58..e5cf1df47a 100644 --- a/ios/Packages/Primitives/Sources/Scan.swift +++ b/ios/Packages/Primitives/Sources/Scan.swift @@ -43,4 +43,5 @@ public enum AddressType: String, Codable, Equatable, Sendable { case contract case validator case contact + case internalWallet } diff --git a/ios/Packages/Primitives/TestKit/Perpetual+PrimitivesTestKit.swift b/ios/Packages/Primitives/TestKit/Perpetual+PrimitivesTestKit.swift index 42ff754904..b76b85f5db 100644 --- a/ios/Packages/Primitives/TestKit/Perpetual+PrimitivesTestKit.swift +++ b/ios/Packages/Primitives/TestKit/Perpetual+PrimitivesTestKit.swift @@ -14,7 +14,8 @@ public extension Perpetual { openInterest: Double = 1_000_000, volume24h: Double = 10_000_000, funding: Double = 0.0001, - maxLeverage: UInt8 = 50 + maxLeverage: UInt8 = 50, + isIsolatedOnly: Bool = false ) -> Perpetual { Perpetual( id: id, @@ -27,7 +28,8 @@ public extension Perpetual { openInterest: openInterest, volume24h: volume24h, funding: funding, - maxLeverage: maxLeverage + maxLeverage: maxLeverage, + isIsolatedOnly: isIsolatedOnly ) } } diff --git a/ios/Packages/Primitives/TestKit/PerpetualConfirmData+PrimitivesTestKit.swift b/ios/Packages/Primitives/TestKit/PerpetualConfirmData+PrimitivesTestKit.swift index 79f1a5316c..a5752913b1 100644 --- a/ios/Packages/Primitives/TestKit/PerpetualConfirmData+PrimitivesTestKit.swift +++ b/ios/Packages/Primitives/TestKit/PerpetualConfirmData+PrimitivesTestKit.swift @@ -6,6 +6,7 @@ import Primitives public extension PerpetualConfirmData { static func mock( direction: PerpetualDirection = .long, + marginType: PerpetualMarginType = .cross, baseAsset: Asset = .mock(), assetIndex: Int32 = 0, price: String = "100", @@ -22,6 +23,7 @@ public extension PerpetualConfirmData { ) -> PerpetualConfirmData { PerpetualConfirmData( direction: direction, + marginType: marginType, baseAsset: baseAsset, assetIndex: assetIndex, price: price, diff --git a/ios/Packages/Primitives/TestKit/PerpetualTransferData+PrimitivesTestKit.swift b/ios/Packages/Primitives/TestKit/PerpetualTransferData+PrimitivesTestKit.swift index 01d3e95d36..fac2887bbc 100644 --- a/ios/Packages/Primitives/TestKit/PerpetualTransferData+PrimitivesTestKit.swift +++ b/ios/Packages/Primitives/TestKit/PerpetualTransferData+PrimitivesTestKit.swift @@ -11,7 +11,8 @@ public extension PerpetualTransferData { baseAsset: Asset = .mock(), assetIndex: Int = 0, price: Double = 100.0, - leverage: UInt8 = 3 + leverage: UInt8 = 3, + marginType: PerpetualMarginType = .cross ) -> PerpetualTransferData { PerpetualTransferData( provider: provider, @@ -20,7 +21,8 @@ public extension PerpetualTransferData { baseAsset: baseAsset, assetIndex: assetIndex, price: price, - leverage: leverage + leverage: leverage, + marginType: marginType ) } } diff --git a/ios/Packages/Primitives/Sources/Extensions/PerpetualMarginType+Primitives.swift b/ios/Packages/PrimitivesComponents/Sources/Extensions/PerpetualMarginType+PrimitivesComponents.swift similarity index 67% rename from ios/Packages/Primitives/Sources/Extensions/PerpetualMarginType+Primitives.swift rename to ios/Packages/PrimitivesComponents/Sources/Extensions/PerpetualMarginType+PrimitivesComponents.swift index 274b01af78..e05e9678ac 100644 --- a/ios/Packages/Primitives/Sources/Extensions/PerpetualMarginType+Primitives.swift +++ b/ios/Packages/PrimitivesComponents/Sources/Extensions/PerpetualMarginType+PrimitivesComponents.swift @@ -1,12 +1,13 @@ // Copyright (c). Gem Wallet. All rights reserved. import Foundation +import Primitives -extension PerpetualMarginType { - public var displayText: String { +public extension PerpetualMarginType { + var title: String { switch self { case .cross: "Cross" case .isolated: "Isolated" } } -} \ No newline at end of file +} diff --git a/ios/Packages/PrimitivesComponents/Sources/ViewModels/AddressListItemViewModel.swift b/ios/Packages/PrimitivesComponents/Sources/ViewModels/AddressListItemViewModel.swift index a848acdf9d..f7230857e0 100644 --- a/ios/Packages/PrimitivesComponents/Sources/ViewModels/AddressListItemViewModel.swift +++ b/ios/Packages/PrimitivesComponents/Sources/ViewModels/AddressListItemViewModel.swift @@ -47,14 +47,14 @@ public struct AddressListItemViewModel { public var assetImageStyle: AssetImageView.Style? { switch account.addressType { case .contact: AssetImageView.Style(foregroundColor: Colors.secondaryText, cornerRadius: 0) - case .address, .contract, .validator, .none: nil + case .address, .contract, .validator, .internalWallet, .none: nil } } public var assetImageSize: CGFloat { switch account.addressType { case .contact: .list.accessory - case .address, .contract, .validator, .none: .list.image + case .address, .contract, .validator, .internalWallet, .none: .list.image } } diff --git a/ios/Packages/Store/Sources/Migrations.swift b/ios/Packages/Store/Sources/Migrations.swift index bdf2715922..6d7b40dd9a 100644 --- a/ios/Packages/Store/Sources/Migrations.swift +++ b/ios/Packages/Store/Sources/Migrations.swift @@ -456,6 +456,14 @@ struct Migrations { try? FiatTransactionRecord.create(db: db) } + migrator.registerMigration("Add isIsolatedOnly to \(PerpetualRecord.databaseTableName)") { db in + try? db.alter(table: PerpetualRecord.databaseTableName) { + $0.add(column: PerpetualRecord.Columns.isIsolatedOnly.name, .boolean) + .notNull() + .defaults(to: false) + } + } + try migrator.migrate(dbQueue) } } diff --git a/ios/Packages/Store/Sources/Models/PerpetualRecord.swift b/ios/Packages/Store/Sources/Models/PerpetualRecord.swift index 27b96f1758..47503ac6a2 100644 --- a/ios/Packages/Store/Sources/Models/PerpetualRecord.swift +++ b/ios/Packages/Store/Sources/Models/PerpetualRecord.swift @@ -6,7 +6,7 @@ import Primitives struct PerpetualRecord: Codable, TableRecord, FetchableRecord, PersistableRecord { static let databaseTableName: String = "perpetuals" - + struct Columns { static let id = Column("id") static let name = Column("name") @@ -19,6 +19,7 @@ struct PerpetualRecord: Codable, TableRecord, FetchableRecord, PersistableRecord static let volume24h = Column("volume24h") static let funding = Column("funding") static let maxLeverage = Column("maxLeverage") + static let isIsolatedOnly = Column("isIsolatedOnly") static let isPinned = Column("isPinned") } @@ -33,6 +34,7 @@ struct PerpetualRecord: Codable, TableRecord, FetchableRecord, PersistableRecord var volume24h: Double var funding: Double var maxLeverage: UInt8 + var isIsolatedOnly: Bool var isPinned: Bool init( @@ -47,6 +49,7 @@ struct PerpetualRecord: Codable, TableRecord, FetchableRecord, PersistableRecord volume24h: Double, funding: Double, maxLeverage: UInt8, + isIsolatedOnly: Bool = false, isPinned: Bool = false ) { self.id = id @@ -60,6 +63,7 @@ struct PerpetualRecord: Codable, TableRecord, FetchableRecord, PersistableRecord self.volume24h = volume24h self.funding = funding self.maxLeverage = maxLeverage + self.isIsolatedOnly = isIsolatedOnly self.isPinned = isPinned } @@ -83,6 +87,7 @@ extension PerpetualRecord: CreateTable { $0.column(Columns.volume24h.name, .double).notNull() $0.column(Columns.funding.name, .double).notNull() $0.column(Columns.maxLeverage.name, .integer).notNull() + $0.column(Columns.isIsolatedOnly.name, .boolean).notNull().defaults(to: false) $0.column(Columns.isPinned.name, .boolean).notNull().defaults(to: false) } } @@ -101,7 +106,8 @@ extension PerpetualRecord { openInterest: openInterest, volume24h: volume24h, funding: funding, - maxLeverage: maxLeverage + maxLeverage: maxLeverage, + isIsolatedOnly: isIsolatedOnly ) } } @@ -119,7 +125,8 @@ extension Perpetual { openInterest: openInterest, volume24h: volume24h, funding: funding, - maxLeverage: maxLeverage + maxLeverage: maxLeverage, + isIsolatedOnly: isIsolatedOnly ) } } diff --git a/ios/Packages/Store/Sources/Requests/Perpetuals/PerpetualRequest.swift b/ios/Packages/Store/Sources/Requests/Perpetuals/PerpetualRequest.swift index 26eee95a91..ed08a28f71 100644 --- a/ios/Packages/Store/Sources/Requests/Perpetuals/PerpetualRequest.swift +++ b/ios/Packages/Store/Sources/Requests/Perpetuals/PerpetualRequest.swift @@ -42,7 +42,8 @@ extension PerpetualData { openInterest: .zero, volume24h: .zero, funding: .zero, - maxLeverage: 1 + maxLeverage: 1, + isIsolatedOnly: false ), asset: Asset(id: .init(chain: .bitcoin, tokenId: .none), name: "", symbol: "", decimals: 0, type: .native), metadata: PerpetualMetadata(isPinned: false) diff --git a/ios/Packages/Store/Sources/Types/PerpetualInfo.swift b/ios/Packages/Store/Sources/Types/PerpetualInfo.swift index aa9699108a..c0eea68550 100644 --- a/ios/Packages/Store/Sources/Types/PerpetualInfo.swift +++ b/ios/Packages/Store/Sources/Types/PerpetualInfo.swift @@ -17,4 +17,4 @@ extension PerpetualInfo { metadata: PerpetualMetadata(isPinned: perpetual.isPinned) ) } -} \ No newline at end of file +} diff --git a/ios/Packages/Store/Tests/StoreTests/Requests/PerpetualRequestTests.swift b/ios/Packages/Store/Tests/StoreTests/Requests/PerpetualRequestTests.swift index c3eb66ee71..48aed7502c 100644 --- a/ios/Packages/Store/Tests/StoreTests/Requests/PerpetualRequestTests.swift +++ b/ios/Packages/Store/Tests/StoreTests/Requests/PerpetualRequestTests.swift @@ -13,7 +13,7 @@ struct PerpetualRequestTests { let db = DB.mockAssets() let store = PerpetualStore(db: db) let eth = AssetId(chain: .ethereum) - let perpetual = Perpetual.mock(assetId: eth, price: 2500.0, maxLeverage: 100) + let perpetual = Perpetual.mock(assetId: eth, price: 2500.0, maxLeverage: 100, isIsolatedOnly: true) try store.upsertPerpetuals([perpetual]) try store.setPinned(for: [perpetual.id], value: true) @@ -24,6 +24,7 @@ struct PerpetualRequestTests { #expect(result.perpetual.id == perpetual.id) #expect(result.perpetual.price == 2500.0) + #expect(result.perpetual.isIsolatedOnly == true) #expect(result.asset.id == eth) #expect(result.metadata.isPinned == true) #expect(notFound == .empty) diff --git a/ios/justfile b/ios/justfile index de3f81f19e..979475402e 100644 --- a/ios/justfile +++ b/ios/justfile @@ -114,8 +114,9 @@ test-all: (_test "test") test-without-building: (_test "test-without-building") +# Example: just test # Example: just test PrimitivesTests -test TARGET: (_test "test" TARGET) +test TARGET="": (_test "test" TARGET) _test-ui action: @set -o pipefail && xcodebuild -project Gem.xcodeproj \ diff --git a/ios/skills/code-style.md b/ios/skills/code-style.md index f09227b280..4c9e359fb7 100644 --- a/ios/skills/code-style.md +++ b/ios/skills/code-style.md @@ -126,7 +126,7 @@ public extension Perpetual { volume24h: 10_000_000, funding: 0.0001, maxLeverage: 50, - onlyIsolated: false + isIsolatedOnly: false ) } }