diff --git a/Jenkinsfile b/Jenkinsfile index 28a32a5f6a..ae1e104dfb 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -3,6 +3,7 @@ // Job properties def jobParams = [ booleanParam(defaultValue: false, description: 'push to the dev profile', name: 'prDeployment'), + booleanParam(defaultValue: false, description: 'Upload builds to nexus(master,develop and staging branches upload always)', name: 'upload_to_nexus'), ] def pipeline = new org.android.AppPipeline( @@ -17,6 +18,7 @@ def pipeline = new org.android.AppPipeline( publishCmd: 'publishReleaseApk', jobParams: jobParams, appPushNoti: true, - dojoProductType: 'android' + dojoProductType: 'android', + uploadToNexusFor: ['master','develop','staging'] ) -pipeline.runPipeline('fearless') +pipeline.runPipeline('fearless') \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 583bd8f5c7..9fde9d42a9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -196,7 +196,6 @@ dependencies { implementation lifeCycleKtxDep -// implementation retrofitDep implementation gsonConvertedDep implementation gifDep diff --git a/app/src/main/java/jp/co/soramitsu/app/root/navigation/Navigator.kt b/app/src/main/java/jp/co/soramitsu/app/root/navigation/Navigator.kt index 030143d015..5e27d07cd3 100644 --- a/app/src/main/java/jp/co/soramitsu/app/root/navigation/Navigator.kt +++ b/app/src/main/java/jp/co/soramitsu/app/root/navigation/Navigator.kt @@ -19,7 +19,7 @@ import it.airgap.beaconsdk.blockchain.substrate.data.SubstrateSignerPayload import java.math.BigDecimal import jp.co.soramitsu.account.api.domain.model.ImportMode import jp.co.soramitsu.account.api.presentation.account.create.ChainAccountCreatePayload -import jp.co.soramitsu.account.api.presentation.actions.AddAccountBottomSheet +import jp.co.soramitsu.account.api.presentation.actions.AddAccountPayload import jp.co.soramitsu.account.api.presentation.create_backup_password.CreateBackupPasswordPayload import jp.co.soramitsu.account.impl.domain.account.details.AccountInChain import jp.co.soramitsu.account.impl.presentation.AccountRouter @@ -1279,7 +1279,7 @@ class Navigator : navController?.navigate(R.id.networkIssuesFragment) } - override fun openOptionsAddAccount(payload: AddAccountBottomSheet.Payload) { + override fun openOptionsAddAccount(payload: AddAccountPayload) { val bundle = OptionsAddAccountFragment.getBundle(payload) navController?.navigate(R.id.optionsAddAccountFragment, bundle) } diff --git a/app/src/main/java/jp/co/soramitsu/app/root/presentation/main/MainFragment.kt b/app/src/main/java/jp/co/soramitsu/app/root/presentation/main/MainFragment.kt index 3ffaa0b033..586a261ad6 100644 --- a/app/src/main/java/jp/co/soramitsu/app/root/presentation/main/MainFragment.kt +++ b/app/src/main/java/jp/co/soramitsu/app/root/presentation/main/MainFragment.kt @@ -1,5 +1,7 @@ package jp.co.soramitsu.app.root.presentation.main +import android.os.Bundle +import android.view.View import androidx.activity.OnBackPressedCallback import androidx.fragment.app.viewModels import androidx.navigation.NavController @@ -25,7 +27,7 @@ class MainFragment : BaseFragment(R.layout.fragment_main) { } } - private val binding by viewBinding(FragmentMainBinding::bind) + private lateinit var binding: FragmentMainBinding override val viewModel: MainViewModel by viewModels() @@ -35,6 +37,11 @@ class MainFragment : BaseFragment(R.layout.fragment_main) { backCallback.isEnabled = false } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + binding = FragmentMainBinding.bind(view) + super.onViewCreated(view, savedInstanceState) + } + override fun initViews() { binding.bottomNavigationView.setOnApplyWindowInsetsListener { _, insets -> // overwrite BottomNavigation behavior and ignore insets @@ -60,7 +67,7 @@ class MainFragment : BaseFragment(R.layout.fragment_main) { binding.bottomNavigationView.setupWithNavController(navController!!) - binding.bottomNavigationView.setOnNavigationItemSelectedListener { item -> + binding.bottomNavigationView.setOnItemSelectedListener { item -> onNavDestinationSelected(item, navController!!) } diff --git a/build.gradle b/build.gradle index bc64dc3901..e7a1355985 100644 --- a/build.gradle +++ b/build.gradle @@ -5,8 +5,8 @@ apply plugin: "org.sonarqube" buildscript { ext { // App version - versionName = '3.2.2' - versionCode = 152 + versionName = '3.3.1' + versionCode = 155 // SDK and tools compileSdkVersion = 34 @@ -79,11 +79,11 @@ buildscript { serializationVersion = '1.5.1' activityKtx = '1.7.0' - fragmentKtx = '1.5.6' + fragmentKtx = '1.6.2' minifyRelease = true beaconVersion = "3.2.4" - sharedFeaturesVersion = "1.1.1.20-FLW" + sharedFeaturesVersion = "1.1.1.23-FLW" coilDep = "io.coil-kt:coil:$coilVersion" coilSvg = "io.coil-kt:coil-svg:$coilVersion" diff --git a/common/src/main/java/jp/co/soramitsu/common/compose/component/AssetListItem.kt b/common/src/main/java/jp/co/soramitsu/common/compose/component/AssetListItem.kt index 38cfb96bfa..e71d7986bf 100644 --- a/common/src/main/java/jp/co/soramitsu/common/compose/component/AssetListItem.kt +++ b/common/src/main/java/jp/co/soramitsu/common/compose/component/AssetListItem.kt @@ -351,7 +351,6 @@ private fun PreviewAssetListItem() { chainAssetId = "", isSupported = true, isHidden = false, - priceId = null, isTestnet = false ) FearlessAppTheme { diff --git a/common/src/main/java/jp/co/soramitsu/common/compose/component/NetworkIssueItem.kt b/common/src/main/java/jp/co/soramitsu/common/compose/component/NetworkIssueItem.kt index 19c3bbc376..1f7f949e80 100644 --- a/common/src/main/java/jp/co/soramitsu/common/compose/component/NetworkIssueItem.kt +++ b/common/src/main/java/jp/co/soramitsu/common/compose/component/NetworkIssueItem.kt @@ -58,8 +58,7 @@ data class NetworkIssueItemState( val chainId: String, val chainName: String, - val assetId: String, - val priceId: String? = null + val assetId: String ) { override fun equals(other: Any?): Boolean { if (this === other) return true diff --git a/common/src/main/java/jp/co/soramitsu/common/compose/component/SwipeBox.kt b/common/src/main/java/jp/co/soramitsu/common/compose/component/SwipeBox.kt index 22c1d066cd..58258f02d8 100644 --- a/common/src/main/java/jp/co/soramitsu/common/compose/component/SwipeBox.kt +++ b/common/src/main/java/jp/co/soramitsu/common/compose/component/SwipeBox.kt @@ -175,7 +175,6 @@ private fun AssetItemSwipeBoxPreview() { chainAssetId = "", isSupported = true, isHidden = false, - priceId = null, isTestnet = false ) diff --git a/common/src/main/java/jp/co/soramitsu/common/compose/viewstate/AssetListItemViewState.kt b/common/src/main/java/jp/co/soramitsu/common/compose/viewstate/AssetListItemViewState.kt index 76b549d632..f1ee3f5c66 100644 --- a/common/src/main/java/jp/co/soramitsu/common/compose/viewstate/AssetListItemViewState.kt +++ b/common/src/main/java/jp/co/soramitsu/common/compose/viewstate/AssetListItemViewState.kt @@ -15,7 +15,6 @@ data class AssetListItemViewState( val chainAssetId: String, val isSupported: Boolean, val isHidden: Boolean, - val priceId: String?, val isTestnet: Boolean ) { val key = listOf(index ?: 0, chainAssetId, chainId, isHidden).joinToString() diff --git a/common/src/main/java/jp/co/soramitsu/common/data/network/subquery/ReefStakingRewardsResponse.kt b/common/src/main/java/jp/co/soramitsu/common/data/network/subquery/ReefStakingRewardsResponse.kt new file mode 100644 index 0000000000..5d76f8c587 --- /dev/null +++ b/common/src/main/java/jp/co/soramitsu/common/data/network/subquery/ReefStakingRewardsResponse.kt @@ -0,0 +1,31 @@ +package jp.co.soramitsu.common.data.network.subquery + +import java.math.BigInteger + +class ReefStakingRewardsResponse ( + val stakingsConnection: ReefRewardsConnection +) + +class SubsquidPageInfo( + val hasNextPage: Boolean, + val endCursor: String +) + +class ReefRewardsConnection( + val edges: List, + val pageInfo: SubsquidPageInfo +) +class ReefRewardsEdge( + val node: ReefRewardsNode +) +class ReefRewardsNode( + val id: String, + val type: String, + val amount: BigInteger, + val timestamp: String, + val signer: ReefAddress +) + +class ReefAddress( + val id: String +) \ No newline at end of file diff --git a/common/src/main/java/jp/co/soramitsu/common/di/modules/NetworkModule.kt b/common/src/main/java/jp/co/soramitsu/common/di/modules/NetworkModule.kt index c3e3fc083d..4af739ce87 100644 --- a/common/src/main/java/jp/co/soramitsu/common/di/modules/NetworkModule.kt +++ b/common/src/main/java/jp/co/soramitsu/common/di/modules/NetworkModule.kt @@ -20,6 +20,7 @@ import jp.co.soramitsu.common.resources.ResourceManager import jp.co.soramitsu.shared_utils.wsrpc.SocketService import jp.co.soramitsu.shared_utils.wsrpc.logging.Logger import jp.co.soramitsu.shared_utils.wsrpc.recovery.Reconnector +import jp.co.soramitsu.shared_utils.wsrpc.request.CoroutinesRequestExecutor import jp.co.soramitsu.shared_utils.wsrpc.request.RequestExecutor import okhttp3.Cache import okhttp3.OkHttpClient @@ -93,7 +94,7 @@ class NetworkModule { @Provides @Singleton - fun provideRequestExecutor() = RequestExecutor() + fun provideRequestExecutor(): RequestExecutor = CoroutinesRequestExecutor() @Provides fun provideSocketService( diff --git a/common/src/main/java/jp/co/soramitsu/common/domain/SelectedFiat.kt b/common/src/main/java/jp/co/soramitsu/common/domain/SelectedFiat.kt index 75d870673c..55baf11ec3 100644 --- a/common/src/main/java/jp/co/soramitsu/common/domain/SelectedFiat.kt +++ b/common/src/main/java/jp/co/soramitsu/common/domain/SelectedFiat.kt @@ -9,6 +9,7 @@ private const val DEFAULT_SELECTED_FIAT = "usd" class SelectedFiat(private val preferences: Preferences) { fun flow() = preferences.stringFlow(SELECTED_FIAT_KEY) { get() }.filterNotNull() fun get() = preferences.getString(SELECTED_FIAT_KEY, DEFAULT_SELECTED_FIAT) + fun isUsd() = get() == DEFAULT_SELECTED_FIAT fun set(value: String) { preferences.putString(SELECTED_FIAT_KEY, value) diff --git a/core-db/src/androidTest/java/jp/co/soramitsu/coredb/dao/Helpers.kt b/core-db/src/androidTest/java/jp/co/soramitsu/coredb/dao/Helpers.kt index 54bd810cd3..6eab1eb513 100644 --- a/core-db/src/androidTest/java/jp/co/soramitsu/coredb/dao/Helpers.kt +++ b/core-db/src/androidTest/java/jp/co/soramitsu/coredb/dao/Helpers.kt @@ -40,7 +40,11 @@ fun chainOf( externalApi = null, hasCrowdloans = false, minSupportedVersion = "2.0.3", - supportStakingPool = false + supportStakingPool = false, + isEthereumChain = false, + paraId = null, + rank = null, + isChainlinkProvider = false ) fun ChainLocal.nodeOf( @@ -64,14 +68,16 @@ fun ChainLocal.assetOf( priceId = null, staking = "test", icon = "", - priceProviders = null, + purchaseProviders = null, symbol = symbol, isUtility = null, type = null, currencyId = null, existentialDeposit = null, color = null, - isNative = null + isNative = null, + ethereumType = null, + priceProvider = null ) suspend fun ChainDao.addChain(joinedChainInfo: JoinedChainInfo) { diff --git a/core-db/src/main/java/jp/co/soramitsu/coredb/AppDatabase.kt b/core-db/src/main/java/jp/co/soramitsu/coredb/AppDatabase.kt index 45459e8db2..2940ee74e0 100644 --- a/core-db/src/main/java/jp/co/soramitsu/coredb/AppDatabase.kt +++ b/core-db/src/main/java/jp/co/soramitsu/coredb/AppDatabase.kt @@ -63,6 +63,7 @@ import jp.co.soramitsu.coredb.migrations.Migration_56_57 import jp.co.soramitsu.coredb.migrations.Migration_57_58 import jp.co.soramitsu.coredb.migrations.Migration_58_59 import jp.co.soramitsu.coredb.migrations.Migration_59_60 +import jp.co.soramitsu.coredb.migrations.Migration_60_61 import jp.co.soramitsu.coredb.migrations.RemoveAccountForeignKeyFromAsset_17_18 import jp.co.soramitsu.coredb.migrations.RemoveLegacyData_35_36 import jp.co.soramitsu.coredb.migrations.RemoveStakingRewardsTable_22_23 @@ -88,7 +89,7 @@ import jp.co.soramitsu.coredb.model.chain.FavoriteChainLocal import jp.co.soramitsu.coredb.model.chain.MetaAccountLocal @Database( - version = 60, + version = 61, entities = [ AccountLocal::class, AddressBookContact::class, @@ -169,6 +170,7 @@ abstract class AppDatabase : RoomDatabase() { .addMigrations(Migration_57_58) .addMigrations(Migration_58_59) .addMigrations(Migration_59_60) + .addMigrations(Migration_60_61) .build() } return instance!! diff --git a/core-db/src/main/java/jp/co/soramitsu/coredb/dao/TokenPriceDao.kt b/core-db/src/main/java/jp/co/soramitsu/coredb/dao/TokenPriceDao.kt index 445b7b4372..5b4eab0d4a 100644 --- a/core-db/src/main/java/jp/co/soramitsu/coredb/dao/TokenPriceDao.kt +++ b/core-db/src/main/java/jp/co/soramitsu/coredb/dao/TokenPriceDao.kt @@ -22,6 +22,9 @@ abstract class TokenPriceDao { @Insert(onConflict = OnConflictStrategy.REPLACE) abstract suspend fun insertTokenPrice(token: TokenPriceLocal) + @Insert(onConflict = OnConflictStrategy.REPLACE) + abstract suspend fun insertTokensPrice(tokens: List) + @Insert(onConflict = OnConflictStrategy.IGNORE) abstract suspend fun insertTokenPriceOrIgnore(token: TokenPriceLocal) diff --git a/core-db/src/main/java/jp/co/soramitsu/coredb/migrations/Migrations.kt b/core-db/src/main/java/jp/co/soramitsu/coredb/migrations/Migrations.kt index 3b871a1ab8..5b83d10592 100644 --- a/core-db/src/main/java/jp/co/soramitsu/coredb/migrations/Migrations.kt +++ b/core-db/src/main/java/jp/co/soramitsu/coredb/migrations/Migrations.kt @@ -3,6 +3,40 @@ package jp.co.soramitsu.coredb.migrations import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase +val Migration_60_61 = object : Migration(60, 61) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("ALTER TABLE chains ADD COLUMN `isChainlinkProvider` INTEGER NOT NULL DEFAULT 0") + + database.execSQL("DROP TABLE IF EXISTS chain_assets") + + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS `chain_assets` ( + `id` TEXT NOT NULL, + `name` TEXT, + `symbol` TEXT NOT NULL, + `chainId` TEXT NOT NULL, + `icon` TEXT NOT NULL, + `priceId` TEXT, + `staking` TEXT NOT NULL, + `precision` INTEGER NOT NULL, + `purchaseProviders` TEXT, + `isUtility` INTEGER, + `type` TEXT, + `currencyId` TEXT, + `existentialDeposit` TEXT, + `color` TEXT, + `isNative` INTEGER, + `ethereumType` TEXT DEFAULT NULL, + `priceProvider` TEXT, + PRIMARY KEY(`chainId`, `id`), + FOREIGN KEY(`chainId`) REFERENCES `chains`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE ) + """.trimIndent() + ) + database.execSQL("CREATE INDEX IF NOT EXISTS `index_chain_assets_chainId` ON `chain_assets` (`chainId`)") + } +} + val Migration_59_60 = object : Migration(59, 60) { override fun migrate(database: SupportSQLiteDatabase) { database.execSQL("ALTER TABLE chains ADD COLUMN `paraId` TEXT NULL") diff --git a/core-db/src/main/java/jp/co/soramitsu/coredb/model/chain/ChainAssetLocal.kt b/core-db/src/main/java/jp/co/soramitsu/coredb/model/chain/ChainAssetLocal.kt index efed568351..df010a5c4c 100644 --- a/core-db/src/main/java/jp/co/soramitsu/coredb/model/chain/ChainAssetLocal.kt +++ b/core-db/src/main/java/jp/co/soramitsu/coredb/model/chain/ChainAssetLocal.kt @@ -28,12 +28,13 @@ data class ChainAssetLocal( val priceId: String?, val staking: String, val precision: Int, - val priceProviders: String?, + val purchaseProviders: String?, val isUtility: Boolean?, val type: String?, val currencyId: String?, val existentialDeposit: String?, val color: String?, val isNative: Boolean?, - val ethereumType: String? + val ethereumType: String?, + val priceProvider: String? ) diff --git a/core-db/src/main/java/jp/co/soramitsu/coredb/model/chain/ChainLocal.kt b/core-db/src/main/java/jp/co/soramitsu/coredb/model/chain/ChainLocal.kt index cc297c4e51..e886b1075b 100644 --- a/core-db/src/main/java/jp/co/soramitsu/coredb/model/chain/ChainLocal.kt +++ b/core-db/src/main/java/jp/co/soramitsu/coredb/model/chain/ChainLocal.kt @@ -20,7 +20,8 @@ class ChainLocal( val isTestNet: Boolean, val hasCrowdloans: Boolean, val supportStakingPool: Boolean, - val isEthereumChain: Boolean + val isEthereumChain: Boolean, + val isChainlinkProvider: Boolean ) { class ExternalApi( diff --git a/feature-account-api/src/main/java/jp/co/soramitsu/account/api/domain/interfaces/AssetNotNeedAccountUseCase.kt b/feature-account-api/src/main/java/jp/co/soramitsu/account/api/domain/interfaces/AssetNotNeedAccountUseCase.kt index 23b169570d..ab3df4da98 100644 --- a/feature-account-api/src/main/java/jp/co/soramitsu/account/api/domain/interfaces/AssetNotNeedAccountUseCase.kt +++ b/feature-account-api/src/main/java/jp/co/soramitsu/account/api/domain/interfaces/AssetNotNeedAccountUseCase.kt @@ -9,7 +9,6 @@ interface AssetNotNeedAccountUseCase { * Mark asset without account - as correct, no need of account * used in lists sort */ - suspend fun markNotNeed(chainId: ChainId, metaId: Long, assetId: String, priceId: String?) suspend fun markChainAssetsNotNeed(chainId: ChainId, metaId: Long) /** diff --git a/feature-account-api/src/main/java/jp/co/soramitsu/account/api/domain/model/MetaAccount.kt b/feature-account-api/src/main/java/jp/co/soramitsu/account/api/domain/model/MetaAccount.kt index 986d1c3080..e372d12c6a 100644 --- a/feature-account-api/src/main/java/jp/co/soramitsu/account/api/domain/model/MetaAccount.kt +++ b/feature-account-api/src/main/java/jp/co/soramitsu/account/api/domain/model/MetaAccount.kt @@ -135,6 +135,13 @@ fun MetaAccount.address(chain: Chain): String? { } } +fun LightMetaAccount.address(chain: Chain): String? { + return when { + chain.isEthereumBased -> ethereumAddress?.ethereumAddressToHex() + else -> substrateAccountId.toAddress(chain.addressPrefix.toShort()) + } +} + fun MetaAccount.chainAddress(chain: Chain): String? { return when { hasChainAccount(chain.id) -> chain.addressOf(chainAccounts.getValue(chain.id).accountId) diff --git a/feature-account-api/src/main/java/jp/co/soramitsu/account/api/presentation/actions/AddAccountBottomSheet.kt b/feature-account-api/src/main/java/jp/co/soramitsu/account/api/presentation/actions/AddAccountBottomSheet.kt deleted file mode 100644 index c76b4172aa..0000000000 --- a/feature-account-api/src/main/java/jp/co/soramitsu/account/api/presentation/actions/AddAccountBottomSheet.kt +++ /dev/null @@ -1,49 +0,0 @@ -package jp.co.soramitsu.account.api.presentation.actions - -import android.content.Context -import android.os.Bundle -import android.os.Parcelable -import jp.co.soramitsu.common.R -import jp.co.soramitsu.common.view.bottomSheet.list.fixed.FixedListBottomSheet -import jp.co.soramitsu.common.view.bottomSheet.list.fixed.textItem -import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId -import kotlinx.parcelize.Parcelize - -class AddAccountBottomSheet( - context: Context, - private val payload: Payload, - private val onCreate: (chainId: ChainId, metaId: Long) -> Unit, - private val onImport: (chainId: ChainId, metaId: Long) -> Unit, - private val onNoNeed: (chainId: ChainId, metaId: Long, assetId: String, priceId: String?) -> Unit -) : FixedListBottomSheet(context) { - - @Parcelize - data class Payload( - val metaId: Long, - val chainId: ChainId, - val chainName: String, - val assetId: String, - val priceId: String?, - val markedAsNotNeed: Boolean - ) : Parcelable - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - setTitle(context.getString(R.string.account_template, payload.chainName)) - - textItem(R.string.create_new_account) { - onCreate(payload.chainId, payload.metaId) - } - - textItem(R.string.already_have_account) { - onImport(payload.chainId, payload.metaId) - } - - if (!payload.markedAsNotNeed) { - textItem(R.string.i_dont_need_account) { - onNoNeed(payload.chainId, payload.metaId, payload.assetId, payload.priceId) - } - } - } -} diff --git a/feature-account-api/src/main/java/jp/co/soramitsu/account/api/presentation/actions/AddAccountPayload.kt b/feature-account-api/src/main/java/jp/co/soramitsu/account/api/presentation/actions/AddAccountPayload.kt new file mode 100644 index 0000000000..e36903340f --- /dev/null +++ b/feature-account-api/src/main/java/jp/co/soramitsu/account/api/presentation/actions/AddAccountPayload.kt @@ -0,0 +1,14 @@ +package jp.co.soramitsu.account.api.presentation.actions + +import android.os.Parcelable +import jp.co.soramitsu.runtime.multiNetwork.chain.model.ChainId +import kotlinx.parcelize.Parcelize + +@Parcelize +data class AddAccountPayload( + val metaId: Long, + val chainId: ChainId, + val chainName: String, + val assetId: String, + val markedAsNotNeed: Boolean +) : Parcelable \ No newline at end of file diff --git a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/di/AccountFeatureModule.kt b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/di/AccountFeatureModule.kt index 68de31b86c..330e80ab2d 100644 --- a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/di/AccountFeatureModule.kt +++ b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/di/AccountFeatureModule.kt @@ -203,9 +203,10 @@ class AccountFeatureModule { fun provideAssetNotNeedAccountUseCase( chainRegistry: ChainRegistry, assetDao: AssetDao, - tokenPriceDao: TokenPriceDao + tokenPriceDao: TokenPriceDao, + selectedFiat: SelectedFiat ): AssetNotNeedAccountUseCase { - return AssetNotNeedAccountUseCaseImpl(chainRegistry, assetDao, tokenPriceDao) + return AssetNotNeedAccountUseCaseImpl(chainRegistry, assetDao, tokenPriceDao, selectedFiat) } @Provides diff --git a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/domain/AssetNotNeedAccountUseCaseImpl.kt b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/domain/AssetNotNeedAccountUseCaseImpl.kt index 6faaa72030..eec8285647 100644 --- a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/domain/AssetNotNeedAccountUseCaseImpl.kt +++ b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/domain/AssetNotNeedAccountUseCaseImpl.kt @@ -1,6 +1,7 @@ package jp.co.soramitsu.account.impl.domain import jp.co.soramitsu.account.api.domain.interfaces.AssetNotNeedAccountUseCase +import jp.co.soramitsu.common.domain.SelectedFiat import jp.co.soramitsu.common.model.AssetKey import jp.co.soramitsu.coredb.dao.AssetDao import jp.co.soramitsu.coredb.dao.TokenPriceDao @@ -14,17 +15,15 @@ import kotlinx.coroutines.flow.map class AssetNotNeedAccountUseCaseImpl( private val chainRegistry: ChainRegistry, private val assetDao: AssetDao, - private val tokenPriceDao: TokenPriceDao + private val tokenPriceDao: TokenPriceDao, + private val selectedFiat: SelectedFiat ) : AssetNotNeedAccountUseCase { - override suspend fun markNotNeed(chainId: ChainId, metaId: Long, assetId: String, priceId: String?) { - updateAssetNotNeed(metaId, chainId, assetId, priceId) - } - override suspend fun markChainAssetsNotNeed(chainId: ChainId, metaId: Long) { val chainAssets = chainRegistry.getChain(chainId).assets chainAssets.forEach { - updateAssetNotNeed(metaId, chainId, it.id, it.priceId) + val priceId = it.priceProvider?.id?.takeIf { selectedFiat.isUsd() } ?: it.priceId + updateAssetNotNeed(metaId, chainId, it.id, priceId) } } diff --git a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/AccountRouter.kt b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/AccountRouter.kt index 9b46754208..79f28b8e8d 100644 --- a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/AccountRouter.kt +++ b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/AccountRouter.kt @@ -1,7 +1,7 @@ package jp.co.soramitsu.account.impl.presentation import jp.co.soramitsu.account.api.presentation.account.create.ChainAccountCreatePayload -import jp.co.soramitsu.account.api.presentation.actions.AddAccountBottomSheet +import jp.co.soramitsu.account.api.presentation.actions.AddAccountPayload import jp.co.soramitsu.account.api.presentation.create_backup_password.CreateBackupPasswordPayload import jp.co.soramitsu.account.impl.domain.account.details.AccountInChain import jp.co.soramitsu.account.impl.presentation.exporting.json.confirm.ExportJsonConfirmPayload @@ -85,7 +85,7 @@ interface AccountRouter : SecureRouter { fun openExperimentalFeatures() - fun openOptionsAddAccount(payload: AddAccountBottomSheet.Payload) + fun openOptionsAddAccount(payload: AddAccountPayload) fun openPolkaswapDisclaimerFromProfile() diff --git a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/account/details/AccountDetailsViewModel.kt b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/account/details/AccountDetailsViewModel.kt index 97f5404b98..d48ced6e4e 100644 --- a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/account/details/AccountDetailsViewModel.kt +++ b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/account/details/AccountDetailsViewModel.kt @@ -9,7 +9,7 @@ import javax.inject.Inject import jp.co.soramitsu.account.api.domain.interfaces.AssetNotNeedAccountUseCase import jp.co.soramitsu.account.api.domain.interfaces.TotalBalanceUseCase import jp.co.soramitsu.account.api.domain.model.hasChainAccount -import jp.co.soramitsu.account.api.presentation.actions.AddAccountBottomSheet +import jp.co.soramitsu.account.api.presentation.actions.AddAccountPayload import jp.co.soramitsu.account.api.presentation.actions.ExternalAccountActions import jp.co.soramitsu.account.api.presentation.exporting.ExportSource import jp.co.soramitsu.account.api.presentation.exporting.ExportSourceChooserPayload @@ -23,6 +23,7 @@ import jp.co.soramitsu.common.base.BaseViewModel import jp.co.soramitsu.common.compose.component.ChangeBalanceViewState import jp.co.soramitsu.common.compose.component.WalletItemViewState import jp.co.soramitsu.common.data.network.BlockExplorerUrlBuilder +import jp.co.soramitsu.common.domain.SelectedFiat import jp.co.soramitsu.common.list.headers.TextHeader import jp.co.soramitsu.common.list.toListWithHeaders import jp.co.soramitsu.common.resources.ResourceManager @@ -251,12 +252,11 @@ class AccountDetailsViewModel @Inject constructor( externalAccountActions.showExternalActions(ExternalAccountActions.Payload(item.address, item.chainId, item.chainName, supportedExplorers)) } else { val utilityAsset = chainRegistry.getChain(item.chainId).utilityAsset - val payload = AddAccountBottomSheet.Payload( + val payload = AddAccountPayload( metaId = walletId, chainId = item.chainId, chainName = item.chainName, assetId = utilityAsset?.id.orEmpty(), - priceId = utilityAsset?.priceId, markedAsNotNeed = item.markedAsNotNeed ) accountRouter.openOptionsAddAccount(payload) @@ -277,18 +277,4 @@ class AccountDetailsViewModel @Inject constructor( fun updateAppClicked() { _openPlayMarket.value = Event(Unit) } - - fun createAccount(chainId: ChainId, metaId: Long) { - accountRouter.openOnboardingNavGraph(chainId = chainId, metaId = metaId, isImport = false) - } - - fun importAccount(chainId: ChainId, metaId: Long) { - accountRouter.openOnboardingNavGraph(chainId = chainId, metaId = metaId, isImport = true) - } - - fun noNeedAccount(chainId: ChainId, metaId: Long, assetId: String, priceId: String?) { - launch { - assetNotNeedAccount.markNotNeed(chainId = chainId, metaId = metaId, assetId = assetId, priceId = priceId) - } - } } diff --git a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/optionsaddaccount/OptionsAddAccountContent.kt b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/optionsaddaccount/OptionsAddAccountContent.kt index 0f623beb82..57f4b3bf3d 100644 --- a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/optionsaddaccount/OptionsAddAccountContent.kt +++ b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/optionsaddaccount/OptionsAddAccountContent.kt @@ -30,8 +30,7 @@ data class OptionsAddAccountScreenViewState( val chainId: ChainId, val chainName: String, val markedAsNotNeed: Boolean, - val assetId: String, - val priceId: String? + val assetId: String ) @Composable @@ -114,8 +113,7 @@ private fun OptionsAddAccountScreenPreview() { chainId = "", chainName = "Kusama", markedAsNotNeed = false, - assetId = "", - priceId = null + assetId = "" ), onCreate = { t, t2 -> }, onImport = { t, t2 -> }, diff --git a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/optionsaddaccount/OptionsAddAccountFragment.kt b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/optionsaddaccount/OptionsAddAccountFragment.kt index 875e2ee759..b53066ee9f 100644 --- a/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/optionsaddaccount/OptionsAddAccountFragment.kt +++ b/feature-account-impl/src/main/java/jp/co/soramitsu/account/impl/presentation/optionsaddaccount/OptionsAddAccountFragment.kt @@ -9,7 +9,7 @@ import androidx.core.os.bundleOf import androidx.fragment.app.viewModels import com.google.android.material.bottomsheet.BottomSheetBehavior import dagger.hilt.android.AndroidEntryPoint -import jp.co.soramitsu.account.api.presentation.actions.AddAccountBottomSheet +import jp.co.soramitsu.account.api.presentation.actions.AddAccountPayload import jp.co.soramitsu.common.base.BaseComposeBottomSheetDialogFragment @AndroidEntryPoint @@ -18,7 +18,7 @@ class OptionsAddAccountFragment : BaseComposeBottomSheetDialogFragment = selectedWallet.mapNotNull { - savedStateHandle.get(KEY_PAYLOAD)?.let { payload -> + savedStateHandle.get(KEY_PAYLOAD)?.let { payload -> OptionsAddAccountScreenViewState( metaId = it.id, chainId = payload.chainId, chainName = payload.chainName, markedAsNotNeed = payload.markedAsNotNeed, - assetId = payload.assetId, - priceId = payload.priceId + assetId = payload.assetId ) } }.stateIn( @@ -49,8 +48,7 @@ class OptionsAddAccountViewModel @Inject constructor( chainId = "", chainName = "Dotsama", markedAsNotNeed = false, - assetId = "", - priceId = null + assetId = "" ) ) diff --git a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/presentation/swap_tokens/SwapTokensViewModel.kt b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/presentation/swap_tokens/SwapTokensViewModel.kt index 9426d63a14..163b4f209e 100644 --- a/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/presentation/swap_tokens/SwapTokensViewModel.kt +++ b/feature-polkaswap-impl/src/main/kotlin/jp/co/soramitsu/polkaswap/impl/presentation/swap_tokens/SwapTokensViewModel.kt @@ -207,7 +207,9 @@ class SwapTokensViewModel @Inject constructor( ) val fee = desiredAsset.token.amountFromPlanks(feeInPlanks) emit(LoadingState.Loaded(fee)) - }.stateIn(viewModelScope, SharingStarted.Eagerly, LoadingState.Loaded(null)) + } + .catch { emit(LoadingState.Loaded(null)) } + .stateIn(viewModelScope, SharingStarted.Eagerly, LoadingState.Loaded(null)) private val networkFeeViewStateFlow = networkFeeFlow.map { amountLoading -> amountLoading.map { diff --git a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/data/network/blockhain/bindings/Era.kt b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/data/network/blockhain/bindings/Era.kt index 55d5e7344f..f89f494f2b 100644 --- a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/data/network/blockhain/bindings/Era.kt +++ b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/data/network/blockhain/bindings/Era.kt @@ -53,31 +53,31 @@ fun bindCurrentEra( @UseCaseBinding fun bindCurrentIndex( - scale: String, + scale: String?, runtime: RuntimeSnapshot ): BigInteger { val returnType = runtime.metadata.storageReturnType("Session", "CurrentIndex") - return bindSessionIndex(returnType.fromHexOrNull(runtime, scale)) + return scale ?. let { bindSessionIndex(returnType.fromHexOrNull(runtime, scale)) } ?: BigInteger.ZERO } @UseCaseBinding fun bindCurrentSlot( - scale: String, + scale: String?, runtime: RuntimeSnapshot ): BigInteger { val returnType = runtime.metadata.storageReturnType("Babe", "CurrentSlot") - return bindSlot(returnType.fromHexOrNull(runtime, scale)) + return scale?.let { bindSlot(returnType.fromHexOrNull(runtime, scale)) } ?: BigInteger.ZERO } @UseCaseBinding fun bindErasStartSessionIndex( - scale: String, + scale: String?, runtime: RuntimeSnapshot ): BigInteger { val returnType = runtime.metadata.storageReturnType("Staking", "ErasStartSessionIndex") - val decoded = returnType.fromHexOrNull(runtime, scale) + val decoded = scale?.let { returnType.fromHexOrNull(runtime, scale) } return bindSessionIndex(decoded) } diff --git a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/data/network/subquery/StakingApi.kt b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/data/network/subquery/StakingApi.kt index c0074d3ded..32678b0eb1 100644 --- a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/data/network/subquery/StakingApi.kt +++ b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/data/network/subquery/StakingApi.kt @@ -2,6 +2,7 @@ package jp.co.soramitsu.staking.impl.data.network.subquery import jp.co.soramitsu.common.data.network.subquery.EraValidatorInfoQueryResponse import jp.co.soramitsu.common.data.network.subquery.GiantsquidRewardAmountResponse +import jp.co.soramitsu.common.data.network.subquery.ReefStakingRewardsResponse import jp.co.soramitsu.common.data.network.subquery.SoraEraInfoValidatorResponse import jp.co.soramitsu.common.data.network.subquery.StakingCollatorsApyResponse import jp.co.soramitsu.common.data.network.subquery.StakingHistoryRemote @@ -16,6 +17,7 @@ import jp.co.soramitsu.common.data.network.subquery.SubsquidRewardResponse import jp.co.soramitsu.common.data.network.subquery.SubsquidSoraStakingRewards import jp.co.soramitsu.common.data.network.subquery.TransactionHistoryRemote import jp.co.soramitsu.staking.impl.data.network.subquery.request.GiantsquidRewardAmountRequest +import jp.co.soramitsu.staking.impl.data.network.subquery.request.ReefStakingRewardsRequest import jp.co.soramitsu.staking.impl.data.network.subquery.request.StakingAllCollatorsApyRequest import jp.co.soramitsu.staking.impl.data.network.subquery.request.StakingCollatorsApyRequest import jp.co.soramitsu.staking.impl.data.network.subquery.request.StakingDelegatorHistoryRequest @@ -118,4 +120,10 @@ interface StakingApi { @Url url: String, @Body body: SubsquidSoraStakingRewardsRequest ): SubsquidResponse + + @POST + suspend fun getReefRewards( + @Url url: String, + @Body body: ReefStakingRewardsRequest + ): SubsquidResponse } diff --git a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/data/network/subquery/request/ReefStakingRewardsRequest.kt b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/data/network/subquery/request/ReefStakingRewardsRequest.kt new file mode 100644 index 0000000000..883ec80c14 --- /dev/null +++ b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/data/network/subquery/request/ReefStakingRewardsRequest.kt @@ -0,0 +1,26 @@ +package jp.co.soramitsu.staking.impl.data.network.subquery.request + +class ReefStakingRewardsRequest(address: String, pageSize: Int = 100, offset: String? = null) { + val query = """ + query MyQuery { + stakingsConnection(orderBy: timestamp_DESC, where: {AND: {signer: {id_eq: "$address"}, amount_gt: "0"}}, first: $pageSize, after: $offset) { + edges { + node { + id + type + amount + timestamp + signer { + id + } + } + } + totalCount + pageInfo { + endCursor + hasNextPage + } + } + } + """.trimIndent() +} \ No newline at end of file diff --git a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/data/repository/datasource/SubqueryStakingRewardsDataSource.kt b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/data/repository/datasource/SubqueryStakingRewardsDataSource.kt index 2cfdad56d5..dddcf43e1c 100644 --- a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/data/repository/datasource/SubqueryStakingRewardsDataSource.kt +++ b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/data/repository/datasource/SubqueryStakingRewardsDataSource.kt @@ -1,5 +1,6 @@ package jp.co.soramitsu.staking.impl.data.repository.datasource +import java.math.BigInteger import jp.co.soramitsu.common.base.errors.RewardsNotSupportedWarning import jp.co.soramitsu.common.utils.orZero import jp.co.soramitsu.common.utils.sumByBigDecimal @@ -19,6 +20,7 @@ import jp.co.soramitsu.staking.impl.data.mappers.mapSubqueryHistoryToTotalReward import jp.co.soramitsu.staking.impl.data.mappers.mapTotalRewardLocalToTotalReward import jp.co.soramitsu.staking.impl.data.network.subquery.StakingApi import jp.co.soramitsu.staking.impl.data.network.subquery.request.GiantsquidRewardAmountRequest +import jp.co.soramitsu.staking.impl.data.network.subquery.request.ReefStakingRewardsRequest import jp.co.soramitsu.staking.impl.data.network.subquery.request.StakingSumRewardRequest import jp.co.soramitsu.staking.impl.data.network.subquery.request.SubsquidEthRewardAmountRequest import jp.co.soramitsu.staking.impl.data.network.subquery.request.SubsquidRelayRewardAmountRequest @@ -73,6 +75,10 @@ class SubqueryStakingRewardsDataSource( syncGiantsquidRelay(stakingUrl, accountAddress) } + stakingType == Chain.ExternalApi.Section.Type.REEF -> { + syncReefRewards(stakingUrl, accountAddress) + } + else -> throw RewardsNotSupportedWarning() } } @@ -106,6 +112,21 @@ class SubqueryStakingRewardsDataSource( stakingTotalRewardDao.insert(TotalRewardLocal(accountAddress, totalInPlanks)) } + private suspend fun syncReefRewards(stakingUrl: String, accountAddress: String) { + var hasNextPage = true + var nextCursor: String? = null + var rewardsTotal = BigInteger.ZERO + + while (hasNextPage) { + val response = stakingApi.getReefRewards(stakingUrl, ReefStakingRewardsRequest(accountAddress, offset = nextCursor?.let { "\"$it\"" })) + hasNextPage = response.data.stakingsConnection.pageInfo.hasNextPage + nextCursor = response.data.stakingsConnection.pageInfo.endCursor + rewardsTotal += response.data.stakingsConnection.edges.map { it.node }.sumByBigInteger { it.amount } + } + + stakingTotalRewardDao.insert(TotalRewardLocal(accountAddress, rewardsTotal)) + } + private suspend fun syncSubquery(stakingUrl: String, accountAddress: String) = withContext(Dispatchers.IO) { val r = stakingApi.getSumReward( stakingUrl, diff --git a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/di/StakingFeatureModule.kt b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/di/StakingFeatureModule.kt index e614ab7117..4c0e5a4e30 100644 --- a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/di/StakingFeatureModule.kt +++ b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/di/StakingFeatureModule.kt @@ -15,6 +15,7 @@ import jp.co.soramitsu.common.data.network.AppLinksProvider import jp.co.soramitsu.common.data.network.rpc.BulkRetriever import jp.co.soramitsu.common.data.network.subquery.SoraEraInfoValidatorResponse import jp.co.soramitsu.common.data.storage.Preferences +import jp.co.soramitsu.common.domain.SelectedFiat import jp.co.soramitsu.common.resources.ResourceManager import jp.co.soramitsu.core.extrinsic.ExtrinsicService import jp.co.soramitsu.core.extrinsic.mortality.IChainStateRepository @@ -587,8 +588,8 @@ class StakingFeatureModule { fun provideIdentitiesUseCase(identityRepository: IdentityRepository) = GetIdentitiesUseCase(identityRepository) @Provides - fun soraTokensRateUseCase(rpcCalls: RpcCalls, chainRegistry: ChainRegistry, tokenPriceDao: TokenPriceDao) = - SoraStakingRewardsScenario(rpcCalls, chainRegistry, tokenPriceDao) + fun soraTokensRateUseCase(rpcCalls: RpcCalls, chainRegistry: ChainRegistry, tokenPriceDao: TokenPriceDao, selectedFiat: SelectedFiat) = + SoraStakingRewardsScenario(rpcCalls, chainRegistry, tokenPriceDao, selectedFiat) @Provides @Singleton diff --git a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/domain/alerts/Alert.kt b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/domain/alerts/Alert.kt index 44d79777fc..7fe0a2a0d0 100644 --- a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/domain/alerts/Alert.kt +++ b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/domain/alerts/Alert.kt @@ -12,6 +12,7 @@ sealed class Alert { class BondMoreTokens(val minimalStake: BigDecimal, val token: Token) : Alert() object ChangeValidators : Alert() + object AllValidatorsAreOversubscribed : Alert() object WaitingForNextEra : Alert() diff --git a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/domain/alerts/AlertsInteractor.kt b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/domain/alerts/AlertsInteractor.kt index ace9cabba1..db7bde09f9 100644 --- a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/domain/alerts/AlertsInteractor.kt +++ b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/domain/alerts/AlertsInteractor.kt @@ -20,6 +20,7 @@ import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import java.math.BigDecimal import java.math.BigInteger +import jp.co.soramitsu.shared_utils.extensions.toHexString import jp.co.soramitsu.core.models.Asset as CoreAsset private const val NOMINATIONS_ACTIVE_MEMO = "NOMINATIONS_ACTIVE_MEMO" @@ -38,7 +39,8 @@ class AlertsInteractor( val maxRewardedNominatorsPerValidator: Int, val minimumNominatorBond: BigInteger, val activeEra: BigInteger, - val asset: Asset + val asset: Asset, + val maxNominators: Int ) { val memo = mutableMapOf() @@ -63,11 +65,17 @@ class AlertsInteractor( private fun produceChangeValidatorsAlert(context: AlertContext): Alert? { return requireState(context.stakingState) { nominatorState: StakingState.Stash.Nominator -> - Alert.ChangeValidators.takeIf { - // staking is inactive - context.isStakingActive(nominatorState.stashId).not() && - // there is no pending change - nominatorState.nominations.isWaiting(context.activeEra).not() + val allValidatorsAreOversubscribed = nominatorState.nominations.targets.mapNotNull { context.exposures[it.toHexString()] }.all { it.others.size > context.maxNominators } + val stakingIsNotActive = context.isStakingActive(nominatorState.stashId).not() + + if (stakingIsNotActive.not()) return null + + return@requireState when { + stakingIsNotActive && allValidatorsAreOversubscribed -> Alert.AllValidatorsAreOversubscribed + stakingIsNotActive && nominatorState.nominations.isWaiting(context.activeEra) + .not() -> Alert.ChangeValidators + + else -> null } } } @@ -124,6 +132,8 @@ class AlertsInteractor( val minimumNominatorBond = stakingRepository.minimumNominatorBond(chainAsset) val meta = accountRepository.getSelectedMetaAccount() + val maxNominators = stakingConstantsRepository.maxRewardedNominatorPerValidator(chain.id) + val alertsFlow = combine( stakingRepository.electedExposuresInActiveEra(chain.id), walletRepository.assetFlow(meta.id, stakingState.accountId, chainAsset, chain.minSupportedVersion), @@ -136,7 +146,8 @@ class AlertsInteractor( maxRewardedNominatorsPerValidator = maxRewardedNominatorsPerValidator, minimumNominatorBond = minimumNominatorBond, asset = asset, - activeEra = activeEra + activeEra = activeEra, + maxNominators = maxNominators ) alertProducers.mapNotNull { it.invoke(context) } diff --git a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/domain/rewards/SoraStakingRewardsScenario.kt b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/domain/rewards/SoraStakingRewardsScenario.kt index 7617aba743..c3c850168d 100644 --- a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/domain/rewards/SoraStakingRewardsScenario.kt +++ b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/domain/rewards/SoraStakingRewardsScenario.kt @@ -1,6 +1,7 @@ package jp.co.soramitsu.staking.impl.domain.rewards import java.math.BigInteger +import jp.co.soramitsu.common.domain.SelectedFiat import jp.co.soramitsu.common.utils.orZero import jp.co.soramitsu.core.rpc.RpcCalls import jp.co.soramitsu.core.rpc.calls.liquidityProxyQuote @@ -12,7 +13,8 @@ import jp.co.soramitsu.wallet.impl.domain.model.Token class SoraStakingRewardsScenario( private val rpcCalls: RpcCalls, private val chainRegistry: ChainRegistry, - private val tokenDao: TokenPriceDao + private val tokenDao: TokenPriceDao, + private val selectedFiat: SelectedFiat ) { companion object { private const val SORA_MAIN_NET_CHAIN_ID = @@ -37,8 +39,9 @@ class SoraStakingRewardsScenario( suspend fun getRewardAsset(): Token { val chain = chainRegistry.getChain(SORA_MAIN_NET_CHAIN_ID) val rewardAsset = requireNotNull(chain.assetsById[REWARD_ASSET_ID]) - val priceId = requireNotNull(rewardAsset.priceId) - val token = tokenDao.getTokenPrice(priceId) + val priceId = rewardAsset.priceProvider?.id?.takeIf { selectedFiat.isUsd() } ?: rewardAsset.priceId + val requirePriceId = requireNotNull(priceId) + val token = tokenDao.getTokenPrice(requirePriceId) return Token( configuration = rewardAsset, diff --git a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/domain/staking/redeem/RedeemConsequences.kt b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/domain/staking/redeem/RedeemConsequences.kt index 37e68a3316..e7951c06a9 100644 --- a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/domain/staking/redeem/RedeemConsequences.kt +++ b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/domain/staking/redeem/RedeemConsequences.kt @@ -1,5 +1,6 @@ package jp.co.soramitsu.staking.impl.domain.staking.redeem class RedeemConsequences( - val willKillStash: Boolean + val willKillStash: Boolean, + val hash: String ) diff --git a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/domain/staking/redeem/RedeemInteractor.kt b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/domain/staking/redeem/RedeemInteractor.kt index c9bd343aac..0f9d2bd527 100644 --- a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/domain/staking/redeem/RedeemInteractor.kt +++ b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/domain/staking/redeem/RedeemInteractor.kt @@ -33,7 +33,8 @@ class RedeemInteractor( formExtrinsic.invoke(this) }.map { RedeemConsequences( - willKillStash = asset.redeemable == asset.locked + willKillStash = asset.redeemable == asset.locked, + hash = it ) } } diff --git a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/confirm/pool/join/ConfirmJoinPoolViewModel.kt b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/confirm/pool/join/ConfirmJoinPoolViewModel.kt index 5ca854e842..154d671e80 100644 --- a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/confirm/pool/join/ConfirmJoinPoolViewModel.kt +++ b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/confirm/pool/join/ConfirmJoinPoolViewModel.kt @@ -22,6 +22,7 @@ import jp.co.soramitsu.wallet.impl.domain.model.amountFromPlanks import jp.co.soramitsu.wallet.impl.domain.model.planksFromAmount import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch @@ -77,7 +78,9 @@ class ConfirmJoinPoolViewModel @Inject constructor( feeFormatted, feeFiat ) - }.stateIn( + } + .catch { emit(defaultFeeState) } + .stateIn( viewModelScope, SharingStarted.Eagerly, defaultFeeState diff --git a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/setup/pool/create/CreatePoolSetupViewModel.kt b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/setup/pool/create/CreatePoolSetupViewModel.kt index 20703c03d7..c3cb02380f 100644 --- a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/setup/pool/create/CreatePoolSetupViewModel.kt +++ b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/setup/pool/create/CreatePoolSetupViewModel.kt @@ -218,10 +218,15 @@ class CreatePoolSetupViewModel @Inject constructor( val fee = feeInPlanksFlow.value val transferableInPlanks = asset.token.planksFromAmount(asset.transferable) - val minToCreate = poolInteractor.getMinToCreate(chain.id) - if (amountInPlanks < minToCreate) { - val minToCreateFormatted = minToCreate.formatCryptoDetailFromPlanks(asset.token.configuration) - showError(MinPoolCreationThresholdException(resourceManager, minToCreateFormatted)) + try { + val minToCreate = poolInteractor.getMinToCreate(chain.id) + if (amountInPlanks < minToCreate) { + val minToCreateFormatted = minToCreate.formatCryptoDetailFromPlanks(asset.token.configuration) + showError(MinPoolCreationThresholdException(resourceManager, minToCreateFormatted)) + return@launch + } + } catch (e: NullPointerException) { + showError(e.message.orEmpty()) return@launch } diff --git a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/setup/pool/join/SetupStakingPoolViewModel.kt b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/setup/pool/join/SetupStakingPoolViewModel.kt index 00ce7c0f11..2ff591a674 100644 --- a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/setup/pool/join/SetupStakingPoolViewModel.kt +++ b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/setup/pool/join/SetupStakingPoolViewModel.kt @@ -22,7 +22,6 @@ import jp.co.soramitsu.common.utils.formatCrypto import jp.co.soramitsu.common.utils.formatCryptoDetail import jp.co.soramitsu.common.utils.formatFiat import jp.co.soramitsu.common.utils.inBackground -import jp.co.soramitsu.common.utils.isNotZero import jp.co.soramitsu.common.utils.orZero import jp.co.soramitsu.common.validation.AmountTooLowToStakeException import jp.co.soramitsu.common.validation.ExistentialDepositCrossedException @@ -155,43 +154,48 @@ class SetupStakingPoolViewModel @Inject constructor( val amount = enteredAmountFlow.value viewModelScope.launch { - isValid(amount).fold({ - stakingPoolSharedStateProvider.joinFlowState.set(setupFlow.copy(amount = amount)) - router.openSelectPool() - }, { throwable -> - val message = - throwable.localizedMessage ?: throwable.message ?: resourceManager.getString(R.string.common_undefined_error_message) - val errorAlertViewState = (throwable as? ValidationException)?.let { (title, message) -> - AlertViewState( - title = title, + isValid(amount).fold( + onSuccess = { + stakingPoolSharedStateProvider.joinFlowState.set(setupFlow.copy(amount = amount)) + router.openSelectPool() + }, + onFailure = { throwable -> + val message = + throwable.localizedMessage ?: throwable.message ?: resourceManager.getString(R.string.common_undefined_error_message) + val errorAlertViewState = (throwable as? ValidationException)?.let { (title, message) -> + AlertViewState( + title = title, + message = message, + buttonText = resourceManager.getString(R.string.common_ok), + iconRes = R.drawable.ic_status_warning_16 + ) + } ?: AlertViewState( + title = resourceManager.getString(R.string.common_error_general_title), message = message, - buttonText = resourceManager.getString(R.string.common_ok), + buttonText = resourceManager.getString(R.string.common_got_it), iconRes = R.drawable.ic_status_warning_16 ) - } ?: AlertViewState( - title = resourceManager.getString(R.string.common_error_general_title), - message = message, - buttonText = resourceManager.getString(R.string.common_got_it), - iconRes = R.drawable.ic_status_warning_16 - ) - router.openAlert(errorAlertViewState) - }) + router.openAlert(errorAlertViewState) + }) } } private suspend fun isValid(amount: BigDecimal): Result { - val amountInPlanks = asset.token.planksFromAmount(amount) - val transferableInPlanks = asset.token.planksFromAmount(asset.transferable) - val minToJoinInPlanks = stakingPoolInteractor.getMinToJoinPool(chain.id) - val minToJoinFormatted = minToJoinInPlanks.formatCryptoDetailFromPlanks(asset.token.configuration) - val existentialDeposit = existentialDepositUseCase(asset.token.configuration) - val feeInPlanks = feeInPlanksFlow.value ?: return Result.failure(WaitForFeeCalculationException(resourceManager)) - - return when { - amountInPlanks + feeInPlanks >= transferableInPlanks -> Result.failure(StakeInsufficientBalanceException(resourceManager)) - transferableInPlanks - amountInPlanks - feeInPlanks <= existentialDeposit -> Result.failure(ExistentialDepositCrossedException(resourceManager, existentialDeposit.formatCryptoDetailFromPlanks(asset.token.configuration))) - amountInPlanks < minToJoinInPlanks -> Result.failure(AmountTooLowToStakeException(resourceManager, minToJoinFormatted)) - else -> Result.success(Unit) + return runCatching { + val amountInPlanks = asset.token.planksFromAmount(amount) + val transferableInPlanks = asset.token.planksFromAmount(asset.transferable) + val minToJoinInPlanks = stakingPoolInteractor.getMinToJoinPool(chain.id) + val minToJoinFormatted = minToJoinInPlanks.formatCryptoDetailFromPlanks(asset.token.configuration) + val existentialDeposit = existentialDepositUseCase(asset.token.configuration) + val feeInPlanks = feeInPlanksFlow.value + + when { + feeInPlanks == null -> throw WaitForFeeCalculationException(resourceManager) + amountInPlanks + feeInPlanks >= transferableInPlanks -> throw StakeInsufficientBalanceException(resourceManager) + transferableInPlanks - amountInPlanks - feeInPlanks <= existentialDeposit -> throw ExistentialDepositCrossedException(resourceManager, existentialDeposit.formatCryptoDetailFromPlanks(asset.token.configuration)) + amountInPlanks < minToJoinInPlanks -> throw AmountTooLowToStakeException(resourceManager, minToJoinFormatted) + else -> Unit + } } } diff --git a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/balance/StakingBalanceFragment.kt b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/balance/StakingBalanceFragment.kt index 68899dc038..5f513c2ec0 100644 --- a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/balance/StakingBalanceFragment.kt +++ b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/balance/StakingBalanceFragment.kt @@ -61,7 +61,7 @@ class StakingBalanceFragment : BaseFragment(R.layout.fr viewModel.redeemTitle?.let { binding.stakingBalanceActions.redeem.setText(it) } - viewModel.stakingBalanceModelLiveData.observe { + viewModel.stakingBalanceModelFlow.observe { binding.stakingBalanceSwipeRefresh.isRefreshing = false with(binding.stakingBalanceInfo) { diff --git a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/balance/StakingBalanceViewModel.kt b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/balance/StakingBalanceViewModel.kt index c1e359447f..232f8117f7 100644 --- a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/balance/StakingBalanceViewModel.kt +++ b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/balance/StakingBalanceViewModel.kt @@ -3,7 +3,11 @@ package jp.co.soramitsu.staking.impl.presentation.staking.balance import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel +import java.math.BigDecimal +import java.math.BigInteger +import javax.inject.Inject import jp.co.soramitsu.common.base.BaseViewModel import jp.co.soramitsu.common.mixin.api.Validatable import jp.co.soramitsu.common.resources.ResourceManager @@ -32,18 +36,19 @@ import jp.co.soramitsu.staking.impl.scenarios.StakingScenarioInteractor import jp.co.soramitsu.wallet.api.presentation.model.mapAmountToAmountModel import jp.co.soramitsu.wallet.impl.domain.model.amountFromPlanks import jp.co.soramitsu.wallet.impl.domain.model.planksFromAmount +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch -import java.math.BigDecimal -import java.math.BigInteger -import javax.inject.Inject +@OptIn(ExperimentalCoroutinesApi::class) @HiltViewModel class StakingBalanceViewModel @Inject constructor( private val router: StakingRouter, @@ -62,8 +67,9 @@ class StakingBalanceViewModel @Inject constructor( private val assetFlow = interactor.currentAssetFlow() .share() - val stakingBalanceModelLiveData: Flow = refresh.flatMapLatest { + val stakingBalanceModelFlow: Flow = refresh.flatMapLatest { stakingScenarioInteractor.getStakingBalanceFlow(collatorAddress?.fromHex()) + .catch { } }.share() private val unbondingsFlow: Flow> = refresh.flatMapLatest { @@ -72,13 +78,13 @@ class StakingBalanceViewModel @Inject constructor( val redeemTitle = stakingScenarioInteractor.overrideRedeemActionTitle() - val redeemEnabledLiveData = stakingBalanceModelLiveData.map { + val redeemEnabledLiveData = stakingBalanceModelFlow.map { it.redeemable.amount > BigDecimal.ZERO }.asLiveData() val pendingAction = MutableLiveData(false) - val shouldBlockStakeMore = stakingBalanceModelLiveData.map { + val shouldBlockStakeMore = stakingBalanceModelFlow.map { val isParachain = assetFlow.first().token.configuration.staking == Asset.StakingType.PARACHAIN val isUnstakingFullAmount = (it.staked.amount - it.unstaking.amount).isZero() val isReadyForUnlockFullAmount = (it.staked.amount - it.redeemable.amount).isZero() @@ -88,7 +94,7 @@ class StakingBalanceViewModel @Inject constructor( isFullUnstake.and(isParachain) }.onStart { emit(true) }.asLiveData() - val shouldBlockUnstake = stakingBalanceModelLiveData.map { + val shouldBlockUnstake = stakingBalanceModelFlow.map { val asset = assetFlow.first() val isParachain = asset.token.configuration.staking == Asset.StakingType.PARACHAIN val stakedAmountIsZero = asset.token.planksFromAmount(it.staked.amount) == BigInteger.ZERO diff --git a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/bond/confirm/ConfirmBondMoreViewModel.kt b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/bond/confirm/ConfirmBondMoreViewModel.kt index 8c5fd9f862..4291f43531 100644 --- a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/bond/confirm/ConfirmBondMoreViewModel.kt +++ b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/bond/confirm/ConfirmBondMoreViewModel.kt @@ -150,6 +150,7 @@ class ConfirmBondMoreViewModel @Inject constructor( showMessage(resourceManager.getString(R.string.common_transaction_submitted)) finishFlow() + result.getOrNull()?.let { router.openOperationSuccess(it, token.configuration.chainId) } } else { showError(result.requireException()) } diff --git a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/main/StakingViewStateOld.kt b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/main/StakingViewStateOld.kt index f019ef0f73..6b8dfdbe3e 100644 --- a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/main/StakingViewStateOld.kt +++ b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/main/StakingViewStateOld.kt @@ -206,7 +206,7 @@ sealed class StakeViewState( return StakeSummaryModel( status = summary.status, - totalStaked = summary.totalStaked.formatCrypto(tokenType.symbol), + totalStaked = summary.totalStaked.formatCryptoDetail(tokenType.symbol), totalStakedFiat = token.fiatAmount(summary.totalStaked)?.formatFiat(token.fiatSymbol), totalRewards = summary.totalReward.formatCryptoDetail(tokenType.symbol), totalRewardsFiat = token.fiatAmount(summary.totalReward)?.formatFiat(token.fiatSymbol), diff --git a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/main/scenarios/StakingPoolViewModel.kt b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/main/scenarios/StakingPoolViewModel.kt index 897d081a9d..2285e60d88 100644 --- a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/main/scenarios/StakingPoolViewModel.kt +++ b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/main/scenarios/StakingPoolViewModel.kt @@ -43,7 +43,7 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.stateIn class StakingPoolViewModel( @@ -148,35 +148,39 @@ class StakingPoolViewModel( } override suspend fun networkInfo(): Flow> { - return stakingInteractor.currentAssetFlow().filter { it.token.configuration.supportStakingPool }.map { asset -> - val config = asset.token.configuration - val chainId = config.chainId - - val minToJoinPoolInPlanks = stakingPoolInteractor.getMinToJoinPool(chainId) - val minToJoinPool = asset.token.configuration.amountFromPlanks(minToJoinPoolInPlanks) - val minToJoinPoolFormatted = minToJoinPool.formatCryptoDetail(config.symbol) - val minToJoinPoolFiat = asset.token.fiatAmount(minToJoinPool)?.formatFiat(asset.token.fiatSymbol) - - val minToCreatePoolInPlanks = stakingPoolInteractor.getMinToCreate(chainId) - val minToCreatePool = asset.token.configuration.amountFromPlanks(minToCreatePoolInPlanks) - val minToCreatePoolFormatted = minToCreatePool.formatCryptoDetail(config.symbol) - val minToCreatePoolFiat = asset.token.fiatAmount(minToCreatePool)?.formatFiat(asset.token.fiatSymbol) - - val existingPools = stakingPoolInteractor.getExistingPools(chainId).toString() - val possiblePools = stakingPoolInteractor.getPossiblePools(chainId).toString() - val maxMembersInPool = stakingPoolInteractor.getMaxMembersInPool(chainId).toString() - val maxPoolsMembers = stakingPoolInteractor.getMaxPoolsMembers(chainId).toString() - - StakingNetworkInfoModel.Pool( - minToJoinPoolFormatted, - minToJoinPoolFiat, - minToCreatePoolFormatted, - minToCreatePoolFiat, - existingPools, - possiblePools, - maxMembersInPool, - maxPoolsMembers - ) + return stakingInteractor.currentAssetFlow().filter { it.token.configuration.supportStakingPool }.mapNotNull { asset -> + try { + val config = asset.token.configuration + val chainId = config.chainId + + val minToJoinPoolInPlanks = stakingPoolInteractor.getMinToJoinPool(chainId) + val minToJoinPool = asset.token.configuration.amountFromPlanks(minToJoinPoolInPlanks) + val minToJoinPoolFormatted = minToJoinPool.formatCryptoDetail(config.symbol) + val minToJoinPoolFiat = asset.token.fiatAmount(minToJoinPool)?.formatFiat(asset.token.fiatSymbol) + + val minToCreatePoolInPlanks = stakingPoolInteractor.getMinToCreate(chainId) + val minToCreatePool = asset.token.configuration.amountFromPlanks(minToCreatePoolInPlanks) + val minToCreatePoolFormatted = minToCreatePool.formatCryptoDetail(config.symbol) + val minToCreatePoolFiat = asset.token.fiatAmount(minToCreatePool)?.formatFiat(asset.token.fiatSymbol) + + val existingPools = stakingPoolInteractor.getExistingPools(chainId).toString() + val possiblePools = stakingPoolInteractor.getPossiblePools(chainId).toString() + val maxMembersInPool = stakingPoolInteractor.getMaxMembersInPool(chainId).toString() + val maxPoolsMembers = stakingPoolInteractor.getMaxPoolsMembers(chainId).toString() + + StakingNetworkInfoModel.Pool( + minToJoinPoolFormatted, + minToJoinPoolFiat, + minToCreatePoolFormatted, + minToCreatePoolFiat, + existingPools, + possiblePools, + maxMembersInPool, + maxPoolsMembers + ) + } catch (e: NullPointerException) { + null + } }.withLoading() } diff --git a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/main/scenarios/StakingRelaychainScenarioViewModel.kt b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/main/scenarios/StakingRelaychainScenarioViewModel.kt index 9234f9e4b5..e1f9f43c7b 100644 --- a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/main/scenarios/StakingRelaychainScenarioViewModel.kt +++ b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/main/scenarios/StakingRelaychainScenarioViewModel.kt @@ -194,7 +194,7 @@ class StakingRelaychainScenarioViewModel( private fun mapAlertToAlertModel(alert: Alert): AlertModel { return when (alert) { - Alert.ChangeValidators -> { + is Alert.ChangeValidators -> { AlertModel( WARNING_ICON, resourceManager.getString(R.string.staking_alert_change_validators), @@ -203,6 +203,15 @@ class StakingRelaychainScenarioViewModel( ) } + is Alert.AllValidatorsAreOversubscribed -> { + AlertModel( + WARNING_ICON, + resourceManager.getString(R.string.staking_alert_change_validators), + resourceManager.getString(R.string.staking_your_oversubscribed_message), + AlertModel.Type.CallToAction { baseViewModel.openCurrentValidators() } + ) + } + is Alert.RedeemTokens -> { AlertModel( WARNING_ICON, @@ -230,7 +239,7 @@ class StakingRelaychainScenarioViewModel( AlertModel.Type.Info ) - Alert.SetValidators -> AlertModel( + is Alert.SetValidators -> AlertModel( WARNING_ICON, resourceManager.getString(R.string.staking_set_validators_title), resourceManager.getString(R.string.staking_set_validators_message), diff --git a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/rebond/confirm/ConfirmRebondViewModel.kt b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/rebond/confirm/ConfirmRebondViewModel.kt index 3615f59cf0..864cfd4fbd 100644 --- a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/rebond/confirm/ConfirmRebondViewModel.kt +++ b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/rebond/confirm/ConfirmRebondViewModel.kt @@ -5,6 +5,8 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject +import javax.inject.Named import jp.co.soramitsu.account.api.presentation.actions.ExternalAccountActions import jp.co.soramitsu.common.address.AddressIconGenerator import jp.co.soramitsu.common.address.createAddressModel @@ -34,8 +36,6 @@ import jp.co.soramitsu.wallet.impl.domain.model.planksFromAmount import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch -import javax.inject.Inject -import javax.inject.Named @HiltViewModel class ConfirmRebondViewModel @Inject constructor( @@ -68,8 +68,14 @@ class ConfirmRebondViewModel @Inject constructor( val assetModelFlow = assetFlow .map { - val retrieveAmount = stakingScenarioInteractor.getRebondAvailableAmount(it, payload.amount) - mapAssetToAssetModel(it, resourceManager, { retrieveAmount }, R.string.staking_unbonding_format) + val retrieveAmount = + stakingScenarioInteractor.getRebondAvailableAmount(it, payload.amount) + mapAssetToAssetModel( + it, + resourceManager, + { retrieveAmount }, + R.string.staking_unbonding_format + ) } .inBackground() .asLiveData() @@ -86,7 +92,8 @@ class ConfirmRebondViewModel @Inject constructor( val address = it.executionAddress val account = interactor.getProjectedAccount(address) - val addressModel = iconGenerator.createAddressModel(address, AddressIconGenerator.SIZE_SMALL, account.name) + val addressModel = + iconGenerator.createAddressModel(address, AddressIconGenerator.SIZE_SMALL, account.name) addressModel } @@ -109,7 +116,10 @@ class ConfirmRebondViewModel @Inject constructor( val originAddressModel = originAddressModelLiveData.value ?: return@launch val chainId = assetFlow.first().token.configuration.chainId val chain = chainRegistry.getChain(chainId) - val supportedExplorers = chain.explorers.getSupportedExplorers(BlockExplorerUrlBuilder.Type.ACCOUNT, originAddressModel.address) + val supportedExplorers = chain.explorers.getSupportedExplorers( + BlockExplorerUrlBuilder.Type.ACCOUNT, + originAddressModel.address + ) val externalActionsPayload = ExternalAccountActions.Payload( value = originAddressModel.address, chainId = chainId, @@ -166,6 +176,10 @@ class ConfirmRebondViewModel @Inject constructor( showMessage(resourceManager.getString(R.string.common_transaction_submitted)) router.returnToStakingBalance() + router.openOperationSuccess( + it, + validPayload.controllerAsset.token.configuration.chainId + ) } .onFailure(::showError) diff --git a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/redeem/RedeemViewModel.kt b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/redeem/RedeemViewModel.kt index c375d7ad75..7b699a6aa3 100644 --- a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/redeem/RedeemViewModel.kt +++ b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/redeem/RedeemViewModel.kt @@ -211,6 +211,7 @@ class RedeemViewModel @Inject constructor( result.requireValue().willKillStash -> router.returnToMain() else -> router.returnToStakingBalance() } + result.getOrNull()?.let { router.openOperationSuccess(it.hash, redeemValidationPayload.asset.token.configuration.chainId) } } else { showError(result.requireException()) } diff --git a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/unbond/PoolUnstakeViewModel.kt b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/unbond/PoolUnstakeViewModel.kt index ff5c1a6850..eb73c1b382 100644 --- a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/unbond/PoolUnstakeViewModel.kt +++ b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/unbond/PoolUnstakeViewModel.kt @@ -36,17 +36,19 @@ class PoolUnstakeViewModel @Inject constructor( ) }, onNextStep = { amount -> - val minToCreate = stakingPoolInteractor.getMinToCreate(stakingPoolSharedStateProvider.requireMainState.requireChain.id) + try { + val minToCreate = stakingPoolInteractor.getMinToCreate(stakingPoolSharedStateProvider.requireMainState.requireChain.id) - val isDepositor = stakingPoolSharedStateProvider.requireManageState.userRole == RoleInPool.Depositor - if (isDepositor && (stakingPoolSharedStateProvider.requireManageState.stakedInPlanks - amount) < minToCreate) { - val asset = stakingPoolSharedStateProvider.requireMainState.requireAsset - val minToCreateFormatted = minToCreate.formatCryptoDetailFromPlanks(asset.token.configuration) - router.openPoolFullUnstakeDepositorAlertFragment(minToCreateFormatted) - } else { - stakingPoolSharedStateProvider.manageState.get()?.copy(amountInPlanks = amount)?.let { stakingPoolSharedStateProvider.manageState.set(it) } - router.openPoolConfirmUnstake() - } + val isDepositor = stakingPoolSharedStateProvider.requireManageState.userRole == RoleInPool.Depositor + if (isDepositor && (stakingPoolSharedStateProvider.requireManageState.stakedInPlanks - amount) < minToCreate) { + val asset = stakingPoolSharedStateProvider.requireMainState.requireAsset + val minToCreateFormatted = minToCreate.formatCryptoDetailFromPlanks(asset.token.configuration) + router.openPoolFullUnstakeDepositorAlertFragment(minToCreateFormatted) + } else { + stakingPoolSharedStateProvider.manageState.get()?.copy(amountInPlanks = amount)?.let { stakingPoolSharedStateProvider.manageState.set(it) } + router.openPoolConfirmUnstake() + } + } catch (_: NullPointerException) {} }, validations = arrayOf( Validation( diff --git a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/unbond/confirm/ConfirmUnbondViewModel.kt b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/unbond/confirm/ConfirmUnbondViewModel.kt index 9585bc4fd2..c894b74bf4 100644 --- a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/unbond/confirm/ConfirmUnbondViewModel.kt +++ b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/presentation/staking/unbond/confirm/ConfirmUnbondViewModel.kt @@ -154,6 +154,7 @@ class ConfirmUnbondViewModel @Inject constructor( showMessage(resourceManager.getString(R.string.common_transaction_submitted)) router.returnToStakingBalance() + result.getOrNull()?.let { router.openOperationSuccess(it, validPayload.asset.token.configuration.chainId) } } else { showError(result.requireException()) } diff --git a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/scenarios/relaychain/StakingRelayChainScenarioRepository.kt b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/scenarios/relaychain/StakingRelayChainScenarioRepository.kt index 5936c3dc48..c51d0758f0 100644 --- a/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/scenarios/relaychain/StakingRelayChainScenarioRepository.kt +++ b/feature-staking-impl/src/main/java/jp/co/soramitsu/staking/impl/scenarios/relaychain/StakingRelayChainScenarioRepository.kt @@ -99,20 +99,20 @@ class StakingRelayChainScenarioRepository( return runtime.metadata.babe().numberConstant("EpochDuration", runtime) // How many blocks per session } - suspend fun currentSessionIndex(chainId: ChainId) = remoteStorage.queryNonNull( + suspend fun currentSessionIndex(chainId: ChainId) = remoteStorage.query( // Current session index keyBuilder = { it.metadata.session().storage("CurrentIndex").storageKey() }, binding = ::bindCurrentIndex, chainId = chainId ) - suspend fun currentSlot(chainId: ChainId) = remoteStorage.queryNonNull( + suspend fun currentSlot(chainId: ChainId) = remoteStorage.query( keyBuilder = { it.metadata.babe().storage("CurrentSlot").storageKey() }, binding = ::bindCurrentSlot, chainId = chainId ) - suspend fun genesisSlot(chainId: ChainId) = remoteStorage.queryNonNull( + suspend fun genesisSlot(chainId: ChainId) = remoteStorage.query( keyBuilder = { it.metadata.babe().storage("GenesisSlot").storageKey() }, binding = ::bindCurrentSlot, chainId = chainId @@ -120,7 +120,7 @@ class StakingRelayChainScenarioRepository( suspend fun eraStartSessionIndex(chainId: ChainId, currentEra: BigInteger): EraIndex { val runtime = runtimeFor(chainId) - return remoteStorage.queryNonNull( // Index of session from with the era started + return remoteStorage.query( // Index of session from with the era started keyBuilder = { it.metadata.staking().storage("ErasStartSessionIndex").storageKey(runtime, currentEra) }, binding = ::bindErasStartSessionIndex, chainId = chainId diff --git a/feature-staking-impl/src/main/res/layout/view_stake_summary.xml b/feature-staking-impl/src/main/res/layout/view_stake_summary.xml index abafac7e25..940a398bff 100644 --- a/feature-staking-impl/src/main/res/layout/view_stake_summary.xml +++ b/feature-staking-impl/src/main/res/layout/view_stake_summary.xml @@ -62,7 +62,7 @@ android:layout_marginEnd="16dp" android:layout_weight="1" app:startWithLoading="true" - app:titleText="@string/staking_rewards_apr" /> + app:titleText="@string/staking_total_rewards_v1.9.0" /> BlockExplorerUrlBuilder(explorerItem.url, explorerItem.types).build(BlockExplorerUrlBuilder.Type.EXTRINSIC, operationHash) Chain.Explorer.Type.UNKNOWN -> null }?.let { url -> explorerItem.type to url diff --git a/feature-wallet-api/src/main/java/jp/co/soramitsu/wallet/api/data/cache/AssetCache.kt b/feature-wallet-api/src/main/java/jp/co/soramitsu/wallet/api/data/cache/AssetCache.kt index 219699b8a6..544a210550 100644 --- a/feature-wallet-api/src/main/java/jp/co/soramitsu/wallet/api/data/cache/AssetCache.kt +++ b/feature-wallet-api/src/main/java/jp/co/soramitsu/wallet/api/data/cache/AssetCache.kt @@ -1,6 +1,7 @@ package jp.co.soramitsu.wallet.api.data.cache import jp.co.soramitsu.account.api.domain.interfaces.AccountRepository +import jp.co.soramitsu.common.domain.SelectedFiat import jp.co.soramitsu.common.mixin.api.UpdatesMixin import jp.co.soramitsu.common.mixin.api.UpdatesProviderUi import jp.co.soramitsu.core.models.Asset @@ -21,7 +22,8 @@ class AssetCache( private val tokenPriceDao: TokenPriceDao, private val accountRepository: AccountRepository, private val assetDao: AssetDao, - private val updatesMixin: UpdatesMixin + private val updatesMixin: UpdatesMixin, + private val selectedFiat: SelectedFiat ) : AssetReadOnlyCache by assetDao, UpdatesProviderUi by updatesMixin { @@ -35,7 +37,12 @@ class AssetCache( ) = withContext(Dispatchers.IO) { val chainId = chainAsset.chainId val assetId = chainAsset.id - val priceId = chainAsset.priceId + val shouldUseChainlinkForRates = selectedFiat.isUsd() && chainAsset.priceProvider?.id != null + val priceId = if (shouldUseChainlinkForRates) { + chainAsset.priceProvider?.id + } else { + chainAsset.priceId + } assetUpdateMutex.withLock { priceId?.let { tokenPriceDao.ensureTokenPrice(it) } @@ -56,26 +63,27 @@ class AssetCache( cachedAsset.accountId.contentEquals(emptyAccountIdValue) -> { assetDao.deleteAsset(metaId, emptyAccountIdValue, chainId, assetId) - assetDao.insertAsset(builder.invoke(cachedAsset.copy(accountId = accountId))) + assetDao.insertAsset(builder.invoke(cachedAsset.copy(accountId = accountId, tokenPriceId = priceId))) } else -> { - val updatedAsset = builder.invoke(cachedAsset) + val updatedAsset = builder.invoke(cachedAsset.copy(tokenPriceId = priceId)) if (cachedAsset.bondedInPlanks == updatedAsset.bondedInPlanks && cachedAsset.feeFrozenInPlanks == updatedAsset.feeFrozenInPlanks && cachedAsset.miscFrozenInPlanks == updatedAsset.miscFrozenInPlanks && cachedAsset.freeInPlanks == updatedAsset.freeInPlanks && cachedAsset.redeemableInPlanks == updatedAsset.redeemableInPlanks && cachedAsset.reservedInPlanks == updatedAsset.reservedInPlanks && - cachedAsset.unbondingInPlanks == updatedAsset.unbondingInPlanks + cachedAsset.unbondingInPlanks == updatedAsset.unbondingInPlanks && + cachedAsset.tokenPriceId == updatedAsset.tokenPriceId ) { return@withLock } assetDao.updateAsset(updatedAsset) } } - updatesMixin.finishUpdateAsset(metaId, chainId, accountId, assetId) } + updatesMixin.finishUpdateAsset(metaId, chainId, accountId, assetId) } suspend fun updateAsset( @@ -90,18 +98,21 @@ class AssetCache( } } + suspend fun updateTokensPrice( + update: List + ) = withContext(Dispatchers.IO) { + tokenPriceDao.insertTokensPrice(update) + } + suspend fun updateTokenPrice( priceId: String, builder: (local: TokenPriceLocal) -> TokenPriceLocal ) = withContext(Dispatchers.IO) { - assetUpdateMutex.withLock { - val tokenPriceLocal = - tokenPriceDao.getTokenPrice(priceId) ?: TokenPriceLocal.createEmpty(priceId) + val tokenPriceLocal = tokenPriceDao.getTokenPrice(priceId) ?: TokenPriceLocal.createEmpty(priceId) - val newToken = builder.invoke(tokenPriceLocal) + val newToken = builder.invoke(tokenPriceLocal) - tokenPriceDao.insertTokenPrice(newToken) - } + tokenPriceDao.insertTokenPrice(newToken) } suspend fun updateAsset(updateModel: List) { diff --git a/feature-wallet-api/src/main/java/jp/co/soramitsu/wallet/api/presentation/BaseConfirmViewModel.kt b/feature-wallet-api/src/main/java/jp/co/soramitsu/wallet/api/presentation/BaseConfirmViewModel.kt index d7e028b39e..44f7c761b4 100644 --- a/feature-wallet-api/src/main/java/jp/co/soramitsu/wallet/api/presentation/BaseConfirmViewModel.kt +++ b/feature-wallet-api/src/main/java/jp/co/soramitsu/wallet/api/presentation/BaseConfirmViewModel.kt @@ -29,6 +29,7 @@ import jp.co.soramitsu.wallet.impl.domain.model.planksFromAmount import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map @@ -99,7 +100,9 @@ abstract class BaseConfirmViewModel( feeFormatted, feeFiat ) - }.stateIn( + } + .catch { emit(defaultFeeState) } + .stateIn( viewModelScope, SharingStarted.Eagerly, defaultFeeState diff --git a/feature-wallet-api/src/main/java/jp/co/soramitsu/wallet/impl/domain/interfaces/WalletInteractor.kt b/feature-wallet-api/src/main/java/jp/co/soramitsu/wallet/impl/domain/interfaces/WalletInteractor.kt index 0d33b5778a..59f75fb54a 100644 --- a/feature-wallet-api/src/main/java/jp/co/soramitsu/wallet/impl/domain/interfaces/WalletInteractor.kt +++ b/feature-wallet-api/src/main/java/jp/co/soramitsu/wallet/impl/domain/interfaces/WalletInteractor.kt @@ -105,8 +105,6 @@ interface WalletInteractor { suspend fun getChainAddressForSelectedMetaAccount(chainId: ChainId): String? - suspend fun updateAssets(newItems: List) - suspend fun markAssetAsHidden(chainId: ChainId, chainAssetId: String) suspend fun markAssetAsShown(chainId: ChainId, chainAssetId: String) diff --git a/feature-wallet-api/src/main/java/jp/co/soramitsu/wallet/impl/domain/interfaces/WalletRepository.kt b/feature-wallet-api/src/main/java/jp/co/soramitsu/wallet/impl/domain/interfaces/WalletRepository.kt index cff7198fe0..18c62e8d6f 100644 --- a/feature-wallet-api/src/main/java/jp/co/soramitsu/wallet/impl/domain/interfaces/WalletRepository.kt +++ b/feature-wallet-api/src/main/java/jp/co/soramitsu/wallet/impl/domain/interfaces/WalletRepository.kt @@ -95,8 +95,6 @@ interface WalletRepository { suspend fun getEquilibriumAccountInfo(asset: CoreAsset, accountId: AccountId): EqAccountInfo? - suspend fun updateAssets(newItems: List) - suspend fun getRemoteConfig(): Result fun chainRegistrySyncUp() diff --git a/feature-wallet-api/src/main/java/jp/co/soramitsu/wallet/impl/domain/model/BuyTokenRegistry.kt b/feature-wallet-api/src/main/java/jp/co/soramitsu/wallet/impl/domain/model/BuyTokenRegistry.kt index e4c539a311..6e42482cfb 100644 --- a/feature-wallet-api/src/main/java/jp/co/soramitsu/wallet/impl/domain/model/BuyTokenRegistry.kt +++ b/feature-wallet-api/src/main/java/jp/co/soramitsu/wallet/impl/domain/model/BuyTokenRegistry.kt @@ -15,7 +15,7 @@ class BuyTokenRegistry(val availableProviders: List>) { @get:DrawableRes val icon: Int - fun isTokenSupported(chainAsset: CoreAsset) = name.lowercase() in chainAsset.priceProviders.orEmpty() + fun isTokenSupported(chainAsset: CoreAsset) = name.lowercase() in chainAsset.purchaseProviders.orEmpty() fun createIntegrator(chainAsset: CoreAsset, address: String): I } diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/HistorySourceProvider.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/HistorySourceProvider.kt index ef2ad5a3a4..c560d874a7 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/HistorySourceProvider.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/HistorySourceProvider.kt @@ -26,6 +26,7 @@ class HistorySourceProvider( Chain.ExternalApi.Section.Type.ETHERSCAN -> EtherscanHistorySource(walletOperationsApi, historyUrl) Chain.ExternalApi.Section.Type.OKLINK -> OkLinkHistorySource(walletOperationsApi, historyUrl) Chain.ExternalApi.Section.Type.ZETA -> ZetaHistorySource(walletOperationsApi, historyUrl) + Chain.ExternalApi.Section.Type.REEF -> ReefHistorySource(walletOperationsApi, historyUrl) else -> null } } diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/ReefHistorySource.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/ReefHistorySource.kt new file mode 100644 index 0000000000..5d1af7333d --- /dev/null +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/historySource/ReefHistorySource.kt @@ -0,0 +1,117 @@ +package jp.co.soramitsu.wallet.impl.data.historySource + +import android.os.Build +import java.text.SimpleDateFormat +import java.time.Instant +import java.util.Locale +import jp.co.soramitsu.common.data.model.CursorPage +import jp.co.soramitsu.core.models.Asset +import jp.co.soramitsu.runtime.multiNetwork.chain.model.Chain +import jp.co.soramitsu.shared_utils.runtime.AccountId +import jp.co.soramitsu.wallet.impl.data.network.model.request.ReefRequestBuilder +import jp.co.soramitsu.wallet.impl.data.network.subquery.OperationsHistoryApi +import jp.co.soramitsu.wallet.impl.domain.interfaces.TransactionFilter +import jp.co.soramitsu.wallet.impl.domain.model.Operation + +class ReefHistorySource( + private val walletOperationsApi: OperationsHistoryApi, + private val url: String +) : HistorySource { + override suspend fun getOperations( + pageSize: Int, + cursor: String?, + filters: Set, + accountId: AccountId, + chain: Chain, + chainAsset: Asset, + accountAddress: String + ): CursorPage { + val overridePageSize = 50 + val offset = cursor?.toIntOrNull().takeIf { it != 0 } + + val response = walletOperationsApi.getReefOperationsHistory( + url = url, + ReefRequestBuilder( + filters, + accountAddress = accountAddress, + overridePageSize, + offset?.toString() + ).buildRequest() + ) + + val operations = mutableListOf() + if(filters.contains(TransactionFilter.TRANSFER) && response.data.transfersConnection != null) { + operations.addAll(response.data.transfersConnection?.edges?.map { it.node }?.map { + Operation( + id = it.extrinsicHash ?: it.id ?: it.hashCode().toString(), + address = accountAddress, + time = parseTimeToMillis(it.timestamp), + chainAsset = chainAsset, + type = Operation.Type.Transfer( + hash = it.extrinsicHash, + myAddress = accountAddress, + amount = it.amount, + receiver = it.to.id, + sender = it.from.id, + status = Operation.Status.fromSuccess(it.success), + fee = it.signedData?.fee?.partialFee + ) + ) + }.orEmpty()) + } + if(filters.contains(TransactionFilter.REWARD) && response.data.stakingsConnection != null) { + operations.addAll(response.data.stakingsConnection?.edges?.map { it.node }?.map { + Operation( + id = it.id, + address = accountAddress, + time = parseTimeToMillis(it.timestamp), + chainAsset = chainAsset, + type = Operation.Type.Reward( + amount = it.amount, + isReward = true, + era = 0, + validator = null + ) + ) + }.orEmpty()) + } + + val transfersPageInfo = response.data.transfersConnection?.pageInfo + val rewardsPageInfo = response.data.stakingsConnection?.pageInfo + + val shouldLoadTransfersNextPage = transfersPageInfo?.hasNextPage ?: false + val shouldLoadRewardsNextPage = rewardsPageInfo?.hasNextPage ?: false + + val hasNextPage = shouldLoadTransfersNextPage || shouldLoadRewardsNextPage + + val nextCursor = if(hasNextPage) { + val transfersOffset = transfersPageInfo?.endCursor?.toIntOrNull() ?: 0 + val rewardsOffset = rewardsPageInfo?.endCursor?.toIntOrNull() ?: 0 + val nextOffset = maxOf(transfersOffset, rewardsOffset) + if(nextOffset >= overridePageSize) nextOffset.toString() else null + } else { + null + } + + return CursorPage(nextCursor, operations.sortedByDescending { it.time }) + } + + private val reefDateFormat by lazy { + SimpleDateFormat( + "yyyy-MM-dd'T'HH:mm:ss.SSSSSSZ", + Locale.getDefault() + ) + } + + private fun parseTimeToMillis(timestamp: String): Long { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + Instant.parse(timestamp).toEpochMilli() + } else { + try { + reefDateFormat.parse(timestamp)?.time ?: 0 + } catch (e: Exception) { + 0 + } + } + } +} diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/blockchain/EthereumRemoteSource.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/blockchain/EthereumRemoteSource.kt index bc4ed8337a..fe4173395a 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/blockchain/EthereumRemoteSource.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/blockchain/EthereumRemoteSource.kt @@ -25,9 +25,13 @@ import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.reactive.asFlow import kotlinx.coroutines.withContext import kotlinx.coroutines.yield +import org.web3j.abi.DefaultFunctionReturnDecoder import org.web3j.abi.FunctionEncoder +import org.web3j.abi.TypeReference import org.web3j.abi.datatypes.Address import org.web3j.abi.datatypes.Function +import org.web3j.abi.datatypes.generated.Int256 +import org.web3j.abi.datatypes.generated.Uint80 import org.web3j.crypto.Credentials import org.web3j.crypto.RawTransaction import org.web3j.crypto.TransactionEncoder @@ -137,6 +141,19 @@ class EthereumRemoteSource(private val ethereumConnectionPool: EthereumConnectio return web3.fetchEthBalance(asset, address) } + suspend fun fetchPriceFeed( + chainId: ChainId, + receiverAddress: String + ): BigInteger? { + val connection = ethereumConnectionPool.get(chainId) + connection?.connect() + + val web3 = connection?.web3j + ?: throw RuntimeException("There is no connection created for chain ${chainId}") + + return web3.fetchPriceFeed(receiverAddress) + } + private suspend fun Ethereum.fetchEthBalance(asset: Asset, address: String): BigInteger { return if (asset.isUtility) { withContext(Dispatchers.IO) { @@ -167,6 +184,44 @@ class EthereumRemoteSource(private val ethereumConnectionPool: EthereumConnectio } } + private suspend fun Ethereum.fetchPriceFeed(address: String): BigInteger? { + val outParameters = listOf( + Uint80::class.java, + Int256::class.java, + Int256::class.java, + Int256::class.java, + Uint80::class.java + ).map { + TypeReference.create(it) + } + + val latestRoundDataFunction = Function( + "latestRoundData", + emptyList(), + outParameters + ) + + val latestRoundData = withContext(Dispatchers.IO) { + kotlin.runCatching { + ethCall( + Transaction.createEthCallTransaction( + null, + Address(address).value, + latestRoundDataFunction.encode() + ), + DefaultBlockParameterName.LATEST + ).send().value + }.getOrNull() + } + + val decodeResult = DefaultFunctionReturnDecoder.decode( + latestRoundData, + latestRoundDataFunction.outputParameters + ) + + return decodeResult[1].value as? BigInteger + } + fun listenGas(transfer: Transfer, chain: Chain): Flow { val connection = requireNotNull(ethereumConnectionPool.get(chain.id)) val web3j = requireNotNull(connection.web3j) diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/blockchain/updaters/BalancesUpdateSystem.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/blockchain/updaters/BalancesUpdateSystem.kt index a1b7ff2dbc..1eae78846e 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/blockchain/updaters/BalancesUpdateSystem.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/blockchain/updaters/BalancesUpdateSystem.kt @@ -175,10 +175,9 @@ class BalancesUpdateSystem( storageKeyToHex.mapNotNull { (key, hexRaw) -> val metadata = storageKeys.firstOrNull { it.key == key } ?: return@mapNotNull null - val balanceData = handleBalanceResponse( runtime, - metadata.asset.typeExtra, + metadata.asset, hexRaw ).onFailure { logError(chain, it) } @@ -240,7 +239,7 @@ class BalancesUpdateSystem( val balanceData = handleBalanceResponse( runtime, - keyWithMetadata.asset.typeExtra, + keyWithMetadata.asset, hexRaw ).onFailure { logError(chain, it) } diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/model/SubstrateBalancesUtils.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/model/SubstrateBalancesUtils.kt index 73800a34a7..ebc8e194f3 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/model/SubstrateBalancesUtils.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/model/SubstrateBalancesUtils.kt @@ -65,11 +65,11 @@ fun constructBalanceKey( fun handleBalanceResponse( runtime: RuntimeSnapshot, - assetType: ChainAssetType?, + asset: Asset, scale: String? ): Result { return runCatching { - when (assetType) { + when (asset.typeExtra) { null, ChainAssetType.Normal, ChainAssetType.SoraUtilityAsset -> { diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/model/request/ReefHistoryRequest.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/model/request/ReefHistoryRequest.kt new file mode 100644 index 0000000000..aaee312017 --- /dev/null +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/model/request/ReefHistoryRequest.kt @@ -0,0 +1,79 @@ +package jp.co.soramitsu.wallet.impl.data.network.model.request + +import jp.co.soramitsu.wallet.impl.domain.interfaces.TransactionFilter + +class ReefRequestBuilder( + private val filters: Set, + private val accountAddress: String, + private val limit: Int = 50, + private val transfersOffset: String? = null +) { + fun buildRequest(): ReefHistoryRequest { + val offset = transfersOffset?.let { "\"$it\"" } + val query = StringBuilder() + if (filters.contains(TransactionFilter.TRANSFER)) { + query.append( + """ + transfersConnection(where: {AND: [{type_eq: Native}, {OR: [{from: {id_eq: "$accountAddress"}}, {to: {id_eq: "$accountAddress"}}]}]}, orderBy: timestamp_DESC, first: $limit, after: $offset) { + edges { + node { + amount + timestamp + success + to { + id + } + from { + id + } + signedData + extrinsicHash + } + } + pageInfo { + endCursor + hasNextPage + startCursor + } + } + """.trimIndent() + ) + } + if (filters.contains(TransactionFilter.REWARD)) { + query.append( + """ + stakingsConnection(orderBy: timestamp_DESC, where: {AND: {signer: {id_eq: "$accountAddress"}, amount_gt: "0"}}, first: ${limit * 2}, after: $offset) { + edges { + node { + id + amount + timestamp + signer { + id + } + } + } + totalCount + pageInfo { + endCursor + hasNextPage + } + } + """.trimIndent() + ) + } + + return ReefHistoryRequest(query.toString()) + } + +} + +class ReefHistoryRequest( + query: String +) { + val query = """ +query MyQuery { + $query +} +""".trimIndent() +} diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/model/response/ReefHistoryResponse.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/model/response/ReefHistoryResponse.kt new file mode 100644 index 0000000000..d8816a6bba --- /dev/null +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/model/response/ReefHistoryResponse.kt @@ -0,0 +1,31 @@ +package jp.co.soramitsu.wallet.impl.data.network.model.response + +import java.math.BigInteger +import jp.co.soramitsu.common.data.network.subquery.ReefAddress +import jp.co.soramitsu.common.data.network.subquery.ReefRewardsConnection + +data class ReefHistoryResponse(val transfersConnection: ReefElementsConnection? = null, val stakingsConnection: ReefRewardsConnection? = null) + +class ReefElementsConnection( + val pageInfo: SubsquidPageInfo, + val edges: List +) + +class ReefHistoryEdge(val node: ReefHistoryNode) +class ReefHistoryNode( + val id: String? = null, + val amount: BigInteger, + val type: String, + val timestamp: String, + val success: Boolean, + val to: ReefAddress, + val from: ReefAddress, + val extrinsicHash: String?, + val signedData: ReefSignedData? +) + +class ReefSignedData(val fee: ReefFeeData?) + +class ReefFeeData( + val partialFee: BigInteger? +) \ No newline at end of file diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/subquery/OperationsHistoryApi.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/subquery/OperationsHistoryApi.kt index 407c3d627d..fada86b5dd 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/subquery/OperationsHistoryApi.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/network/subquery/OperationsHistoryApi.kt @@ -5,11 +5,13 @@ import jp.co.soramitsu.common.data.network.subquery.GiantsquidResponse import jp.co.soramitsu.common.data.network.subquery.SubQueryResponse import jp.co.soramitsu.common.data.network.subquery.SubsquidResponse import jp.co.soramitsu.wallet.impl.data.network.model.request.GiantsquidHistoryRequest +import jp.co.soramitsu.wallet.impl.data.network.model.request.ReefHistoryRequest import jp.co.soramitsu.wallet.impl.data.network.model.request.SubqueryHistoryRequest import jp.co.soramitsu.wallet.impl.data.network.model.request.SubsquidHistoryRequest import jp.co.soramitsu.wallet.impl.data.network.model.response.EtherscanHistoryResponse import jp.co.soramitsu.wallet.impl.data.network.model.response.GiantsquidHistoryResponse import jp.co.soramitsu.wallet.impl.data.network.model.response.OkLinkHistoryResponse +import jp.co.soramitsu.wallet.impl.data.network.model.response.ReefHistoryResponse import jp.co.soramitsu.wallet.impl.data.network.model.response.SubqueryHistoryElementResponse import jp.co.soramitsu.wallet.impl.data.network.model.response.SubsquidHistoryElementsConnectionResponse import jp.co.soramitsu.wallet.impl.data.network.model.response.ZetaHistoryResponse @@ -65,4 +67,10 @@ interface OperationsHistoryApi { suspend fun getZetaOperationsHistory( @Url url: String ): ZetaHistoryResponse + + @POST + suspend fun getReefOperationsHistory( + @Url url: String, + @Body body: ReefHistoryRequest + ): SubsquidResponse } \ No newline at end of file diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/repository/TokenRepositoryImpl.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/repository/TokenRepositoryImpl.kt index 7b8fcd99d8..6ff1ee7daf 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/repository/TokenRepositoryImpl.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/repository/TokenRepositoryImpl.kt @@ -1,5 +1,6 @@ package jp.co.soramitsu.wallet.impl.data.repository +import jp.co.soramitsu.common.domain.SelectedFiat import jp.co.soramitsu.common.utils.flowOf import jp.co.soramitsu.core.models.Asset import jp.co.soramitsu.coredb.dao.TokenPriceDao @@ -9,26 +10,41 @@ import jp.co.soramitsu.wallet.impl.domain.interfaces.TokenRepository import jp.co.soramitsu.wallet.impl.domain.model.Token import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map import kotlinx.coroutines.withContext class TokenRepositoryImpl( - private val tokenPriceDao: TokenPriceDao + private val tokenPriceDao: TokenPriceDao, + private val selectedFiat: SelectedFiat ) : TokenRepository { override suspend fun getToken(chainAsset: Asset): Token = withContext(Dispatchers.Default) { - val tokenPriceLocal = chainAsset.priceId?.let { tokenPriceDao.getTokenPrice(it) ?: TokenPriceLocal.createEmpty(it) } + val priceId = if (selectedFiat.isUsd() && chainAsset.priceProvider?.id != null) { + chainAsset.priceProvider?.id + } else { + chainAsset.priceId + } + val tokenPriceLocal = priceId?.let { tokenPriceDao.getTokenPrice(it) ?: TokenPriceLocal.createEmpty(it) } combineAssetWithPrices(chainAsset, tokenPriceLocal) } override fun observeToken(chainAsset: Asset): Flow { - return when (val priceId = chainAsset.priceId) { - null -> flowOf { - combineAssetWithPrices(chainAsset, null) + return selectedFiat.flow().map { + if (it == "usd" && chainAsset.priceProvider?.id != null) { + chainAsset.priceProvider?.id + } else { + chainAsset.priceId } - else -> tokenPriceDao.observeTokenPrice(priceId).map { - combineAssetWithPrices(chainAsset, it) + }.flatMapLatest { priceId -> + when (priceId) { + null -> flowOf { + combineAssetWithPrices(chainAsset, null) + } + else -> tokenPriceDao.observeTokenPrice(priceId).map { + combineAssetWithPrices(chainAsset, it) + } } } } diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/repository/WalletRepositoryImpl.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/repository/WalletRepositoryImpl.kt index de96091cc0..7882c35263 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/repository/WalletRepositoryImpl.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/data/repository/WalletRepositoryImpl.kt @@ -16,18 +16,22 @@ import jp.co.soramitsu.common.data.secrets.v2.KeyPairSchema import jp.co.soramitsu.common.data.secrets.v2.MetaAccountSecrets import jp.co.soramitsu.common.data.storage.Preferences import jp.co.soramitsu.common.domain.GetAvailableFiatCurrencies +import jp.co.soramitsu.common.domain.SelectedFiat import jp.co.soramitsu.common.mixin.api.UpdatesMixin import jp.co.soramitsu.common.mixin.api.UpdatesProviderUi import jp.co.soramitsu.common.utils.orZero import jp.co.soramitsu.common.utils.requireValue +import jp.co.soramitsu.core.models.Asset.PriceProvider import jp.co.soramitsu.core.utils.utilityAsset import jp.co.soramitsu.coredb.dao.OperationDao import jp.co.soramitsu.coredb.dao.PhishingDao +import jp.co.soramitsu.coredb.dao.TokenPriceDao import jp.co.soramitsu.coredb.dao.emptyAccountIdValue import jp.co.soramitsu.coredb.model.AssetUpdateItem import jp.co.soramitsu.coredb.model.AssetWithToken import jp.co.soramitsu.coredb.model.OperationLocal import jp.co.soramitsu.coredb.model.PhishingLocal +import jp.co.soramitsu.coredb.model.TokenPriceLocal import jp.co.soramitsu.runtime.ext.accountIdOf import jp.co.soramitsu.runtime.ext.addressOf import jp.co.soramitsu.runtime.multiNetwork.ChainRegistry @@ -81,7 +85,9 @@ class WalletRepositoryImpl( private val remoteConfigFetcher: RemoteConfigFetcher, private val preferences: Preferences, private val accountRepository: AccountRepository, - private val chainsRepository: ChainsRepository + private val chainsRepository: ChainsRepository, + private val selectedFiat: SelectedFiat, + private val tokenPriceDao: TokenPriceDao ) : WalletRepository, UpdatesProviderUi by updatesMixin { companion object { @@ -151,14 +157,14 @@ class WalletRepositoryImpl( private fun buildNetworkIssues(items: List): Set { return items.map { + val configuration = it.asset.token.configuration NetworkIssueItemState( - iconUrl = it.asset.token.configuration.iconUrl, - title = "${it.asset.token.configuration.chainName} ${it.asset.token.configuration.name}", + iconUrl = configuration.iconUrl, + title = "${configuration.chainName} ${configuration.name}", type = NetworkIssueType.Node, - chainId = it.asset.token.configuration.chainId, - chainName = it.asset.token.configuration.chainName, - assetId = it.asset.token.configuration.id, - priceId = it.asset.token.configuration.priceId + chainId = configuration.chainId, + chainName = configuration.chainName, + assetId = configuration.id ) }.toSet() } @@ -189,23 +195,94 @@ class WalletRepositoryImpl( override suspend fun syncAssetsRates(currencyId: String) { val chains = chainsRepository.getChains() + if (currencyId == "usd") { + syncChainlinkRates(chains) + } + syncCoingeckoRates(chains, currencyId) + } + + private suspend fun syncChainlinkRates(chains: List) { + val chainlinkIds = chains.map { it.assets.mapNotNull { it.priceProvider?.id } }.flatten().toSet() + + val chainlinkProvider = chains.firstOrNull { it.chainlinkProvider } + val chainlinkProviderId = chainlinkProvider?.id + + val chainlinkTokens = chainlinkIds.map { priceId -> + val assetConfig = chains.flatMap { it.assets }.firstOrNull { it.priceProvider?.id == priceId } + val priceProvider = assetConfig?.priceProvider + + val price = if (chainlinkProviderId != null && priceProvider != null) { + getChainlinkPrices(priceProvider = priceProvider, chainId = chainlinkProviderId) + } else { + null + } + val usdCurrency = availableFiatCurrencies["usd"] + + TokenPriceLocal( + priceId = priceId, + fiatRate = price, + fiatSymbol = usdCurrency?.symbol, + recentRateChange = null + ) + } + + updatesMixin.startUpdateTokens(chainlinkIds) + updateAssetsRates(chainlinkTokens) + updatesMixin.finishUpdateTokens(chainlinkIds) + } + + private suspend fun syncCoingeckoRates(chains: List, currencyId: String) { val priceIds = chains.map { it.assets.mapNotNull { it.priceId } }.flatten().toSet() - val priceStats = getAssetPriceCoingecko(*priceIds.toTypedArray(), currencyId = currencyId) + val coingeckoPriceStats = getAssetPriceCoingecko(*priceIds.toTypedArray(), currencyId = currencyId) updatesMixin.startUpdateTokens(priceIds) priceIds.forEach { priceId -> - val stat = priceStats[priceId] ?: return@forEach + val stat = coingeckoPriceStats[priceId] ?: return@forEach val price = stat[currencyId] val changeKey = "${currencyId}_24h_change" val change = stat[changeKey] val fiatCurrency = availableFiatCurrencies[currencyId] updateAssetRates(priceId, fiatCurrency?.symbol, price, change) + + if (currencyId == "usd") { + val assetsWithChainlinkPrice = chains.map { it.assets.filter { it.priceProvider?.id != null } }.flatten().toSet() + + updateChainlinkPriceWithCoingeckoChange(assetsWithChainlinkPrice, priceId, change) + } } updatesMixin.finishUpdateTokens(priceIds) } + private suspend fun WalletRepositoryImpl.updateChainlinkPriceWithCoingeckoChange(assetsWithChainlinkPrice: Set, priceId: String, change: BigDecimal?) { + val chainlinkId = assetsWithChainlinkPrice.firstOrNull { + it.priceId == priceId + }?.priceProvider?.id + + chainlinkId?.let { + val tokenPrice = tokenPriceDao.getTokenPrice(chainlinkId) + + updateAssetRates( + priceId = chainlinkId, + fiatSymbol = tokenPrice?.fiatSymbol, + price = tokenPrice?.fiatRate, + change = change + ) + } + } + + private suspend fun getChainlinkPrices(priceProvider: PriceProvider, chainId: ChainId): BigDecimal? { + return runCatching { + ethereumSource.fetchPriceFeed( + chainId = chainId, + receiverAddress = priceProvider.id + )?.let { price -> + BigDecimal(price, priceProvider.precision) + } + }.getOrNull() + } + override fun assetFlow( metaId: Long, accountId: AccountId, @@ -235,6 +312,7 @@ class WalletRepositoryImpl( isHidden: Boolean, chainAsset: CoreAsset ) { + val tokenPriceId = chainAsset.priceProvider?.id?.takeIf { selectedFiat.isUsd() } ?: chainAsset.priceId val updateItems = listOf( AssetUpdateItem( metaId = metaId, @@ -243,7 +321,7 @@ class WalletRepositoryImpl( id = chainAsset.id, sortIndex = Int.MAX_VALUE, // Int.MAX_VALUE on sorting because we don't use it anymore - just random value enabled = !isHidden, - tokenPriceId = chainAsset.priceId + tokenPriceId = tokenPriceId ) ) @@ -441,10 +519,6 @@ class WalletRepositoryImpl( override suspend fun getEquilibriumAccountInfo(asset: CoreAsset, accountId: AccountId) = substrateSource.getEquilibriumAccountInfo(asset, accountId) - override suspend fun updateAssets(newItems: List) { - assetCache.updateAsset(newItems) - } - private fun createOperation( hash: String, transfer: Transfer, @@ -479,6 +553,10 @@ class WalletRepositoryImpl( ) } + private suspend fun updateAssetsRates( + items: List + ) = assetCache.updateTokensPrice(items) + override suspend fun getSingleAssetPriceCoingecko( priceId: String, currency: String diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/di/WalletFeatureModule.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/di/WalletFeatureModule.kt index 9ba651da32..76badf314c 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/di/WalletFeatureModule.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/di/WalletFeatureModule.kt @@ -113,9 +113,10 @@ class WalletFeatureModule { tokenPriceDao: TokenPriceDao, assetDao: AssetDao, accountRepository: AccountRepository, - updatesMixin: UpdatesMixin + updatesMixin: UpdatesMixin, + selectedFiat: SelectedFiat ): AssetCache { - return AssetCache(tokenPriceDao, accountRepository, assetDao, updatesMixin) + return AssetCache(tokenPriceDao, accountRepository, assetDao, updatesMixin, selectedFiat) } @Provides @@ -144,9 +145,11 @@ class WalletFeatureModule { @Provides fun provideTokenRepository( - tokenPriceDao: TokenPriceDao + tokenPriceDao: TokenPriceDao, + selectedFiat: SelectedFiat ): TokenRepository = TokenRepositoryImpl( - tokenPriceDao + tokenPriceDao, + selectedFiat ) @Provides @@ -170,7 +173,9 @@ class WalletFeatureModule { remoteConfigFetcher: RemoteConfigFetcher, preferences: Preferences, accountRepository: AccountRepository, - chainsRepository: ChainsRepository + chainsRepository: ChainsRepository, + selectedFiat: SelectedFiat, + tokenPriceDao: TokenPriceDao ): WalletRepository = WalletRepositoryImpl( substrateSource, ethereumRemoteSource, @@ -187,7 +192,9 @@ class WalletFeatureModule { remoteConfigFetcher, preferences, accountRepository, - chainsRepository + chainsRepository, + selectedFiat, + tokenPriceDao ) @Provides diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/domain/ChainInteractor.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/domain/ChainInteractor.kt index 556e309cf3..3f8f3921e5 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/domain/ChainInteractor.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/domain/ChainInteractor.kt @@ -6,6 +6,7 @@ import jp.co.soramitsu.core.models.ChainAssetType import jp.co.soramitsu.core.models.ChainId import jp.co.soramitsu.coredb.dao.ChainDao import jp.co.soramitsu.runtime.multiNetwork.chain.mapChainLocalToChain +import jp.co.soramitsu.runtime.multiNetwork.chain.mapToPriceProvider import jp.co.soramitsu.runtime.multiNetwork.chain.model.Chain import jp.co.soramitsu.runtime.multiNetwork.chain.model.polkadotChainId import jp.co.soramitsu.wallet.api.domain.model.XcmChainType @@ -37,7 +38,7 @@ class ChainInteractor( priceId = it.priceId, precision = it.precision, staking = Asset.StakingType.UNSUPPORTED, - priceProviders = emptyList(), + purchaseProviders = emptyList(), chainName = "", chainIcon = it.icon, isTestNet = false, @@ -47,7 +48,8 @@ class ChainInteractor( currencyId = it.currencyId, existentialDeposit = it.existentialDeposit, color = it.color, - isNative = it.isNative + isNative = it.isNative, + priceProvider = mapToPriceProvider(it.priceProvider) ) } } diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/domain/WalletInteractorImpl.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/domain/WalletInteractorImpl.kt index 9969afd203..a3e37f2840 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/domain/WalletInteractorImpl.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/domain/WalletInteractorImpl.kt @@ -413,10 +413,6 @@ class WalletInteractorImpl( } } - override suspend fun updateAssets(newItems: List) { - walletRepository.updateAssets(newItems) - } - private fun mapAccountToWalletAccount(chain: Chain, account: MetaAccount) = with(account) { WalletAccount(account.address(chain)!!, name) } diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/WalletRouter.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/WalletRouter.kt index 16399320e5..4eedfdf180 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/WalletRouter.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/WalletRouter.kt @@ -4,7 +4,7 @@ import android.graphics.drawable.Drawable import it.airgap.beaconsdk.blockchain.substrate.data.SubstrateSignerPayload import java.math.BigDecimal import jp.co.soramitsu.account.api.domain.model.ImportMode -import jp.co.soramitsu.account.api.presentation.actions.AddAccountBottomSheet +import jp.co.soramitsu.account.api.presentation.actions.AddAccountPayload import jp.co.soramitsu.common.AlertViewState import jp.co.soramitsu.common.navigation.DelayedNavigation import jp.co.soramitsu.common.navigation.PinRequired @@ -144,7 +144,7 @@ interface WalletRouter : SecureRouter, WalletRouterApi { fun openGetSoraCard() - fun openOptionsAddAccount(payload: AddAccountBottomSheet.Payload) + fun openOptionsAddAccount(payload: AddAccountPayload) fun openOptionsSwitchNode( metaId: Long, diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/detail/BalanceDetailViewModel.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/detail/BalanceDetailViewModel.kt index 2cc49758b1..06cea90e58 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/detail/BalanceDetailViewModel.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/detail/BalanceDetailViewModel.kt @@ -65,7 +65,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch @@ -112,20 +112,22 @@ class BalanceDetailViewModel @Inject constructor( private val chainsItemStateFlow = chainsFlow.mapList { it.toChainItemState() } @OptIn(ExperimentalCoroutinesApi::class) - private val assetModelFlow = selectedChainId.map { selectedChainId -> - val chains = chainsFlow.first() - val selectedChain = chains.first { it.id == selectedChainId } - val initialSelectedChain = chains.first { it.id == assetPayloadInitial.chainId } + private val assetModelFlow = selectedChainId.mapNotNull { selectedChainId -> + val chains = chainsFlow.firstOrNull() + val selectedChain = chains?.firstOrNull { it.id == selectedChainId } + val initialSelectedChain = chains?.firstOrNull { it.id == assetPayloadInitial.chainId } val initialSelectedAssetSymbol = - initialSelectedChain.assets.first { it.id == assetPayloadInitial.chainAssetId }.symbol + initialSelectedChain?.assets?.firstOrNull { it.id == assetPayloadInitial.chainAssetId }?.symbol val newSelectedAsset = - selectedChain.assets.first { it.symbol == initialSelectedAssetSymbol } + selectedChain?.assets?.firstOrNull { it.symbol == initialSelectedAssetSymbol } - AssetPayload( - chainId = selectedChainId, - chainAssetId = newSelectedAsset.id - ) + newSelectedAsset?.id?.let { + AssetPayload( + chainId = selectedChainId, + chainAssetId = it + ) + } } .flatMapLatest { interactor.assetFlow(it.chainId, it.chainAssetId) @@ -170,14 +172,14 @@ class BalanceDetailViewModel @Inject constructor( selectedChainId, chainsItemStateFlow ) { chainId, chainItems -> - val selectedChain = chainItems.first { + val selectedChain = chainItems.firstOrNull { it.id == chainId } LoadingState.Loaded( MainToolbarViewState( title = interactor.getSelectedMetaAccount().name, homeIconState = ToolbarHomeIconState(navigationIcon = R.drawable.ic_arrow_back_24dp), - selectorViewState = ChainSelectorViewState(selectedChain.title, selectedChain.id) + selectorViewState = ChainSelectorViewState(selectedChain?.title, selectedChain?.id) ) ) }.stateIn(scope = this, started = SharingStarted.Eagerly, initialValue = LoadingState.Loading()) @@ -492,15 +494,16 @@ class BalanceDetailViewModel @Inject constructor( private fun openBalanceDetails() { launch { - val assetModel = assetModelFlow.first() - router.openFrozenTokens( - FrozenAssetPayload( - assetSymbol = assetModel.token.configuration.symbol, - locked = assetModel.locked, - reserved = assetModel.reserved, - redeemable = assetModel.redeemable + assetModelFlow.firstOrNull()?.let { assetModel -> + router.openFrozenTokens( + FrozenAssetPayload( + assetSymbol = assetModel.token.configuration.symbol, + locked = assetModel.locked, + reserved = assetModel.reserved, + redeemable = assetModel.redeemable + ) ) - ) + } } } } diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/BalanceListViewModel.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/BalanceListViewModel.kt index a2940ff0e2..b45185c9fd 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/BalanceListViewModel.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/BalanceListViewModel.kt @@ -273,7 +273,6 @@ class BalanceListViewModel @Inject constructor( chainAssetId = chainAsset.id, isSupported = true, isHidden = false, - priceId = chainAsset.priceId, isTestnet = chainAsset.isTestNet ?: false ) }.filter { selectedChainId.value == null || selectedChainId.value == it.chainId } diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/WalletScreen.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/WalletScreen.kt index 8c12e23fc1..7752e0459b 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/WalletScreen.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/WalletScreen.kt @@ -243,7 +243,6 @@ private fun PreviewWalletScreen() { chainAssetId = "", isSupported = true, isHidden = false, - priceId = null, isTestnet = false ) ) diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/model/BalanceListItemModel.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/model/BalanceListItemModel.kt index 156c598727..f57fdedc7f 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/model/BalanceListItemModel.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/list/model/BalanceListItemModel.kt @@ -36,6 +36,5 @@ fun BalanceListItemModel.toAssetState(index: Int? = null) = AssetListItemViewSta chainAssetId = asset.id, isSupported = chain?.isSupported != false, isHidden = isHidden, - priceId = asset.priceId, isTestnet = chain?.isTestNet == true ) diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/networkissues/NetworkIssuesViewModel.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/networkissues/NetworkIssuesViewModel.kt index 22b0be2281..2716e848b8 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/networkissues/NetworkIssuesViewModel.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/balance/networkissues/NetworkIssuesViewModel.kt @@ -5,7 +5,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject import jp.co.soramitsu.account.api.domain.interfaces.AccountInteractor import jp.co.soramitsu.account.api.domain.interfaces.AssetNotNeedAccountUseCase -import jp.co.soramitsu.account.api.presentation.actions.AddAccountBottomSheet +import jp.co.soramitsu.account.api.presentation.actions.AddAccountPayload import jp.co.soramitsu.common.AlertViewState import jp.co.soramitsu.common.base.BaseViewModel import jp.co.soramitsu.common.compose.component.NetworkIssueItemState @@ -20,7 +20,6 @@ import jp.co.soramitsu.wallet.impl.domain.interfaces.WalletInteractor import jp.co.soramitsu.wallet.impl.presentation.WalletRouter import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map @@ -104,12 +103,11 @@ class NetworkIssuesViewModel @Inject constructor( NetworkIssueType.Account -> launch { val meta = accountInteractor.selectedMetaAccountFlow().first() - val payload = AddAccountBottomSheet.Payload( + val payload = AddAccountPayload( metaId = meta.id, chainId = issue.chainId, chainName = issue.chainName, assetId = issue.assetId, - priceId = issue.priceId, markedAsNotNeed = false ) walletRouter.openOptionsAddAccount(payload) diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/transaction/detail/swap/SwapDetailContent.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/transaction/detail/swap/SwapDetailContent.kt index a229f3dd16..2dc36db556 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/transaction/detail/swap/SwapDetailContent.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/transaction/detail/swap/SwapDetailContent.kt @@ -39,6 +39,7 @@ data class SwapDetailState( val toTokenName: String, val statusAppearance: SwapStatusAppearance, val address: String, + val addressName: String?, val hash: String, val fromTokenOnToToken: String, val liquidityProviderFee: String, @@ -110,7 +111,8 @@ fun SwapPreviewContent( ), TitleValueViewState( title = stringResource(R.string.polkaswap_from), - value = state.address + value = state.addressName ?: state.address, + additionalValue = state.address.takeIf { state.addressName != null } ), TitleValueViewState( title = stringResource(R.string.common_date), @@ -175,6 +177,7 @@ fun SwapDetailContentPreview() { fromTokenOnToToken = "0", networkFee = "0.0007", address = "asdfqwaefgqwef2fr", + addressName = "Contact addressbook name", hash = "asdfqwaefgqwef2fr", statusAppearance = SwapStatusAppearance.COMPLETED, time = 1675834923575L, diff --git a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/transaction/detail/swap/SwapDetailViewModel.kt b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/transaction/detail/swap/SwapDetailViewModel.kt index e9f368e640..bb2b5bf7b7 100644 --- a/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/transaction/detail/swap/SwapDetailViewModel.kt +++ b/feature-wallet-impl/src/main/java/jp/co/soramitsu/wallet/impl/presentation/transaction/detail/swap/SwapDetailViewModel.kt @@ -7,6 +7,8 @@ import androidx.lifecycle.SavedStateHandle import dagger.hilt.android.lifecycle.HiltViewModel import java.math.BigDecimal import javax.inject.Inject +import jp.co.soramitsu.account.api.domain.interfaces.AccountRepository +import jp.co.soramitsu.account.api.domain.model.address import jp.co.soramitsu.common.base.BaseViewModel import jp.co.soramitsu.common.compose.component.GradientIconState import jp.co.soramitsu.common.compose.theme.greenText @@ -16,6 +18,7 @@ import jp.co.soramitsu.common.mixin.api.Browserable import jp.co.soramitsu.common.resources.ClipboardManager import jp.co.soramitsu.common.resources.ResourceManager import jp.co.soramitsu.common.utils.Event +import jp.co.soramitsu.common.utils.flowOf import jp.co.soramitsu.common.utils.formatCryptoDetail import jp.co.soramitsu.common.utils.orZero import jp.co.soramitsu.feature_wallet_impl.R @@ -30,6 +33,7 @@ import jp.co.soramitsu.wallet.impl.domain.model.amountFromPlanks import jp.co.soramitsu.wallet.impl.presentation.WalletRouter import jp.co.soramitsu.wallet.impl.presentation.model.OperationParcelizeModel import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map @@ -42,6 +46,7 @@ class SwapDetailViewModel @Inject constructor( private val chainRegistry: ChainRegistry, private val resourceManager: ResourceManager, private val clipboardManager: ClipboardManager, + private val accountRepository: AccountRepository, savedStateHandle: SavedStateHandle ) : BaseViewModel(), SwapDetailCallbacks, Browserable { @@ -78,6 +83,7 @@ class SwapDetailViewModel @Inject constructor( toTokenName = swap.targetAsset?.symbol?.uppercase() ?: "???", statusAppearance = swap.status.mapToStatusAppearance(), address = swap.address, + addressName = null, hash = swap.hash, fromTokenOnToToken = swapRate.formatCryptoDetail(), liquidityProviderFee = swap.liquidityProviderFee.formatCryptoDetailFromPlanks(swap.chainAsset), @@ -87,8 +93,22 @@ class SwapDetailViewModel @Inject constructor( isShowSubscanButtons = false ) - val state = subscanUrlFlow.map { url -> - initialState.copy(isShowSubscanButtons = url.isNullOrEmpty().not()) + // swaps are only for selected account + private val addressNameFlow = flowOf { swap.address }.map { + val selectedLightMetaAccount = accountRepository.getSelectedLightMetaAccount() + val chain = chainRegistry.getChain(swap.chainAsset.chainId) + if (selectedLightMetaAccount.address(chain) == swap.address) { + selectedLightMetaAccount.name + } else { + null + } + } + + val state = combine( + subscanUrlFlow, + addressNameFlow + ) { url, name -> + initialState.copy(isShowSubscanButtons = url.isNullOrEmpty().not(), addressName = name) }.stateIn(this, SharingStarted.Eagerly, initialState) override fun onBackClick() { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e186b8eb5b..936fa4203c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] compose = "1.5.1" composeFoundation = "1.5.1" -fragmentKtx = "1.5.6" +fragmentKtx = "1.6.2" composeThemeAdapter = "1.2.1" googleServices = "4.3.15" junit = "4.13.2" @@ -16,7 +16,7 @@ detekt = "1.23.0" kotlin = "1.8.10" dagger = "2.47" android_plugin = "7.4.2" -sharedFeaturesVersion = "1.1.1.20-FLW" +sharedFeaturesVersion = "1.1.1.23-FLW" web3j = "4.8.8-android" walletconnectBom = "1.18.0" coilVersion = "2.2.2" diff --git a/runtime/build.gradle b/runtime/build.gradle index c10fa10dd9..90fba27a5f 100644 --- a/runtime/build.gradle +++ b/runtime/build.gradle @@ -14,6 +14,7 @@ android { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" buildConfigField "String", "TYPES_URL", "\"https://raw.githubusercontent.com/soramitsu/shared-features-utils/master/chains/all_chains_types_android.json\"" buildConfigField "String", "APP_VERSION_NAME", "\"${rootProject.versionName}\"" + buildConfigField("String", "DEFAULT_V13_TYPES_URL", "\"https://raw.githubusercontent.com/soramitsu/shared-features-utils/master/chains/default_v13_types.json\"") } buildTypes { @@ -22,7 +23,7 @@ android { } release { - buildConfigField "String", "CHAINS_URL", "\"https://raw.githubusercontent.com/soramitsu/shared-features-utils/master/chains/v7/chains.json\"" + buildConfigField "String", "CHAINS_URL", "\"https://raw.githubusercontent.com/soramitsu/shared-features-utils/master/chains/v8/chains.json\"" } } diff --git a/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/ChainRegistry.kt b/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/ChainRegistry.kt index 7c1efda9c9..aa1facfca4 100644 --- a/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/ChainRegistry.kt +++ b/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/ChainRegistry.kt @@ -266,7 +266,6 @@ fun Chain.toSyncIssue(): NetworkIssueItemState { }, chainId = this.id, chainName = this.name, - assetId = this.utilityAsset?.id.orEmpty(), - priceId = this.utilityAsset?.priceId + assetId = this.utilityAsset?.id.orEmpty() ) } diff --git a/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/chain/ChainSyncService.kt b/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/chain/ChainSyncService.kt index c9f207ef4d..80f3f21d99 100644 --- a/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/chain/ChainSyncService.kt +++ b/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/chain/ChainSyncService.kt @@ -19,7 +19,7 @@ class ChainSyncService( val remoteChains = chainFetcher.getChains() .filter { - !it.disabled && (it.assets?.isNotEmpty() == true) && it.chainId != reefChainId // todo reef + !it.disabled && (it.assets?.isNotEmpty() == true) } .map { it.toChain() diff --git a/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/chain/Mappers.kt b/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/chain/Mappers.kt index 9bc190bc94..7c11dcae54 100644 --- a/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/chain/Mappers.kt +++ b/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/chain/Mappers.kt @@ -5,6 +5,7 @@ import com.google.gson.reflect.TypeToken import jp.co.soramitsu.core.models.Asset import jp.co.soramitsu.core.models.ChainAssetType import jp.co.soramitsu.core.models.ChainNode +import jp.co.soramitsu.core.runtime.mappers.mapRemoteToModel import jp.co.soramitsu.coredb.model.chain.ChainAssetLocal import jp.co.soramitsu.coredb.model.chain.ChainExplorerLocal import jp.co.soramitsu.coredb.model.chain.ChainLocal @@ -16,6 +17,7 @@ import jp.co.soramitsu.runtime.multiNetwork.chain.remote.model.ChainRemote private const val ETHEREUM_BASED_OPTION = "ethereumBased" private const val ETHEREUM_OPTION = "ethereum" +private const val CHAINLINK_PROVIDER_OPTION = "chainlinkProvider" private const val CROWDLOAN_OPTION = "crowdloans" private const val TESTNET_OPTION = "testnet" private const val NOMINATION_POOL_OPTION = "poolStaking" @@ -29,6 +31,7 @@ private fun mapSectionTypeRemoteToSectionType(section: String) = when (section) "etherscan" -> Chain.ExternalApi.Section.Type.ETHERSCAN "oklink" -> Chain.ExternalApi.Section.Type.OKLINK "zeta" -> Chain.ExternalApi.Section.Type.ZETA + "reef" -> Chain.ExternalApi.Section.Type.REEF else -> Chain.ExternalApi.Section.Type.UNKNOWN } @@ -38,6 +41,7 @@ private fun mapExplorerTypeRemoteToExplorerType(explorer: String) = when (explor "etherscan" -> Chain.Explorer.Type.ETHERSCAN "oklink" -> Chain.Explorer.Type.OKLINK "zeta" -> Chain.Explorer.Type.ZETA + "reef" -> Chain.Explorer.Type.REEF else -> Chain.Explorer.Type.UNKNOWN } @@ -146,6 +150,7 @@ fun ChainRemote.toChain(): Chain { hasCrowdloans = CROWDLOAN_OPTION in optionsOrEmpty, supportStakingPool = NOMINATION_POOL_OPTION in optionsOrEmpty, isEthereumChain = ETHEREUM_OPTION in optionsOrEmpty, + chainlinkProvider = CHAINLINK_PROVIDER_OPTION in optionsOrEmpty, paraId = this.paraId ) } @@ -166,7 +171,7 @@ private fun ChainRemote.assetConfigs(): List? { priceId = chainAsset.priceId, precision = chainAsset.precision ?: DEFAULT_PRECISION, staking = mapStakingStringToStakingType(chainAsset.staking), - priceProviders = chainAsset.purchaseProviders, + purchaseProviders = chainAsset.purchaseProviders, supportStakingPool = NOMINATION_POOL_OPTION in this.options.orEmpty(), isUtility = chainAsset.isUtility ?: false, type = ChainAssetType.from(chainAsset.type), @@ -174,7 +179,8 @@ private fun ChainRemote.assetConfigs(): List? { existentialDeposit = chainAsset.existentialDeposit, color = chainAsset.color, isNative = chainAsset.isNative, - ethereumType = mapEthereumTypeStringToEthereumType(chainAsset.ethereumType) + ethereumType = mapEthereumTypeStringToEthereumType(chainAsset.ethereumType), + priceProvider = chainAsset.priceProvider?.mapRemoteToModel() ) } }.getOrNull() @@ -201,7 +207,7 @@ fun mapChainLocalToChain(chainLocal: JoinedChainInfo): Chain { priceId = it.priceId, precision = it.precision, staking = mapStakingTypeFromLocal(it.staking), - priceProviders = mapToList(it.priceProviders), + purchaseProviders = mapToList(it.purchaseProviders), chainName = chainLocal.chain.name, chainIcon = chainLocal.chain.icon, isTestNet = chainLocal.chain.isTestNet, @@ -212,7 +218,8 @@ fun mapChainLocalToChain(chainLocal: JoinedChainInfo): Chain { existentialDeposit = it.existentialDeposit, color = it.color, isNative = it.isNative, - ethereumType = mapEthereumTypeStringToEthereumType(it.ethereumType) + ethereumType = mapEthereumTypeStringToEthereumType(it.ethereumType), + priceProvider = mapToPriceProvider(it.priceProvider) ) } @@ -250,7 +257,8 @@ fun mapChainLocalToChain(chainLocal: JoinedChainInfo): Chain { hasCrowdloans = hasCrowdloans, supportStakingPool = supportStakingPool, isEthereumChain = isEthereumChain, - paraId = paraId + paraId = paraId, + chainlinkProvider = isChainlinkProvider ) } } @@ -276,14 +284,15 @@ fun mapChainToChainLocal(chain: Chain): JoinedChainInfo { chainId = chain.id, priceId = it.priceId, staking = mapStakingTypeToLocal(it.staking), - priceProviders = it.priceProviders?.let { Gson().toJson(it) }, + purchaseProviders = it.purchaseProviders?.let { Gson().toJson(it) }, isUtility = it.isUtility, type = it.type?.name, currencyId = it.currencyId, existentialDeposit = it.existentialDeposit, color = it.color, isNative = it.isNative, - ethereumType = mapEthereumTypeToLocal(it.ethereumType) + ethereumType = mapEthereumTypeToLocal(it.ethereumType), + priceProvider = it.priceProvider?.let { Gson().toJson(it) } ) } @@ -319,7 +328,8 @@ fun mapChainToChainLocal(chain: Chain): JoinedChainInfo { hasCrowdloans = hasCrowdloans, supportStakingPool = supportStakingPool, isEthereumChain = isEthereumChain, - paraId = paraId + paraId = paraId, + isChainlinkProvider = chainlinkProvider ) } @@ -333,3 +343,6 @@ fun mapChainToChainLocal(chain: Chain): JoinedChainInfo { private fun mapToList(json: String?) = json?.let { Gson().fromJson>(it, object : TypeToken>() {}.type) } + +fun mapToPriceProvider(json: String?) = + json?.let { Gson().fromJson(it, object : TypeToken() {}.type) } \ No newline at end of file diff --git a/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/chain/model/Chain.kt b/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/chain/model/Chain.kt index 6f97706acb..f1a16f545a 100644 --- a/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/chain/model/Chain.kt +++ b/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/chain/model/Chain.kt @@ -49,7 +49,8 @@ data class Chain( val hasCrowdloans: Boolean, override val parentId: String?, val supportStakingPool: Boolean, - val isEthereumChain: Boolean + val isEthereumChain: Boolean, + val chainlinkProvider: Boolean, ) : IChain { val assetsById = assets.associateBy(CoreAsset::id) @@ -63,16 +64,16 @@ data class Chain( ) { data class Section(val type: Type, val url: String) { enum class Type { - SUBQUERY, SORA, SUBSQUID, GIANTSQUID, ETHERSCAN, OKLINK, ZETA, UNKNOWN, GITHUB; + SUBQUERY, SORA, SUBSQUID, GIANTSQUID, ETHERSCAN, OKLINK, ZETA, REEF, UNKNOWN, GITHUB; - fun isHistory() = this in listOf(SUBQUERY, SORA, SUBSQUID, GIANTSQUID, ETHERSCAN, OKLINK, ZETA) + fun isHistory() = this in listOf(SUBQUERY, SORA, SUBSQUID, GIANTSQUID, ETHERSCAN, OKLINK, ZETA, REEF) } } } data class Explorer(val type: Type, val types: List, val url: String) { enum class Type { - POLKASCAN, SUBSCAN, ETHERSCAN, OKLINK, ZETA, UNKNOWN; + POLKASCAN, SUBSCAN, ETHERSCAN, OKLINK, ZETA, REEF, UNKNOWN; val capitalizedName: String = name.lowercase().replaceFirstChar { it.titlecase() } } @@ -85,6 +86,7 @@ data class Chain( other as Chain if (id != other.id) return false + if (paraId != other.paraId) return false if (rank != other.rank) return false if (name != other.name) return false if (minSupportedVersion != other.minSupportedVersion) return false @@ -97,6 +99,9 @@ data class Chain( if (isTestNet != other.isTestNet) return false if (hasCrowdloans != other.hasCrowdloans) return false if (parentId != other.parentId) return false + if (supportStakingPool != other.supportStakingPool) return false + if (isEthereumChain != other.isEthereumChain) return false + if (chainlinkProvider != other.chainlinkProvider) return false // custom comparison logic val defaultNodes = nodes.filter { it.isDefault } @@ -111,6 +116,7 @@ data class Chain( override fun hashCode(): Int { var result = id.hashCode() + result = 31 * result + (paraId?.hashCode() ?: 0) result = 31 * result + (rank?.hashCode() ?: 0) result = 31 * result + name.hashCode() result = 31 * result + (minSupportedVersion?.hashCode() ?: 0) @@ -124,6 +130,9 @@ data class Chain( result = 31 * result + isTestNet.hashCode() result = 31 * result + hasCrowdloans.hashCode() result = 31 * result + (parentId?.hashCode() ?: 0) + result = 31 * result + supportStakingPool.hashCode() + result = 31 * result + isEthereumChain.hashCode() + result = 31 * result + chainlinkProvider.hashCode() return result } } diff --git a/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/chain/remote/model/ChainAssetRemote.kt b/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/chain/remote/model/ChainAssetRemote.kt index 8b8bef0b83..04942ed29e 100644 --- a/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/chain/remote/model/ChainAssetRemote.kt +++ b/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/chain/remote/model/ChainAssetRemote.kt @@ -1,5 +1,7 @@ package jp.co.soramitsu.runtime.multiNetwork.chain.remote.model +import jp.co.soramitsu.core.models.remote.PriceProvider + class ChainAssetRemote( val id: String?, val name: String?, @@ -15,5 +17,6 @@ class ChainAssetRemote( val staking: String?, val purchaseProviders: List?, val type: String?, - val ethereumType: String? + val ethereumType: String?, + val priceProvider: PriceProvider? ) diff --git a/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/connection/ConnectionPool.kt b/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/connection/ConnectionPool.kt index f072056b39..09bf2bd629 100644 --- a/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/connection/ConnectionPool.kt +++ b/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/connection/ConnectionPool.kt @@ -70,8 +70,7 @@ class ConnectionPool @Inject constructor( }, chainId = chain.id, chainName = chain.name, - assetId = chain.utilityAsset?.id.orEmpty(), - priceId = chain.utilityAsset?.priceId + assetId = chain.utilityAsset?.id.orEmpty() ) } issues diff --git a/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/runtime/RuntimeProvider.kt b/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/runtime/RuntimeProvider.kt index c10acd556e..799235418c 100644 --- a/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/runtime/RuntimeProvider.kt +++ b/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/runtime/RuntimeProvider.kt @@ -5,6 +5,7 @@ import jp.co.soramitsu.core.runtime.ConstructedRuntime import jp.co.soramitsu.core.runtime.RuntimeFactory import jp.co.soramitsu.coredb.dao.ChainDao import jp.co.soramitsu.runtime.multiNetwork.chain.model.Chain +import jp.co.soramitsu.runtime.multiNetwork.chain.model.reefChainId import jp.co.soramitsu.runtime.multiNetwork.toSyncIssue import jp.co.soramitsu.shared_utils.runtime.RuntimeSnapshot import kotlinx.coroutines.CoroutineScope @@ -126,8 +127,16 @@ class RuntimeProvider( runCatching { chainDao.getTypes(chainId) ?: throw ChainInfoNotInCacheException } .getOrElse { throw ChainInfoNotInCacheException } - val runtime = + val runtime = if (chainId == reefChainId) { + val defaultTypes = + runCatching { + chainDao.getTypes("default") ?: throw ChainInfoNotInCacheException + } + .getOrElse { throw ChainInfoNotInCacheException } + runtimeFactory.constructRuntimeV13(metadataRaw, ownTypesRaw, defaultTypes, runtimeVersion) + } else { runtimeFactory.constructRuntime(metadataRaw, ownTypesRaw, runtimeVersion) + } runtimeFlow.emit(runtime) networkStateMixin.notifyChainSyncSuccess(chainId) }.onFailure { diff --git a/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/runtime/RuntimeSyncService.kt b/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/runtime/RuntimeSyncService.kt index abcea704dc..bed5f4e901 100644 --- a/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/runtime/RuntimeSyncService.kt +++ b/runtime/src/main/java/jp/co/soramitsu/runtime/multiNetwork/runtime/RuntimeSyncService.kt @@ -109,7 +109,6 @@ class RuntimeSyncService( GetMetadataRequest, mapper = pojo().nonNull() ).getOrNull() - runtimeMetadata?.let { runtimeFilesCache.saveChainMetadata(chainId, runtimeMetadata) @@ -138,13 +137,14 @@ class RuntimeSyncService( suspend fun syncTypes() { val types = typesFetcher.getTypes(BuildConfig.TYPES_URL) + val defaultTypes = typesFetcher.getTypes(BuildConfig.DEFAULT_V13_TYPES_URL) val array = Json.decodeFromString(types) val chainIdToTypes = array.mapNotNull { element -> val chainId = element.jsonObject["chainId"]?.jsonPrimitive?.content ?: return@mapNotNull null ChainTypesLocal(chainId, element.toString()) - } + }.toMutableList().apply { add(ChainTypesLocal("default", defaultTypes)) } chainDao.insertTypes(chainIdToTypes) } diff --git a/runtime/src/test/java/jp/co/soramitsu/runtime/multiNetwork/chain/ChainSyncServiceTest.kt b/runtime/src/test/java/jp/co/soramitsu/runtime/multiNetwork/chain/ChainSyncServiceTest.kt index e22b8558a5..0fdbd7427e 100644 --- a/runtime/src/test/java/jp/co/soramitsu/runtime/multiNetwork/chain/ChainSyncServiceTest.kt +++ b/runtime/src/test/java/jp/co/soramitsu/runtime/multiNetwork/chain/ChainSyncServiceTest.kt @@ -41,7 +41,8 @@ class ChainSyncServiceTest { existentialDeposit = null, color = null, isNative = null, - ethereumType = null + ethereumType = null, + priceProvider = null ) ), nodes = listOf( diff --git a/test-shared/src/main/java/jp/co/soramitsu/testshared/CreateTestSocket.kt b/test-shared/src/main/java/jp/co/soramitsu/testshared/CreateTestSocket.kt index 2f1441bc93..66a920f71b 100644 --- a/test-shared/src/main/java/jp/co/soramitsu/testshared/CreateTestSocket.kt +++ b/test-shared/src/main/java/jp/co/soramitsu/testshared/CreateTestSocket.kt @@ -4,6 +4,6 @@ import com.google.gson.Gson import com.neovisionaries.ws.client.WebSocketFactory import jp.co.soramitsu.shared_utils.wsrpc.SocketService import jp.co.soramitsu.shared_utils.wsrpc.recovery.Reconnector -import jp.co.soramitsu.shared_utils.wsrpc.request.RequestExecutor +import jp.co.soramitsu.shared_utils.wsrpc.request.CoroutinesRequestExecutor -fun createTestSocket() = SocketService(Gson(), NoOpLogger, WebSocketFactory(), Reconnector(), RequestExecutor()) +fun createTestSocket() = SocketService(Gson(), NoOpLogger, WebSocketFactory(), Reconnector(), CoroutinesRequestExecutor())