mirror of
https://github.com/pezkuwichain/pezkuwi-wallet-android.git
synced 2026-04-22 02:07:58 +00:00
Initial commit: Pezkuwi Wallet Android
Security hardened release: - Code obfuscation enabled (minifyEnabled=true, shrinkResources=true) - Sensitive files excluded (google-services.json, keystores) - Branch.io key moved to BuildConfig placeholder - Updated dependencies: OkHttp 4.12.0, Gson 2.10.1, BouncyCastle 1.77 - Comprehensive ProGuard rules for crypto wallet - Navigation 2.7.7, Lifecycle 2.7.0, ConstraintLayout 2.1.4
This commit is contained in:
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -0,0 +1,86 @@
|
||||
apply plugin: 'kotlin-parcelize'
|
||||
apply from: '../tests.gradle'
|
||||
apply from: '../scripts/secrets.gradle'
|
||||
|
||||
android {
|
||||
|
||||
defaultConfig {
|
||||
|
||||
|
||||
|
||||
buildConfigField "String", "KARURA_NOVA_REFERRAL", "\"0x9642d0db9f3b301b44df74b63b0b930011e3f52154c5ca24b4dc67b3c7322f15\""
|
||||
buildConfigField "String", "ACALA_NOVA_REFERRAL", "\"0x08eb319467ea54784cd9edfbd03bbcc53f7a021ed8d9ed2ca97b6ae46b3f6014\""
|
||||
buildConfigField "String", "BIFROST_NOVA_REFERRAL", "\"FRLS69\""
|
||||
buildConfigField "String", "BIFROST_TERMS_LINKS", "\"https://docs.google.com/document/d/1PDpgHnIcAmaa7dEFusmLYgjlvAbk2VKtMd755bdEsf4\""
|
||||
|
||||
buildConfigField "String", "ACALA_TERMS_LINK", "\"https://acala.network/acala/terms\""
|
||||
|
||||
buildConfigField "String", "ACALA_TEST_AUTH_TOKEN", readStringSecret("ACALA_TEST_AUTH_TOKEN")
|
||||
buildConfigField "String", "ACALA_PROD_AUTH_TOKEN", readStringSecret("ACALA_PROD_AUTH_TOKEN")
|
||||
|
||||
buildConfigField "String", "MOONBEAM_TEST_AUTH_TOKEN", readStringSecret("MOONBEAM_TEST_AUTH_TOKEN")
|
||||
buildConfigField "String", "MOONBEAM_PROD_AUTH_TOKEN", readStringSecret("MOONBEAM_PROD_AUTH_TOKEN")
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
namespace 'io.novafoundation.nova.feature_crowdloan_impl'
|
||||
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':core-db')
|
||||
implementation project(':common')
|
||||
implementation project(':feature-crowdloan-api')
|
||||
implementation project(':feature-account-api')
|
||||
implementation project(':feature-wallet-api')
|
||||
implementation project(':feature-currency-api')
|
||||
implementation project(':runtime')
|
||||
|
||||
implementation kotlinDep
|
||||
|
||||
implementation androidDep
|
||||
implementation materialDep
|
||||
implementation cardViewDep
|
||||
implementation constraintDep
|
||||
|
||||
implementation permissionsDep
|
||||
|
||||
implementation coroutinesDep
|
||||
implementation coroutinesAndroidDep
|
||||
implementation viewModelKtxDep
|
||||
implementation liveDataKtxDep
|
||||
implementation lifeCycleKtxDep
|
||||
|
||||
implementation insetterDep
|
||||
|
||||
implementation daggerDep
|
||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
ksp daggerCompiler
|
||||
|
||||
implementation roomDep
|
||||
ksp roomCompiler
|
||||
|
||||
implementation lifecycleDep
|
||||
ksp lifecycleCompiler
|
||||
|
||||
testImplementation jUnitDep
|
||||
testImplementation mockitoDep
|
||||
|
||||
implementation substrateSdkDep
|
||||
compileOnly wsDep
|
||||
|
||||
implementation gsonDep
|
||||
implementation retrofitDep
|
||||
|
||||
implementation shimmerDep
|
||||
|
||||
implementation coilDep
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest>
|
||||
|
||||
</manifest>
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.data
|
||||
|
||||
import io.novafoundation.nova.common.data.storage.Preferences
|
||||
import io.novafoundation.nova.runtime.ext.isUtilityAsset
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
import io.novafoundation.nova.runtime.state.SelectableSingleAssetSharedState
|
||||
import io.novafoundation.nova.runtime.state.NothingAdditional
|
||||
import io.novafoundation.nova.runtime.state.uniqueOption
|
||||
|
||||
private const val CROWDLOAN_SHARED_STATE = "CROWDLOAN_SHARED_STATE"
|
||||
|
||||
class CrowdloanSharedState(
|
||||
chainRegistry: ChainRegistry,
|
||||
preferences: Preferences,
|
||||
) : SelectableSingleAssetSharedState<NothingAdditional>(
|
||||
preferences = preferences,
|
||||
chainRegistry = chainRegistry,
|
||||
supportedOptions = uniqueOption { chain, chainAsset -> chain.hasCrowdloans and chainAsset.isUtilityAsset },
|
||||
preferencesKey = CROWDLOAN_SHARED_STATE
|
||||
)
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.data.network.api.acala
|
||||
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.BuildConfig
|
||||
import io.novafoundation.nova.runtime.ext.Geneses
|
||||
import io.novafoundation.nova.runtime.ext.addressOf
|
||||
import io.novafoundation.nova.runtime.ext.requireGenesisHash
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novasama.substrate_sdk_android.runtime.AccountId
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Header
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Path
|
||||
|
||||
private fun authHeader(token: String) = "Bearer $token"
|
||||
|
||||
interface AcalaApi {
|
||||
|
||||
companion object {
|
||||
private val URL_BY_GENESIS = mapOf(
|
||||
Chain.Geneses.ROCOCO_ACALA to "crowdloan.aca-dev.network",
|
||||
Chain.Geneses.POLKADOT to "crowdloan.aca-api.network",
|
||||
Chain.Geneses.KUSAMA to "api.aca-staging.network"
|
||||
)
|
||||
|
||||
private val AUTH_BY_GENESIS = mapOf(
|
||||
Chain.Geneses.POLKADOT to authHeader(BuildConfig.ACALA_PROD_AUTH_TOKEN),
|
||||
Chain.Geneses.ROCOCO_ACALA to authHeader(BuildConfig.ACALA_TEST_AUTH_TOKEN)
|
||||
)
|
||||
|
||||
fun getAuthHeader(chain: Chain) = AUTH_BY_GENESIS[chain.requireGenesisHash()]
|
||||
?: notSupportedChain(chain)
|
||||
|
||||
fun getBaseUrl(chain: Chain) = URL_BY_GENESIS[chain.requireGenesisHash()]
|
||||
?: notSupportedChain(chain)
|
||||
|
||||
private fun notSupportedChain(chain: Chain): Nothing {
|
||||
throw UnsupportedOperationException("Chain ${chain.name} is not supported for Acala/Karura crowdloans")
|
||||
}
|
||||
}
|
||||
|
||||
@GET("//{baseUrl}/referral/{referral}")
|
||||
suspend fun isReferralValid(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Path("baseUrl") baseUrl: String,
|
||||
@Path("referral") referral: String,
|
||||
): ReferralCheck
|
||||
|
||||
@GET("//{baseUrl}/statement")
|
||||
suspend fun getStatement(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Path("baseUrl") baseUrl: String,
|
||||
): AcalaStatement
|
||||
|
||||
@POST("//{baseUrl}/contribute")
|
||||
suspend fun directContribute(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Path("baseUrl") baseUrl: String,
|
||||
@Body body: AcalaDirectContributeRequest,
|
||||
): Any?
|
||||
|
||||
@POST("//{baseUrl}/transfer")
|
||||
suspend fun liquidContribute(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Path("baseUrl") baseUrl: String,
|
||||
@Body body: AcalaLiquidContributeRequest,
|
||||
): Any?
|
||||
|
||||
@GET("//{baseUrl}/contribution/{address}")
|
||||
suspend fun getContributions(
|
||||
@Path("baseUrl") baseUrl: String,
|
||||
@Path("address") address: String,
|
||||
): AcalaContribution
|
||||
}
|
||||
|
||||
suspend fun AcalaApi.getContributions(chain: Chain, accountId: AccountId) = getContributions(AcalaApi.getBaseUrl(chain), chain.addressOf(accountId))
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.data.network.api.acala
|
||||
|
||||
import java.math.BigInteger
|
||||
|
||||
class AcalaContribution(
|
||||
val proxyAmount: BigInteger?,
|
||||
)
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.data.network.api.acala
|
||||
|
||||
import java.math.BigInteger
|
||||
|
||||
class AcalaDirectContributeRequest(
|
||||
val address: String,
|
||||
val amount: BigInteger,
|
||||
val referral: String?,
|
||||
val signature: String,
|
||||
)
|
||||
|
||||
class AcalaLiquidContributeRequest(
|
||||
val address: String,
|
||||
val amount: BigInteger,
|
||||
val referral: String?,
|
||||
)
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.data.network.api.acala
|
||||
|
||||
class AcalaStatement(
|
||||
val statement: String,
|
||||
val proxyAddress: String,
|
||||
)
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.data.network.api.acala
|
||||
|
||||
class ReferralCheck(
|
||||
val result: Boolean
|
||||
)
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.data.network.api.bifrost
|
||||
|
||||
import io.novafoundation.nova.common.data.network.subquery.SubQueryResponse
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.POST
|
||||
|
||||
interface BifrostApi {
|
||||
|
||||
companion object {
|
||||
const val BASE_URL = "https://salp-api.bifrost.finance"
|
||||
}
|
||||
|
||||
@POST("/")
|
||||
suspend fun getAccountByReferralCode(@Body body: BifrostReferralCheckRequest): SubQueryResponse<GetAccountByReferralCodeResponse>
|
||||
}
|
||||
|
||||
suspend fun BifrostApi.getAccountByReferralCode(code: String) = getAccountByReferralCode(BifrostReferralCheckRequest(code))
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.data.network.api.bifrost
|
||||
|
||||
class BifrostReferralCheckRequest(code: String) {
|
||||
val query = """
|
||||
{
|
||||
getAccountByInvitationCode(code: "$code") {
|
||||
account
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.data.network.api.bifrost
|
||||
|
||||
class GetAccountByReferralCodeResponse(
|
||||
val getAccountByInvitationCode: GetAccountByReferralCode
|
||||
) {
|
||||
|
||||
class GetAccountByReferralCode(val account: String?)
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.data.network.api.moonbeam
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
class AgreeRemarkRequest(
|
||||
val address: String,
|
||||
@SerializedName("signed-message")
|
||||
val signedMessage: String,
|
||||
)
|
||||
|
||||
class AgreeRemarkResponse(
|
||||
val remark: String,
|
||||
)
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.data.network.api.moonbeam
|
||||
|
||||
class CheckRemarkResponse(
|
||||
val verified: Boolean,
|
||||
)
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.data.network.api.moonbeam
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import java.util.UUID
|
||||
|
||||
class MakeSignatureRequest(
|
||||
val address: String,
|
||||
@SerializedName("previous-total-contribution")
|
||||
val previousTotalContribution: String,
|
||||
val contribution: String,
|
||||
val guid: String = UUID.randomUUID().toString(),
|
||||
)
|
||||
|
||||
class MakeSignatureResponse(
|
||||
val signature: String,
|
||||
)
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.data.network.api.moonbeam
|
||||
|
||||
import io.novafoundation.nova.common.data.network.TimeHeaderInterceptor
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.repository.ParachainMetadata
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.repository.getExtra
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.BuildConfig
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Header
|
||||
import retrofit2.http.Headers
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Path
|
||||
|
||||
private val AUTH_TOKENS = mapOf(
|
||||
"MOONBEAM_TEST_AUTH_TOKEN" to BuildConfig.MOONBEAM_TEST_AUTH_TOKEN,
|
||||
"MOONBEAM_PROD_AUTH_TOKEN" to BuildConfig.MOONBEAM_PROD_AUTH_TOKEN
|
||||
)
|
||||
|
||||
interface MoonbeamApi {
|
||||
|
||||
@GET("https://raw.githubusercontent.com/moonbeam-foundation/crowdloan-self-attestation/main/moonbeam/README.md")
|
||||
suspend fun getLegalText(): String
|
||||
|
||||
@GET("//{baseUrl}/check-remark/{address}")
|
||||
suspend fun checkRemark(
|
||||
@Path("baseUrl") baseUrl: String,
|
||||
@Header("x-api-key") apiToken: String?,
|
||||
@Path("address") address: String,
|
||||
): CheckRemarkResponse
|
||||
|
||||
@POST("//{baseUrl}/agree-remark")
|
||||
suspend fun agreeRemark(
|
||||
@Path("baseUrl") baseUrl: String,
|
||||
@Header("x-api-key") apiToken: String?,
|
||||
@Body body: AgreeRemarkRequest,
|
||||
): AgreeRemarkResponse
|
||||
|
||||
@POST("//{baseUrl}/verify-remark")
|
||||
@Headers(TimeHeaderInterceptor.LONG_CONNECT, TimeHeaderInterceptor.LONG_READ, TimeHeaderInterceptor.LONG_WRITE)
|
||||
suspend fun verifyRemark(
|
||||
@Path("baseUrl") baseUrl: String,
|
||||
@Header("x-api-key") apiToken: String?,
|
||||
@Body body: VerifyRemarkRequest,
|
||||
): VerifyRemarkResponse
|
||||
|
||||
@POST("//{baseUrl}/make-signature")
|
||||
suspend fun makeSignature(
|
||||
@Path("baseUrl") baseUrl: String,
|
||||
@Header("x-api-key") apiToken: String?,
|
||||
@Body body: MakeSignatureRequest,
|
||||
): MakeSignatureResponse
|
||||
}
|
||||
|
||||
fun ParachainMetadata.moonbeamChainId() = getExtra("paraId")
|
||||
|
||||
private fun ParachainMetadata.apiBaseUrl() = getExtra("apiLink")
|
||||
private fun ParachainMetadata.apiToken() = AUTH_TOKENS[getExtra("apiTokenName")]
|
||||
|
||||
suspend fun MoonbeamApi.checkRemark(chainMetadata: ParachainMetadata, address: String): CheckRemarkResponse {
|
||||
return checkRemark(chainMetadata.apiBaseUrl(), chainMetadata.apiToken(), address)
|
||||
}
|
||||
|
||||
suspend fun MoonbeamApi.agreeRemark(chainMetadata: ParachainMetadata, body: AgreeRemarkRequest): AgreeRemarkResponse {
|
||||
return agreeRemark(chainMetadata.apiBaseUrl(), chainMetadata.apiToken(), body)
|
||||
}
|
||||
|
||||
suspend fun MoonbeamApi.verifyRemark(chainMetadata: ParachainMetadata, body: VerifyRemarkRequest): VerifyRemarkResponse {
|
||||
return verifyRemark(chainMetadata.apiBaseUrl(), chainMetadata.apiToken(), body)
|
||||
}
|
||||
|
||||
suspend fun MoonbeamApi.makeSignature(chainMetadata: ParachainMetadata, body: MakeSignatureRequest): MakeSignatureResponse {
|
||||
return makeSignature(chainMetadata.apiBaseUrl(), chainMetadata.apiToken(), body)
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.data.network.api.moonbeam
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
class VerifyRemarkRequest(
|
||||
val address: String,
|
||||
@SerializedName("extrinsic-hash")
|
||||
val extrinsicHash: String,
|
||||
@SerializedName("block-hash")
|
||||
val blockHash: String,
|
||||
)
|
||||
|
||||
class VerifyRemarkResponse(
|
||||
val verified: Boolean,
|
||||
)
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.data.network.api.parachain
|
||||
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.repository.ParachainMetadata
|
||||
|
||||
fun mapParachainMetadataRemoteToParachainMetadata(parachainMetadata: ParachainMetadataRemote) =
|
||||
with(parachainMetadata) {
|
||||
ParachainMetadata(
|
||||
paraId = paraid,
|
||||
movedToParaId = movedToParaId,
|
||||
iconLink = icon,
|
||||
name = name,
|
||||
description = description,
|
||||
rewardRate = rewardRate?.toBigDecimal(),
|
||||
website = website,
|
||||
customFlow = customFlow,
|
||||
token = token,
|
||||
extras = extras.orEmpty()
|
||||
)
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.data.network.api.parachain
|
||||
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Url
|
||||
|
||||
interface ParachainMetadataApi {
|
||||
|
||||
@GET()
|
||||
suspend fun getParachainMetadata(
|
||||
@Url url: String
|
||||
): List<ParachainMetadataRemote>
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.data.network.api.parachain
|
||||
|
||||
import java.math.BigInteger
|
||||
|
||||
class ParachainMetadataRemote(
|
||||
val description: String,
|
||||
val icon: String,
|
||||
val name: String,
|
||||
val paraid: BigInteger,
|
||||
val token: String,
|
||||
val rewardRate: Double?,
|
||||
val customFlow: String?,
|
||||
val website: String,
|
||||
val extras: Map<String, String>?,
|
||||
val movedToParaId: BigInteger?,
|
||||
)
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.data.network.api.parallel
|
||||
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Path
|
||||
|
||||
interface ParallelApi {
|
||||
|
||||
companion object {
|
||||
const val BASE_URL = "https://auction-service-prod.parallel.fi/crowdloan/rewards/"
|
||||
}
|
||||
|
||||
@GET("{network}/{address}")
|
||||
suspend fun getContributions(
|
||||
@Path("network") network: String,
|
||||
@Path("address") address: String,
|
||||
): List<ParallelContribution>
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.data.network.api.parallel
|
||||
|
||||
import java.math.BigInteger
|
||||
|
||||
class ParallelContribution(
|
||||
val paraId: BigInteger,
|
||||
val amount: BigInteger,
|
||||
)
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.data.network.blockhain.extrinsic
|
||||
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.BlockNumber
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.ParaId
|
||||
import io.novasama.substrate_sdk_android.runtime.AccountId
|
||||
import io.novasama.substrate_sdk_android.runtime.extrinsic.builder.ExtrinsicBuilder
|
||||
import io.novasama.substrate_sdk_android.runtime.extrinsic.call
|
||||
import java.math.BigInteger
|
||||
|
||||
fun ExtrinsicBuilder.contribute(
|
||||
parachainId: ParaId,
|
||||
contribution: BigInteger,
|
||||
signature: Any?,
|
||||
): ExtrinsicBuilder {
|
||||
return call(
|
||||
moduleName = "Crowdloan",
|
||||
callName = "contribute",
|
||||
arguments = mapOf(
|
||||
"index" to parachainId,
|
||||
"value" to contribution,
|
||||
"signature" to signature
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun ExtrinsicBuilder.addMemo(parachainId: ParaId, memo: String): ExtrinsicBuilder {
|
||||
return addMemo(parachainId, memo.toByteArray())
|
||||
}
|
||||
|
||||
fun ExtrinsicBuilder.addMemo(parachainId: ParaId, memo: ByteArray): ExtrinsicBuilder {
|
||||
return call(
|
||||
moduleName = "Crowdloan",
|
||||
callName = "add_memo",
|
||||
arguments = mapOf(
|
||||
"index" to parachainId,
|
||||
"memo" to memo
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun ExtrinsicBuilder.claimContribution(parachainId: ParaId, block: BlockNumber, depositor: AccountId) {
|
||||
call(
|
||||
moduleName = "AhOps",
|
||||
callName = "withdraw_crowdloan_contribution",
|
||||
arguments = mapOf(
|
||||
"block" to block,
|
||||
"para_id" to parachainId,
|
||||
"depositor" to depositor
|
||||
)
|
||||
)
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.data.network.updater
|
||||
|
||||
import android.util.Log
|
||||
import io.novafoundation.nova.common.utils.LOG_TAG
|
||||
import io.novafoundation.nova.common.utils.flowOfAll
|
||||
import io.novafoundation.nova.common.utils.transformLatestDiffed
|
||||
import io.novafoundation.nova.core.updater.UpdateSystem
|
||||
import io.novafoundation.nova.core.updater.Updater
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.network.updater.ContributionsUpdateSystemFactory
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.network.updater.ContributionsUpdaterFactory
|
||||
import io.novafoundation.nova.runtime.ethereum.StorageSharedRequestsBuilderFactory
|
||||
import io.novafoundation.nova.runtime.ext.isFullSync
|
||||
import io.novafoundation.nova.runtime.ext.utilityAsset
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.emitAll
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.coroutines.flow.transformLatest
|
||||
|
||||
class RealContributionsUpdateSystemFactory(
|
||||
private val chainRegistry: ChainRegistry,
|
||||
private val contributionsUpdaterFactory: ContributionsUpdaterFactory,
|
||||
private val assetBalanceScopeFactory: AssetBalanceScopeFactory,
|
||||
private val storageSharedRequestsBuilderFactory: StorageSharedRequestsBuilderFactory,
|
||||
) : ContributionsUpdateSystemFactory {
|
||||
|
||||
override fun create(): UpdateSystem {
|
||||
return ContributionsUpdateSystem(
|
||||
chainRegistry,
|
||||
contributionsUpdaterFactory,
|
||||
assetBalanceScopeFactory,
|
||||
storageSharedRequestsBuilderFactory
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class ContributionsUpdateSystem(
|
||||
private val chainRegistry: ChainRegistry,
|
||||
private val contributionsUpdaterFactory: ContributionsUpdaterFactory,
|
||||
private val assetBalanceScopeFactory: AssetBalanceScopeFactory,
|
||||
private val storageSharedRequestsBuilderFactory: StorageSharedRequestsBuilderFactory,
|
||||
) : UpdateSystem {
|
||||
|
||||
override fun start(): Flow<Updater.SideEffect> {
|
||||
return flowOfAll {
|
||||
chainRegistry.currentChains.mapLatest { chains ->
|
||||
chains.filter { it.connectionState.isFullSync && it.hasCrowdloans }
|
||||
}.transformLatestDiffed {
|
||||
emitAll(run(it))
|
||||
}
|
||||
}.flowOn(Dispatchers.Default)
|
||||
}
|
||||
|
||||
private fun run(chain: Chain): Flow<Updater.SideEffect> {
|
||||
return flowOfAll {
|
||||
// we do not start subscription builder since it is not needed for contributions
|
||||
val subscriptionBuilder = storageSharedRequestsBuilderFactory.create(chain.id)
|
||||
val invalidationScope = assetBalanceScopeFactory.create(chain, chain.utilityAsset)
|
||||
val updater = contributionsUpdaterFactory.create(chain, invalidationScope)
|
||||
|
||||
invalidationScope.invalidationFlow().transformLatest {
|
||||
kotlin.runCatching {
|
||||
updater.listenForUpdates(subscriptionBuilder, it)
|
||||
.catch { logError(chain, it) }
|
||||
}.onSuccess { updaterFlow ->
|
||||
emitAll(updaterFlow)
|
||||
}
|
||||
}
|
||||
}.catch { logError(chain, it) }
|
||||
}
|
||||
|
||||
private fun logError(chain: Chain, exception: Throwable) {
|
||||
Log.e(LOG_TAG, "Failed to run contributions updater for ${chain.name}", exception)
|
||||
}
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.data.network.updater
|
||||
|
||||
import io.novafoundation.nova.common.utils.CollectionDiffer
|
||||
import io.novafoundation.nova.common.utils.flowOfAll
|
||||
import io.novafoundation.nova.common.utils.sumByBigInteger
|
||||
import io.novafoundation.nova.core.updater.SharedRequestsBuilder
|
||||
import io.novafoundation.nova.core.updater.Updater
|
||||
import io.novafoundation.nova.core_db.dao.ContributionDao
|
||||
import io.novafoundation.nova.core_db.dao.ExternalBalanceDao
|
||||
import io.novafoundation.nova.core_db.dao.updateExternalBalance
|
||||
import io.novafoundation.nova.core_db.model.ContributionLocal
|
||||
import io.novafoundation.nova.core_db.model.ExternalBalanceLocal
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.network.updater.AssetBalanceScope
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.network.updater.AssetBalanceScope.ScopeValue
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.network.updater.ContributionsUpdaterFactory
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.repository.ContributionsRepository
|
||||
import io.novafoundation.nova.feature_crowdloan_api.domain.contributions.mapContributionToLocal
|
||||
import io.novafoundation.nova.runtime.ext.utilityAsset
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
||||
class RealContributionsUpdaterFactory(
|
||||
private val contributionsRepository: ContributionsRepository,
|
||||
private val contributionDao: ContributionDao,
|
||||
private val externalBalanceDao: ExternalBalanceDao,
|
||||
) : ContributionsUpdaterFactory {
|
||||
|
||||
override fun create(chain: Chain, assetBalanceScope: AssetBalanceScope): Updater<ScopeValue> {
|
||||
return ContributionsUpdater(
|
||||
assetBalanceScope,
|
||||
chain,
|
||||
contributionsRepository,
|
||||
contributionDao,
|
||||
externalBalanceDao,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class ContributionsUpdater(
|
||||
override val scope: AssetBalanceScope,
|
||||
private val chain: Chain,
|
||||
private val contributionsRepository: ContributionsRepository,
|
||||
private val contributionDao: ContributionDao,
|
||||
private val externalBalanceDao: ExternalBalanceDao,
|
||||
) : Updater<ScopeValue> {
|
||||
|
||||
override val requiredModules: List<String> = emptyList()
|
||||
|
||||
override suspend fun listenForUpdates(
|
||||
storageSubscriptionBuilder: SharedRequestsBuilder,
|
||||
scopeValue: ScopeValue,
|
||||
): Flow<Updater.SideEffect> {
|
||||
return flowOfAll {
|
||||
if (scopeValue.asset.token.configuration.enabled) {
|
||||
sync(scopeValue)
|
||||
} else {
|
||||
emptyFlow()
|
||||
}
|
||||
}.noSideAffects()
|
||||
}
|
||||
|
||||
private suspend fun sync(scopeValue: ScopeValue): Flow<Any> {
|
||||
val metaAccount = scopeValue.metaAccount
|
||||
val chainAsset = chain.utilityAsset
|
||||
val accountId = metaAccount.accountIdIn(chain) ?: return emptyFlow()
|
||||
|
||||
return contributionsRepository.loadContributionsGraduallyFlow(
|
||||
chain = chain,
|
||||
accountId = accountId,
|
||||
).onEach { (sourceId, contributionsResult) ->
|
||||
contributionsResult.onSuccess { contributions ->
|
||||
val newContributions = contributions.map { mapContributionToLocal(metaAccount.id, it) }
|
||||
val oldContributions = contributionDao.getContributions(metaAccount.id, chain.id, chainAsset.id, sourceId)
|
||||
|
||||
val collectionDiffer = CollectionDiffer.findDiff(newContributions, oldContributions, false)
|
||||
contributionDao.updateContributions(collectionDiffer)
|
||||
insertExternalBalance(newContributions, sourceId, chainAsset, metaAccount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun insertExternalBalance(
|
||||
contributions: List<ContributionLocal>,
|
||||
sourceId: String,
|
||||
chainAsset: Chain.Asset,
|
||||
metaAccount: MetaAccount
|
||||
) {
|
||||
val totalSourceContributions = contributions.sumByBigInteger { it.amountInPlanks }
|
||||
|
||||
val externalBalance = ExternalBalanceLocal(
|
||||
metaId = metaAccount.id,
|
||||
chainId = chain.id,
|
||||
assetId = chainAsset.id,
|
||||
type = ExternalBalanceLocal.Type.CROWDLOAN,
|
||||
subtype = sourceId,
|
||||
amount = totalSourceContributions
|
||||
)
|
||||
|
||||
externalBalanceDao.updateExternalBalance(externalBalance)
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.data.network.updater
|
||||
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.network.updater.AssetBalanceScope
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.network.updater.AssetBalanceScope.ScopeValue
|
||||
import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletRepository
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
class AssetBalanceScopeFactory(
|
||||
private val walletRepository: WalletRepository,
|
||||
private val accountRepository: AccountRepository,
|
||||
) {
|
||||
|
||||
fun create(chain: Chain, asset: Chain.Asset): AssetBalanceScope {
|
||||
return RealAssetBalanceScope(chain, asset, walletRepository, accountRepository)
|
||||
}
|
||||
}
|
||||
|
||||
class RealAssetBalanceScope(
|
||||
private val chain: Chain,
|
||||
private val asset: Chain.Asset,
|
||||
private val walletRepository: WalletRepository,
|
||||
private val accountRepository: AccountRepository,
|
||||
) : AssetBalanceScope {
|
||||
|
||||
override fun invalidationFlow(): Flow<ScopeValue> {
|
||||
return accountRepository.selectedMetaAccountFlow().flatMapLatest { metaAccount ->
|
||||
walletRepository.assetFlow(metaAccount.id, asset).map { asset ->
|
||||
ScopeValue(metaAccount, asset)
|
||||
}
|
||||
}
|
||||
.distinctUntilChanged { old, new ->
|
||||
old.asset.totalInPlanks == new.asset.totalInPlanks &&
|
||||
old.metaAccount.id == new.metaAccount.id &&
|
||||
old.metaAccount.accountIdIn(chain).contentEquals(new.metaAccount.accountIdIn(chain))
|
||||
}
|
||||
}
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.data.repository
|
||||
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.ParaId
|
||||
import io.novafoundation.nova.common.utils.crowdloan
|
||||
import io.novafoundation.nova.common.utils.numberConstant
|
||||
import io.novafoundation.nova.common.utils.slots
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.network.blockhain.binding.FundInfo
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.network.blockhain.binding.LeaseEntry
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.network.blockhain.binding.bindFundInfo
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.network.blockhain.binding.bindLeases
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.repository.CrowdloanRepository
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.repository.LeasePeriodToBlocksConverter
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.repository.ParachainMetadata
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.network.api.parachain.ParachainMetadataApi
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.network.api.parachain.mapParachainMetadataRemoteToParachainMetadata
|
||||
import io.novafoundation.nova.runtime.ext.externalApi
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
import io.novafoundation.nova.runtime.multiNetwork.getRuntime
|
||||
import io.novafoundation.nova.runtime.storage.source.StorageDataSource
|
||||
import io.novasama.substrate_sdk_android.runtime.AccountId
|
||||
import io.novasama.substrate_sdk_android.runtime.metadata.storage
|
||||
import io.novasama.substrate_sdk_android.runtime.metadata.storageKey
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.math.BigInteger
|
||||
|
||||
class CrowdloanRepositoryImpl(
|
||||
private val remoteStorage: StorageDataSource,
|
||||
private val chainRegistry: ChainRegistry,
|
||||
private val parachainMetadataApi: ParachainMetadataApi
|
||||
) : CrowdloanRepository {
|
||||
|
||||
override suspend fun allFundInfos(chainId: ChainId): Map<ParaId, FundInfo> {
|
||||
return remoteStorage.query(chainId) {
|
||||
runtime.metadata.crowdloan().storage("Funds").entries(
|
||||
keyExtractor = { (paraId: BigInteger) -> paraId },
|
||||
binding = { instance, paraId -> bindFundInfo(instance, runtime, paraId) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getWinnerInfo(chainId: ChainId, funds: Map<ParaId, FundInfo>): Map<ParaId, Boolean> {
|
||||
return remoteStorage.query(chainId) {
|
||||
runtime.metadata.slots().storage("Leases").singleArgumentEntries(
|
||||
keysArguments = funds.keys,
|
||||
binding = { decoded, paraId ->
|
||||
val leases = decoded?.let { bindLeases(it) }
|
||||
val fund = funds.getValue(paraId)
|
||||
|
||||
leases?.let { isWinner(leases, fund) } ?: false
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isWinner(leases: List<LeaseEntry?>, fundInfo: FundInfo): Boolean {
|
||||
return leases.any { it.isOwnedBy(fundInfo.bidderAccountId) || it.isOwnedBy(fundInfo.pre9180BidderAccountId) }
|
||||
}
|
||||
|
||||
private fun LeaseEntry?.isOwnedBy(accountId: AccountId): Boolean = this?.accountId.contentEquals(accountId)
|
||||
|
||||
override suspend fun getParachainMetadata(chain: Chain): Map<ParaId, ParachainMetadata> {
|
||||
return withContext(Dispatchers.Default) {
|
||||
chain.externalApi<Chain.ExternalApi.Crowdloans>()?.let { section ->
|
||||
parachainMetadataApi.getParachainMetadata(section.url)
|
||||
.associateBy { it.paraid }
|
||||
.mapValues { (_, remoteMetadata) -> mapParachainMetadataRemoteToParachainMetadata(remoteMetadata) }
|
||||
} ?: emptyMap()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun leasePeriodToBlocksConverter(chainId: ChainId): LeasePeriodToBlocksConverter {
|
||||
val runtime = runtimeFor(chainId)
|
||||
val slots = runtime.metadata.slots()
|
||||
|
||||
return LeasePeriodToBlocksConverter(
|
||||
blocksPerLease = slots.numberConstant("LeasePeriod", runtime),
|
||||
blocksOffset = slots.numberConstant("LeaseOffset", runtime)
|
||||
)
|
||||
}
|
||||
|
||||
override fun fundInfoFlow(chainId: ChainId, parachainId: ParaId): Flow<FundInfo> {
|
||||
return remoteStorage.observe(
|
||||
keyBuilder = { it.metadata.crowdloan().storage("Funds").storageKey(it, parachainId) },
|
||||
binder = { scale, runtime -> bindFundInfo(scale!!, runtime, parachainId) },
|
||||
chainId = chainId
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun minContribution(chainId: ChainId): BigInteger {
|
||||
val runtime = runtimeFor(chainId)
|
||||
|
||||
return runtime.metadata.crowdloan().numberConstant("MinContribution", runtime)
|
||||
}
|
||||
|
||||
private suspend fun runtimeFor(chainId: String) = chainRegistry.getRuntime(chainId)
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.data.repository.contributions.network
|
||||
|
||||
import io.novafoundation.nova.common.address.AccountIdKey
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.BlockNumber
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.ParaId
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.bindAccountIdKey
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.bindNumber
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.castToList
|
||||
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance
|
||||
import io.novafoundation.nova.runtime.storage.source.query.StorageQueryContext
|
||||
import io.novafoundation.nova.runtime.storage.source.query.api.QueryableModule
|
||||
import io.novafoundation.nova.runtime.storage.source.query.api.QueryableStorageEntry3
|
||||
import io.novafoundation.nova.runtime.storage.source.query.api.storage3
|
||||
import io.novasama.substrate_sdk_android.runtime.metadata.RuntimeMetadata
|
||||
import io.novasama.substrate_sdk_android.runtime.metadata.module
|
||||
import io.novasama.substrate_sdk_android.runtime.metadata.module.Module
|
||||
|
||||
@JvmInline
|
||||
value class AhOpsApi(override val module: Module) : QueryableModule
|
||||
|
||||
context(StorageQueryContext)
|
||||
val RuntimeMetadata.ahOps: AhOpsApi
|
||||
get() = AhOpsApi(module("AhOps"))
|
||||
|
||||
context(StorageQueryContext)
|
||||
val AhOpsApi.rcCrowdloanReserve: QueryableStorageEntry3<BlockNumber, ParaId, AccountIdKey, Any>
|
||||
get() = storage3(
|
||||
name = "RcCrowdloanReserve",
|
||||
binding = { _, _, _, decoded -> decoded },
|
||||
key3ToInternalConverter = { it.value },
|
||||
key3FromInternalConverter = ::bindAccountIdKey
|
||||
)
|
||||
|
||||
context(StorageQueryContext)
|
||||
val AhOpsApi.rcCrowdloanContribution: QueryableStorageEntry3<BlockNumber, ParaId, AccountIdKey, Balance>
|
||||
get() = storage3(
|
||||
name = "RcCrowdloanContribution",
|
||||
binding = { decoded, _, _, _ -> bindContribution(decoded) },
|
||||
key3ToInternalConverter = { it.value },
|
||||
key3FromInternalConverter = ::bindAccountIdKey
|
||||
)
|
||||
|
||||
private fun bindContribution(decoded: Any?): Balance {
|
||||
val (_, balance) = decoded.castToList() // Tuple
|
||||
|
||||
return bindNumber(balance)
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.data.repository.contributions.source
|
||||
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.source.contribution.ExternalContributionSource
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.source.contribution.ExternalContributionSource.ExternalContribution
|
||||
import io.novafoundation.nova.feature_crowdloan_api.domain.contributions.Contribution
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.network.api.acala.AcalaApi
|
||||
import io.novafoundation.nova.runtime.ext.Geneses
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novafoundation.nova.runtime.repository.ParachainInfoRepository
|
||||
import io.novasama.substrate_sdk_android.runtime.AccountId
|
||||
|
||||
class LiquidAcalaContributionSource(
|
||||
private val acalaApi: AcalaApi,
|
||||
private val parachainInfoRepository: ParachainInfoRepository,
|
||||
) : ExternalContributionSource {
|
||||
|
||||
override val supportedChains = setOf(Chain.Geneses.POLKADOT)
|
||||
|
||||
override val sourceId: String = Contribution.LIQUID_SOURCE_ID
|
||||
|
||||
override suspend fun getContributions(
|
||||
chain: Chain,
|
||||
accountId: AccountId,
|
||||
): Result<List<ExternalContribution>> {
|
||||
return Result.success(emptyList())
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.data.repository.contributions.source
|
||||
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.source.contribution.ExternalContributionSource
|
||||
import io.novafoundation.nova.feature_crowdloan_api.domain.contributions.Contribution
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.network.api.parallel.ParallelApi
|
||||
import io.novafoundation.nova.runtime.ext.Geneses
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novasama.substrate_sdk_android.runtime.AccountId
|
||||
|
||||
class ParallelContributionSource(
|
||||
private val parallelApi: ParallelApi,
|
||||
) : ExternalContributionSource {
|
||||
|
||||
override val supportedChains = setOf(Chain.Geneses.POLKADOT)
|
||||
|
||||
override val sourceId: String = Contribution.PARALLEL_SOURCE_ID
|
||||
|
||||
override suspend fun getContributions(
|
||||
chain: Chain,
|
||||
accountId: AccountId,
|
||||
): Result<List<ExternalContributionSource.ExternalContribution>> {
|
||||
return Result.success(emptyList())
|
||||
}
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.di
|
||||
|
||||
import dagger.BindsInstance
|
||||
import dagger.Component
|
||||
import io.novafoundation.nova.common.di.CommonApi
|
||||
import io.novafoundation.nova.common.di.scope.FeatureScope
|
||||
import io.novafoundation.nova.core_db.di.DbApi
|
||||
import io.novafoundation.nova.feature_account_api.di.AccountFeatureApi
|
||||
import io.novafoundation.nova.feature_crowdloan_api.di.CrowdloanFeatureApi
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.di.contributions.ContributionsModule
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.di.validations.CrowdloansValidationsModule
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.CrowdloanRouter
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.claimControbution.di.ClaimContributionComponent
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.confirm.di.ConfirmContributeComponent
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.di.CustomContributeComponent
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.moonbeam.terms.di.MoonbeamCrowdloanTermsComponent
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.referral.ReferralContributeView
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.select.di.CrowdloanContributeComponent
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contributions.di.UserContributionsComponent
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.main.di.CrowdloanComponent
|
||||
import io.novafoundation.nova.feature_wallet_api.di.WalletFeatureApi
|
||||
import io.novafoundation.nova.runtime.di.RuntimeApi
|
||||
|
||||
@Component(
|
||||
dependencies = [
|
||||
CrowdloanFeatureDependencies::class
|
||||
],
|
||||
modules = [
|
||||
CrowdloanFeatureModule::class,
|
||||
CrowdloanUpdatersModule::class,
|
||||
CrowdloansValidationsModule::class,
|
||||
ContributionsModule::class
|
||||
]
|
||||
)
|
||||
@FeatureScope
|
||||
interface CrowdloanFeatureComponent : CrowdloanFeatureApi {
|
||||
|
||||
fun crowdloansFactory(): CrowdloanComponent.Factory
|
||||
|
||||
fun userContributionsFactory(): UserContributionsComponent.Factory
|
||||
|
||||
fun selectContributeFactory(): CrowdloanContributeComponent.Factory
|
||||
|
||||
fun confirmContributeFactory(): ConfirmContributeComponent.Factory
|
||||
|
||||
fun customContributeFactory(): CustomContributeComponent.Factory
|
||||
|
||||
fun moonbeamTermsFactory(): MoonbeamCrowdloanTermsComponent.Factory
|
||||
|
||||
fun claimContributions(): ClaimContributionComponent.Factory
|
||||
|
||||
fun inject(view: ReferralContributeView)
|
||||
|
||||
@Component.Factory
|
||||
interface Factory {
|
||||
|
||||
fun create(
|
||||
@BindsInstance router: CrowdloanRouter,
|
||||
deps: CrowdloanFeatureDependencies,
|
||||
): CrowdloanFeatureComponent
|
||||
}
|
||||
|
||||
@Component(
|
||||
dependencies = [
|
||||
CommonApi::class,
|
||||
DbApi::class,
|
||||
RuntimeApi::class,
|
||||
AccountFeatureApi::class,
|
||||
WalletFeatureApi::class
|
||||
]
|
||||
)
|
||||
interface CrowdloanFeatureDependenciesComponent : CrowdloanFeatureDependencies
|
||||
}
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.di
|
||||
|
||||
import coil.ImageLoader
|
||||
import com.google.gson.Gson
|
||||
import io.novafoundation.nova.common.address.AddressIconGenerator
|
||||
import io.novafoundation.nova.common.data.network.AppLinksProvider
|
||||
import io.novafoundation.nova.common.data.network.HttpExceptionHandler
|
||||
import io.novafoundation.nova.common.data.network.NetworkApiCreator
|
||||
import io.novafoundation.nova.common.data.secrets.v2.SecretStoreV2
|
||||
import io.novafoundation.nova.common.data.storage.Preferences
|
||||
import io.novafoundation.nova.common.mixin.api.CustomDialogDisplayer
|
||||
import io.novafoundation.nova.common.presentation.AssetIconProvider
|
||||
import io.novafoundation.nova.common.presentation.masking.formatter.MaskableValueFormatterFactory
|
||||
import io.novafoundation.nova.common.presentation.masking.formatter.MaskableValueFormatterProvider
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.common.validation.ValidationExecutor
|
||||
import io.novafoundation.nova.core.storage.StorageCache
|
||||
import io.novafoundation.nova.core_db.dao.ContributionDao
|
||||
import io.novafoundation.nova.core_db.dao.ExternalBalanceDao
|
||||
import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService
|
||||
import io.novafoundation.nova.feature_account_api.data.signer.SignerProvider
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.SelectedAccountUseCase
|
||||
import io.novafoundation.nova.feature_account_api.domain.updaters.AccountUpdateScope
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.account.AddressDisplayUseCase
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.WalletUiUseCase
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.actions.ExternalActions
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.navigation.ExtrinsicNavigationWrapper
|
||||
import io.novafoundation.nova.feature_wallet_api.domain.interfaces.TokenRepository
|
||||
import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletConstants
|
||||
import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletRepository
|
||||
import io.novafoundation.nova.feature_wallet_api.presentation.formatters.AssetModelFormatter
|
||||
import io.novafoundation.nova.feature_wallet_api.presentation.formatters.amount.AmountFormatter
|
||||
import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin
|
||||
import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.v2.FeeLoaderMixinV2
|
||||
import io.novafoundation.nova.runtime.di.LOCAL_STORAGE_SOURCE
|
||||
import io.novafoundation.nova.runtime.di.REMOTE_STORAGE_SOURCE
|
||||
import io.novafoundation.nova.runtime.ethereum.StorageSharedRequestsBuilderFactory
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
import io.novafoundation.nova.runtime.repository.ChainStateRepository
|
||||
import io.novafoundation.nova.runtime.repository.ParachainInfoRepository
|
||||
import io.novafoundation.nova.runtime.storage.source.StorageDataSource
|
||||
import javax.inject.Named
|
||||
|
||||
interface CrowdloanFeatureDependencies {
|
||||
|
||||
val maskableValueFormatterFactory: MaskableValueFormatterFactory
|
||||
|
||||
val maskableValueFormatterProvider: MaskableValueFormatterProvider
|
||||
|
||||
val amountFormatter: AmountFormatter
|
||||
|
||||
val parachainInfoRepository: ParachainInfoRepository
|
||||
|
||||
val signerProvider: SignerProvider
|
||||
|
||||
val storageStorageSharedRequestsBuilderFactory: StorageSharedRequestsBuilderFactory
|
||||
|
||||
val externalBalanceDao: ExternalBalanceDao
|
||||
|
||||
val assetModelFormatter: AssetModelFormatter
|
||||
|
||||
val assetIconProvider: AssetIconProvider
|
||||
|
||||
val extrinsicNavigationWrapper: ExtrinsicNavigationWrapper
|
||||
|
||||
val feeLoaderMixinV2Factory: FeeLoaderMixinV2.Factory
|
||||
|
||||
val walletUIUseCase: WalletUiUseCase
|
||||
|
||||
fun contributionDao(): ContributionDao
|
||||
|
||||
fun accountUpdaterScope(): AccountUpdateScope
|
||||
|
||||
fun selectedAccountUseCase(): SelectedAccountUseCase
|
||||
|
||||
fun walletConstants(): WalletConstants
|
||||
|
||||
fun storageCache(): StorageCache
|
||||
|
||||
fun imageLoader(): ImageLoader
|
||||
|
||||
fun accountRepository(): AccountRepository
|
||||
|
||||
fun addressIconGenerator(): AddressIconGenerator
|
||||
|
||||
fun appLinksProvider(): AppLinksProvider
|
||||
|
||||
fun walletRepository(): WalletRepository
|
||||
|
||||
fun tokenRepository(): TokenRepository
|
||||
|
||||
fun resourceManager(): ResourceManager
|
||||
|
||||
fun externalAccountActions(): ExternalActions.Presentation
|
||||
|
||||
fun networkApiCreator(): NetworkApiCreator
|
||||
|
||||
fun httpExceptionHandler(): HttpExceptionHandler
|
||||
|
||||
fun gson(): Gson
|
||||
|
||||
fun addressxDisplayUseCase(): AddressDisplayUseCase
|
||||
|
||||
fun extrinsicService(): ExtrinsicService
|
||||
|
||||
fun validationExecutor(): ValidationExecutor
|
||||
|
||||
@Named(REMOTE_STORAGE_SOURCE)
|
||||
fun remoteStorageSource(): StorageDataSource
|
||||
|
||||
@Named(LOCAL_STORAGE_SOURCE)
|
||||
fun localStorageSource(): StorageDataSource
|
||||
|
||||
fun chainStateRepository(): ChainStateRepository
|
||||
|
||||
fun chainRegistry(): ChainRegistry
|
||||
|
||||
fun preferences(): Preferences
|
||||
|
||||
fun secretStoreV2(): SecretStoreV2
|
||||
|
||||
fun customDialogDisplayer(): CustomDialogDisplayer.Presentation
|
||||
|
||||
fun feeLoaderMixinFactory(): FeeLoaderMixin.Factory
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.di
|
||||
|
||||
import io.novafoundation.nova.common.di.FeatureApiHolder
|
||||
import io.novafoundation.nova.common.di.FeatureContainer
|
||||
import io.novafoundation.nova.common.di.scope.ApplicationScope
|
||||
import io.novafoundation.nova.core_db.di.DbApi
|
||||
import io.novafoundation.nova.feature_account_api.di.AccountFeatureApi
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.CrowdloanRouter
|
||||
import io.novafoundation.nova.feature_wallet_api.di.WalletFeatureApi
|
||||
import io.novafoundation.nova.runtime.di.RuntimeApi
|
||||
import javax.inject.Inject
|
||||
|
||||
@ApplicationScope
|
||||
class CrowdloanFeatureHolder @Inject constructor(
|
||||
featureContainer: FeatureContainer,
|
||||
private val router: CrowdloanRouter
|
||||
) : FeatureApiHolder(featureContainer) {
|
||||
|
||||
override fun initializeDependencies(): Any {
|
||||
val dependencies = DaggerCrowdloanFeatureComponent_CrowdloanFeatureDependenciesComponent.builder()
|
||||
.commonApi(commonApi())
|
||||
.runtimeApi(getFeature(RuntimeApi::class.java))
|
||||
.dbApi(getFeature(DbApi::class.java))
|
||||
.walletFeatureApi(getFeature(WalletFeatureApi::class.java))
|
||||
.accountFeatureApi(getFeature(AccountFeatureApi::class.java))
|
||||
.build()
|
||||
|
||||
return DaggerCrowdloanFeatureComponent.factory()
|
||||
.create(router, dependencies)
|
||||
}
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.di
|
||||
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import io.novafoundation.nova.common.data.network.NetworkApiCreator
|
||||
import io.novafoundation.nova.common.data.storage.Preferences
|
||||
import io.novafoundation.nova.common.di.scope.FeatureScope
|
||||
import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.repository.ContributionsRepository
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.repository.CrowdloanRepository
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.CrowdloanSharedState
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.network.api.parachain.ParachainMetadataApi
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.repository.CrowdloanRepositoryImpl
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.di.customCrowdloan.CustomContributeManager
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.di.customCrowdloan.CustomContributeModule
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.CrowdloanContributeInteractor
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.main.CrowdloanInteractor
|
||||
import io.novafoundation.nova.feature_wallet_api.di.common.SelectableAssetUseCaseModule
|
||||
import io.novafoundation.nova.feature_wallet_api.domain.TokenUseCase
|
||||
import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin
|
||||
import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.create
|
||||
import io.novafoundation.nova.runtime.di.REMOTE_STORAGE_SOURCE
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
import io.novafoundation.nova.runtime.repository.ChainStateRepository
|
||||
import io.novafoundation.nova.runtime.state.SelectableSingleAssetSharedState
|
||||
import io.novafoundation.nova.runtime.storage.source.StorageDataSource
|
||||
import javax.inject.Named
|
||||
|
||||
@Module(
|
||||
includes = [
|
||||
CustomContributeModule::class,
|
||||
SelectableAssetUseCaseModule::class,
|
||||
]
|
||||
)
|
||||
class CrowdloanFeatureModule {
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideCrowdloanSharedState(
|
||||
chainRegistry: ChainRegistry,
|
||||
preferences: Preferences,
|
||||
) = CrowdloanSharedState(chainRegistry, preferences)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideSelectableSharedState(crowdloanSharedState: CrowdloanSharedState): SelectableSingleAssetSharedState<*> = crowdloanSharedState
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideFeeLoaderMixin(
|
||||
feeLoaderMixinFactory: FeeLoaderMixin.Factory,
|
||||
tokenUseCase: TokenUseCase,
|
||||
): FeeLoaderMixin.Presentation = feeLoaderMixinFactory.create(tokenUseCase)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun crowdloanRepository(
|
||||
@Named(REMOTE_STORAGE_SOURCE) remoteStorageSource: StorageDataSource,
|
||||
crowdloanMetadataApi: ParachainMetadataApi,
|
||||
chainRegistry: ChainRegistry,
|
||||
): CrowdloanRepository = CrowdloanRepositoryImpl(
|
||||
remoteStorageSource,
|
||||
chainRegistry,
|
||||
crowdloanMetadataApi
|
||||
)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideCrowdloanInteractor(
|
||||
crowdloanRepository: CrowdloanRepository,
|
||||
chainStateRepository: ChainStateRepository,
|
||||
contributionsRepository: ContributionsRepository
|
||||
) = CrowdloanInteractor(
|
||||
crowdloanRepository,
|
||||
chainStateRepository,
|
||||
contributionsRepository
|
||||
)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideCrowdloanMetadataApi(networkApiCreator: NetworkApiCreator): ParachainMetadataApi {
|
||||
return networkApiCreator.create(ParachainMetadataApi::class.java)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideCrowdloanContributeInteractor(
|
||||
extrinsicService: ExtrinsicService,
|
||||
accountRepository: AccountRepository,
|
||||
chainStateRepository: ChainStateRepository,
|
||||
sharedState: CrowdloanSharedState,
|
||||
crowdloanRepository: CrowdloanRepository,
|
||||
customContributeManager: CustomContributeManager,
|
||||
contributionsRepository: ContributionsRepository
|
||||
) = CrowdloanContributeInteractor(
|
||||
extrinsicService,
|
||||
accountRepository,
|
||||
chainStateRepository,
|
||||
customContributeManager,
|
||||
sharedState,
|
||||
crowdloanRepository,
|
||||
contributionsRepository
|
||||
)
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.di
|
||||
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import io.novafoundation.nova.common.di.scope.FeatureScope
|
||||
import io.novafoundation.nova.core.storage.StorageCache
|
||||
import io.novafoundation.nova.core.updater.UpdateSystem
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.CrowdloanSharedState
|
||||
import io.novafoundation.nova.runtime.ethereum.StorageSharedRequestsBuilderFactory
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
import io.novafoundation.nova.runtime.network.updaters.SharedAssetBlockNumberUpdater
|
||||
import io.novafoundation.nova.runtime.network.updaters.multiChain.DelegateToTimeLineChainUpdater
|
||||
import io.novafoundation.nova.runtime.network.updaters.multiChain.DelegateToTimelineChainIdHolder
|
||||
import io.novafoundation.nova.runtime.network.updaters.multiChain.GroupBySyncChainMultiChainUpdateSystem
|
||||
|
||||
@Module
|
||||
class CrowdloanUpdatersModule {
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideTimelineDelegatingHolder(sharedState: CrowdloanSharedState) = DelegateToTimelineChainIdHolder(sharedState)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideBlockNumberUpdater(
|
||||
chainRegistry: ChainRegistry,
|
||||
chainIdHolder: DelegateToTimelineChainIdHolder,
|
||||
storageCache: StorageCache,
|
||||
) = SharedAssetBlockNumberUpdater(chainRegistry, chainIdHolder, storageCache)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideCrowdloanUpdateSystem(
|
||||
chainRegistry: ChainRegistry,
|
||||
crowdloanSharedState: CrowdloanSharedState,
|
||||
blockNumberUpdater: SharedAssetBlockNumberUpdater,
|
||||
storageSharedRequestsBuilderFactory: StorageSharedRequestsBuilderFactory,
|
||||
): UpdateSystem = GroupBySyncChainMultiChainUpdateSystem(
|
||||
updaters = listOf(
|
||||
DelegateToTimeLineChainUpdater(blockNumberUpdater)
|
||||
),
|
||||
chainRegistry = chainRegistry,
|
||||
singleAssetSharedState = crowdloanSharedState,
|
||||
storageSharedRequestsBuilderFactory = storageSharedRequestsBuilderFactory
|
||||
)
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.di.contributions
|
||||
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.multibindings.IntoSet
|
||||
import io.novafoundation.nova.common.di.scope.FeatureScope
|
||||
import io.novafoundation.nova.core_db.dao.ContributionDao
|
||||
import io.novafoundation.nova.core_db.dao.ExternalBalanceDao
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.network.updater.ContributionsUpdateSystemFactory
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.network.updater.ContributionsUpdaterFactory
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.repository.ContributionsRepository
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.repository.CrowdloanRepository
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.source.contribution.ExternalContributionSource
|
||||
import io.novafoundation.nova.feature_crowdloan_api.domain.contributions.ContributionsInteractor
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.CrowdloanSharedState
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.network.api.acala.AcalaApi
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.network.api.parallel.ParallelApi
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.network.updater.AssetBalanceScopeFactory
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.network.updater.RealContributionsUpdateSystemFactory
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.network.updater.RealContributionsUpdaterFactory
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.repository.contributions.source.LiquidAcalaContributionSource
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.repository.contributions.source.ParallelContributionSource
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.contributions.RealContributionsInteractor
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.contributions.RealContributionsRepository
|
||||
import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletRepository
|
||||
import io.novafoundation.nova.runtime.di.REMOTE_STORAGE_SOURCE
|
||||
import io.novafoundation.nova.runtime.ethereum.StorageSharedRequestsBuilderFactory
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
import io.novafoundation.nova.runtime.repository.ChainStateRepository
|
||||
import io.novafoundation.nova.runtime.repository.ParachainInfoRepository
|
||||
import io.novafoundation.nova.runtime.storage.source.StorageDataSource
|
||||
import javax.inject.Named
|
||||
|
||||
@Module
|
||||
class ContributionsModule {
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
@IntoSet
|
||||
fun acalaLiquidSource(
|
||||
acalaApi: AcalaApi,
|
||||
parachainInfoRepository: ParachainInfoRepository
|
||||
): ExternalContributionSource = LiquidAcalaContributionSource(acalaApi, parachainInfoRepository)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
@IntoSet
|
||||
fun parallelSource(
|
||||
parallelApi: ParallelApi,
|
||||
): ExternalContributionSource = ParallelContributionSource(parallelApi)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideContributionsInteractor(
|
||||
crowdloanRepository: CrowdloanRepository,
|
||||
accountRepository: AccountRepository,
|
||||
crowdloanSharedState: CrowdloanSharedState,
|
||||
chainStateRepository: ChainStateRepository,
|
||||
contributionsRepository: ContributionsRepository,
|
||||
chainRegistry: ChainRegistry,
|
||||
contributionsUpdateSystemFactory: ContributionsUpdateSystemFactory
|
||||
): ContributionsInteractor = RealContributionsInteractor(
|
||||
crowdloanRepository = crowdloanRepository,
|
||||
accountRepository = accountRepository,
|
||||
selectedAssetCrowdloanState = crowdloanSharedState,
|
||||
chainStateRepository = chainStateRepository,
|
||||
contributionsRepository = contributionsRepository,
|
||||
chainRegistry = chainRegistry,
|
||||
contributionsUpdateSystemFactory = contributionsUpdateSystemFactory
|
||||
)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideContributionsRepository(
|
||||
externalContributionsSources: Set<@JvmSuppressWildcards ExternalContributionSource>,
|
||||
chainRegistry: ChainRegistry,
|
||||
@Named(REMOTE_STORAGE_SOURCE) remoteStorageSource: StorageDataSource,
|
||||
contributionDao: ContributionDao
|
||||
): ContributionsRepository {
|
||||
return RealContributionsRepository(
|
||||
externalContributionsSources.toList(),
|
||||
chainRegistry,
|
||||
remoteStorageSource,
|
||||
contributionDao
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideContributionsUpdaterFactory(
|
||||
contributionsRepository: ContributionsRepository,
|
||||
contributionDao: ContributionDao,
|
||||
externalBalanceDao: ExternalBalanceDao
|
||||
): ContributionsUpdaterFactory = RealContributionsUpdaterFactory(
|
||||
contributionsRepository,
|
||||
contributionDao,
|
||||
externalBalanceDao
|
||||
)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideContributionUpdateSystemFactory(
|
||||
contributionsUpdaterFactory: ContributionsUpdaterFactory,
|
||||
chainRegistry: ChainRegistry,
|
||||
assetBalanceScopeFactory: AssetBalanceScopeFactory,
|
||||
storageSharedRequestsBuilderFactory: StorageSharedRequestsBuilderFactory,
|
||||
): ContributionsUpdateSystemFactory = RealContributionsUpdateSystemFactory(
|
||||
chainRegistry = chainRegistry,
|
||||
contributionsUpdaterFactory = contributionsUpdaterFactory,
|
||||
assetBalanceScopeFactory = assetBalanceScopeFactory,
|
||||
storageSharedRequestsBuilderFactory = storageSharedRequestsBuilderFactory
|
||||
)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideAssetBalanceScopeFactory(
|
||||
walletRepository: WalletRepository,
|
||||
accountRepository: AccountRepository
|
||||
) = AssetBalanceScopeFactory(walletRepository, accountRepository)
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.di.customCrowdloan
|
||||
|
||||
import android.content.Context
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.custom.PrivateCrowdloanSignatureProvider
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.ConfirmContributeCustomization
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.CustomContributeSubmitter
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.CustomContributeView
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.CustomContributeViewState
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.SelectContributeCustomization
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.StartFlowInterceptor
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.model.CustomContributePayload
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
||||
interface CustomContributeFactory {
|
||||
|
||||
val flowType: String
|
||||
|
||||
val privateCrowdloanSignatureProvider: PrivateCrowdloanSignatureProvider?
|
||||
get() = null
|
||||
|
||||
val submitter: CustomContributeSubmitter
|
||||
|
||||
val startFlowInterceptor: StartFlowInterceptor?
|
||||
get() = null
|
||||
|
||||
val extraBonusFlow: ExtraBonusFlow?
|
||||
get() = null
|
||||
|
||||
val selectContributeCustomization: SelectContributeCustomization?
|
||||
get() = null
|
||||
|
||||
val confirmContributeCustomization: ConfirmContributeCustomization?
|
||||
get() = null
|
||||
}
|
||||
|
||||
interface ExtraBonusFlow {
|
||||
|
||||
fun createViewState(scope: CoroutineScope, payload: CustomContributePayload): CustomContributeViewState
|
||||
|
||||
fun createView(context: Context): CustomContributeView
|
||||
}
|
||||
|
||||
fun CustomContributeFactory.supports(otherFlowType: String): Boolean {
|
||||
return otherFlowType == flowType
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.di.customCrowdloan
|
||||
|
||||
class CustomContributeManager(
|
||||
private val factories: Set<CustomContributeFactory>
|
||||
) {
|
||||
|
||||
fun getFactoryOrNull(flowType: String): CustomContributeFactory? = relevantFactoryOrNull(flowType)
|
||||
|
||||
private fun relevantFactory(flowType: String) = relevantFactoryOrNull(flowType) ?: noFactoryFound(flowType)
|
||||
|
||||
private fun relevantFactoryOrNull(
|
||||
flowType: String,
|
||||
): CustomContributeFactory? {
|
||||
return factories.firstOrNull { it.supports(flowType) }
|
||||
}
|
||||
|
||||
fun relevantExtraBonusFlow(flowType: String): ExtraBonusFlow {
|
||||
val factory = relevantFactory(flowType)
|
||||
|
||||
return factory.extraBonusFlow ?: unexpectedBonusFlow(flowType)
|
||||
}
|
||||
|
||||
private fun noFactoryFound(flowType: String): Nothing = throw NoSuchElementException("Factory for $flowType was not found")
|
||||
|
||||
private fun unexpectedBonusFlow(flowType: String): Nothing = throw IllegalStateException("No extra bonus flow found for flow $flowType")
|
||||
}
|
||||
|
||||
fun CustomContributeManager.hasExtraBonusFlow(flowType: String) = getFactoryOrNull(flowType)?.extraBonusFlow != null
|
||||
|
||||
fun CustomContributeManager.supportsPrivateCrowdloans(flowType: String) = getFactoryOrNull(flowType)?.privateCrowdloanSignatureProvider != null
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.di.customCrowdloan
|
||||
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import io.novafoundation.nova.common.di.scope.FeatureScope
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.di.customCrowdloan.acala.AcalaContributionModule
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.di.customCrowdloan.astar.AstarContributionModule
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.di.customCrowdloan.bifrost.BifrostContributionModule
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.di.customCrowdloan.moonbeam.MoonbeamContributionModule
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.di.customCrowdloan.parallel.ParallelContributionModule
|
||||
|
||||
@Module(
|
||||
includes = [
|
||||
AcalaContributionModule::class,
|
||||
BifrostContributionModule::class,
|
||||
MoonbeamContributionModule::class,
|
||||
AstarContributionModule::class,
|
||||
ParallelContributionModule::class
|
||||
]
|
||||
)
|
||||
class CustomContributeModule {
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideCustomContributionManager(
|
||||
factories: @JvmSuppressWildcards Set<CustomContributeFactory>,
|
||||
) = CustomContributeManager(factories)
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.di.customCrowdloan.acala
|
||||
|
||||
import android.content.Context
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.BuildConfig
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.di.customCrowdloan.CustomContributeFactory
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.di.customCrowdloan.ExtraBonusFlow
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.custom.acala.AcalaContributeInteractor
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.CustomContributeViewState
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.acala.bonus.AcalaContributeSubmitter
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.acala.bonus.AcalaContributeViewState
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.acala.main.confirm.AcalaConfirmContributeCustomization
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.acala.main.select.AcalaSelectContributeCustomization
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.model.CustomContributePayload
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.referral.ReferralContributeView
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import java.math.BigDecimal
|
||||
|
||||
abstract class AcalaBasedContributeFactory(
|
||||
override val submitter: AcalaContributeSubmitter,
|
||||
override val extraBonusFlow: AcalaBasedExtraBonusFlow,
|
||||
) : CustomContributeFactory
|
||||
|
||||
abstract class AcalaBasedExtraBonusFlow(
|
||||
private val interactor: AcalaContributeInteractor,
|
||||
private val resourceManager: ResourceManager,
|
||||
private val defaultReferralCode: String,
|
||||
) : ExtraBonusFlow {
|
||||
|
||||
protected open val bonusMultiplier: BigDecimal = 0.05.toBigDecimal()
|
||||
|
||||
override fun createViewState(scope: CoroutineScope, payload: CustomContributePayload): CustomContributeViewState {
|
||||
return AcalaContributeViewState(interactor, payload, resourceManager, defaultReferralCode, bonusMultiplier)
|
||||
}
|
||||
|
||||
override fun createView(context: Context) = ReferralContributeView(context)
|
||||
}
|
||||
|
||||
class AcalaContributeFactory(
|
||||
submitter: AcalaContributeSubmitter,
|
||||
extraBonusFlow: AcalaExtraBonusFlow,
|
||||
override val selectContributeCustomization: AcalaSelectContributeCustomization,
|
||||
override val confirmContributeCustomization: AcalaConfirmContributeCustomization,
|
||||
) : AcalaBasedContributeFactory(
|
||||
submitter = submitter,
|
||||
extraBonusFlow = extraBonusFlow
|
||||
) {
|
||||
|
||||
override val flowType: String = "Acala"
|
||||
}
|
||||
|
||||
class AcalaExtraBonusFlow(
|
||||
interactor: AcalaContributeInteractor,
|
||||
resourceManager: ResourceManager,
|
||||
) : AcalaBasedExtraBonusFlow(
|
||||
interactor = interactor,
|
||||
resourceManager = resourceManager,
|
||||
defaultReferralCode = BuildConfig.ACALA_NOVA_REFERRAL
|
||||
)
|
||||
|
||||
class KaruraContributeFactory(
|
||||
submitter: AcalaContributeSubmitter,
|
||||
extraBonusFlow: KaruraExtraBonusFlow,
|
||||
) : AcalaBasedContributeFactory(
|
||||
submitter = submitter,
|
||||
extraBonusFlow = extraBonusFlow
|
||||
) {
|
||||
|
||||
override val flowType: String = "Karura"
|
||||
}
|
||||
|
||||
class KaruraExtraBonusFlow(
|
||||
interactor: AcalaContributeInteractor,
|
||||
resourceManager: ResourceManager,
|
||||
) : AcalaBasedExtraBonusFlow(
|
||||
interactor = interactor,
|
||||
resourceManager = resourceManager,
|
||||
defaultReferralCode = BuildConfig.KARURA_NOVA_REFERRAL
|
||||
)
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.di.customCrowdloan.acala
|
||||
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.multibindings.IntoSet
|
||||
import io.novafoundation.nova.common.data.network.HttpExceptionHandler
|
||||
import io.novafoundation.nova.common.data.network.NetworkApiCreator
|
||||
import io.novafoundation.nova.common.di.scope.FeatureScope
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.feature_account_api.data.signer.SignerProvider
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.CrowdloanSharedState
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.network.api.acala.AcalaApi
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.di.customCrowdloan.CustomContributeFactory
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.custom.acala.AcalaContributeInteractor
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.acala.bonus.AcalaContributeSubmitter
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.acala.main.confirm.AcalaConfirmContributeCustomization
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.acala.main.confirm.AcalaConfirmContributeViewStateFactory
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.acala.main.select.AcalaSelectContributeCustomization
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
|
||||
@Module
|
||||
class AcalaContributionModule {
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideAcalaApi(
|
||||
networkApiCreator: NetworkApiCreator,
|
||||
) = networkApiCreator.create(AcalaApi::class.java)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideAcalaInteractor(
|
||||
acalaApi: AcalaApi,
|
||||
httpExceptionHandler: HttpExceptionHandler,
|
||||
selectAssetSharedState: CrowdloanSharedState,
|
||||
chainRegistry: ChainRegistry,
|
||||
accountRepository: AccountRepository,
|
||||
signerProvider: SignerProvider
|
||||
) = AcalaContributeInteractor(
|
||||
acalaApi = acalaApi,
|
||||
httpExceptionHandler = httpExceptionHandler,
|
||||
accountRepository = accountRepository,
|
||||
chainRegistry = chainRegistry,
|
||||
selectedAssetState = selectAssetSharedState,
|
||||
signerProvider = signerProvider
|
||||
)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideAcalaSubmitter(
|
||||
interactor: AcalaContributeInteractor,
|
||||
) = AcalaContributeSubmitter(interactor)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideAcalaExtraBonusFlow(
|
||||
acalaInteractor: AcalaContributeInteractor,
|
||||
resourceManager: ResourceManager,
|
||||
): AcalaExtraBonusFlow = AcalaExtraBonusFlow(
|
||||
interactor = acalaInteractor,
|
||||
resourceManager = resourceManager,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideKaruraExtraBonusFlow(
|
||||
acalaInteractor: AcalaContributeInteractor,
|
||||
resourceManager: ResourceManager,
|
||||
): KaruraExtraBonusFlow = KaruraExtraBonusFlow(
|
||||
interactor = acalaInteractor,
|
||||
resourceManager = resourceManager,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideAcalaSelectContributeCustomization(): AcalaSelectContributeCustomization = AcalaSelectContributeCustomization()
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideAcalaConfirmContributeViewStateFactory(
|
||||
resourceManager: ResourceManager,
|
||||
) = AcalaConfirmContributeViewStateFactory(resourceManager)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideAcalaConfirmContributeCustomization(
|
||||
viewStateFactory: AcalaConfirmContributeViewStateFactory,
|
||||
): AcalaConfirmContributeCustomization = AcalaConfirmContributeCustomization(viewStateFactory)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
@IntoSet
|
||||
fun provideAcalaFactory(
|
||||
submitter: AcalaContributeSubmitter,
|
||||
acalaExtraBonusFlow: AcalaExtraBonusFlow,
|
||||
acalaSelectContributeCustomization: AcalaSelectContributeCustomization,
|
||||
acalaConfirmContributeCustomization: AcalaConfirmContributeCustomization,
|
||||
): CustomContributeFactory = AcalaContributeFactory(
|
||||
submitter = submitter,
|
||||
extraBonusFlow = acalaExtraBonusFlow,
|
||||
selectContributeCustomization = acalaSelectContributeCustomization,
|
||||
confirmContributeCustomization = acalaConfirmContributeCustomization
|
||||
)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
@IntoSet
|
||||
fun provideKaruraFactory(
|
||||
submitter: AcalaContributeSubmitter,
|
||||
karuraExtraBonusFlow: KaruraExtraBonusFlow,
|
||||
): CustomContributeFactory = KaruraContributeFactory(
|
||||
submitter = submitter,
|
||||
extraBonusFlow = karuraExtraBonusFlow
|
||||
)
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.di.customCrowdloan.astar
|
||||
|
||||
import android.content.Context
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.di.customCrowdloan.CustomContributeFactory
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.di.customCrowdloan.ExtraBonusFlow
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.custom.astar.AstarContributeInteractor
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.astar.AstarContributeSubmitter
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.astar.AstarContributeViewState
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.model.CustomContributePayload
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.referral.ReferralContributeView
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import java.math.BigDecimal
|
||||
|
||||
private const val ASTAR_TERMS_LINK = "https://docs.google.com/document/d/1vKZrDqSdh706hg0cqJ_NnxfRSlXR2EThVHwoRl0nAkk"
|
||||
private const val NOVA_REFERRAL_CODE = "1ChFWeNRLarAPRCTM3bfJmncJbSAbSS9yqjueWz7jX7iTVZ"
|
||||
private val ASTAR_BONUS = 0.01.toBigDecimal() // 1%
|
||||
|
||||
class AstarExtraBonusFlow(
|
||||
private val interactor: AstarContributeInteractor,
|
||||
private val resourceManager: ResourceManager,
|
||||
private val termsLink: String = ASTAR_TERMS_LINK,
|
||||
private val novaReferralCode: String = NOVA_REFERRAL_CODE,
|
||||
private val bonusPercentage: BigDecimal = ASTAR_BONUS,
|
||||
) : ExtraBonusFlow {
|
||||
|
||||
override fun createViewState(scope: CoroutineScope, payload: CustomContributePayload): AstarContributeViewState {
|
||||
return AstarContributeViewState(
|
||||
interactor = interactor,
|
||||
customContributePayload = payload,
|
||||
resourceManager = resourceManager,
|
||||
defaultReferralCode = novaReferralCode,
|
||||
bonusPercentage = bonusPercentage,
|
||||
termsLink = termsLink
|
||||
)
|
||||
}
|
||||
|
||||
override fun createView(context: Context) = ReferralContributeView(context)
|
||||
}
|
||||
|
||||
class AstarContributeFactory(
|
||||
override val submitter: AstarContributeSubmitter,
|
||||
override val extraBonusFlow: AstarExtraBonusFlow,
|
||||
) : CustomContributeFactory {
|
||||
|
||||
override val flowType = "Astar"
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.di.customCrowdloan.astar
|
||||
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.multibindings.IntoSet
|
||||
import io.novafoundation.nova.common.di.scope.FeatureScope
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.CrowdloanSharedState
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.di.customCrowdloan.CustomContributeFactory
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.custom.astar.AstarContributeInteractor
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.astar.AstarContributeSubmitter
|
||||
|
||||
@Module
|
||||
class AstarContributionModule {
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideAstarInteractor(
|
||||
selectedAssetSharedState: CrowdloanSharedState,
|
||||
) = AstarContributeInteractor(selectedAssetSharedState)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideAstarSubmitter(
|
||||
interactor: AstarContributeInteractor,
|
||||
) = AstarContributeSubmitter(interactor)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideAstarExtraFlow(
|
||||
interactor: AstarContributeInteractor,
|
||||
resourceManager: ResourceManager,
|
||||
) = AstarExtraBonusFlow(
|
||||
interactor = interactor,
|
||||
resourceManager = resourceManager
|
||||
)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
@IntoSet
|
||||
fun provideAstarFactory(
|
||||
submitter: AstarContributeSubmitter,
|
||||
astarExtraBonusFlow: AstarExtraBonusFlow,
|
||||
): CustomContributeFactory = AstarContributeFactory(
|
||||
submitter = submitter,
|
||||
extraBonusFlow = astarExtraBonusFlow
|
||||
)
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.di.customCrowdloan.bifrost
|
||||
|
||||
import android.content.Context
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.BuildConfig
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.di.customCrowdloan.CustomContributeFactory
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.di.customCrowdloan.ExtraBonusFlow
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.custom.bifrost.BifrostContributeInteractor
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.bifrost.BifrostContributeSubmitter
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.bifrost.BifrostContributeViewState
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.model.CustomContributePayload
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.referral.ReferralContributeView
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
||||
private val BIFROST_BONUS_MULTIPLIER = 0.05.toBigDecimal() // 5%
|
||||
|
||||
class BifrostExtraBonusFlow(
|
||||
private val interactor: BifrostContributeInteractor,
|
||||
private val resourceManager: ResourceManager,
|
||||
private val termsLink: String = BuildConfig.BIFROST_TERMS_LINKS,
|
||||
) : ExtraBonusFlow {
|
||||
|
||||
override fun createViewState(scope: CoroutineScope, payload: CustomContributePayload): BifrostContributeViewState {
|
||||
return BifrostContributeViewState(
|
||||
interactor = interactor,
|
||||
customContributePayload = payload,
|
||||
resourceManager = resourceManager,
|
||||
termsLink = termsLink,
|
||||
bonusPercentage = BIFROST_BONUS_MULTIPLIER,
|
||||
bifrostInteractor = interactor
|
||||
)
|
||||
}
|
||||
|
||||
override fun createView(context: Context) = ReferralContributeView(context)
|
||||
}
|
||||
|
||||
class BifrostContributeFactory(
|
||||
override val submitter: BifrostContributeSubmitter,
|
||||
override val extraBonusFlow: BifrostExtraBonusFlow,
|
||||
) : CustomContributeFactory {
|
||||
|
||||
override val flowType = "Bifrost"
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.di.customCrowdloan.bifrost
|
||||
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.multibindings.IntoSet
|
||||
import io.novafoundation.nova.common.data.network.HttpExceptionHandler
|
||||
import io.novafoundation.nova.common.data.network.NetworkApiCreator
|
||||
import io.novafoundation.nova.common.di.scope.FeatureScope
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.BuildConfig
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.network.api.bifrost.BifrostApi
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.di.customCrowdloan.CustomContributeFactory
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.custom.bifrost.BifrostContributeInteractor
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.bifrost.BifrostContributeSubmitter
|
||||
|
||||
@Module
|
||||
class BifrostContributionModule {
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideBifrostApi(networkApiCreator: NetworkApiCreator): BifrostApi {
|
||||
return networkApiCreator.create(BifrostApi::class.java, customBaseUrl = BifrostApi.BASE_URL)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideBifrostInteractor(
|
||||
bifrostApi: BifrostApi,
|
||||
httpExceptionHandler: HttpExceptionHandler,
|
||||
) = BifrostContributeInteractor(BuildConfig.BIFROST_NOVA_REFERRAL, bifrostApi, httpExceptionHandler)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideBifrostSubmitter(
|
||||
interactor: BifrostContributeInteractor,
|
||||
) = BifrostContributeSubmitter(interactor)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideBifrostExtraFlow(
|
||||
interactor: BifrostContributeInteractor,
|
||||
resourceManager: ResourceManager,
|
||||
) = BifrostExtraBonusFlow(interactor, resourceManager)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
@IntoSet
|
||||
fun provideBifrostFactory(
|
||||
submitter: BifrostContributeSubmitter,
|
||||
bifrostExtraBonusFlow: BifrostExtraBonusFlow,
|
||||
): CustomContributeFactory = BifrostContributeFactory(
|
||||
submitter,
|
||||
bifrostExtraBonusFlow
|
||||
)
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.di.customCrowdloan.moonbeam
|
||||
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.di.customCrowdloan.CustomContributeFactory
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.custom.moonbeam.MoonbeamPrivateSignatureProvider
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.CustomContributeSubmitter
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.moonbeam.MoonbeamStartFlowInterceptor
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.moonbeam.main.ConfirmContributeMoonbeamCustomization
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.moonbeam.main.SelectContributeMoonbeamCustomization
|
||||
|
||||
class MoonbeamContributeFactory(
|
||||
override val submitter: CustomContributeSubmitter,
|
||||
override val startFlowInterceptor: MoonbeamStartFlowInterceptor,
|
||||
override val privateCrowdloanSignatureProvider: MoonbeamPrivateSignatureProvider,
|
||||
override val selectContributeCustomization: SelectContributeMoonbeamCustomization,
|
||||
override val confirmContributeCustomization: ConfirmContributeMoonbeamCustomization,
|
||||
) : CustomContributeFactory {
|
||||
|
||||
override val flowType: String = "Moonbeam"
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.di.customCrowdloan.moonbeam
|
||||
|
||||
import coil.ImageLoader
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.multibindings.IntoSet
|
||||
import io.novafoundation.nova.common.address.AddressIconGenerator
|
||||
import io.novafoundation.nova.common.data.network.HttpExceptionHandler
|
||||
import io.novafoundation.nova.common.data.network.NetworkApiCreator
|
||||
import io.novafoundation.nova.common.di.scope.FeatureScope
|
||||
import io.novafoundation.nova.common.mixin.api.CustomDialogDisplayer
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService
|
||||
import io.novafoundation.nova.feature_account_api.data.signer.SignerProvider
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.CrowdloanSharedState
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.network.api.moonbeam.MoonbeamApi
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.di.customCrowdloan.CustomContributeFactory
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.custom.moonbeam.MoonbeamCrowdloanInteractor
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.custom.moonbeam.MoonbeamPrivateSignatureProvider
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.CrowdloanRouter
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.moonbeam.MoonbeamCrowdloanSubmitter
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.moonbeam.MoonbeamStartFlowInterceptor
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.moonbeam.main.ConfirmContributeMoonbeamCustomization
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.moonbeam.main.MoonbeamMainFlowCustomViewStateFactory
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.moonbeam.main.SelectContributeMoonbeamCustomization
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
|
||||
@Module
|
||||
class MoonbeamContributionModule {
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideMoonbeamApi(
|
||||
networkApiCreator: NetworkApiCreator,
|
||||
) = networkApiCreator.create(MoonbeamApi::class.java)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideMoonbeamInteractor(
|
||||
accountRepository: AccountRepository,
|
||||
extrinsicService: ExtrinsicService,
|
||||
moonbeamApi: MoonbeamApi,
|
||||
selectedAssetSharedState: CrowdloanSharedState,
|
||||
httpExceptionHandler: HttpExceptionHandler,
|
||||
chainRegistry: ChainRegistry,
|
||||
signerProvider: SignerProvider
|
||||
) = MoonbeamCrowdloanInteractor(
|
||||
accountRepository = accountRepository,
|
||||
extrinsicService = extrinsicService,
|
||||
moonbeamApi = moonbeamApi,
|
||||
selectedChainAssetState = selectedAssetSharedState,
|
||||
chainRegistry = chainRegistry,
|
||||
httpExceptionHandler = httpExceptionHandler,
|
||||
signerProvider = signerProvider
|
||||
)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideMoonbeamSubmitter(interactor: MoonbeamCrowdloanInteractor) = MoonbeamCrowdloanSubmitter(interactor)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideMoonbeamStartFlowInterceptor(
|
||||
router: CrowdloanRouter,
|
||||
resourceManager: ResourceManager,
|
||||
interactor: MoonbeamCrowdloanInteractor,
|
||||
customDialogDisplayer: CustomDialogDisplayer.Presentation,
|
||||
) = MoonbeamStartFlowInterceptor(
|
||||
crowdloanRouter = router,
|
||||
resourceManager = resourceManager,
|
||||
moonbeamInteractor = interactor,
|
||||
customDialogDisplayer = customDialogDisplayer,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideMoonbeamPrivateSignatureProvider(
|
||||
moonbeamApi: MoonbeamApi,
|
||||
httpExceptionHandler: HttpExceptionHandler,
|
||||
) = MoonbeamPrivateSignatureProvider(moonbeamApi, httpExceptionHandler)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideSelectContributeMoonbeamViewStateFactory(
|
||||
interactor: MoonbeamCrowdloanInteractor,
|
||||
resourceManager: ResourceManager,
|
||||
iconGenerator: AddressIconGenerator,
|
||||
) = MoonbeamMainFlowCustomViewStateFactory(interactor, resourceManager, iconGenerator)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideSelectContributeMoonbeamCustomization(
|
||||
viewStateFactory: MoonbeamMainFlowCustomViewStateFactory,
|
||||
imageLoader: ImageLoader,
|
||||
) = SelectContributeMoonbeamCustomization(viewStateFactory, imageLoader)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideConfirmContributeMoonbeamCustomization(
|
||||
viewStateFactory: MoonbeamMainFlowCustomViewStateFactory,
|
||||
imageLoader: ImageLoader,
|
||||
) = ConfirmContributeMoonbeamCustomization(viewStateFactory, imageLoader)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
@IntoSet
|
||||
fun provideMoonbeamFactory(
|
||||
submitter: MoonbeamCrowdloanSubmitter,
|
||||
moonbeamStartFlowInterceptor: MoonbeamStartFlowInterceptor,
|
||||
privateSignatureProvider: MoonbeamPrivateSignatureProvider,
|
||||
selectContributeMoonbeamCustomization: SelectContributeMoonbeamCustomization,
|
||||
confirmContributeMoonbeamCustomization: ConfirmContributeMoonbeamCustomization,
|
||||
): CustomContributeFactory = MoonbeamContributeFactory(
|
||||
submitter = submitter,
|
||||
startFlowInterceptor = moonbeamStartFlowInterceptor,
|
||||
privateCrowdloanSignatureProvider = privateSignatureProvider,
|
||||
selectContributeCustomization = selectContributeMoonbeamCustomization,
|
||||
confirmContributeCustomization = confirmContributeMoonbeamCustomization
|
||||
)
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.di.customCrowdloan.parallel
|
||||
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import io.novafoundation.nova.common.data.network.NetworkApiCreator
|
||||
import io.novafoundation.nova.common.di.scope.FeatureScope
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.network.api.parallel.ParallelApi
|
||||
|
||||
@Module
|
||||
class ParallelContributionModule {
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideParallelApi(
|
||||
networkApiCreator: NetworkApiCreator,
|
||||
) = networkApiCreator.create(ParallelApi::class.java, customBaseUrl = ParallelApi.BASE_URL)
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.di.validations
|
||||
|
||||
import javax.inject.Qualifier
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class Select
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class Confirm
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.di.validations
|
||||
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import io.novafoundation.nova.common.di.scope.FeatureScope
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.repository.CrowdloanRepository
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.di.customCrowdloan.CustomContributeManager
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.validations.BonusAppliedValidation
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.validations.CapExceededValidation
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.validations.ContributeEnoughToPayFeesValidation
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.validations.ContributeExistentialDepositValidation
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.validations.ContributeValidation
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.validations.ContributeValidationFailure
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.validations.CrowdloanNotEndedValidation
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.validations.DefaultMinContributionValidation
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.validations.PublicCrowdloanValidation
|
||||
import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletConstants
|
||||
import io.novafoundation.nova.feature_wallet_api.domain.model.amountFromPlanks
|
||||
import io.novafoundation.nova.feature_wallet_api.domain.model.balanceCountedTowardsED
|
||||
import io.novafoundation.nova.feature_wallet_api.domain.validation.EnoughAmountToTransferValidationGeneric
|
||||
import io.novafoundation.nova.runtime.repository.ChainStateRepository
|
||||
|
||||
@Module
|
||||
class ContributeValidationsModule {
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideFeesValidation(): ContributeEnoughToPayFeesValidation = EnoughAmountToTransferValidationGeneric(
|
||||
feeExtractor = { it.fee },
|
||||
availableBalanceProducer = { it.asset.transferable },
|
||||
extraAmountExtractor = { it.contributionAmount },
|
||||
errorProducer = { ContributeValidationFailure.CannotPayFees }
|
||||
)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideMinContributionValidation(
|
||||
crowdloanRepository: CrowdloanRepository,
|
||||
) = DefaultMinContributionValidation(crowdloanRepository)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideCapExceededValidation() = CapExceededValidation()
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideCrowdloanNotEndedValidation(
|
||||
chainStateRepository: ChainStateRepository,
|
||||
crowdloanRepository: CrowdloanRepository,
|
||||
) = CrowdloanNotEndedValidation(chainStateRepository, crowdloanRepository)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideExistentialWarningValidation(
|
||||
walletConstants: WalletConstants,
|
||||
) = ContributeExistentialDepositValidation(
|
||||
countableTowardsEdBalance = { it.asset.balanceCountedTowardsED() },
|
||||
feeProducer = { listOf(it.fee) },
|
||||
extraAmountProducer = { it.contributionAmount },
|
||||
existentialDeposit = {
|
||||
val inPlanks = walletConstants.existentialDeposit(it.asset.token.configuration.chainId)
|
||||
|
||||
it.asset.token.amountFromPlanks(inPlanks)
|
||||
},
|
||||
errorProducer = { _, _ -> ContributeValidationFailure.ExistentialDepositCrossed },
|
||||
)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun providePublicCrowdloanValidation(
|
||||
customContributeManager: CustomContributeManager,
|
||||
) = PublicCrowdloanValidation(customContributeManager)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideBonusAppliedValidation(
|
||||
customContributeManager: CustomContributeManager,
|
||||
) = BonusAppliedValidation(customContributeManager)
|
||||
|
||||
@Provides
|
||||
@Select
|
||||
@FeatureScope
|
||||
fun provideSelectContributeValidationSet(
|
||||
feesValidation: ContributeEnoughToPayFeesValidation,
|
||||
minContributionValidation: DefaultMinContributionValidation,
|
||||
capExceededValidation: CapExceededValidation,
|
||||
crowdloanNotEndedValidation: CrowdloanNotEndedValidation,
|
||||
contributeExistentialDepositValidation: ContributeExistentialDepositValidation,
|
||||
publicCrowdloanValidation: PublicCrowdloanValidation,
|
||||
bonusAppliedValidation: BonusAppliedValidation,
|
||||
): Set<ContributeValidation> = setOf(
|
||||
feesValidation,
|
||||
minContributionValidation,
|
||||
capExceededValidation,
|
||||
crowdloanNotEndedValidation,
|
||||
contributeExistentialDepositValidation,
|
||||
publicCrowdloanValidation,
|
||||
bonusAppliedValidation
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Confirm
|
||||
@FeatureScope
|
||||
fun provideConfirmContributeValidationSet(
|
||||
feesValidation: ContributeEnoughToPayFeesValidation,
|
||||
minContributionValidation: DefaultMinContributionValidation,
|
||||
capExceededValidation: CapExceededValidation,
|
||||
crowdloanNotEndedValidation: CrowdloanNotEndedValidation,
|
||||
contributeExistentialDepositValidation: ContributeExistentialDepositValidation,
|
||||
publicCrowdloanValidation: PublicCrowdloanValidation,
|
||||
): Set<ContributeValidation> = setOf(
|
||||
feesValidation,
|
||||
minContributionValidation,
|
||||
capExceededValidation,
|
||||
crowdloanNotEndedValidation,
|
||||
contributeExistentialDepositValidation,
|
||||
publicCrowdloanValidation,
|
||||
)
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.di.validations
|
||||
|
||||
import dagger.Module
|
||||
|
||||
@Module(
|
||||
includes = [
|
||||
ContributeValidationsModule::class,
|
||||
MoonbeamTermsValidationsModule::class
|
||||
]
|
||||
)
|
||||
class CrowdloansValidationsModule
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.di.validations
|
||||
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.multibindings.IntoSet
|
||||
import io.novafoundation.nova.common.di.scope.FeatureScope
|
||||
import io.novafoundation.nova.common.validation.CompositeValidation
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.validations.custom.moonbeam.MoonbeamTermsValidation
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.validations.custom.moonbeam.MoonbeamTermsValidationFailure
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.validations.custom.moonbeam.MoonbeamTermsValidationSystem
|
||||
import io.novafoundation.nova.feature_wallet_api.domain.validation.EnoughAmountToTransferValidationGeneric
|
||||
|
||||
@Module
|
||||
class MoonbeamTermsValidationsModule {
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
@FeatureScope
|
||||
fun provideFeesValidation(): MoonbeamTermsValidation = EnoughAmountToTransferValidationGeneric(
|
||||
feeExtractor = { it.fee },
|
||||
availableBalanceProducer = { it.asset.transferable },
|
||||
errorProducer = { MoonbeamTermsValidationFailure.CANNOT_PAY_FEES }
|
||||
)
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideValidationSystem(
|
||||
contributeValidations: @JvmSuppressWildcards Set<MoonbeamTermsValidation>
|
||||
) = MoonbeamTermsValidationSystem(
|
||||
validation = CompositeValidation(contributeValidations.toList())
|
||||
)
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.domain.claimContributions
|
||||
|
||||
import io.novafoundation.nova.common.di.scope.ScreenScope
|
||||
import io.novafoundation.nova.common.utils.flowOfAll
|
||||
import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.TransactionOrigin
|
||||
import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService
|
||||
import io.novafoundation.nova.feature_account_api.data.extrinsic.execution.ExtrinsicExecutionResult
|
||||
import io.novafoundation.nova.feature_account_api.data.extrinsic.execution.requireOk
|
||||
import io.novafoundation.nova.feature_account_api.data.model.SubmissionFee
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.repository.ContributionsRepository
|
||||
import io.novafoundation.nova.feature_crowdloan_api.domain.contributions.Contribution
|
||||
import io.novafoundation.nova.feature_crowdloan_api.domain.contributions.ContributionClaimStatus
|
||||
import io.novafoundation.nova.feature_crowdloan_api.domain.contributions.ContributionsWithTotalAmount
|
||||
import io.novafoundation.nova.feature_crowdloan_api.domain.contributions.claimStatusOf
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.CrowdloanSharedState
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.network.blockhain.extrinsic.claimContribution
|
||||
import io.novafoundation.nova.runtime.ext.timelineChainIdOrSelf
|
||||
import io.novafoundation.nova.runtime.repository.ChainStateRepository
|
||||
import io.novafoundation.nova.runtime.repository.blockDurationEstimatorFlow
|
||||
import io.novafoundation.nova.runtime.state.chain
|
||||
import io.novafoundation.nova.runtime.state.chainAndAsset
|
||||
import io.novasama.substrate_sdk_android.runtime.AccountId
|
||||
import io.novasama.substrate_sdk_android.runtime.extrinsic.builder.ExtrinsicBuilder
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.map
|
||||
import javax.inject.Inject
|
||||
|
||||
@ScreenScope
|
||||
class ClaimContributionsInteractor @Inject constructor(
|
||||
private val accountRepository: AccountRepository,
|
||||
private val crowdloanState: CrowdloanSharedState,
|
||||
private val chainStateRepository: ChainStateRepository,
|
||||
private val contributionsRepository: ContributionsRepository,
|
||||
private val extrinsicService: ExtrinsicService,
|
||||
) {
|
||||
|
||||
fun claimableContributions(): Flow<ContributionsWithTotalAmount<Contribution>> {
|
||||
return flowOfAll {
|
||||
val account = accountRepository.getSelectedMetaAccount()
|
||||
val (chain, asset) = crowdloanState.chainAndAsset()
|
||||
|
||||
combine(
|
||||
chainStateRepository.blockDurationEstimatorFlow(chain.timelineChainIdOrSelf()),
|
||||
contributionsRepository.observeContributions(account, chain, asset)
|
||||
) { blockDurationEstimator, contributions ->
|
||||
contributions.filter {
|
||||
val claimStatus = blockDurationEstimator.claimStatusOf(it)
|
||||
claimStatus is ContributionClaimStatus.Claimable
|
||||
}
|
||||
}
|
||||
.map { claimableContributions -> ContributionsWithTotalAmount.fromContributions(claimableContributions) }
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun estimateFee(contributions: List<Contribution>): SubmissionFee {
|
||||
val chain = crowdloanState.chain()
|
||||
|
||||
return extrinsicService.estimateFee(chain, TransactionOrigin.SelectedWallet) { context ->
|
||||
val depositor = context.submissionOrigin.executingAccount
|
||||
claim(contributions, depositor)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun claim(contributions: List<Contribution>): Result<ExtrinsicExecutionResult> {
|
||||
val chain = crowdloanState.chain()
|
||||
|
||||
return extrinsicService.submitExtrinsicAndAwaitExecution(chain, TransactionOrigin.SelectedWallet) { context ->
|
||||
val depositor = context.submissionOrigin.executingAccount
|
||||
claim(contributions, depositor)
|
||||
}
|
||||
.requireOk()
|
||||
}
|
||||
|
||||
private fun ExtrinsicBuilder.claim(contributions: List<Contribution>, depositor: AccountId) {
|
||||
contributions.forEach { contribution ->
|
||||
claimContribution(contribution.paraId, contribution.unlockBlock, depositor)
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.domain.claimContributions.validation
|
||||
|
||||
import io.novafoundation.nova.feature_wallet_api.domain.validation.NotEnoughToPayFeesError
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import java.math.BigDecimal
|
||||
|
||||
sealed class ClaimContributionValidationFailure {
|
||||
|
||||
class NotEnoughBalanceToPayFees(
|
||||
override val chainAsset: Chain.Asset,
|
||||
override val maxUsable: BigDecimal,
|
||||
override val fee: BigDecimal
|
||||
) : ClaimContributionValidationFailure(), NotEnoughToPayFeesError
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.domain.claimContributions.validation
|
||||
|
||||
import io.novafoundation.nova.feature_account_api.data.model.Fee
|
||||
import io.novafoundation.nova.feature_wallet_api.domain.model.Asset
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
|
||||
class ClaimContributionValidationPayload(
|
||||
val fee: Fee,
|
||||
val asset: Asset,
|
||||
)
|
||||
|
||||
val ClaimContributionValidationPayload.chainId: ChainId
|
||||
get() = asset.token.configuration.chainId
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.domain.claimContributions.validation
|
||||
|
||||
import io.novafoundation.nova.common.validation.ValidationSystem
|
||||
import io.novafoundation.nova.common.validation.ValidationSystemBuilder
|
||||
import io.novafoundation.nova.feature_wallet_api.domain.validation.sufficientBalance
|
||||
|
||||
typealias ClaimContributionValidationSystem = ValidationSystem<ClaimContributionValidationPayload, ClaimContributionValidationFailure>
|
||||
typealias ClaimContributionValidationSystemBuilder = ValidationSystemBuilder<ClaimContributionValidationPayload, ClaimContributionValidationFailure>
|
||||
|
||||
fun ValidationSystem.Companion.claimContribution(): ClaimContributionValidationSystem = ValidationSystem {
|
||||
enoughToPayFees()
|
||||
}
|
||||
|
||||
private fun ClaimContributionValidationSystemBuilder.enoughToPayFees() {
|
||||
sufficientBalance(
|
||||
fee = { it.fee },
|
||||
available = { it.asset.transferable },
|
||||
error = {
|
||||
ClaimContributionValidationFailure.NotEnoughBalanceToPayFees(
|
||||
chainAsset = it.payload.asset.token.configuration,
|
||||
maxUsable = it.maxUsable,
|
||||
fee = it.fee
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.domain.contribute
|
||||
|
||||
import android.os.Parcelable
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.ParaId
|
||||
import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.TransactionOrigin
|
||||
import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService
|
||||
import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicSubmission
|
||||
import io.novafoundation.nova.feature_account_api.data.model.Fee
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.addressIn
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.repository.ContributionsRepository
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.repository.CrowdloanRepository
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.repository.ParachainMetadata
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.CrowdloanSharedState
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.network.blockhain.extrinsic.contribute
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.di.customCrowdloan.CustomContributeManager
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.custom.PrivateCrowdloanSignatureProvider.Mode
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.main.Crowdloan
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.BonusPayload
|
||||
import io.novafoundation.nova.feature_wallet_api.domain.model.planksFromAmount
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novafoundation.nova.runtime.repository.ChainStateRepository
|
||||
import io.novafoundation.nova.runtime.state.chainAndAsset
|
||||
import io.novasama.substrate_sdk_android.runtime.extrinsic.builder.ExtrinsicBuilder
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.math.BigDecimal
|
||||
import java.math.BigInteger
|
||||
|
||||
typealias OnChainSubmission = suspend ExtrinsicBuilder.() -> Unit
|
||||
|
||||
class CrowdloanContributeInteractor(
|
||||
private val extrinsicService: ExtrinsicService,
|
||||
private val accountRepository: AccountRepository,
|
||||
private val chainStateRepository: ChainStateRepository,
|
||||
private val customContributeManager: CustomContributeManager,
|
||||
private val crowdloanSharedState: CrowdloanSharedState,
|
||||
private val crowdloanRepository: CrowdloanRepository,
|
||||
private val contributionsRepository: ContributionsRepository
|
||||
) {
|
||||
|
||||
fun crowdloanStateFlow(
|
||||
parachainId: ParaId,
|
||||
parachainMetadata: ParachainMetadata?,
|
||||
): Flow<Crowdloan> = emptyFlow() // this flow is no longer accessible and deprecated. We will remove entire crowdloan feature soon
|
||||
|
||||
suspend fun estimateFee(
|
||||
crowdloan: Crowdloan,
|
||||
contribution: BigDecimal,
|
||||
bonusPayload: BonusPayload?,
|
||||
customizationPayload: Parcelable?,
|
||||
): Fee = formingSubmission(
|
||||
crowdloan = crowdloan,
|
||||
contribution = contribution,
|
||||
bonusPayload = bonusPayload,
|
||||
customizationPayload = customizationPayload,
|
||||
toCalculateFee = true
|
||||
) { submission, chain, _ ->
|
||||
extrinsicService.estimateFee(chain, TransactionOrigin.SelectedWallet, formExtrinsic = { submission() })
|
||||
}
|
||||
|
||||
suspend fun contribute(
|
||||
crowdloan: Crowdloan,
|
||||
contribution: BigDecimal,
|
||||
bonusPayload: BonusPayload?,
|
||||
customizationPayload: Parcelable?,
|
||||
): Result<ExtrinsicSubmission> = runCatching {
|
||||
crowdloan.parachainMetadata?.customFlow?.let {
|
||||
customContributeManager.getFactoryOrNull(it)?.submitter?.submitOffChain(customizationPayload, bonusPayload, contribution)
|
||||
}
|
||||
|
||||
formingSubmission(
|
||||
crowdloan = crowdloan,
|
||||
contribution = contribution,
|
||||
bonusPayload = bonusPayload,
|
||||
toCalculateFee = false,
|
||||
customizationPayload = customizationPayload
|
||||
) { submission, chain, account ->
|
||||
extrinsicService.submitExtrinsic(chain, TransactionOrigin.Wallet(account)) { submission() }
|
||||
}.getOrThrow()
|
||||
}
|
||||
|
||||
private suspend fun <T> formingSubmission(
|
||||
crowdloan: Crowdloan,
|
||||
contribution: BigDecimal,
|
||||
bonusPayload: BonusPayload?,
|
||||
customizationPayload: Parcelable?,
|
||||
toCalculateFee: Boolean,
|
||||
finalAction: suspend (OnChainSubmission, Chain, MetaAccount) -> T,
|
||||
): T = withContext(Dispatchers.Default) {
|
||||
val (chain, chainAsset) = crowdloanSharedState.chainAndAsset()
|
||||
val contributionInPlanks = chainAsset.planksFromAmount(contribution)
|
||||
val account = accountRepository.getSelectedMetaAccount()
|
||||
|
||||
val privateSignature = crowdloan.parachainMetadata?.customFlow?.let {
|
||||
val previousContribution = crowdloan.myContribution?.amountInPlanks ?: BigInteger.ZERO
|
||||
|
||||
val signatureProvider = customContributeManager.getFactoryOrNull(it)?.privateCrowdloanSignatureProvider
|
||||
val address = account.addressIn(chain)!!
|
||||
|
||||
signatureProvider?.provideSignature(
|
||||
chainMetadata = crowdloan.parachainMetadata,
|
||||
previousContribution = previousContribution,
|
||||
newContribution = contributionInPlanks,
|
||||
address = address,
|
||||
mode = if (toCalculateFee) Mode.FEE else Mode.SUBMIT
|
||||
)
|
||||
}
|
||||
|
||||
val submitter = crowdloan.parachainMetadata?.customFlow?.let {
|
||||
customContributeManager.getFactoryOrNull(it)?.submitter
|
||||
}
|
||||
|
||||
val submission: OnChainSubmission = {
|
||||
contribute(crowdloan.parachainId, contributionInPlanks, privateSignature)
|
||||
|
||||
submitter?.let {
|
||||
val injection = if (toCalculateFee) submitter::injectFeeCalculation else submitter::injectOnChainSubmission
|
||||
|
||||
injection(crowdloan, customizationPayload, bonusPayload, contribution, this)
|
||||
}
|
||||
}
|
||||
|
||||
finalAction(submission, chain, account)
|
||||
}
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.domain.contribute
|
||||
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.BlockNumber
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.network.blockhain.binding.FundInfo
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.repository.LeasePeriodToBlocksConverter
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.repository.ParachainMetadata
|
||||
import io.novafoundation.nova.feature_crowdloan_api.domain.contributions.Contribution
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.main.Crowdloan
|
||||
import java.math.BigInteger
|
||||
import java.math.MathContext
|
||||
|
||||
fun mapFundInfoToCrowdloan(
|
||||
fundInfo: FundInfo,
|
||||
parachainMetadata: ParachainMetadata?,
|
||||
parachainId: BigInteger,
|
||||
currentBlockNumber: BlockNumber,
|
||||
expectedBlockTimeInMillis: BigInteger,
|
||||
leasePeriodToBlocksConverter: LeasePeriodToBlocksConverter,
|
||||
contribution: Contribution?,
|
||||
hasWonAuction: Boolean,
|
||||
): Crowdloan {
|
||||
val leasePeriodInMillis = leasePeriodInMillis(leasePeriodToBlocksConverter, currentBlockNumber, fundInfo.lastSlot, expectedBlockTimeInMillis)
|
||||
|
||||
val state = if (isCrowdloanActive(fundInfo, currentBlockNumber, leasePeriodToBlocksConverter, hasWonAuction)) {
|
||||
val remainingTime = expectedRemainingTime(currentBlockNumber, fundInfo.end, expectedBlockTimeInMillis)
|
||||
|
||||
Crowdloan.State.Active(remainingTime)
|
||||
} else {
|
||||
Crowdloan.State.Finished
|
||||
}
|
||||
|
||||
return Crowdloan(
|
||||
parachainMetadata = parachainMetadata,
|
||||
raisedFraction = fundInfo.raised.toBigDecimal().divide(fundInfo.cap.toBigDecimal(), MathContext.DECIMAL32),
|
||||
parachainId = parachainId,
|
||||
leasePeriodInMillis = leasePeriodInMillis,
|
||||
leasedUntilInMillis = System.currentTimeMillis() + leasePeriodInMillis,
|
||||
state = state,
|
||||
fundInfo = fundInfo,
|
||||
myContribution = contribution
|
||||
)
|
||||
}
|
||||
|
||||
private fun isCrowdloanActive(
|
||||
fundInfo: FundInfo,
|
||||
currentBlockNumber: BigInteger,
|
||||
leasePeriodToBlocksConverter: LeasePeriodToBlocksConverter,
|
||||
hasWonAuction: Boolean,
|
||||
): Boolean {
|
||||
return currentBlockNumber < fundInfo.end && // crowdloan is not ended
|
||||
// first slot is not yet passed
|
||||
leasePeriodToBlocksConverter.leaseIndexFromBlock(currentBlockNumber) <= fundInfo.firstSlot &&
|
||||
// cap is not reached
|
||||
fundInfo.raised < fundInfo.cap &&
|
||||
// crowdloan considered closed if parachain already won auction
|
||||
!hasWonAuction
|
||||
}
|
||||
|
||||
fun leasePeriodInMillis(
|
||||
leasePeriodToBlocksConverter: LeasePeriodToBlocksConverter,
|
||||
currentBlockNumber: BigInteger,
|
||||
endingLeasePeriod: BigInteger,
|
||||
expectedBlockTimeInMillis: BigInteger
|
||||
): Long {
|
||||
val unlockedAtPeriod = endingLeasePeriod + BigInteger.ONE // next period after end one
|
||||
val unlockedAtBlock = leasePeriodToBlocksConverter.startBlockFor(unlockedAtPeriod)
|
||||
|
||||
return expectedRemainingTime(
|
||||
currentBlockNumber,
|
||||
unlockedAtBlock,
|
||||
expectedBlockTimeInMillis
|
||||
)
|
||||
}
|
||||
|
||||
private fun expectedRemainingTime(
|
||||
currentBlock: BlockNumber,
|
||||
targetBlock: BlockNumber,
|
||||
expectedBlockTimeInMillis: BigInteger,
|
||||
): Long {
|
||||
val blockDifference = targetBlock - currentBlock
|
||||
val expectedTimeDifference = blockDifference * expectedBlockTimeInMillis
|
||||
|
||||
return expectedTimeDifference.toLong()
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.custom
|
||||
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.repository.ParachainMetadata
|
||||
import java.math.BigInteger
|
||||
|
||||
interface PrivateCrowdloanSignatureProvider {
|
||||
|
||||
enum class Mode {
|
||||
FEE, SUBMIT
|
||||
}
|
||||
|
||||
suspend fun provideSignature(
|
||||
chainMetadata: ParachainMetadata,
|
||||
previousContribution: BigInteger,
|
||||
newContribution: BigInteger,
|
||||
address: String,
|
||||
mode: Mode,
|
||||
): Any?
|
||||
}
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.custom.acala
|
||||
|
||||
import io.novafoundation.nova.common.base.BaseException
|
||||
import io.novafoundation.nova.common.data.network.HttpExceptionHandler
|
||||
import io.novafoundation.nova.common.utils.asHexString
|
||||
import io.novafoundation.nova.feature_account_api.data.signer.SignerProvider
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.network.api.acala.AcalaApi
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.network.api.acala.AcalaDirectContributeRequest
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.network.api.acala.AcalaLiquidContributeRequest
|
||||
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.nativeTransfer
|
||||
import io.novafoundation.nova.feature_wallet_api.domain.model.planksFromAmount
|
||||
import io.novafoundation.nova.runtime.ext.ChainGeneses
|
||||
import io.novafoundation.nova.runtime.ext.accountIdOf
|
||||
import io.novafoundation.nova.runtime.ext.addressOf
|
||||
import io.novafoundation.nova.runtime.extrinsic.systemRemarkWithEvent
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novafoundation.nova.runtime.state.SingleAssetSharedState
|
||||
import io.novafoundation.nova.runtime.state.chain
|
||||
import io.novafoundation.nova.runtime.state.chainAndAsset
|
||||
import io.novafoundation.nova.runtime.state.chainAsset
|
||||
import io.novasama.substrate_sdk_android.runtime.extrinsic.builder.ExtrinsicBuilder
|
||||
import io.novasama.substrate_sdk_android.runtime.extrinsic.signer.SignerPayloadRaw
|
||||
import io.novasama.substrate_sdk_android.runtime.extrinsic.signer.fromUtf8
|
||||
import java.math.BigDecimal
|
||||
|
||||
class AcalaContributeInteractor(
|
||||
private val acalaApi: AcalaApi,
|
||||
private val httpExceptionHandler: HttpExceptionHandler,
|
||||
private val accountRepository: AccountRepository,
|
||||
private val chainRegistry: ChainRegistry,
|
||||
private val selectedAssetState: SingleAssetSharedState,
|
||||
private val signerProvider: SignerProvider,
|
||||
) {
|
||||
|
||||
suspend fun registerContributionOffChain(
|
||||
amount: BigDecimal,
|
||||
contributionType: ContributionType,
|
||||
referralCode: String?,
|
||||
): Result<Unit> = runCatching {
|
||||
httpExceptionHandler.wrap {
|
||||
val selectedMetaAccount = accountRepository.getSelectedMetaAccount()
|
||||
val signer = signerProvider.rootSignerFor(selectedMetaAccount)
|
||||
|
||||
val (chain, chainAsset) = selectedAssetState.chainAndAsset()
|
||||
|
||||
val accountIdInCurrentChain = selectedMetaAccount.accountIdIn(chain)!!
|
||||
// api requires polkadot address even in rococo testnet
|
||||
val addressInPolkadot = chainRegistry.getChain(ChainGeneses.POLKADOT).addressOf(accountIdInCurrentChain)
|
||||
val amountInPlanks = chainAsset.planksFromAmount(amount)
|
||||
|
||||
val statement = getStatement(chain).statement
|
||||
|
||||
val signerPayload = SignerPayloadRaw.fromUtf8(statement, accountIdInCurrentChain)
|
||||
|
||||
when (contributionType) {
|
||||
ContributionType.DIRECT -> {
|
||||
val request = AcalaDirectContributeRequest(
|
||||
address = addressInPolkadot,
|
||||
amount = amountInPlanks,
|
||||
referral = referralCode,
|
||||
signature = signer.signRaw(signerPayload).asHexString()
|
||||
)
|
||||
|
||||
acalaApi.directContribute(
|
||||
baseUrl = AcalaApi.getBaseUrl(chain),
|
||||
authHeader = AcalaApi.getAuthHeader(chain),
|
||||
body = request
|
||||
)
|
||||
}
|
||||
|
||||
ContributionType.LIQUID -> {
|
||||
val request = AcalaLiquidContributeRequest(
|
||||
address = addressInPolkadot,
|
||||
amount = amountInPlanks,
|
||||
referral = referralCode
|
||||
)
|
||||
|
||||
acalaApi.liquidContribute(
|
||||
baseUrl = AcalaApi.getBaseUrl(chain),
|
||||
authHeader = AcalaApi.getAuthHeader(chain),
|
||||
body = request
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun isReferralValid(referralCode: String) = try {
|
||||
val chain = selectedAssetState.chain()
|
||||
|
||||
httpExceptionHandler.wrap {
|
||||
acalaApi.isReferralValid(
|
||||
baseUrl = AcalaApi.getBaseUrl(chain),
|
||||
authHeader = AcalaApi.getAuthHeader(chain),
|
||||
referral = referralCode
|
||||
).result
|
||||
}
|
||||
} catch (e: BaseException) {
|
||||
if (e.kind == BaseException.Kind.HTTP) {
|
||||
false // acala api return an error http code for some invalid codes, so catch it here
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun injectOnChainSubmission(
|
||||
contributionType: ContributionType,
|
||||
referralCode: String?,
|
||||
amount: BigDecimal,
|
||||
extrinsicBuilder: ExtrinsicBuilder,
|
||||
) = with(extrinsicBuilder) {
|
||||
if (contributionType == ContributionType.LIQUID) {
|
||||
resetCalls()
|
||||
|
||||
val (chain, chainAsset) = selectedAssetState.chainAndAsset()
|
||||
val amountInPlanks = chainAsset.planksFromAmount(amount)
|
||||
|
||||
val statement = httpExceptionHandler.wrap { getStatement(chain) }
|
||||
val proxyAccountId = chain.accountIdOf(statement.proxyAddress)
|
||||
|
||||
nativeTransfer(proxyAccountId, amountInPlanks)
|
||||
systemRemarkWithEvent(statement.statement)
|
||||
referralCode?.let { systemRemarkWithEvent(referralRemark(it)) }
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun injectFeeCalculation(
|
||||
contributionType: ContributionType,
|
||||
referralCode: String?,
|
||||
amount: BigDecimal,
|
||||
extrinsicBuilder: ExtrinsicBuilder,
|
||||
) = with(extrinsicBuilder) {
|
||||
if (contributionType == ContributionType.LIQUID) {
|
||||
resetCalls()
|
||||
|
||||
val chainAsset = selectedAssetState.chainAsset()
|
||||
val amountInPlanks = chainAsset.planksFromAmount(amount)
|
||||
|
||||
val fakeDestination = ByteArray(32)
|
||||
nativeTransfer(accountId = fakeDestination, amount = amountInPlanks)
|
||||
|
||||
val fakeAgreementRemark = ByteArray(185) // acala agreement is 185 bytes
|
||||
systemRemarkWithEvent(fakeAgreementRemark)
|
||||
|
||||
referralCode?.let { systemRemarkWithEvent(referralRemark(referralCode)) }
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getStatement(
|
||||
chain: Chain,
|
||||
) = acalaApi.getStatement(
|
||||
baseUrl = AcalaApi.getBaseUrl(chain),
|
||||
authHeader = AcalaApi.getAuthHeader(chain)
|
||||
)
|
||||
|
||||
private fun referralRemark(referralCode: String) = "referrer:$referralCode"
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.custom.acala
|
||||
|
||||
enum class ContributionType {
|
||||
DIRECT, LIQUID
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.custom.astar
|
||||
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.ParaId
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.network.blockhain.extrinsic.addMemo
|
||||
import io.novafoundation.nova.runtime.ext.accountIdOf
|
||||
import io.novafoundation.nova.runtime.ext.isValidAddress
|
||||
import io.novafoundation.nova.runtime.state.SingleAssetSharedState
|
||||
import io.novafoundation.nova.runtime.state.chain
|
||||
import io.novasama.substrate_sdk_android.runtime.extrinsic.builder.ExtrinsicBuilder
|
||||
|
||||
class AstarContributeInteractor(
|
||||
private val selectedAssetSharedState: SingleAssetSharedState,
|
||||
) {
|
||||
|
||||
suspend fun isReferralCodeValid(code: String): Boolean {
|
||||
val currentChain = selectedAssetSharedState.chain()
|
||||
|
||||
return currentChain.isValidAddress(code)
|
||||
}
|
||||
|
||||
suspend fun submitOnChain(
|
||||
paraId: ParaId,
|
||||
referralCode: String,
|
||||
extrinsicBuilder: ExtrinsicBuilder,
|
||||
) {
|
||||
val currentChain = selectedAssetSharedState.chain()
|
||||
val referralAccountId = currentChain.accountIdOf(referralCode)
|
||||
|
||||
extrinsicBuilder.addMemo(paraId, referralAccountId)
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.custom.bifrost
|
||||
|
||||
import io.novafoundation.nova.common.data.network.HttpExceptionHandler
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.ParaId
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.network.api.bifrost.BifrostApi
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.network.api.bifrost.getAccountByReferralCode
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.network.blockhain.extrinsic.addMemo
|
||||
import io.novasama.substrate_sdk_android.runtime.extrinsic.builder.ExtrinsicBuilder
|
||||
|
||||
class BifrostContributeInteractor(
|
||||
val novaReferralCode: String,
|
||||
private val bifrostApi: BifrostApi,
|
||||
private val httpExceptionHandler: HttpExceptionHandler,
|
||||
) {
|
||||
|
||||
suspend fun isCodeValid(code: String): Boolean {
|
||||
val response = httpExceptionHandler.wrap { bifrostApi.getAccountByReferralCode(code) }
|
||||
|
||||
return response.data.getAccountByInvitationCode.account.isNullOrEmpty().not()
|
||||
}
|
||||
|
||||
fun submitOnChain(
|
||||
paraId: ParaId,
|
||||
referralCode: String,
|
||||
extrinsicBuilder: ExtrinsicBuilder,
|
||||
) = extrinsicBuilder.addMemo(paraId, referralCode)
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.custom.moonbeam
|
||||
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
|
||||
class CrossChainRewardDestination(
|
||||
val addressInDestination: String,
|
||||
val destination: Chain,
|
||||
)
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.custom.moonbeam
|
||||
|
||||
import android.util.Log
|
||||
import io.novafoundation.nova.common.data.network.HttpExceptionHandler
|
||||
import io.novafoundation.nova.common.utils.LOG_TAG
|
||||
import io.novafoundation.nova.common.utils.asHexString
|
||||
import io.novafoundation.nova.common.utils.sha256
|
||||
import io.novafoundation.nova.core.model.CryptoType
|
||||
import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.TransactionOrigin
|
||||
import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService
|
||||
import io.novafoundation.nova.feature_account_api.data.extrinsic.awaitStatus
|
||||
import io.novafoundation.nova.feature_account_api.data.extrinsic.execution.watch.ExtrinsicWatchResult
|
||||
import io.novafoundation.nova.feature_account_api.data.model.Fee
|
||||
import io.novafoundation.nova.feature_account_api.data.signer.SignerProvider
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.addressIn
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.cryptoTypeIn
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.repository.ParachainMetadata
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.network.api.moonbeam.AgreeRemarkRequest
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.network.api.moonbeam.MoonbeamApi
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.network.api.moonbeam.VerifyRemarkRequest
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.network.api.moonbeam.agreeRemark
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.network.api.moonbeam.checkRemark
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.network.api.moonbeam.moonbeamChainId
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.network.api.moonbeam.verifyRemark
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.network.blockhain.extrinsic.addMemo
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.main.Crowdloan
|
||||
import io.novafoundation.nova.runtime.extrinsic.ExtrinsicStatus
|
||||
import io.novafoundation.nova.runtime.extrinsic.systemRemark
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
import io.novafoundation.nova.runtime.state.SingleAssetSharedState
|
||||
import io.novafoundation.nova.runtime.state.chain
|
||||
import io.novasama.substrate_sdk_android.extensions.fromHex
|
||||
import io.novasama.substrate_sdk_android.extensions.toHexString
|
||||
import io.novasama.substrate_sdk_android.runtime.extrinsic.builder.ExtrinsicBuilder
|
||||
import io.novasama.substrate_sdk_android.runtime.extrinsic.signer.SignerPayloadRaw
|
||||
import io.novasama.substrate_sdk_android.runtime.extrinsic.signer.fromUtf8
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import retrofit2.HttpException
|
||||
|
||||
class VerificationError : Exception()
|
||||
|
||||
private val SUPPORTED_CRYPTO_TYPES = setOf(CryptoType.SR25519, CryptoType.ED25519)
|
||||
|
||||
class MoonbeamCrowdloanInteractor(
|
||||
private val accountRepository: AccountRepository,
|
||||
private val extrinsicService: ExtrinsicService,
|
||||
private val moonbeamApi: MoonbeamApi,
|
||||
private val selectedChainAssetState: SingleAssetSharedState,
|
||||
private val chainRegistry: ChainRegistry,
|
||||
private val httpExceptionHandler: HttpExceptionHandler,
|
||||
private val signerProvider: SignerProvider,
|
||||
) {
|
||||
|
||||
fun getTermsLink() = "https://github.com/moonbeam-foundation/crowdloan-self-attestation/blob/main/moonbeam/README.md"
|
||||
|
||||
suspend fun getMoonbeamRewardDestination(parachainMetadata: ParachainMetadata): CrossChainRewardDestination {
|
||||
val currentAccount = accountRepository.getSelectedMetaAccount()
|
||||
val moonbeamChain = chainRegistry.getChain(parachainMetadata.moonbeamChainId())
|
||||
|
||||
return CrossChainRewardDestination(
|
||||
addressInDestination = currentAccount.addressIn(moonbeamChain)!!,
|
||||
destination = moonbeamChain
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun additionalSubmission(
|
||||
crowdloan: Crowdloan,
|
||||
extrinsicBuilder: ExtrinsicBuilder,
|
||||
) {
|
||||
val rewardDestination = getMoonbeamRewardDestination(crowdloan.parachainMetadata!!)
|
||||
|
||||
extrinsicBuilder.addMemo(
|
||||
parachainId = crowdloan.parachainId,
|
||||
memo = rewardDestination.addressInDestination.fromHex()
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun flowStatus(parachainMetadata: ParachainMetadata): Result<MoonbeamFlowStatus> = withContext(Dispatchers.Default) {
|
||||
runCatching {
|
||||
val metaAccount = accountRepository.getSelectedMetaAccount()
|
||||
|
||||
val moonbeamChainId = parachainMetadata.moonbeamChainId()
|
||||
val moonbeamChain = chainRegistry.getChain(moonbeamChainId)
|
||||
|
||||
val currentChain = selectedChainAssetState.chain()
|
||||
val currentAddress = metaAccount.addressIn(currentChain)!!
|
||||
|
||||
when {
|
||||
!metaAccount.hasAccountIn(moonbeamChain) -> MoonbeamFlowStatus.NeedsChainAccount(
|
||||
chainId = moonbeamChainId,
|
||||
metaId = metaAccount.id
|
||||
)
|
||||
|
||||
metaAccount.cryptoTypeIn(currentChain) !in SUPPORTED_CRYPTO_TYPES -> MoonbeamFlowStatus.UnsupportedAccountEncryption
|
||||
|
||||
else -> when (checkRemark(parachainMetadata, currentAddress)) {
|
||||
null -> MoonbeamFlowStatus.RegionNotSupported
|
||||
true -> MoonbeamFlowStatus.Completed
|
||||
false -> MoonbeamFlowStatus.ReadyToComplete
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun calculateTermsFee(): Fee = withContext(Dispatchers.Default) {
|
||||
val chain = selectedChainAssetState.chain()
|
||||
|
||||
extrinsicService.estimateFee(chain, TransactionOrigin.SelectedWallet) {
|
||||
systemRemark(fakeRemark())
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun submitAgreement(parachainMetadata: ParachainMetadata): Result<ExtrinsicWatchResult<*>> = withContext(Dispatchers.Default) {
|
||||
runCatching {
|
||||
val chain = selectedChainAssetState.chain()
|
||||
val metaAccount = accountRepository.getSelectedMetaAccount()
|
||||
|
||||
val currentAddress = metaAccount.addressIn(chain)!!
|
||||
val accountId = metaAccount.accountIdIn(chain)!!
|
||||
|
||||
val legalText = httpExceptionHandler.wrap { moonbeamApi.getLegalText() }
|
||||
val legalHash = legalText.encodeToByteArray().sha256().toHexString(withPrefix = false)
|
||||
|
||||
val signer = signerProvider.rootSignerFor(metaAccount)
|
||||
val signerPayload = SignerPayloadRaw.fromUtf8(legalHash, accountId)
|
||||
|
||||
val signedHash = signer.signRaw(signerPayload).asHexString()
|
||||
|
||||
val agreeRemarkRequest = AgreeRemarkRequest(currentAddress, signedHash)
|
||||
val remark = httpExceptionHandler.wrap { moonbeamApi.agreeRemark(parachainMetadata, agreeRemarkRequest) }.remark
|
||||
|
||||
val result = extrinsicService.submitAndWatchExtrinsic(chain, TransactionOrigin.SelectedWallet) {
|
||||
systemRemark(remark.encodeToByteArray())
|
||||
}
|
||||
.getOrThrow()
|
||||
.awaitStatus<ExtrinsicStatus.Finalized>()
|
||||
|
||||
Log.d(this@MoonbeamCrowdloanInteractor.LOG_TAG, "Finalized ${result.status.extrinsicHash} in block ${result.status.blockHash}")
|
||||
|
||||
val verificationRequest = VerifyRemarkRequest(
|
||||
address = currentAddress,
|
||||
extrinsicHash = result.status.extrinsicHash,
|
||||
blockHash = result.status.blockHash
|
||||
)
|
||||
val verificationResponse = httpExceptionHandler.wrap { moonbeamApi.verifyRemark(parachainMetadata, verificationRequest) }
|
||||
|
||||
if (!verificationResponse.verified) throw VerificationError()
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
private fun fakeRemark() = ByteArray(32)
|
||||
|
||||
/**
|
||||
* @return null if Geo-fenced or application unavailable. True if user already agreed with terms. False otherwise
|
||||
*/
|
||||
private suspend fun checkRemark(parachainMetadata: ParachainMetadata, address: String): Boolean? = try {
|
||||
moonbeamApi.checkRemark(parachainMetadata, address).verified
|
||||
} catch (e: HttpException) {
|
||||
if (e.code() == 403) { // Moonbeam answers with 403 in case geo-fenced or application unavailable
|
||||
null
|
||||
} else {
|
||||
throw httpExceptionHandler.transformException(e)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
throw httpExceptionHandler.transformException(e)
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.custom.moonbeam
|
||||
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
|
||||
sealed class MoonbeamFlowStatus {
|
||||
|
||||
object RegionNotSupported : MoonbeamFlowStatus()
|
||||
|
||||
object Completed : MoonbeamFlowStatus()
|
||||
|
||||
class NeedsChainAccount(val chainId: ChainId, val metaId: Long) : MoonbeamFlowStatus()
|
||||
|
||||
object UnsupportedAccountEncryption : MoonbeamFlowStatus()
|
||||
|
||||
object ReadyToComplete : MoonbeamFlowStatus()
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.custom.moonbeam
|
||||
|
||||
import io.novafoundation.nova.common.data.network.HttpExceptionHandler
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.repository.ParachainMetadata
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.network.api.moonbeam.MakeSignatureRequest
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.network.api.moonbeam.MoonbeamApi
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.network.api.moonbeam.makeSignature
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.custom.PrivateCrowdloanSignatureProvider
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.custom.PrivateCrowdloanSignatureProvider.Mode
|
||||
import io.novasama.substrate_sdk_android.encrypt.EncryptionType
|
||||
import io.novasama.substrate_sdk_android.extensions.fromHex
|
||||
import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.MultiSignature
|
||||
import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.prepareForEncoding
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.math.BigInteger
|
||||
|
||||
class MoonbeamPrivateSignatureProvider(
|
||||
private val moonbeamApi: MoonbeamApi,
|
||||
private val httpExceptionHandler: HttpExceptionHandler,
|
||||
) : PrivateCrowdloanSignatureProvider {
|
||||
|
||||
override suspend fun provideSignature(
|
||||
chainMetadata: ParachainMetadata,
|
||||
previousContribution: BigInteger,
|
||||
newContribution: BigInteger,
|
||||
address: String,
|
||||
mode: Mode,
|
||||
): Any = withContext(Dispatchers.Default) {
|
||||
when (mode) {
|
||||
Mode.FEE -> sr25519SignatureOf(ByteArray(64)) // sr25519 is 65 bytes
|
||||
Mode.SUBMIT -> {
|
||||
val request = MakeSignatureRequest(address, previousContribution.toString(), newContribution.toString())
|
||||
val response = httpExceptionHandler.wrap { moonbeamApi.makeSignature(chainMetadata, request) }
|
||||
|
||||
sr25519SignatureOf(response.signature.fromHex())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun sr25519SignatureOf(bytes: ByteArray): Any {
|
||||
return MultiSignature(EncryptionType.SR25519, bytes).prepareForEncoding()
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.validations
|
||||
|
||||
import io.novafoundation.nova.common.validation.DefaultFailureLevel
|
||||
import io.novafoundation.nova.common.validation.ValidationStatus
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.di.customCrowdloan.CustomContributeManager
|
||||
|
||||
class BonusAppliedValidation(
|
||||
private val customContributeManager: CustomContributeManager,
|
||||
) : ContributeValidation {
|
||||
|
||||
override suspend fun validate(value: ContributeValidationPayload): ValidationStatus<ContributeValidationFailure> {
|
||||
val factory = value.crowdloan.parachainMetadata?.customFlow?.let {
|
||||
customContributeManager.getFactoryOrNull(it)
|
||||
}
|
||||
|
||||
val shouldHaveBonusPayload = factory?.extraBonusFlow != null
|
||||
|
||||
return if (shouldHaveBonusPayload && value.bonusPayload == null) {
|
||||
ValidationStatus.NotValid(DefaultFailureLevel.WARNING, ContributeValidationFailure.BonusNotApplied)
|
||||
} else {
|
||||
ValidationStatus.Valid()
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.validations
|
||||
|
||||
import io.novafoundation.nova.common.validation.DefaultFailureLevel
|
||||
import io.novafoundation.nova.common.validation.ValidationStatus
|
||||
import io.novafoundation.nova.feature_wallet_api.domain.model.amountFromPlanks
|
||||
|
||||
class CapExceededValidation : ContributeValidation {
|
||||
|
||||
override suspend fun validate(value: ContributeValidationPayload): ValidationStatus<ContributeValidationFailure> {
|
||||
val token = value.asset.token
|
||||
|
||||
return with(value.crowdloan.fundInfo) {
|
||||
val raisedAmount = token.amountFromPlanks(raised)
|
||||
val capAmount = token.amountFromPlanks(cap)
|
||||
|
||||
when {
|
||||
raisedAmount >= capAmount -> ValidationStatus.NotValid(DefaultFailureLevel.ERROR, ContributeValidationFailure.CapExceeded.FromRaised)
|
||||
raisedAmount + value.contributionAmount > capAmount -> {
|
||||
val maxAllowedContribution = capAmount - raisedAmount
|
||||
|
||||
val reason = ContributeValidationFailure.CapExceeded.FromAmount(maxAllowedContribution, token.configuration)
|
||||
|
||||
ValidationStatus.NotValid(DefaultFailureLevel.ERROR, reason)
|
||||
}
|
||||
else -> ValidationStatus.Valid()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.validations
|
||||
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import java.math.BigDecimal
|
||||
|
||||
sealed class ContributeValidationFailure {
|
||||
|
||||
class LessThanMinContribution(
|
||||
val minContribution: BigDecimal,
|
||||
val chainAsset: Chain.Asset
|
||||
) : ContributeValidationFailure()
|
||||
|
||||
sealed class CapExceeded : ContributeValidationFailure() {
|
||||
|
||||
class FromAmount(
|
||||
val maxAllowedContribution: BigDecimal,
|
||||
val chainAsset: Chain.Asset
|
||||
) : CapExceeded()
|
||||
|
||||
object FromRaised : CapExceeded()
|
||||
}
|
||||
|
||||
object CrowdloanEnded : ContributeValidationFailure()
|
||||
|
||||
object CannotPayFees : ContributeValidationFailure()
|
||||
|
||||
object ExistentialDepositCrossed : ContributeValidationFailure()
|
||||
|
||||
object BonusNotApplied : ContributeValidationFailure()
|
||||
|
||||
object PrivateCrowdloanNotSupported : ContributeValidationFailure()
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.validations
|
||||
|
||||
import android.os.Parcelable
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.main.Crowdloan
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.BonusPayload
|
||||
import io.novafoundation.nova.feature_wallet_api.domain.model.Asset
|
||||
import io.novafoundation.nova.feature_account_api.data.model.Fee
|
||||
import java.math.BigDecimal
|
||||
|
||||
class ContributeValidationPayload(
|
||||
val crowdloan: Crowdloan,
|
||||
val customizationPayload: Parcelable?,
|
||||
val asset: Asset,
|
||||
val fee: Fee,
|
||||
val bonusPayload: BonusPayload?,
|
||||
val contributionAmount: BigDecimal,
|
||||
)
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.validations
|
||||
|
||||
import io.novafoundation.nova.common.validation.DefaultFailureLevel
|
||||
import io.novafoundation.nova.common.validation.ValidationStatus
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.repository.CrowdloanRepository
|
||||
import io.novafoundation.nova.runtime.repository.ChainStateRepository
|
||||
|
||||
class CrowdloanNotEndedValidation(
|
||||
private val chainStateRepository: ChainStateRepository,
|
||||
private val crowdloanRepository: CrowdloanRepository
|
||||
) : ContributeValidation {
|
||||
|
||||
override suspend fun validate(value: ContributeValidationPayload): ValidationStatus<ContributeValidationFailure> {
|
||||
val chainId = value.asset.token.configuration.chainId
|
||||
val currentBlock = chainStateRepository.currentBlock(chainId)
|
||||
|
||||
val leasePeriodToBlocksConverter = crowdloanRepository.leasePeriodToBlocksConverter(chainId)
|
||||
|
||||
val currentLeaseIndex = leasePeriodToBlocksConverter.leaseIndexFromBlock(currentBlock)
|
||||
|
||||
return when {
|
||||
currentBlock >= value.crowdloan.fundInfo.end -> crowdloanEndedFailure()
|
||||
currentLeaseIndex > value.crowdloan.fundInfo.firstSlot -> crowdloanEndedFailure()
|
||||
else -> ValidationStatus.Valid()
|
||||
}
|
||||
}
|
||||
|
||||
private fun crowdloanEndedFailure(): ValidationStatus.NotValid<ContributeValidationFailure> =
|
||||
ValidationStatus.NotValid(DefaultFailureLevel.ERROR, ContributeValidationFailure.CrowdloanEnded)
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.validations
|
||||
|
||||
import io.novafoundation.nova.common.validation.Validation
|
||||
import io.novafoundation.nova.feature_account_api.data.model.Fee
|
||||
import io.novafoundation.nova.feature_wallet_api.domain.validation.EnoughAmountToTransferValidation
|
||||
import io.novafoundation.nova.feature_wallet_api.domain.validation.ExistentialDepositValidation
|
||||
|
||||
typealias ContributeValidation = Validation<ContributeValidationPayload, ContributeValidationFailure>
|
||||
|
||||
typealias ContributeEnoughToPayFeesValidation = EnoughAmountToTransferValidation<ContributeValidationPayload, ContributeValidationFailure>
|
||||
typealias ContributeExistentialDepositValidation = ExistentialDepositValidation<ContributeValidationPayload, ContributeValidationFailure, Fee>
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.validations
|
||||
|
||||
import io.novafoundation.nova.common.validation.ValidationStatus
|
||||
import io.novafoundation.nova.common.validation.validOrError
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.repository.CrowdloanRepository
|
||||
import io.novafoundation.nova.feature_wallet_api.domain.model.amountFromPlanks
|
||||
import java.math.BigInteger
|
||||
|
||||
class DefaultMinContributionValidation(
|
||||
private val crowdloanRepository: CrowdloanRepository,
|
||||
) : MinContributionValidation() {
|
||||
|
||||
override suspend fun minContribution(payload: ContributeValidationPayload): BigInteger {
|
||||
val chainAsset = payload.asset.token.configuration
|
||||
|
||||
return crowdloanRepository.minContribution(chainAsset.chainId)
|
||||
}
|
||||
}
|
||||
|
||||
abstract class MinContributionValidation : ContributeValidation {
|
||||
|
||||
abstract suspend fun minContribution(payload: ContributeValidationPayload): BigInteger
|
||||
|
||||
override suspend fun validate(value: ContributeValidationPayload): ValidationStatus<ContributeValidationFailure> {
|
||||
val chainAsset = value.asset.token.configuration
|
||||
|
||||
val minContributionInPlanks = minContribution(value)
|
||||
val minContribution = chainAsset.amountFromPlanks(minContributionInPlanks)
|
||||
|
||||
return validOrError(value.contributionAmount >= minContribution) {
|
||||
ContributeValidationFailure.LessThanMinContribution(minContribution, chainAsset)
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.validations
|
||||
|
||||
import io.novafoundation.nova.common.validation.ValidationStatus
|
||||
import io.novafoundation.nova.common.validation.validOrError
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.di.customCrowdloan.CustomContributeManager
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.di.customCrowdloan.supportsPrivateCrowdloans
|
||||
|
||||
class PublicCrowdloanValidation(
|
||||
private val customContributeManager: CustomContributeManager,
|
||||
) : ContributeValidation {
|
||||
|
||||
override suspend fun validate(value: ContributeValidationPayload): ValidationStatus<ContributeValidationFailure> {
|
||||
val isPublic = value.crowdloan.fundInfo.verifier == null
|
||||
|
||||
val flowType = value.crowdloan.parachainMetadata?.customFlow
|
||||
val supportsPrivate = flowType?.let(customContributeManager::supportsPrivateCrowdloans) ?: false
|
||||
|
||||
return validOrError(isPublic || supportsPrivate) {
|
||||
ContributeValidationFailure.PrivateCrowdloanNotSupported
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.validations.custom.acala
|
||||
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.custom.acala.ContributionType
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.validations.ContributeValidationPayload
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.validations.MinContributionValidation
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.acala.main.AcalaCustomizationPayload
|
||||
import io.novafoundation.nova.feature_wallet_api.domain.model.planksFromAmount
|
||||
import java.math.BigInteger
|
||||
|
||||
private val ACALA_MIN_CONTRIBUTION = 1.toBigDecimal()
|
||||
|
||||
class AcalaMinContributionValidation(
|
||||
private val fallback: MinContributionValidation,
|
||||
) : MinContributionValidation() {
|
||||
|
||||
override suspend fun minContribution(payload: ContributeValidationPayload): BigInteger {
|
||||
val customization = payload.customizationPayload
|
||||
require(customization is AcalaCustomizationPayload)
|
||||
|
||||
return when (customization.contributionType) {
|
||||
ContributionType.DIRECT -> fallback.minContribution(payload)
|
||||
ContributionType.LIQUID -> {
|
||||
val asset = payload.asset.token.configuration
|
||||
|
||||
return asset.planksFromAmount(ACALA_MIN_CONTRIBUTION)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.validations.custom.moonbeam
|
||||
|
||||
import io.novafoundation.nova.feature_wallet_api.domain.model.Asset
|
||||
import io.novafoundation.nova.feature_account_api.data.model.Fee
|
||||
|
||||
class MoonbeamTermsPayload(
|
||||
val fee: Fee,
|
||||
val asset: Asset
|
||||
)
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.validations.custom.moonbeam
|
||||
|
||||
enum class MoonbeamTermsValidationFailure {
|
||||
CANNOT_PAY_FEES
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.validations.custom.moonbeam
|
||||
|
||||
import io.novafoundation.nova.common.validation.Validation
|
||||
import io.novafoundation.nova.common.validation.ValidationSystem
|
||||
import io.novafoundation.nova.feature_wallet_api.domain.validation.EnoughAmountToTransferValidation
|
||||
|
||||
typealias MoonbeamTermsValidationSystem = ValidationSystem<MoonbeamTermsPayload, MoonbeamTermsValidationFailure>
|
||||
typealias MoonbeamTermsValidation = Validation<MoonbeamTermsPayload, MoonbeamTermsValidationFailure>
|
||||
|
||||
typealias MoonbeamTermsFeeValidation = EnoughAmountToTransferValidation<MoonbeamTermsPayload, MoonbeamTermsValidationFailure>
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.domain.contributions
|
||||
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.ParaId
|
||||
import io.novafoundation.nova.common.utils.combineToPair
|
||||
import io.novafoundation.nova.common.utils.sumByBigInteger
|
||||
import io.novafoundation.nova.core.updater.Updater
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.network.updater.ContributionsUpdateSystemFactory
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.repository.ContributionsRepository
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.repository.CrowdloanRepository
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.repository.ParachainMetadata
|
||||
import io.novafoundation.nova.feature_crowdloan_api.domain.contributions.Contribution
|
||||
import io.novafoundation.nova.feature_crowdloan_api.domain.contributions.ContributionMetadata
|
||||
import io.novafoundation.nova.feature_crowdloan_api.domain.contributions.ContributionWithMetadata
|
||||
import io.novafoundation.nova.feature_crowdloan_api.domain.contributions.ContributionsInteractor
|
||||
import io.novafoundation.nova.feature_crowdloan_api.domain.contributions.ContributionsWithTotalAmount
|
||||
import io.novafoundation.nova.feature_crowdloan_api.domain.contributions.claimStatusOf
|
||||
import io.novafoundation.nova.runtime.ext.timelineChainIdOrSelf
|
||||
import io.novafoundation.nova.runtime.ext.utilityAsset
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainAssetId
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chainWithAsset
|
||||
import io.novafoundation.nova.runtime.repository.ChainStateRepository
|
||||
import io.novafoundation.nova.runtime.repository.blockDurationEstimatorFlow
|
||||
import io.novafoundation.nova.runtime.state.SingleAssetSharedState
|
||||
import io.novafoundation.nova.runtime.state.selectedChainFlow
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.emitAll
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import java.math.BigInteger
|
||||
|
||||
class RealContributionsInteractor(
|
||||
private val crowdloanRepository: CrowdloanRepository,
|
||||
private val accountRepository: AccountRepository,
|
||||
private val selectedAssetCrowdloanState: SingleAssetSharedState,
|
||||
private val chainStateRepository: ChainStateRepository,
|
||||
private val contributionsRepository: ContributionsRepository,
|
||||
private val contributionsUpdateSystemFactory: ContributionsUpdateSystemFactory,
|
||||
private val chainRegistry: ChainRegistry,
|
||||
) : ContributionsInteractor {
|
||||
|
||||
override fun runUpdate(): Flow<Updater.SideEffect> {
|
||||
return contributionsUpdateSystemFactory.create()
|
||||
.start()
|
||||
}
|
||||
|
||||
override fun observeSelectedChainContributionsWithMetadata(): Flow<ContributionsWithTotalAmount<ContributionWithMetadata>> {
|
||||
val metaAccountFlow = accountRepository.selectedMetaAccountFlow()
|
||||
val chainFlow = selectedAssetCrowdloanState.selectedChainFlow()
|
||||
return combineToPair(metaAccountFlow, chainFlow)
|
||||
.flatMapLatest { (metaAccount, chain) ->
|
||||
observeChainContributionsWithMetadata(metaAccount, chain, chain.utilityAsset)
|
||||
}
|
||||
}
|
||||
|
||||
override fun observeChainContributions(
|
||||
metaAccount: MetaAccount,
|
||||
chainId: ChainId,
|
||||
assetId: ChainAssetId
|
||||
): Flow<ContributionsWithTotalAmount<Contribution>> {
|
||||
return flow {
|
||||
val (chain, asset) = chainRegistry.chainWithAsset(chainId, assetId)
|
||||
|
||||
emitAll(contributionsRepository.observeContributions(metaAccount, chain, asset))
|
||||
}.map { contributions ->
|
||||
contributions.totalContributions { it.amountInPlanks }
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getParachainMetadata(chain: Chain): Map<ParaId, ParachainMetadata> {
|
||||
return runCatching {
|
||||
crowdloanRepository.getParachainMetadata(chain)
|
||||
}.getOrDefault(emptyMap())
|
||||
}
|
||||
|
||||
private suspend fun observeChainContributionsWithMetadata(
|
||||
metaAccount: MetaAccount,
|
||||
chain: Chain,
|
||||
asset: Chain.Asset
|
||||
): Flow<ContributionsWithTotalAmount<ContributionWithMetadata>> {
|
||||
val parachainMetadatas = getParachainMetadata(chain)
|
||||
|
||||
return combine(
|
||||
chainStateRepository.blockDurationEstimatorFlow(chain.timelineChainIdOrSelf()),
|
||||
contributionsRepository.observeContributions(metaAccount, chain, asset)
|
||||
) { blockDurationEstimator, contributions ->
|
||||
contributions.map { contribution ->
|
||||
val parachainMetadata = parachainMetadatas[contribution.paraId]
|
||||
|
||||
val claimStatus = blockDurationEstimator.claimStatusOf(contribution)
|
||||
|
||||
ContributionWithMetadata(
|
||||
contribution = contribution,
|
||||
metadata = ContributionMetadata(
|
||||
claimStatus = claimStatus,
|
||||
parachainMetadata = parachainMetadata,
|
||||
)
|
||||
)
|
||||
}
|
||||
.sortedWith(
|
||||
compareBy<ContributionWithMetadata> { it.contribution.unlockBlock }
|
||||
.thenBy { it.contribution.paraId }
|
||||
)
|
||||
.totalContributions { it.contribution.amountInPlanks }
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T> List<T>.totalContributions(amount: (T) -> BigInteger): ContributionsWithTotalAmount<T> {
|
||||
return ContributionsWithTotalAmount(
|
||||
totalContributed = sumByBigInteger(amount),
|
||||
contributions = this
|
||||
)
|
||||
}
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.domain.contributions
|
||||
|
||||
import android.util.Log
|
||||
import io.novafoundation.nova.common.address.AccountIdKey
|
||||
import io.novafoundation.nova.common.address.intoKey
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.BlockNumber
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.ParaId
|
||||
import io.novafoundation.nova.common.utils.mapList
|
||||
import io.novafoundation.nova.common.utils.metadata
|
||||
import io.novafoundation.nova.core_db.dao.ContributionDao
|
||||
import io.novafoundation.nova.core_db.dao.DeleteAssetContributionsParams
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.repository.ContributionsRepository
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.source.contribution.ExternalContributionSource
|
||||
import io.novafoundation.nova.feature_crowdloan_api.domain.contributions.Contribution
|
||||
import io.novafoundation.nova.feature_crowdloan_api.domain.contributions.Contribution.Companion.DIRECT_SOURCE_ID
|
||||
import io.novafoundation.nova.feature_crowdloan_api.domain.contributions.mapContributionFromLocal
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.repository.contributions.network.ahOps
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.repository.contributions.network.rcCrowdloanContribution
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.repository.contributions.network.rcCrowdloanReserve
|
||||
import io.novafoundation.nova.runtime.ext.utilityAsset
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.FullChainAssetId
|
||||
import io.novafoundation.nova.runtime.storage.source.StorageDataSource
|
||||
import io.novafoundation.nova.runtime.storage.source.queryCatching
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class RealContributionsRepository(
|
||||
private val externalContributionsSources: List<ExternalContributionSource>,
|
||||
private val chainRegistry: ChainRegistry,
|
||||
private val remoteStorage: StorageDataSource,
|
||||
private val contributionDao: ContributionDao
|
||||
) : ContributionsRepository {
|
||||
|
||||
override fun observeContributions(metaAccount: MetaAccount): Flow<List<Contribution>> {
|
||||
val contributionsFlow = contributionDao.observeContributions(metaAccount.id)
|
||||
val chainsFlow = chainRegistry.chainsById
|
||||
return combine(contributionsFlow, chainsFlow) { contributions, chains ->
|
||||
contributions.map {
|
||||
mapContributionFromLocal(it, chains.getValue(it.chainId))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun observeContributions(metaAccount: MetaAccount, chain: Chain, asset: Chain.Asset): Flow<List<Contribution>> {
|
||||
return contributionDao.observeContributions(metaAccount.id, chain.id, asset.id)
|
||||
.mapList { mapContributionFromLocal(it, chain) }
|
||||
}
|
||||
|
||||
override fun loadContributionsGraduallyFlow(
|
||||
chain: Chain,
|
||||
accountId: ByteArray,
|
||||
): Flow<Pair<String, Result<List<Contribution>>>> = flow {
|
||||
if (!chain.hasCrowdloans) {
|
||||
return@flow
|
||||
}
|
||||
|
||||
val directContributions = getDirectContributions(chain, chain.utilityAsset, accountId)
|
||||
.onFailure { Log.e("RealContributionsRepository", "Failed to fetch direct contributions on ${chain.name}", it) }
|
||||
emit(DIRECT_SOURCE_ID to directContributions)
|
||||
}
|
||||
|
||||
override suspend fun getDirectContributions(
|
||||
chain: Chain,
|
||||
asset: Chain.Asset,
|
||||
accountId: ByteArray,
|
||||
): Result<List<Contribution>> {
|
||||
return withContext(Dispatchers.Default) {
|
||||
remoteStorage.queryCatching(chain.id) {
|
||||
val reserves = metadata.ahOps.rcCrowdloanReserve.keys()
|
||||
val contributionKeys = reserves.map { (unlockBlock, paraId, _) -> Triple(unlockBlock, paraId, accountId.intoKey()) }
|
||||
|
||||
val contributionEntries = metadata.ahOps.rcCrowdloanContribution.entries(contributionKeys)
|
||||
|
||||
contributionEntries.map { (key, balance) ->
|
||||
val (unlockBlock, paraId) = key
|
||||
Contribution(
|
||||
chain = chain,
|
||||
asset = asset,
|
||||
amountInPlanks = balance,
|
||||
paraId = paraId,
|
||||
sourceId = DIRECT_SOURCE_ID,
|
||||
unlockBlock = unlockBlock,
|
||||
leaseDepositor = reserves.getLeaseDepositor(paraId)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun LeaseReserves.getLeaseDepositor(paraId: ParaId): AccountIdKey {
|
||||
return first { it.second == paraId }.third
|
||||
}
|
||||
|
||||
override suspend fun deleteContributions(assetIds: List<FullChainAssetId>) {
|
||||
val params = assetIds.map { DeleteAssetContributionsParams(it.chainId, it.assetId) }
|
||||
|
||||
contributionDao.deleteAssetContributions(params)
|
||||
}
|
||||
}
|
||||
private typealias LeaseReserves = Set<Triple<BlockNumber, ParaId, AccountIdKey>>
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.domain.main
|
||||
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.ParaId
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.network.blockhain.binding.FundInfo
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.repository.ParachainMetadata
|
||||
import io.novafoundation.nova.feature_crowdloan_api.domain.contributions.Contribution
|
||||
import java.math.BigDecimal
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
class Crowdloan(
|
||||
val parachainMetadata: ParachainMetadata?,
|
||||
val parachainId: ParaId,
|
||||
val raisedFraction: BigDecimal,
|
||||
val state: State,
|
||||
val leasePeriodInMillis: Long,
|
||||
val leasedUntilInMillis: Long,
|
||||
val fundInfo: FundInfo,
|
||||
val myContribution: Contribution?,
|
||||
) {
|
||||
|
||||
sealed class State {
|
||||
|
||||
companion object {
|
||||
|
||||
val STATE_CLASS_COMPARATOR = Comparator<KClass<out State>> { first, _ ->
|
||||
when (first) {
|
||||
Active::class -> -1
|
||||
Finished::class -> 1
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object Finished : State()
|
||||
|
||||
class Active(val remainingTimeInMillis: Long) : State()
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.domain.main
|
||||
|
||||
import io.novafoundation.nova.common.list.GroupedList
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.repository.ContributionsRepository
|
||||
import io.novafoundation.nova.feature_crowdloan_api.data.repository.CrowdloanRepository
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novafoundation.nova.runtime.repository.ChainStateRepository
|
||||
import io.novasama.substrate_sdk_android.runtime.AccountId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.emitAll
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
typealias GroupedCrowdloans = GroupedList<KClass<out Crowdloan.State>, Crowdloan>
|
||||
|
||||
class CrowdloanInteractor(
|
||||
private val crowdloanRepository: CrowdloanRepository,
|
||||
private val chainStateRepository: ChainStateRepository,
|
||||
private val contributionsRepository: ContributionsRepository
|
||||
) {
|
||||
|
||||
fun groupedCrowdloansFlow(chain: Chain, account: MetaAccount): Flow<GroupedCrowdloans> {
|
||||
return crowdloansFlow(chain, account)
|
||||
.map { groupCrowdloans(it) }
|
||||
}
|
||||
|
||||
private fun crowdloansFlow(chain: Chain, account: MetaAccount): Flow<List<Crowdloan>> {
|
||||
return flow {
|
||||
val accountId = account.accountIdIn(chain)
|
||||
|
||||
emitAll(crowdloanListFlow(chain, accountId))
|
||||
}
|
||||
}
|
||||
|
||||
private fun groupCrowdloans(crowdloans: List<Crowdloan>): GroupedCrowdloans {
|
||||
return crowdloans.groupBy { it.state::class }
|
||||
.toSortedMap(Crowdloan.State.STATE_CLASS_COMPARATOR)
|
||||
}
|
||||
|
||||
private suspend fun crowdloanListFlow(
|
||||
chain: Chain,
|
||||
contributor: AccountId?,
|
||||
): Flow<List<Crowdloan>> {
|
||||
// Crowdloans are no longer accessible and are deprecated. We will remove entire crowdloan feature soon
|
||||
return flowOf(emptyList())
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.domain.main.statefull
|
||||
|
||||
import io.novafoundation.nova.common.presentation.LoadingState
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.main.GroupedCrowdloans
|
||||
import io.novafoundation.nova.feature_wallet_api.presentation.model.AmountModel
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface StatefulCrowdloanMixin {
|
||||
|
||||
interface Factory {
|
||||
|
||||
fun create(scope: CoroutineScope): StatefulCrowdloanMixin
|
||||
}
|
||||
|
||||
class ContributionsInfo(
|
||||
val contributionsCount: Int,
|
||||
val isUserHasContributions: Boolean,
|
||||
val totalContributed: AmountModel
|
||||
)
|
||||
|
||||
val selectedAccount: Flow<MetaAccount>
|
||||
val selectedChain: Flow<Chain>
|
||||
|
||||
val contributionsInfoFlow: Flow<LoadingState<ContributionsInfo>>
|
||||
|
||||
val groupedCrowdloansFlow: Flow<LoadingState<GroupedCrowdloans>>
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.domain.main.statefull
|
||||
|
||||
import io.novafoundation.nova.common.presentation.mapLoading
|
||||
import io.novafoundation.nova.common.utils.WithCoroutineScopeExtensions
|
||||
import io.novafoundation.nova.common.utils.combineToPair
|
||||
import io.novafoundation.nova.common.utils.withLoading
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.SelectedAccountUseCase
|
||||
import io.novafoundation.nova.feature_crowdloan_api.domain.contributions.ContributionsInteractor
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.main.CrowdloanInteractor
|
||||
import io.novafoundation.nova.feature_wallet_api.domain.AssetUseCase
|
||||
import io.novafoundation.nova.feature_wallet_api.domain.getCurrentAsset
|
||||
import io.novafoundation.nova.feature_wallet_api.presentation.formatters.amount.AmountFormatter
|
||||
import io.novafoundation.nova.feature_wallet_api.presentation.formatters.amount.formatAmountToAmountModel
|
||||
import io.novafoundation.nova.runtime.ext.utilityAsset
|
||||
import io.novafoundation.nova.runtime.state.SingleAssetSharedState
|
||||
import io.novafoundation.nova.runtime.state.selectedChainFlow
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
||||
class StatefulCrowdloanProviderFactory(
|
||||
private val singleAssetSharedState: SingleAssetSharedState,
|
||||
private val crowdloanInteractor: CrowdloanInteractor,
|
||||
private val contributionsInteractor: ContributionsInteractor,
|
||||
private val selectedAccountUseCase: SelectedAccountUseCase,
|
||||
private val amountFormatter: AmountFormatter,
|
||||
private val assetUseCase: AssetUseCase,
|
||||
) : StatefulCrowdloanMixin.Factory {
|
||||
|
||||
override fun create(scope: CoroutineScope): StatefulCrowdloanMixin {
|
||||
return StatefulCrowdloanProvider(
|
||||
singleAssetSharedState = singleAssetSharedState,
|
||||
crowdloanInteractor = crowdloanInteractor,
|
||||
contributionsInteractor = contributionsInteractor,
|
||||
selectedAccountUseCase = selectedAccountUseCase,
|
||||
assetUseCase = assetUseCase,
|
||||
amountFormatter = amountFormatter,
|
||||
coroutineScope = scope
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class StatefulCrowdloanProvider(
|
||||
singleAssetSharedState: SingleAssetSharedState,
|
||||
selectedAccountUseCase: SelectedAccountUseCase,
|
||||
private val crowdloanInteractor: CrowdloanInteractor,
|
||||
private val contributionsInteractor: ContributionsInteractor,
|
||||
private val assetUseCase: AssetUseCase,
|
||||
coroutineScope: CoroutineScope,
|
||||
private val amountFormatter: AmountFormatter,
|
||||
) : StatefulCrowdloanMixin,
|
||||
CoroutineScope by coroutineScope,
|
||||
WithCoroutineScopeExtensions by WithCoroutineScopeExtensions(coroutineScope) {
|
||||
|
||||
override val selectedChain = singleAssetSharedState.selectedChainFlow()
|
||||
.shareInBackground()
|
||||
|
||||
override val selectedAccount = selectedAccountUseCase.selectedMetaAccountFlow()
|
||||
.shareInBackground()
|
||||
|
||||
private val chainAndAccount = combineToPair(selectedChain, selectedAccount)
|
||||
.shareInBackground()
|
||||
|
||||
override val groupedCrowdloansFlow = chainAndAccount.withLoading { (chain, account) ->
|
||||
crowdloanInteractor.groupedCrowdloansFlow(chain, account)
|
||||
}
|
||||
.shareInBackground()
|
||||
|
||||
override val contributionsInfoFlow = chainAndAccount.withLoading { (chain, account) ->
|
||||
contributionsInteractor.observeChainContributions(account, chain.id, chain.utilityAsset.id)
|
||||
}
|
||||
.mapLoading {
|
||||
val amountModel = amountFormatter.formatAmountToAmountModel(
|
||||
it.totalContributed,
|
||||
assetUseCase.getCurrentAsset()
|
||||
)
|
||||
|
||||
StatefulCrowdloanMixin.ContributionsInfo(
|
||||
contributionsCount = it.contributions.size,
|
||||
isUserHasContributions = it.contributions.isNotEmpty(),
|
||||
totalContributed = amountModel
|
||||
)
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.domain.main.validations
|
||||
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
|
||||
import io.novafoundation.nova.feature_account_api.domain.validation.NoChainAccountFoundError
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
|
||||
sealed class MainCrowdloanValidationFailure {
|
||||
|
||||
class NoRelaychainAccount(
|
||||
override val chain: Chain,
|
||||
override val account: MetaAccount,
|
||||
override val addAccountState: NoChainAccountFoundError.AddAccountState
|
||||
) : MainCrowdloanValidationFailure(), NoChainAccountFoundError
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.domain.main.validations
|
||||
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
|
||||
class MainCrowdloanValidationPayload(
|
||||
val metaAccount: MetaAccount,
|
||||
val chain: Chain
|
||||
)
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.domain.main.validations
|
||||
|
||||
import io.novafoundation.nova.common.validation.ValidationSystem
|
||||
import io.novafoundation.nova.feature_account_api.domain.validation.hasChainAccount
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.main.validations.MainCrowdloanValidationFailure.NoRelaychainAccount
|
||||
|
||||
typealias MainCrowdloanValidationSystem = ValidationSystem<MainCrowdloanValidationPayload, MainCrowdloanValidationFailure>
|
||||
|
||||
fun ValidationSystem.Companion.mainCrowdloan(): MainCrowdloanValidationSystem = ValidationSystem {
|
||||
hasChainAccount(
|
||||
chain = MainCrowdloanValidationPayload::chain,
|
||||
metaAccount = MainCrowdloanValidationPayload::metaAccount,
|
||||
error = ::NoRelaychainAccount
|
||||
)
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.presentation
|
||||
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.account.add.AddAccountPayload
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.confirm.parcel.ConfirmContributePayload
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.BonusPayload
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.model.CustomContributePayload
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.select.parcel.ContributePayload
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface CrowdloanRouter {
|
||||
|
||||
fun openContribute(payload: ContributePayload)
|
||||
|
||||
val customBonusFlow: Flow<BonusPayload?>
|
||||
|
||||
val latestCustomBonus: BonusPayload?
|
||||
|
||||
fun openCustomContribute(payload: CustomContributePayload)
|
||||
|
||||
fun setCustomBonus(payload: BonusPayload)
|
||||
|
||||
fun openConfirmContribute(payload: ConfirmContributePayload)
|
||||
|
||||
fun back()
|
||||
|
||||
fun returnToMain()
|
||||
|
||||
fun openMoonbeamFlow(payload: ContributePayload)
|
||||
fun openAddAccount(payload: AddAccountPayload)
|
||||
|
||||
fun openUserContributions()
|
||||
|
||||
fun openSwitchWallet()
|
||||
|
||||
fun openWalletDetails(metaId: Long)
|
||||
|
||||
fun openClaimContribution()
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.presentation.claimControbution
|
||||
|
||||
import io.novafoundation.nova.common.base.BaseFragment
|
||||
import io.novafoundation.nova.common.di.FeatureUtils
|
||||
import io.novafoundation.nova.common.mixin.impl.observeValidations
|
||||
import io.novafoundation.nova.common.presentation.showLoadingState
|
||||
import io.novafoundation.nova.common.view.setProgressState
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.actions.setupExternalActions
|
||||
import io.novafoundation.nova.feature_crowdloan_api.di.CrowdloanFeatureApi
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.databinding.FragmentClaimContributionsBinding
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.di.CrowdloanFeatureComponent
|
||||
import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.setupFeeLoading
|
||||
|
||||
class ClaimContributionFragment : BaseFragment<ClaimContributionViewModel, FragmentClaimContributionsBinding>() {
|
||||
|
||||
override fun createBinding() = FragmentClaimContributionsBinding.inflate(layoutInflater)
|
||||
|
||||
override fun initViews() {
|
||||
binder.crowdloanClaimContributionsToolbar.setHomeButtonListener { viewModel.backClicked() }
|
||||
|
||||
binder.crowdloanClaimContributionsExtrinsicInfo.setOnAccountClickedListener { viewModel.originAccountClicked() }
|
||||
|
||||
binder.crowdloanClaimContributionsConfirm.prepareForProgress(viewLifecycleOwner)
|
||||
binder.crowdloanClaimContributionsConfirm.setOnClickListener { viewModel.confirmClicked() }
|
||||
}
|
||||
|
||||
override fun inject() {
|
||||
FeatureUtils.getFeature<CrowdloanFeatureComponent>(
|
||||
requireContext(),
|
||||
CrowdloanFeatureApi::class.java
|
||||
)
|
||||
.claimContributions()
|
||||
.create(this)
|
||||
.inject(this)
|
||||
}
|
||||
|
||||
override fun subscribe(viewModel: ClaimContributionViewModel) {
|
||||
observeValidations(viewModel)
|
||||
setupExternalActions(viewModel)
|
||||
setupFeeLoading(viewModel.originFeeMixin, binder.crowdloanClaimContributionsExtrinsicInfo.fee)
|
||||
|
||||
viewModel.showNextProgress.observe(binder.crowdloanClaimContributionsConfirm::setProgressState)
|
||||
|
||||
viewModel.currentAccountModelFlow.observe(binder.crowdloanClaimContributionsExtrinsicInfo::setAccount)
|
||||
viewModel.walletFlow.observe(binder.crowdloanClaimContributionsExtrinsicInfo::setWallet)
|
||||
|
||||
viewModel.redeemableAmountModelFlow.observe(binder.crowdloanClaimContributionsAmount::showLoadingState)
|
||||
}
|
||||
}
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.presentation.claimControbution
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import io.novafoundation.nova.common.base.BaseViewModel
|
||||
import io.novafoundation.nova.common.base.TitleAndMessage
|
||||
import io.novafoundation.nova.common.mixin.api.Validatable
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.common.utils.launchUnit
|
||||
import io.novafoundation.nova.common.utils.withSafeLoading
|
||||
import io.novafoundation.nova.common.validation.ValidationExecutor
|
||||
import io.novafoundation.nova.common.validation.progressConsumer
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.SelectedAccountUseCase
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.WalletUiUseCase
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.actions.ExternalActions
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.actions.showAddressActions
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.navigation.ExtrinsicNavigationWrapper
|
||||
import io.novafoundation.nova.feature_crowdloan_api.domain.contributions.Contribution
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.R
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.claimContributions.ClaimContributionsInteractor
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.claimContributions.validation.ClaimContributionValidationFailure
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.claimContributions.validation.ClaimContributionValidationPayload
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.claimContributions.validation.ClaimContributionValidationSystem
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.CrowdloanRouter
|
||||
import io.novafoundation.nova.feature_wallet_api.domain.AssetUseCase
|
||||
import io.novafoundation.nova.feature_wallet_api.domain.validation.handleNotEnoughFeeError
|
||||
import io.novafoundation.nova.feature_wallet_api.presentation.formatters.amount.AmountFormatter
|
||||
import io.novafoundation.nova.feature_wallet_api.presentation.formatters.amount.formatAmountToAmountModel
|
||||
import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.v2.FeeLoaderMixinV2
|
||||
import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.v2.awaitFee
|
||||
import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.v2.connectWith
|
||||
import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.v2.createDefault
|
||||
import io.novafoundation.nova.runtime.state.AnySelectedAssetOptionSharedState
|
||||
import io.novafoundation.nova.runtime.state.chain
|
||||
import io.novafoundation.nova.runtime.state.selectedAssetFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class ClaimContributionViewModel(
|
||||
private val router: CrowdloanRouter,
|
||||
private val resourceManager: ResourceManager,
|
||||
private val validationSystem: ClaimContributionValidationSystem,
|
||||
private val interactor: ClaimContributionsInteractor,
|
||||
private val feeLoaderMixinV2Factory: FeeLoaderMixinV2.Factory,
|
||||
private val externalActions: ExternalActions.Presentation,
|
||||
private val selectedAssetState: AnySelectedAssetOptionSharedState,
|
||||
private val validationExecutor: ValidationExecutor,
|
||||
private val extrinsicNavigationWrapper: ExtrinsicNavigationWrapper,
|
||||
selectedAccountUseCase: SelectedAccountUseCase,
|
||||
assetUseCase: AssetUseCase,
|
||||
walletUiUseCase: WalletUiUseCase,
|
||||
private val amountFormatter: AmountFormatter
|
||||
) : BaseViewModel(),
|
||||
Validatable by validationExecutor,
|
||||
ExternalActions by externalActions,
|
||||
ExtrinsicNavigationWrapper by extrinsicNavigationWrapper {
|
||||
|
||||
private val assetFlow = assetUseCase.currentAssetFlow()
|
||||
.shareInBackground()
|
||||
|
||||
private val claimableContributionsFlow = interactor.claimableContributions()
|
||||
.shareInBackground()
|
||||
|
||||
val redeemableAmountModelFlow = combine(claimableContributionsFlow, assetFlow) { claimableContributions, asset ->
|
||||
amountFormatter.formatAmountToAmountModel(claimableContributions.totalContributed, asset)
|
||||
}
|
||||
.withSafeLoading()
|
||||
.shareInBackground()
|
||||
|
||||
val currentAccountModelFlow = selectedAccountUseCase.selectedAddressModelFlow { selectedAssetState.chain() }
|
||||
.shareInBackground()
|
||||
|
||||
val walletFlow = walletUiUseCase.selectedWalletUiFlow()
|
||||
.shareInBackground()
|
||||
|
||||
val originFeeMixin = feeLoaderMixinV2Factory.createDefault(viewModelScope, selectedAssetState.selectedAssetFlow())
|
||||
|
||||
private val _showNextProgress = MutableStateFlow(false)
|
||||
val showNextProgress: StateFlow<Boolean> = _showNextProgress
|
||||
|
||||
init {
|
||||
originFeeMixin.connectWith(claimableContributionsFlow) { _, claimableContributions ->
|
||||
interactor.estimateFee(claimableContributions.contributions)
|
||||
}
|
||||
}
|
||||
|
||||
fun confirmClicked() {
|
||||
sendTransactionIfValid()
|
||||
}
|
||||
|
||||
fun backClicked() {
|
||||
router.back()
|
||||
}
|
||||
|
||||
fun originAccountClicked() = launch {
|
||||
val address = currentAccountModelFlow.first().address
|
||||
|
||||
externalActions.showAddressActions(address, selectedAssetState.chain())
|
||||
}
|
||||
|
||||
private fun sendTransactionIfValid() = launchUnit {
|
||||
_showNextProgress.value = true
|
||||
|
||||
val payload = ClaimContributionValidationPayload(
|
||||
fee = originFeeMixin.awaitFee(),
|
||||
asset = assetFlow.first()
|
||||
)
|
||||
|
||||
val claimableContributions = claimableContributionsFlow.first()
|
||||
|
||||
validationExecutor.requireValid(
|
||||
validationSystem = validationSystem,
|
||||
payload = payload,
|
||||
validationFailureTransformer = ::formatRedeemFailure,
|
||||
progressConsumer = _showNextProgress.progressConsumer()
|
||||
) {
|
||||
sendTransaction(claimableContributions.contributions)
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendTransaction(redeemableContributions: List<Contribution>) = launch {
|
||||
interactor.claim(redeemableContributions)
|
||||
.onFailure(::showError)
|
||||
.onSuccess { submissionResult ->
|
||||
showToast(resourceManager.getString(R.string.common_transaction_submitted))
|
||||
startNavigation(submissionResult.submissionHierarchy) { router.back() }
|
||||
}
|
||||
|
||||
_showNextProgress.value = false
|
||||
}
|
||||
|
||||
private fun formatRedeemFailure(failure: ClaimContributionValidationFailure): TitleAndMessage {
|
||||
return when (failure) {
|
||||
is ClaimContributionValidationFailure.NotEnoughBalanceToPayFees -> handleNotEnoughFeeError(failure, resourceManager)
|
||||
}
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.presentation.claimControbution.di
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import dagger.BindsInstance
|
||||
import dagger.Subcomponent
|
||||
import io.novafoundation.nova.common.di.scope.ScreenScope
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.claimControbution.ClaimContributionFragment
|
||||
|
||||
@Subcomponent(
|
||||
modules = [
|
||||
ClaimContributionModule::class
|
||||
]
|
||||
)
|
||||
@ScreenScope
|
||||
interface ClaimContributionComponent {
|
||||
|
||||
@Subcomponent.Factory
|
||||
interface Factory {
|
||||
|
||||
fun create(
|
||||
@BindsInstance fragment: Fragment,
|
||||
): ClaimContributionComponent
|
||||
}
|
||||
|
||||
fun inject(fragment: ClaimContributionFragment)
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.presentation.claimControbution.di
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.multibindings.IntoMap
|
||||
import io.novafoundation.nova.common.di.scope.ScreenScope
|
||||
import io.novafoundation.nova.common.di.viewmodel.ViewModelKey
|
||||
import io.novafoundation.nova.common.di.viewmodel.ViewModelModule
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.common.validation.ValidationExecutor
|
||||
import io.novafoundation.nova.common.validation.ValidationSystem
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.SelectedAccountUseCase
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.WalletUiUseCase
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.actions.ExternalActions
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.navigation.ExtrinsicNavigationWrapper
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.claimContributions.ClaimContributionsInteractor
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.claimContributions.validation.ClaimContributionValidationSystem
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.claimContributions.validation.claimContribution
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.CrowdloanRouter
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.claimControbution.ClaimContributionViewModel
|
||||
import io.novafoundation.nova.feature_wallet_api.domain.AssetUseCase
|
||||
import io.novafoundation.nova.feature_wallet_api.presentation.formatters.amount.AmountFormatter
|
||||
import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.v2.FeeLoaderMixinV2
|
||||
import io.novafoundation.nova.runtime.state.AnySelectedAssetOptionSharedState
|
||||
|
||||
@Module(includes = [ViewModelModule::class])
|
||||
class ClaimContributionModule {
|
||||
|
||||
@ScreenScope
|
||||
@Provides
|
||||
fun provideValidationSystem() = ValidationSystem.claimContribution()
|
||||
|
||||
@Provides
|
||||
@IntoMap
|
||||
@ViewModelKey(ClaimContributionViewModel::class)
|
||||
fun provideViewModel(
|
||||
router: CrowdloanRouter,
|
||||
resourceManager: ResourceManager,
|
||||
validationSystem: ClaimContributionValidationSystem,
|
||||
interactor: ClaimContributionsInteractor,
|
||||
feeLoaderMixinV2Factory: FeeLoaderMixinV2.Factory,
|
||||
externalActions: ExternalActions.Presentation,
|
||||
selectedAssetState: AnySelectedAssetOptionSharedState,
|
||||
validationExecutor: ValidationExecutor,
|
||||
extrinsicNavigationWrapper: ExtrinsicNavigationWrapper,
|
||||
selectedAccountUseCase: SelectedAccountUseCase,
|
||||
assetUseCase: AssetUseCase,
|
||||
walletUiUseCase: WalletUiUseCase,
|
||||
amountFormatter: AmountFormatter
|
||||
): ViewModel {
|
||||
return ClaimContributionViewModel(
|
||||
router = router,
|
||||
resourceManager = resourceManager,
|
||||
validationSystem = validationSystem,
|
||||
interactor = interactor,
|
||||
feeLoaderMixinV2Factory = feeLoaderMixinV2Factory,
|
||||
externalActions = externalActions,
|
||||
selectedAssetState = selectedAssetState,
|
||||
validationExecutor = validationExecutor,
|
||||
selectedAccountUseCase = selectedAccountUseCase,
|
||||
assetUseCase = assetUseCase,
|
||||
walletUiUseCase = walletUiUseCase,
|
||||
extrinsicNavigationWrapper = extrinsicNavigationWrapper,
|
||||
amountFormatter = amountFormatter
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideViewModelCreator(
|
||||
fragment: Fragment,
|
||||
viewModelFactory: ViewModelProvider.Factory,
|
||||
): ClaimContributionViewModel {
|
||||
return ViewModelProvider(fragment, viewModelFactory).get(ClaimContributionViewModel::class.java)
|
||||
}
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute
|
||||
|
||||
import io.novafoundation.nova.common.mixin.api.Action
|
||||
import io.novafoundation.nova.common.mixin.api.CustomDialogDisplayer
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.common.validation.TransformedFailure
|
||||
import io.novafoundation.nova.common.validation.ValidationFlowActions
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.R
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.validations.ContributeValidationFailure
|
||||
import io.novafoundation.nova.feature_wallet_api.presentation.formatters.formatTokenAmount
|
||||
|
||||
fun contributeValidationFailure(
|
||||
reason: ContributeValidationFailure,
|
||||
validationFlowActions: ValidationFlowActions<*>,
|
||||
resourceManager: ResourceManager,
|
||||
onOpenCustomContribute: Action?,
|
||||
): TransformedFailure {
|
||||
return when (reason) {
|
||||
ContributeValidationFailure.CannotPayFees -> {
|
||||
TransformedFailure.Default(
|
||||
resourceManager.getString(R.string.common_not_enough_funds_title) to
|
||||
resourceManager.getString(R.string.common_not_enough_funds_message)
|
||||
)
|
||||
}
|
||||
|
||||
ContributeValidationFailure.ExistentialDepositCrossed -> {
|
||||
TransformedFailure.Default(
|
||||
resourceManager.getString(R.string.common_existential_warning_title) to
|
||||
resourceManager.getString(R.string.common_existential_warning_message_v2_2_0)
|
||||
)
|
||||
}
|
||||
|
||||
ContributeValidationFailure.CrowdloanEnded -> {
|
||||
TransformedFailure.Default(
|
||||
resourceManager.getString(R.string.crowdloan_ended_title) to
|
||||
resourceManager.getString(R.string.crowdloan_ended_message)
|
||||
)
|
||||
}
|
||||
|
||||
ContributeValidationFailure.CapExceeded.FromRaised -> {
|
||||
TransformedFailure.Default(
|
||||
resourceManager.getString(R.string.crowdloan_cap_reached_title) to
|
||||
resourceManager.getString(R.string.crowdloan_cap_reached_raised_message)
|
||||
)
|
||||
}
|
||||
|
||||
is ContributeValidationFailure.CapExceeded.FromAmount -> {
|
||||
val formattedAmount = with(reason) {
|
||||
maxAllowedContribution.formatTokenAmount(chainAsset)
|
||||
}
|
||||
|
||||
TransformedFailure.Default(
|
||||
resourceManager.getString(R.string.crowdloan_cap_reached_title) to
|
||||
resourceManager.getString(R.string.crowdloan_cap_reached_amount_message, formattedAmount)
|
||||
)
|
||||
}
|
||||
|
||||
is ContributeValidationFailure.LessThanMinContribution -> {
|
||||
val formattedAmount = with(reason) {
|
||||
minContribution.formatTokenAmount(chainAsset)
|
||||
}
|
||||
|
||||
TransformedFailure.Default(
|
||||
resourceManager.getString(R.string.crowdloan_too_small_contribution_title) to
|
||||
resourceManager.getString(R.string.crowdloan_too_small_contribution_message, formattedAmount)
|
||||
)
|
||||
}
|
||||
|
||||
ContributeValidationFailure.PrivateCrowdloanNotSupported -> {
|
||||
TransformedFailure.Default(
|
||||
resourceManager.getString(R.string.crodloan_private_crowdloan_title) to
|
||||
resourceManager.getString(R.string.crodloan_private_crowdloan_message)
|
||||
)
|
||||
}
|
||||
ContributeValidationFailure.BonusNotApplied -> {
|
||||
TransformedFailure.Custom(
|
||||
CustomDialogDisplayer.Payload(
|
||||
title = resourceManager.getString(R.string.crowdloan_missing_bonus_title),
|
||||
message = resourceManager.getString(R.string.crowdloan_missing_bonus_message),
|
||||
okAction = CustomDialogDisplayer.Payload.DialogAction(
|
||||
title = resourceManager.getString(R.string.crowdloan_missing_bonus_action),
|
||||
action = { onOpenCustomContribute?.invoke() }
|
||||
),
|
||||
cancelAction = CustomDialogDisplayer.Payload.DialogAction(
|
||||
title = resourceManager.getString(R.string.common_skip),
|
||||
action = { validationFlowActions.resumeFlow() }
|
||||
),
|
||||
customStyle = R.style.AccentNegativeAlertDialogTheme
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.confirm
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
|
||||
import coil.ImageLoader
|
||||
import io.novafoundation.nova.common.base.BaseFragment
|
||||
import io.novafoundation.nova.common.di.FeatureUtils
|
||||
import io.novafoundation.nova.common.mixin.impl.observeBrowserEvents
|
||||
import io.novafoundation.nova.common.mixin.impl.observeValidations
|
||||
import io.novafoundation.nova.common.presentation.masking.dataOrNull
|
||||
import io.novafoundation.nova.common.utils.setVisible
|
||||
import io.novafoundation.nova.common.view.setProgressState
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.actions.setupExternalActions
|
||||
import io.novafoundation.nova.feature_crowdloan_api.di.CrowdloanFeatureApi
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.databinding.FragmentContributeConfirmBinding
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.di.CrowdloanFeatureComponent
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.confirm.parcel.ConfirmContributePayload
|
||||
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val KEY_PAYLOAD = "KEY_PAYLOAD"
|
||||
|
||||
class ConfirmContributeFragment : BaseFragment<ConfirmContributeViewModel, FragmentContributeConfirmBinding>() {
|
||||
|
||||
@Inject
|
||||
lateinit var imageLoader: ImageLoader
|
||||
|
||||
companion object {
|
||||
|
||||
fun getBundle(payload: ConfirmContributePayload) = Bundle().apply {
|
||||
putParcelable(KEY_PAYLOAD, payload)
|
||||
}
|
||||
}
|
||||
|
||||
override fun createBinding() = FragmentContributeConfirmBinding.inflate(layoutInflater)
|
||||
|
||||
override fun initViews() {
|
||||
binder.confirmContributeToolbar.setHomeButtonListener { viewModel.backClicked() }
|
||||
binder.confirmContributeConfirm.prepareForProgress(viewLifecycleOwner)
|
||||
binder.confirmContributeConfirm.setOnClickListener { viewModel.nextClicked() }
|
||||
|
||||
binder.confirmContributeOriginAcount.setWholeClickListener { viewModel.originAccountClicked() }
|
||||
}
|
||||
|
||||
override fun inject() {
|
||||
val payload = argument<ConfirmContributePayload>("KEY_PAYLOAD")
|
||||
|
||||
FeatureUtils.getFeature<CrowdloanFeatureComponent>(
|
||||
requireContext(),
|
||||
CrowdloanFeatureApi::class.java
|
||||
)
|
||||
.confirmContributeFactory()
|
||||
.create(this, payload)
|
||||
.inject(this)
|
||||
}
|
||||
|
||||
override fun subscribe(viewModel: ConfirmContributeViewModel) {
|
||||
observeBrowserEvents(viewModel)
|
||||
observeValidations(viewModel)
|
||||
setupExternalActions(viewModel)
|
||||
|
||||
viewModel.showNextProgress.observe(binder.confirmContributeConfirm::setProgressState)
|
||||
|
||||
viewModel.assetModelFlow.observe {
|
||||
binder.confirmContributeAmount.setAssetBalance(it.assetBalance.dataOrNull() ?: "")
|
||||
binder.confirmContributeAmount.setAssetName(it.tokenSymbol)
|
||||
binder.confirmContributeAmount.loadAssetImage(it.icon)
|
||||
}
|
||||
|
||||
binder.confirmContributeAmount.amountInput.setText(viewModel.selectedAmount)
|
||||
|
||||
viewModel.enteredFiatAmountFlow.observe {
|
||||
it.let(binder.confirmContributeAmount::setFiatAmount)
|
||||
}
|
||||
|
||||
viewModel.feeFlow.observe(binder.confirmContributeFee::setFeeStatus)
|
||||
|
||||
with(binder.confirmContributeReward) {
|
||||
val reward = viewModel.estimatedReward
|
||||
|
||||
setVisible(reward != null)
|
||||
|
||||
reward?.let { showValue(it) }
|
||||
}
|
||||
|
||||
viewModel.crowdloanInfoFlow.observe {
|
||||
binder.confirmContributeLeasingPeriod.showValue(it.leasePeriod, it.leasedUntil)
|
||||
}
|
||||
|
||||
viewModel.selectedAddressModelFlow.observe {
|
||||
binder.confirmContributeOriginAcount.setMessage(it.nameOrAddress)
|
||||
binder.confirmContributeOriginAcount.setTextIcon(it.image)
|
||||
}
|
||||
|
||||
viewModel.bonusFlow.observe {
|
||||
binder.confirmContributeBonus.setVisible(it != null)
|
||||
|
||||
it?.let(binder.confirmContributeBonus::showValue)
|
||||
}
|
||||
|
||||
viewModel.customizationConfiguration.filterNotNull().observe { (customization, customViewState) ->
|
||||
customization.injectViews(binder.confirmContributeContainer, customViewState, viewLifecycleOwner.lifecycleScope)
|
||||
}
|
||||
}
|
||||
}
|
||||
+228
@@ -0,0 +1,228 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.confirm
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import io.novafoundation.nova.common.address.AddressIconGenerator
|
||||
import io.novafoundation.nova.common.base.BaseViewModel
|
||||
import io.novafoundation.nova.common.mixin.api.Validatable
|
||||
import io.novafoundation.nova.common.presentation.AssetIconProvider
|
||||
import io.novafoundation.nova.common.presentation.masking.MaskableModel
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.common.utils.Event
|
||||
import io.novafoundation.nova.common.utils.flowOf
|
||||
import io.novafoundation.nova.common.utils.inBackground
|
||||
import io.novafoundation.nova.common.utils.lazyAsync
|
||||
import io.novafoundation.nova.common.validation.CompositeValidation
|
||||
import io.novafoundation.nova.common.validation.ValidationExecutor
|
||||
import io.novafoundation.nova.common.validation.ValidationSystem
|
||||
import io.novafoundation.nova.common.validation.progressConsumer
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.SelectedAccountUseCase
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.account.icon.createAccountAddressModel
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.actions.ExternalActions
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.actions.showAddressActions
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.navigation.ExtrinsicNavigationWrapper
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.R
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.di.customCrowdloan.CustomContributeManager
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.CrowdloanContributeInteractor
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.validations.ContributeValidation
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.validations.ContributeValidationPayload
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.CrowdloanRouter
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.confirm.model.LeasePeriodModel
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.confirm.parcel.ConfirmContributePayload
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.contributeValidationFailure
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.ConfirmContributeCustomization
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.select.parcel.mapParachainMetadataFromParcel
|
||||
import io.novafoundation.nova.feature_currency_api.presentation.formatters.formatAsCurrency
|
||||
import io.novafoundation.nova.feature_wallet_api.data.mappers.mapAssetToAssetModel
|
||||
import io.novafoundation.nova.feature_wallet_api.data.mappers.mapFeeToFeeModel
|
||||
import io.novafoundation.nova.feature_wallet_api.domain.AssetUseCase
|
||||
import io.novafoundation.nova.feature_wallet_api.presentation.formatters.amount.AmountFormatter
|
||||
import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.model.FeeStatus
|
||||
import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.mapFeeFromParcel
|
||||
import io.novafoundation.nova.runtime.state.SingleAssetSharedState
|
||||
import io.novafoundation.nova.runtime.state.chain
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class ConfirmContributeViewModel(
|
||||
private val assetIconProvider: AssetIconProvider,
|
||||
private val router: CrowdloanRouter,
|
||||
private val contributionInteractor: CrowdloanContributeInteractor,
|
||||
private val resourceManager: ResourceManager,
|
||||
assetUseCase: AssetUseCase,
|
||||
accountUseCase: SelectedAccountUseCase,
|
||||
addressModelGenerator: AddressIconGenerator,
|
||||
private val validationExecutor: ValidationExecutor,
|
||||
private val payload: ConfirmContributePayload,
|
||||
private val validations: Collection<ContributeValidation>,
|
||||
private val customContributeManager: CustomContributeManager,
|
||||
private val externalActions: ExternalActions.Presentation,
|
||||
private val assetSharedState: SingleAssetSharedState,
|
||||
private val extrinsicNavigationWrapper: ExtrinsicNavigationWrapper,
|
||||
private val amountFormatter: AmountFormatter
|
||||
) : BaseViewModel(),
|
||||
Validatable by validationExecutor,
|
||||
ExternalActions by externalActions,
|
||||
ExtrinsicNavigationWrapper by extrinsicNavigationWrapper {
|
||||
|
||||
private val decimalFee = mapFeeFromParcel(payload.fee)
|
||||
|
||||
private val chain by lazyAsync { assetSharedState.chain() }
|
||||
|
||||
override val openBrowserEvent = MutableLiveData<Event<String>>()
|
||||
|
||||
private val _showNextProgress = MutableLiveData(false)
|
||||
val showNextProgress: LiveData<Boolean> = _showNextProgress
|
||||
|
||||
private val assetFlow = assetUseCase.currentAssetFlow()
|
||||
.share()
|
||||
|
||||
val assetModelFlow = assetFlow
|
||||
.map {
|
||||
mapAssetToAssetModel(
|
||||
assetIconProvider,
|
||||
it,
|
||||
resourceManager,
|
||||
// Very rude way to show transferable balance but we don't support contributions so I don't se a reason for deeper refactoring
|
||||
MaskableModel.Unmasked(it.transferableInPlanks)
|
||||
)
|
||||
}
|
||||
.inBackground()
|
||||
.share()
|
||||
|
||||
val selectedAddressModelFlow = accountUseCase.selectedMetaAccountFlow()
|
||||
.map { metaAccount ->
|
||||
addressModelGenerator.createAccountAddressModel(chain.await(), metaAccount)
|
||||
}
|
||||
.shareInBackground()
|
||||
|
||||
val selectedAmount = payload.amount.toString()
|
||||
|
||||
val feeFlow = assetFlow.map { asset ->
|
||||
val feeModel = mapFeeToFeeModel(decimalFee, asset.token, amountFormatter = amountFormatter)
|
||||
|
||||
FeeStatus.Loaded(feeModel)
|
||||
}
|
||||
.inBackground()
|
||||
.share()
|
||||
|
||||
val enteredFiatAmountFlow = assetFlow.map { asset ->
|
||||
asset.token.amountToFiat(payload.amount).formatAsCurrency(asset.token.currency)
|
||||
}
|
||||
.inBackground()
|
||||
.share()
|
||||
|
||||
private val parachainMetadata = payload.metadata?.let(::mapParachainMetadataFromParcel)
|
||||
|
||||
private val relevantCustomFlowFactory = parachainMetadata?.customFlow?.let {
|
||||
customContributeManager.getFactoryOrNull(it)
|
||||
}
|
||||
|
||||
val customizationConfiguration: Flow<Pair<ConfirmContributeCustomization, ConfirmContributeCustomization.ViewState>?> = flowOf {
|
||||
relevantCustomFlowFactory?.confirmContributeCustomization?.let {
|
||||
it to it.createViewState(coroutineScope = this, parachainMetadata!!, payload.customizationPayload)
|
||||
}
|
||||
}
|
||||
.inBackground()
|
||||
.share()
|
||||
|
||||
val estimatedReward = payload.estimatedRewardDisplay
|
||||
|
||||
private val crowdloanFlow = contributionInteractor.crowdloanStateFlow(payload.paraId, parachainMetadata)
|
||||
.inBackground()
|
||||
.share()
|
||||
|
||||
val crowdloanInfoFlow = crowdloanFlow.map { crowdloan ->
|
||||
LeasePeriodModel(
|
||||
leasePeriod = resourceManager.formatDuration(crowdloan.leasePeriodInMillis),
|
||||
leasedUntil = resourceManager.formatDateTime(crowdloan.leasedUntilInMillis)
|
||||
)
|
||||
}
|
||||
.inBackground()
|
||||
.share()
|
||||
|
||||
val bonusFlow = flow {
|
||||
val bonusDisplay = payload.bonusPayload?.bonusText(payload.amount)
|
||||
|
||||
emit(bonusDisplay)
|
||||
}
|
||||
.inBackground()
|
||||
.share()
|
||||
|
||||
private val customizedValidationSystem = flowOf {
|
||||
val validations = relevantCustomFlowFactory?.confirmContributeCustomization?.modifyValidations(validations)
|
||||
?: validations
|
||||
|
||||
ValidationSystem(CompositeValidation(validations))
|
||||
}
|
||||
.inBackground()
|
||||
.share()
|
||||
|
||||
fun nextClicked() {
|
||||
maybeGoToNext()
|
||||
}
|
||||
|
||||
fun backClicked() {
|
||||
router.back()
|
||||
}
|
||||
|
||||
fun originAccountClicked() {
|
||||
launch {
|
||||
val accountAddress = selectedAddressModelFlow.first().address
|
||||
val chain = assetSharedState.chain()
|
||||
|
||||
externalActions.showAddressActions(accountAddress, chain)
|
||||
}
|
||||
}
|
||||
|
||||
private fun maybeGoToNext() = launch {
|
||||
val validationPayload = ContributeValidationPayload(
|
||||
crowdloan = crowdloanFlow.first(),
|
||||
fee = decimalFee,
|
||||
asset = assetFlow.first(),
|
||||
customizationPayload = payload.customizationPayload,
|
||||
bonusPayload = payload.bonusPayload,
|
||||
contributionAmount = payload.amount
|
||||
)
|
||||
|
||||
validationExecutor.requireValid(
|
||||
validationSystem = customizedValidationSystem.first(),
|
||||
payload = validationPayload,
|
||||
progressConsumer = _showNextProgress.progressConsumer(),
|
||||
validationFailureTransformerCustom = { status, actions ->
|
||||
contributeValidationFailure(
|
||||
reason = status.reason,
|
||||
validationFlowActions = actions,
|
||||
resourceManager = resourceManager,
|
||||
onOpenCustomContribute = null
|
||||
)
|
||||
}
|
||||
) {
|
||||
sendTransaction()
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendTransaction() {
|
||||
launch {
|
||||
val crowdloan = crowdloanFlow.first()
|
||||
|
||||
contributionInteractor.contribute(
|
||||
crowdloan = crowdloan,
|
||||
contribution = payload.amount,
|
||||
bonusPayload = payload.bonusPayload,
|
||||
customizationPayload = payload.customizationPayload
|
||||
)
|
||||
.onFailure(::showError)
|
||||
.onSuccess {
|
||||
showToast(resourceManager.getString(R.string.common_transaction_submitted))
|
||||
|
||||
startNavigation(it.submissionHierarchy) { router.returnToMain() }
|
||||
}
|
||||
|
||||
_showNextProgress.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.confirm.di
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import dagger.BindsInstance
|
||||
import dagger.Subcomponent
|
||||
import io.novafoundation.nova.common.di.scope.ScreenScope
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.confirm.ConfirmContributeFragment
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.confirm.parcel.ConfirmContributePayload
|
||||
|
||||
@Subcomponent(
|
||||
modules = [
|
||||
ConfirmContributeModule::class
|
||||
]
|
||||
)
|
||||
@ScreenScope
|
||||
interface ConfirmContributeComponent {
|
||||
|
||||
@Subcomponent.Factory
|
||||
interface Factory {
|
||||
|
||||
fun create(
|
||||
@BindsInstance fragment: Fragment,
|
||||
@BindsInstance payload: ConfirmContributePayload
|
||||
): ConfirmContributeComponent
|
||||
}
|
||||
|
||||
fun inject(fragment: ConfirmContributeFragment)
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
package io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.confirm.di
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.multibindings.IntoMap
|
||||
import io.novafoundation.nova.common.address.AddressIconGenerator
|
||||
import io.novafoundation.nova.common.di.viewmodel.ViewModelKey
|
||||
import io.novafoundation.nova.common.di.viewmodel.ViewModelModule
|
||||
import io.novafoundation.nova.common.presentation.AssetIconProvider
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.common.validation.ValidationExecutor
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.SelectedAccountUseCase
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.actions.ExternalActions
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.navigation.ExtrinsicNavigationWrapper
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.data.CrowdloanSharedState
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.di.customCrowdloan.CustomContributeManager
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.di.validations.Confirm
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.CrowdloanContributeInteractor
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.validations.ContributeValidation
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.CrowdloanRouter
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.confirm.ConfirmContributeViewModel
|
||||
import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.confirm.parcel.ConfirmContributePayload
|
||||
import io.novafoundation.nova.feature_wallet_api.domain.AssetUseCase
|
||||
import io.novafoundation.nova.feature_wallet_api.presentation.formatters.amount.AmountFormatter
|
||||
|
||||
@Module(includes = [ViewModelModule::class])
|
||||
class ConfirmContributeModule {
|
||||
|
||||
@Provides
|
||||
@IntoMap
|
||||
@ViewModelKey(ConfirmContributeViewModel::class)
|
||||
fun provideViewModel(
|
||||
assetIconProvider: AssetIconProvider,
|
||||
interactor: CrowdloanContributeInteractor,
|
||||
router: CrowdloanRouter,
|
||||
resourceManager: ResourceManager,
|
||||
assetUseCase: AssetUseCase,
|
||||
validationExecutor: ValidationExecutor,
|
||||
payload: ConfirmContributePayload,
|
||||
accountUseCase: SelectedAccountUseCase,
|
||||
addressIconGenerator: AddressIconGenerator,
|
||||
@Confirm contributeValidations: @JvmSuppressWildcards Set<ContributeValidation>,
|
||||
externalActions: ExternalActions.Presentation,
|
||||
customContributeManager: CustomContributeManager,
|
||||
singleAssetSharedState: CrowdloanSharedState,
|
||||
extrinsicNavigationWrapper: ExtrinsicNavigationWrapper,
|
||||
amountFormatter: AmountFormatter
|
||||
): ViewModel {
|
||||
return ConfirmContributeViewModel(
|
||||
assetIconProvider,
|
||||
router,
|
||||
interactor,
|
||||
resourceManager,
|
||||
assetUseCase,
|
||||
accountUseCase,
|
||||
addressIconGenerator,
|
||||
validationExecutor,
|
||||
payload,
|
||||
contributeValidations,
|
||||
customContributeManager,
|
||||
externalActions,
|
||||
singleAssetSharedState,
|
||||
extrinsicNavigationWrapper,
|
||||
amountFormatter = amountFormatter
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideViewModelCreator(
|
||||
fragment: Fragment,
|
||||
viewModelFactory: ViewModelProvider.Factory
|
||||
): ConfirmContributeViewModel {
|
||||
return ViewModelProvider(fragment, viewModelFactory).get(ConfirmContributeViewModel::class.java)
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user