mirror of
https://github.com/pezkuwichain/pezkuwi-wallet-android.git
synced 2026-04-24 22:48:02 +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,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest>
|
||||
|
||||
</manifest>
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.cloudBackup
|
||||
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount
|
||||
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.CloudBackup
|
||||
|
||||
fun CloudBackup.WalletPublicInfo.Type.toMetaAccountType(): LightMetaAccount.Type {
|
||||
return when (this) {
|
||||
CloudBackup.WalletPublicInfo.Type.SECRETS -> LightMetaAccount.Type.SECRETS
|
||||
CloudBackup.WalletPublicInfo.Type.WATCH_ONLY -> LightMetaAccount.Type.WATCH_ONLY
|
||||
CloudBackup.WalletPublicInfo.Type.PARITY_SIGNER -> LightMetaAccount.Type.PARITY_SIGNER
|
||||
CloudBackup.WalletPublicInfo.Type.LEDGER -> LightMetaAccount.Type.LEDGER_LEGACY
|
||||
CloudBackup.WalletPublicInfo.Type.POLKADOT_VAULT -> LightMetaAccount.Type.POLKADOT_VAULT
|
||||
CloudBackup.WalletPublicInfo.Type.LEDGER_GENERIC -> LightMetaAccount.Type.LEDGER
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.cloudBackup
|
||||
|
||||
import io.novafoundation.nova.common.data.secrets.v2.MetaAccountSecrets
|
||||
import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal
|
||||
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.CloudBackup
|
||||
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.diff.CloudBackupDiff
|
||||
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.diff.localVsCloudDiff
|
||||
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.diff.strategy.BackupDiffStrategyFactory
|
||||
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.errors.CannotApplyNonDestructiveDiff
|
||||
import io.novasama.substrate_sdk_android.scale.EncodableStruct
|
||||
|
||||
const val CLOUD_BACKUP_APPLY_SOURCE = "CLOUD_BACKUP_APPLY_SOURCE"
|
||||
|
||||
interface LocalAccountsCloudBackupFacade {
|
||||
|
||||
/**
|
||||
* Constructs full backup instance, including sensitive information
|
||||
* Should only be used when full backup instance is needed, for example when writing backup to cloud. Otherwise use [publicBackupInfoFromLocalSnapshot]
|
||||
*
|
||||
* Important note: Should only be called as the result of direct user interaction!
|
||||
* We don't want to exposure secrets to RAM until user explicitly directs app to do so
|
||||
*/
|
||||
suspend fun fullBackupInfoFromLocalSnapshot(): CloudBackup
|
||||
|
||||
/**
|
||||
* Constructs partial backup instance, including only the public information (addresses, metadata e.t.c)
|
||||
*
|
||||
* Can be used without direct user interaction (e.g. in background) to compare backup states between local and remote sources
|
||||
*/
|
||||
suspend fun publicBackupInfoFromLocalSnapshot(): CloudBackup.PublicData
|
||||
|
||||
/**
|
||||
* Creates a backup from external input. Useful for creating initial backup
|
||||
*/
|
||||
suspend fun constructCloudBackupForFirstWallet(
|
||||
metaAccount: MetaAccountLocal,
|
||||
baseSecrets: EncodableStruct<MetaAccountSecrets>,
|
||||
): CloudBackup
|
||||
|
||||
/**
|
||||
* Check if it is possible to apply given [diff] to local state in non-destructive manner
|
||||
* In other words, whether it is possible to apply backup without notifying the user
|
||||
*/
|
||||
suspend fun canPerformNonDestructiveApply(diff: CloudBackupDiff): Boolean
|
||||
|
||||
/**
|
||||
* Applies cloud version of the backup to the local state.
|
||||
* This is a destructive action as may overwrite or delete secrets stored in the app
|
||||
*
|
||||
* Important note: Should only be called as the result of direct user interaction!
|
||||
*/
|
||||
suspend fun applyBackupDiff(diff: CloudBackupDiff, cloudVersion: CloudBackup)
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to apply cloud backup version to current local application state in non-destructive manner
|
||||
* Will do nothing if it is not possible to apply changes in non-destructive manner
|
||||
*
|
||||
* @return whether the attempt succeeded
|
||||
*/
|
||||
suspend fun LocalAccountsCloudBackupFacade.applyNonDestructiveCloudVersionOrThrow(
|
||||
cloudVersion: CloudBackup,
|
||||
diffStrategy: BackupDiffStrategyFactory
|
||||
): CloudBackupDiff {
|
||||
val localSnapshot = publicBackupInfoFromLocalSnapshot()
|
||||
val diff = localSnapshot.localVsCloudDiff(cloudVersion.publicData, diffStrategy)
|
||||
|
||||
return if (canPerformNonDestructiveApply(diff)) {
|
||||
applyBackupDiff(diff, cloudVersion)
|
||||
|
||||
diff
|
||||
} else {
|
||||
throw CannotApplyNonDestructiveDiff(diff, cloudVersion)
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
@file:Suppress("RedundantUnitExpression")
|
||||
|
||||
package io.novafoundation.nova.feature_account_api.data.conversion.assethub
|
||||
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.bindPair
|
||||
import io.novafoundation.nova.common.utils.assetConversionOrNull
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.RelativeMultiLocation
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.bindMultiLocation
|
||||
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.QueryableStorageEntry1
|
||||
import io.novafoundation.nova.runtime.storage.source.query.api.storage1
|
||||
import io.novasama.substrate_sdk_android.runtime.metadata.RuntimeMetadata
|
||||
import io.novasama.substrate_sdk_android.runtime.metadata.module.Module
|
||||
|
||||
@JvmInline
|
||||
value class AssetConversionApi(override val module: Module) : QueryableModule
|
||||
|
||||
context(StorageQueryContext)
|
||||
val RuntimeMetadata.assetConversionOrNull: AssetConversionApi?
|
||||
get() = assetConversionOrNull()?.let(::AssetConversionApi)
|
||||
|
||||
context(StorageQueryContext)
|
||||
val AssetConversionApi.pools: QueryableStorageEntry1<Pair<RelativeMultiLocation, RelativeMultiLocation>, Unit>
|
||||
get() = storage1(
|
||||
name = "Pools",
|
||||
binding = { _, _ -> Unit },
|
||||
keyBinding = { bindPair(it, ::bindMultiLocation, ::bindMultiLocation) }
|
||||
)
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.derivationPath
|
||||
|
||||
import io.novasama.substrate_sdk_android.encrypt.junction.BIP32JunctionDecoder
|
||||
import io.novasama.substrate_sdk_android.encrypt.junction.JunctionDecoder
|
||||
import io.novasama.substrate_sdk_android.encrypt.junction.SubstrateJunctionDecoder
|
||||
|
||||
object DerivationPathDecoder {
|
||||
|
||||
@Throws
|
||||
fun decodeEthereumDerivationPath(derivationPath: String?): JunctionDecoder.DecodeResult? {
|
||||
if (derivationPath.isNullOrEmpty()) return null
|
||||
|
||||
return BIP32JunctionDecoder.decode(derivationPath)
|
||||
}
|
||||
|
||||
@Throws
|
||||
fun decodeSubstrateDerivationPath(derivationPath: String?): JunctionDecoder.DecodeResult? {
|
||||
if (derivationPath.isNullOrEmpty()) return null
|
||||
|
||||
return SubstrateJunctionDecoder.decode(derivationPath)
|
||||
}
|
||||
|
||||
fun isEthereumDerivationPathValid(derivationPath: String?): Boolean {
|
||||
return try {
|
||||
decodeEthereumDerivationPath(derivationPath)
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun isSubstrateDerivationPathValid(derivationPath: String?): Boolean {
|
||||
return try {
|
||||
decodeSubstrateDerivationPath(derivationPath)
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.ethereum.transaction
|
||||
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.BlockHash
|
||||
import io.novafoundation.nova.feature_account_api.data.signer.SubmissionHierarchy
|
||||
|
||||
class EthereumTransactionExecution(
|
||||
val extrinsicHash: String,
|
||||
val blockHash: BlockHash,
|
||||
val submissionHierarchy: SubmissionHierarchy
|
||||
)
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.ethereum.transaction
|
||||
|
||||
import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicSubmission
|
||||
import io.novafoundation.nova.feature_account_api.data.model.Fee
|
||||
import io.novafoundation.nova.runtime.ethereum.transaction.builder.EvmTransactionBuilder
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
import org.web3j.tx.gas.DefaultGasProvider
|
||||
import java.math.BigInteger
|
||||
|
||||
typealias EvmTransactionBuilding = EvmTransactionBuilder.() -> Unit
|
||||
|
||||
interface EvmTransactionService {
|
||||
|
||||
suspend fun calculateFee(
|
||||
chainId: ChainId,
|
||||
origin: TransactionOrigin,
|
||||
fallbackGasLimit: BigInteger = DefaultGasProvider.GAS_LIMIT,
|
||||
building: EvmTransactionBuilding,
|
||||
): Fee
|
||||
|
||||
suspend fun transact(
|
||||
chainId: ChainId,
|
||||
presetFee: Fee?,
|
||||
origin: TransactionOrigin,
|
||||
fallbackGasLimit: BigInteger = DefaultGasProvider.GAS_LIMIT,
|
||||
building: EvmTransactionBuilding,
|
||||
): Result<ExtrinsicSubmission>
|
||||
|
||||
suspend fun transactAndAwaitExecution(
|
||||
chainId: ChainId,
|
||||
presetFee: Fee?,
|
||||
origin: TransactionOrigin,
|
||||
fallbackGasLimit: BigInteger,
|
||||
building: EvmTransactionBuilding
|
||||
): Result<EthereumTransactionExecution>
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.ethereum.transaction
|
||||
|
||||
typealias TransactionHash = String
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.ethereum.transaction
|
||||
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
|
||||
import io.novasama.substrate_sdk_android.runtime.AccountId
|
||||
|
||||
sealed class TransactionOrigin {
|
||||
|
||||
data object SelectedWallet : TransactionOrigin()
|
||||
|
||||
class WalletWithAccount(val accountId: AccountId) : TransactionOrigin()
|
||||
|
||||
class Wallet(val metaAccount: MetaAccount) : TransactionOrigin()
|
||||
|
||||
class WalletWithId(val metaId: Long) : TransactionOrigin()
|
||||
}
|
||||
|
||||
fun AccountId.intoOrigin(): TransactionOrigin = TransactionOrigin.WalletWithAccount(this)
|
||||
|
||||
fun MetaAccount.intoOrigin(): TransactionOrigin = TransactionOrigin.Wallet(this)
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.events
|
||||
|
||||
import io.novafoundation.nova.common.utils.bus.EventBus
|
||||
import io.novafoundation.nova.feature_account_api.data.events.MetaAccountChangesEventBus.Event
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount
|
||||
|
||||
interface MetaAccountChangesEventBus : EventBus<Event> {
|
||||
|
||||
sealed interface Event : EventBus.Event {
|
||||
|
||||
data class BatchUpdate(val updates: Collection<Event>) : Event
|
||||
|
||||
data class AccountAdded(override val metaId: Long, override val metaAccountType: LightMetaAccount.Type) : Event, SingleUpdateEvent
|
||||
|
||||
data class AccountStructureChanged(override val metaId: Long, override val metaAccountType: LightMetaAccount.Type) : Event, SingleUpdateEvent
|
||||
|
||||
data class AccountRemoved(override val metaId: Long, override val metaAccountType: LightMetaAccount.Type) : Event, SingleUpdateEvent
|
||||
|
||||
data class AccountNameChanged(override val metaId: Long, override val metaAccountType: LightMetaAccount.Type) : Event, SingleUpdateEvent
|
||||
}
|
||||
|
||||
interface SingleUpdateEvent {
|
||||
|
||||
val metaId: Long
|
||||
|
||||
val metaAccountType: LightMetaAccount.Type
|
||||
}
|
||||
|
||||
interface EventVisitor {
|
||||
|
||||
fun visitAccountAdded(added: Event.AccountAdded) {}
|
||||
|
||||
fun visitAccountStructureChanged(structureChanged: Event.AccountStructureChanged) {}
|
||||
|
||||
fun visitAccountNameChanged(accountNameChanged: Event.AccountNameChanged) {}
|
||||
|
||||
fun visitAccountRemoved(accountRemoved: Event.AccountRemoved) {}
|
||||
}
|
||||
}
|
||||
|
||||
inline fun buildChangesEvent(builder: MutableList<Event>.() -> Unit): Event? {
|
||||
val allEvents = buildList(builder)
|
||||
return allEvents.combineBusEvents()
|
||||
}
|
||||
|
||||
fun List<Event>.combineBusEvents(): Event? {
|
||||
return when (size) {
|
||||
0 -> null
|
||||
1 -> single()
|
||||
else -> Event.BatchUpdate(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun Event.visit(visitor: MetaAccountChangesEventBus.EventVisitor) {
|
||||
when (this) {
|
||||
is Event.AccountAdded -> visitor.visitAccountAdded(this)
|
||||
is Event.AccountNameChanged -> visitor.visitAccountNameChanged(this)
|
||||
is Event.AccountRemoved -> visitor.visitAccountRemoved(this)
|
||||
is Event.AccountStructureChanged -> visitor.visitAccountStructureChanged(this)
|
||||
is Event.BatchUpdate -> updates.onEach { it.visit(visitor) }
|
||||
}
|
||||
}
|
||||
|
||||
typealias EventBusEventCollator<E, T> = (E) -> T?
|
||||
|
||||
fun <T> Event.collect(
|
||||
onAdd: EventBusEventCollator<Event.AccountAdded, T>? = null,
|
||||
onStructureChanged: EventBusEventCollator<Event.AccountStructureChanged, T>? = null,
|
||||
onNameChanged: EventBusEventCollator<Event.AccountNameChanged, T>? = null,
|
||||
onRemoved: EventBusEventCollator<Event.AccountRemoved, T>? = null,
|
||||
): List<T> {
|
||||
val result = mutableListOf<T>()
|
||||
|
||||
visit(object : MetaAccountChangesEventBus.EventVisitor {
|
||||
override fun visitAccountAdded(added: Event.AccountAdded) {
|
||||
onAdd?.invoke(added)?.let(result::add)
|
||||
}
|
||||
|
||||
override fun visitAccountStructureChanged(structureChanged: Event.AccountStructureChanged) {
|
||||
onStructureChanged?.invoke(structureChanged)?.let(result::add)
|
||||
}
|
||||
|
||||
override fun visitAccountNameChanged(accountNameChanged: Event.AccountNameChanged) {
|
||||
onNameChanged?.invoke(accountNameChanged)?.let(result::add)
|
||||
}
|
||||
|
||||
override fun visitAccountRemoved(accountRemoved: Event.AccountRemoved) {
|
||||
onRemoved?.invoke(accountRemoved)?.let(result::add)
|
||||
}
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
typealias SingleAccountEventVisitor<T> = (T) -> Unit
|
||||
|
||||
fun Event.visit(
|
||||
onAdd: SingleAccountEventVisitor<Event.AccountAdded>? = null,
|
||||
onStructureChanged: SingleAccountEventVisitor<Event.AccountStructureChanged>? = null,
|
||||
onNameChanged: SingleAccountEventVisitor<Event.AccountNameChanged>? = null,
|
||||
onRemoved: SingleAccountEventVisitor<Event.AccountRemoved>? = null,
|
||||
) {
|
||||
visit(object : MetaAccountChangesEventBus.EventVisitor {
|
||||
override fun visitAccountAdded(added: Event.AccountAdded) {
|
||||
onAdd?.invoke(added)
|
||||
}
|
||||
|
||||
override fun visitAccountStructureChanged(structureChanged: Event.AccountStructureChanged) {
|
||||
onStructureChanged?.invoke(structureChanged)
|
||||
}
|
||||
|
||||
override fun visitAccountNameChanged(accountNameChanged: Event.AccountNameChanged) {
|
||||
onNameChanged?.invoke(accountNameChanged)
|
||||
}
|
||||
|
||||
override fun visitAccountRemoved(accountRemoved: Event.AccountRemoved) {
|
||||
onRemoved?.invoke(accountRemoved)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun Event.checkIncludes(
|
||||
checkAdd: Boolean = false,
|
||||
checkStructureChange: Boolean = false,
|
||||
checkNameChange: Boolean = false,
|
||||
checkAccountRemoved: Boolean = false
|
||||
): Boolean {
|
||||
var includes = false
|
||||
val updateClosure: SingleAccountEventVisitor<Any> = {
|
||||
includes = true
|
||||
}
|
||||
|
||||
visit(
|
||||
onAdd = updateClosure.takeIf { checkAdd },
|
||||
onStructureChanged = updateClosure.takeIf { checkStructureChange },
|
||||
onNameChanged = updateClosure.takeIf { checkNameChange },
|
||||
onRemoved = updateClosure.takeIf { checkAccountRemoved }
|
||||
)
|
||||
|
||||
return includes
|
||||
}
|
||||
|
||||
fun Event.allAffectedMetaAccountTypes(): List<LightMetaAccount.Type> {
|
||||
return collect(
|
||||
onAdd = { it.metaAccountType },
|
||||
onRemoved = { it.metaAccountType },
|
||||
onNameChanged = { it.metaAccountType },
|
||||
onStructureChanged = { it.metaAccountType }
|
||||
)
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.externalAccounts
|
||||
|
||||
import io.novafoundation.nova.feature_account_api.data.events.MetaAccountChangesEventBus
|
||||
|
||||
interface ExternalAccountsSyncService {
|
||||
|
||||
fun syncOnAccountChange(event: MetaAccountChangesEventBus.Event, changeSource: String?)
|
||||
|
||||
fun sync()
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.extrinsic
|
||||
|
||||
import io.novafoundation.nova.common.data.network.runtime.model.FeeResponse
|
||||
import io.novafoundation.nova.common.utils.multiResult.RetriableMultiResult
|
||||
import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.TransactionOrigin
|
||||
import io.novafoundation.nova.feature_account_api.data.extrinsic.execution.ExtrinsicExecutionResult
|
||||
import io.novafoundation.nova.feature_account_api.data.extrinsic.execution.watch.ExtrinsicWatchResult
|
||||
import io.novafoundation.nova.feature_account_api.data.fee.FeePaymentCurrency
|
||||
import io.novafoundation.nova.feature_account_api.data.fee.FeePaymentProviderRegistry
|
||||
import io.novafoundation.nova.feature_account_api.data.model.Fee
|
||||
import io.novafoundation.nova.feature_account_api.data.signer.CallExecutionType
|
||||
import io.novafoundation.nova.runtime.extrinsic.ExtrinsicStatus
|
||||
import io.novafoundation.nova.runtime.extrinsic.multi.CallBuilder
|
||||
import io.novafoundation.nova.feature_account_api.data.signer.NovaSigner
|
||||
import io.novafoundation.nova.feature_account_api.data.signer.SubmissionHierarchy
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novasama.substrate_sdk_android.runtime.extrinsic.BatchMode
|
||||
import io.novasama.substrate_sdk_android.runtime.extrinsic.builder.ExtrinsicBuilder
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
typealias FormExtrinsicWithOrigin = suspend ExtrinsicBuilder.(context: ExtrinsicBuildingContext) -> Unit
|
||||
typealias FormMultiExtrinsicWithOrigin = suspend CallBuilder.(context: ExtrinsicBuildingContext) -> Unit
|
||||
|
||||
class ExtrinsicSubmission(
|
||||
val hash: String,
|
||||
val submissionOrigin: SubmissionOrigin,
|
||||
val callExecutionType: CallExecutionType,
|
||||
val submissionHierarchy: SubmissionHierarchy
|
||||
)
|
||||
|
||||
class ExtrinsicBuildingContext(
|
||||
val submissionOrigin: SubmissionOrigin,
|
||||
val signer: NovaSigner,
|
||||
val chain: Chain
|
||||
)
|
||||
|
||||
private val DEFAULT_BATCH_MODE = BatchMode.BATCH_ALL
|
||||
|
||||
interface ExtrinsicService {
|
||||
|
||||
interface Factory {
|
||||
|
||||
fun create(feeConfig: FeePaymentConfig): ExtrinsicService
|
||||
}
|
||||
|
||||
class SubmissionOptions(
|
||||
val feePaymentCurrency: FeePaymentCurrency = FeePaymentCurrency.Native,
|
||||
val batchMode: BatchMode = DEFAULT_BATCH_MODE,
|
||||
)
|
||||
|
||||
class FeePaymentConfig(
|
||||
val coroutineScope: CoroutineScope,
|
||||
/**
|
||||
* Specify to use it instead of default [FeePaymentProviderRegistry] to perform fee computations
|
||||
*/
|
||||
val customFeePaymentRegistry: FeePaymentProviderRegistry? = null,
|
||||
)
|
||||
|
||||
suspend fun submitExtrinsic(
|
||||
chain: Chain,
|
||||
origin: TransactionOrigin,
|
||||
submissionOptions: SubmissionOptions = SubmissionOptions(),
|
||||
formExtrinsic: FormExtrinsicWithOrigin
|
||||
): Result<ExtrinsicSubmission>
|
||||
|
||||
suspend fun submitAndWatchExtrinsic(
|
||||
chain: Chain,
|
||||
origin: TransactionOrigin,
|
||||
submissionOptions: SubmissionOptions = SubmissionOptions(),
|
||||
formExtrinsic: FormExtrinsicWithOrigin
|
||||
): Result<Flow<ExtrinsicWatchResult<ExtrinsicStatus>>>
|
||||
|
||||
suspend fun submitExtrinsicAndAwaitExecution(
|
||||
chain: Chain,
|
||||
origin: TransactionOrigin,
|
||||
submissionOptions: SubmissionOptions = SubmissionOptions(),
|
||||
formExtrinsic: FormExtrinsicWithOrigin
|
||||
): Result<ExtrinsicExecutionResult>
|
||||
|
||||
suspend fun submitMultiExtrinsicAwaitingInclusion(
|
||||
chain: Chain,
|
||||
origin: TransactionOrigin,
|
||||
submissionOptions: SubmissionOptions = SubmissionOptions(),
|
||||
formExtrinsic: FormMultiExtrinsicWithOrigin
|
||||
): RetriableMultiResult<ExtrinsicWatchResult<ExtrinsicStatus.InBlock>>
|
||||
|
||||
suspend fun paymentInfo(
|
||||
chain: Chain,
|
||||
origin: TransactionOrigin,
|
||||
submissionOptions: SubmissionOptions = SubmissionOptions(),
|
||||
formExtrinsic: FormExtrinsicWithOrigin
|
||||
): FeeResponse
|
||||
|
||||
suspend fun estimateFee(
|
||||
chain: Chain,
|
||||
origin: TransactionOrigin,
|
||||
submissionOptions: SubmissionOptions = SubmissionOptions(),
|
||||
formExtrinsic: FormExtrinsicWithOrigin
|
||||
): Fee
|
||||
|
||||
suspend fun estimateMultiFee(
|
||||
chain: Chain,
|
||||
origin: TransactionOrigin,
|
||||
submissionOptions: SubmissionOptions = SubmissionOptions(),
|
||||
formExtrinsic: FormMultiExtrinsicWithOrigin
|
||||
): Fee
|
||||
|
||||
suspend fun estimateFee(
|
||||
chain: Chain,
|
||||
extrinsic: String,
|
||||
usedSigner: NovaSigner,
|
||||
): Fee
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.extrinsic
|
||||
|
||||
import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService.FeePaymentConfig
|
||||
import io.novafoundation.nova.feature_account_api.data.extrinsic.execution.watch.ExtrinsicWatchResult
|
||||
import io.novafoundation.nova.feature_account_api.data.extrinsic.execution.watch.mapWithStatus
|
||||
import io.novafoundation.nova.runtime.extrinsic.ExtrinsicStatus
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
suspend fun Result<Flow<ExtrinsicWatchResult<ExtrinsicStatus>>>.awaitInBlock(): Result<ExtrinsicWatchResult<ExtrinsicStatus.InBlock>> =
|
||||
mapCatching { watchResult ->
|
||||
watchResult.filter { it.status is ExtrinsicStatus.InBlock }
|
||||
.map { it.mapWithStatus<ExtrinsicStatus.InBlock>() }
|
||||
.first()
|
||||
}
|
||||
|
||||
suspend inline fun <reified T : ExtrinsicStatus> Flow<ExtrinsicWatchResult<*>>.awaitStatus(): ExtrinsicWatchResult<T> {
|
||||
return filterStatus<T>().first()
|
||||
}
|
||||
|
||||
inline fun <reified T : ExtrinsicStatus> Flow<ExtrinsicWatchResult<*>>.filterStatus(): Flow<ExtrinsicWatchResult<T>> {
|
||||
return filter { it.status is T }
|
||||
.map { it.mapWithStatus<T>() }
|
||||
}
|
||||
|
||||
fun ExtrinsicService.Factory.createDefault(coroutineScope: CoroutineScope): ExtrinsicService {
|
||||
return create(FeePaymentConfig(coroutineScope))
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.extrinsic
|
||||
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.WeightV2
|
||||
import io.novafoundation.nova.feature_account_api.data.signer.NovaSigner
|
||||
import io.novafoundation.nova.runtime.extrinsic.multi.CallBuilder
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.GenericCall
|
||||
|
||||
typealias SplitCalls = List<List<GenericCall.Instance>>
|
||||
|
||||
interface ExtrinsicSplitter {
|
||||
|
||||
suspend fun split(signer: NovaSigner, callBuilder: CallBuilder, chain: Chain): SplitCalls
|
||||
|
||||
suspend fun estimateCallWeight(signer: NovaSigner, call: GenericCall.Instance, chain: Chain): WeightV2
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.extrinsic
|
||||
|
||||
import io.novasama.substrate_sdk_android.runtime.AccountId
|
||||
import io.novasama.substrate_sdk_android.runtime.extrinsic.signer.Signer
|
||||
|
||||
data class SubmissionOrigin(
|
||||
/**
|
||||
* Account on which behalf the operation will be executed
|
||||
*/
|
||||
val executingAccount: AccountId,
|
||||
|
||||
/**
|
||||
* Account that will sign and submit transaction
|
||||
* It might differ from [executingAccount] if [Signer] modified the origin.
|
||||
* For example in the case of Proxied wallet [executingAccount] is proxied and [signingAccount] is proxy
|
||||
*/
|
||||
val signingAccount: AccountId
|
||||
) {
|
||||
|
||||
companion object {
|
||||
|
||||
fun singleOrigin(origin: AccountId) = SubmissionOrigin(origin, origin)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as SubmissionOrigin
|
||||
|
||||
if (!executingAccount.contentEquals(other.executingAccount)) return false
|
||||
return signingAccount.contentEquals(other.signingAccount)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = executingAccount.contentHashCode()
|
||||
result = 31 * result + signingAccount.contentHashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.extrinsic.execution
|
||||
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.BlockHash
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.DispatchError
|
||||
import io.novafoundation.nova.feature_account_api.data.signer.SubmissionHierarchy
|
||||
import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.GenericEvent
|
||||
|
||||
data class ExtrinsicExecutionResult(
|
||||
val extrinsicHash: String,
|
||||
val blockHash: BlockHash,
|
||||
val outcome: ExtrinsicDispatch,
|
||||
val submissionHierarchy: SubmissionHierarchy
|
||||
)
|
||||
|
||||
sealed interface ExtrinsicDispatch {
|
||||
|
||||
data class Ok(val emittedEvents: List<GenericEvent.Instance>) : ExtrinsicDispatch
|
||||
|
||||
data class Failed(val error: DispatchError) : ExtrinsicDispatch
|
||||
|
||||
object Unknown : ExtrinsicDispatch
|
||||
}
|
||||
|
||||
fun ExtrinsicExecutionResult.requireOk(): ExtrinsicExecutionResult {
|
||||
return when (outcome) {
|
||||
is ExtrinsicDispatch.Failed -> throw outcome.error
|
||||
is ExtrinsicDispatch.Ok -> this
|
||||
ExtrinsicDispatch.Unknown -> throw IllegalArgumentException("Unknown extrinsic execution result")
|
||||
}
|
||||
}
|
||||
|
||||
fun Result<ExtrinsicExecutionResult>.requireOk(): Result<ExtrinsicExecutionResult> {
|
||||
return mapCatching {
|
||||
it.requireOk()
|
||||
}
|
||||
}
|
||||
|
||||
fun ExtrinsicExecutionResult.requireOutcomeOk(): ExtrinsicDispatch.Ok {
|
||||
return requireOk().outcome as ExtrinsicDispatch.Ok
|
||||
}
|
||||
|
||||
fun ExtrinsicDispatch.isOk(): Boolean {
|
||||
return this is ExtrinsicDispatch.Ok
|
||||
}
|
||||
|
||||
fun ExtrinsicDispatch.isModuleError(moduleName: String, errorName: String): Boolean {
|
||||
return this is ExtrinsicDispatch.Failed &&
|
||||
error is DispatchError.Module &&
|
||||
error.module.name == moduleName &&
|
||||
error.error.name == errorName
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.extrinsic.execution.watch
|
||||
|
||||
import io.novafoundation.nova.feature_account_api.data.signer.SubmissionHierarchy
|
||||
import io.novafoundation.nova.runtime.extrinsic.ExtrinsicStatus
|
||||
|
||||
class ExtrinsicWatchResult<T : ExtrinsicStatus>(
|
||||
val status: T,
|
||||
val submissionHierarchy: SubmissionHierarchy
|
||||
)
|
||||
|
||||
fun List<ExtrinsicWatchResult<*>>.submissionHierarchy() = first().submissionHierarchy
|
||||
|
||||
inline fun <reified T : ExtrinsicStatus> ExtrinsicWatchResult<*>.mapWithStatus() = ExtrinsicWatchResult(status as T, submissionHierarchy)
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.fee
|
||||
|
||||
import io.novafoundation.nova.feature_account_api.data.fee.capability.FastLookupCustomFeeCapability
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainAssetId
|
||||
|
||||
class DefaultFastLookupCustomFeeCapability : FastLookupCustomFeeCapability {
|
||||
|
||||
override val nonUtilityFeeCapableTokens: Set<ChainAssetId> = emptySet()
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.fee
|
||||
|
||||
import android.util.Log
|
||||
import io.novafoundation.nova.feature_account_api.data.fee.capability.FastLookupCustomFeeCapability
|
||||
import io.novafoundation.nova.feature_account_api.data.model.Fee
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
import io.novasama.substrate_sdk_android.runtime.extrinsic.builder.ExtrinsicBuilder
|
||||
import io.novasama.substrate_sdk_android.runtime.extrinsic.signer.SendableExtrinsic
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
||||
interface FeePayment {
|
||||
|
||||
suspend fun modifyExtrinsic(extrinsicBuilder: ExtrinsicBuilder)
|
||||
|
||||
suspend fun convertNativeFee(nativeFee: Fee): Fee
|
||||
}
|
||||
|
||||
interface FeePaymentProvider {
|
||||
|
||||
val chain: Chain
|
||||
|
||||
suspend fun feePaymentFor(feePaymentCurrency: FeePaymentCurrency, coroutineScope: CoroutineScope?): FeePayment
|
||||
|
||||
suspend fun detectFeePaymentFromExtrinsic(extrinsic: SendableExtrinsic): FeePayment
|
||||
|
||||
suspend fun canPayFee(feePaymentCurrency: FeePaymentCurrency): Result<Boolean>
|
||||
|
||||
suspend fun fastLookupCustomFeeCapability(): Result<FastLookupCustomFeeCapability>
|
||||
}
|
||||
|
||||
interface FeePaymentProviderRegistry {
|
||||
|
||||
suspend fun providerFor(chainId: ChainId): FeePaymentProvider
|
||||
}
|
||||
|
||||
suspend fun FeePaymentProvider.fastLookupCustomFeeCapabilityOrDefault(): FastLookupCustomFeeCapability {
|
||||
return fastLookupCustomFeeCapability()
|
||||
.onFailure { Log.e("FeePaymentProvider", "Failed to construct fast custom fee lookup for chain ${chain.name}", it) }
|
||||
.getOrElse { DefaultFastLookupCustomFeeCapability() }
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.fee
|
||||
|
||||
import io.novafoundation.nova.runtime.ext.fullId
|
||||
import io.novafoundation.nova.runtime.ext.isCommissionAsset
|
||||
import io.novafoundation.nova.runtime.ext.utilityAsset
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
|
||||
sealed interface FeePaymentCurrency {
|
||||
|
||||
/**
|
||||
* Use native currency of the chain to pay the fee
|
||||
*/
|
||||
object Native : FeePaymentCurrency {
|
||||
|
||||
override fun toString(): String {
|
||||
return "Native"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Request to use a specific [asset] for payment fees
|
||||
* This does not guarantee that the exact [asset] will be used for fee payments,
|
||||
* for example if the chain doesn't support paying fees in asset. In that case it will fall-back to using [FeePaymentCurrency.Native]
|
||||
*
|
||||
* The actual asset used to pay fees will be available in [Fee.asset]
|
||||
*/
|
||||
class Asset private constructor(val asset: Chain.Asset) : FeePaymentCurrency {
|
||||
|
||||
companion object {
|
||||
|
||||
fun Chain.Asset.toFeePaymentCurrency(): FeePaymentCurrency {
|
||||
return when {
|
||||
isCommissionAsset -> Native
|
||||
else -> Asset(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other !is Asset) return false
|
||||
return asset.fullId == other.asset.fullId
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return asset.hashCode()
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "Asset(${asset.symbol})"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun FeePaymentCurrency.toChainAsset(chain: Chain): Chain.Asset {
|
||||
return toChainAsset(chain.utilityAsset)
|
||||
}
|
||||
|
||||
fun FeePaymentCurrency.toChainAsset(chainUtilityAsset: Chain.Asset): Chain.Asset {
|
||||
return when (this) {
|
||||
is FeePaymentCurrency.Asset -> asset
|
||||
FeePaymentCurrency.Native -> chainUtilityAsset
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.fee.capability
|
||||
|
||||
import io.novafoundation.nova.feature_account_api.data.fee.FeePaymentCurrency
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainAssetId
|
||||
|
||||
interface FastLookupCustomFeeCapability {
|
||||
|
||||
val nonUtilityFeeCapableTokens: Set<ChainAssetId>
|
||||
|
||||
fun canPayFeeInNonUtilityToken(chainAssetId: ChainAssetId): Boolean {
|
||||
return chainAssetId in nonUtilityFeeCapableTokens
|
||||
}
|
||||
}
|
||||
|
||||
interface CustomFeeCapabilityFacade {
|
||||
|
||||
suspend fun canPayFeeInCurrency(currency: FeePaymentCurrency): Boolean
|
||||
|
||||
/**
|
||||
* Whether fee payment in custom assets is not possible at all in the current environment
|
||||
* This check is also accounted for internally in [canPayFeeInNonUtilityToken]
|
||||
* but can be used separately for optimizing bulk checks
|
||||
*/
|
||||
suspend fun hasGlobalFeePaymentRestrictions(): Boolean
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.fee.chains
|
||||
|
||||
import io.novafoundation.nova.feature_account_api.data.fee.FeePayment
|
||||
import io.novafoundation.nova.feature_account_api.data.fee.FeePaymentCurrency
|
||||
import io.novafoundation.nova.feature_account_api.data.fee.FeePaymentProvider
|
||||
import io.novafoundation.nova.feature_account_api.data.fee.types.NativeFeePayment
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
||||
abstract class CustomOrNativeFeePaymentProvider : FeePaymentProvider {
|
||||
|
||||
protected abstract suspend fun feePaymentFor(customFeeAsset: Chain.Asset, coroutineScope: CoroutineScope?): FeePayment
|
||||
|
||||
protected abstract suspend fun canPayFeeInNonUtilityToken(customFeeAsset: Chain.Asset): Result<Boolean>
|
||||
|
||||
final override suspend fun feePaymentFor(feePaymentCurrency: FeePaymentCurrency, coroutineScope: CoroutineScope?): FeePayment {
|
||||
return when (feePaymentCurrency) {
|
||||
is FeePaymentCurrency.Asset -> feePaymentFor(feePaymentCurrency.asset, coroutineScope)
|
||||
|
||||
FeePaymentCurrency.Native -> NativeFeePayment()
|
||||
}
|
||||
}
|
||||
|
||||
final override suspend fun canPayFee(feePaymentCurrency: FeePaymentCurrency): Result<Boolean> {
|
||||
return when (feePaymentCurrency) {
|
||||
is FeePaymentCurrency.Asset -> canPayFeeInNonUtilityToken(feePaymentCurrency.asset)
|
||||
FeePaymentCurrency.Native -> Result.success(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.fee.types
|
||||
|
||||
import io.novafoundation.nova.feature_account_api.data.fee.FeePayment
|
||||
import io.novafoundation.nova.feature_account_api.data.model.Fee
|
||||
import io.novasama.substrate_sdk_android.runtime.extrinsic.builder.ExtrinsicBuilder
|
||||
|
||||
class NativeFeePayment : FeePayment {
|
||||
|
||||
override suspend fun modifyExtrinsic(extrinsicBuilder: ExtrinsicBuilder) {
|
||||
// no modifications needed
|
||||
}
|
||||
|
||||
override suspend fun convertNativeFee(nativeFee: Fee): Fee {
|
||||
return nativeFee
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.fee.types.assetHub
|
||||
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.BalanceOf
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.bindNumber
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.cast
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.castToStruct
|
||||
import io.novafoundation.nova.common.utils.transactionExtensionOrNull
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.RelativeMultiLocation
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.bindMultiLocation
|
||||
import io.novafoundation.nova.runtime.extrinsic.extensions.ChargeAssetTxPayment
|
||||
import io.novasama.substrate_sdk_android.runtime.RuntimeSnapshot
|
||||
import io.novasama.substrate_sdk_android.runtime.definitions.types.Type
|
||||
import io.novasama.substrate_sdk_android.runtime.definitions.types.composite.Struct
|
||||
import io.novasama.substrate_sdk_android.runtime.definitions.types.fromHex
|
||||
import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.Extrinsic
|
||||
import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.findExplicitOrNull
|
||||
|
||||
fun Extrinsic.Instance.findChargeAssetTxPayment(): ChargeAssetTxPaymentValue? {
|
||||
val value = findExplicitOrNull(ChargeAssetTxPayment.ID) ?: return null
|
||||
return ChargeAssetTxPaymentValue.bind(value)
|
||||
}
|
||||
|
||||
fun RuntimeSnapshot.decodeCustomTxPaymentId(assetIdHex: String): Any? {
|
||||
val chargeAssetTxPaymentType = metadata.extrinsic.transactionExtensionOrNull(ChargeAssetTxPayment.ID) ?: return null
|
||||
val type = chargeAssetTxPaymentType.includedInExtrinsic!!
|
||||
val assetIdType = type.cast<Struct>().get<Type<*>>("assetId")!!
|
||||
|
||||
return assetIdType.fromHex(this, assetIdHex)
|
||||
}
|
||||
|
||||
class ChargeAssetTxPaymentValue(
|
||||
val tip: BalanceOf,
|
||||
val assetId: RelativeMultiLocation?
|
||||
) {
|
||||
|
||||
companion object {
|
||||
|
||||
fun bind(decoded: Any?): ChargeAssetTxPaymentValue {
|
||||
val asStruct = decoded.castToStruct()
|
||||
return ChargeAssetTxPaymentValue(
|
||||
tip = bindNumber(asStruct["tip"]),
|
||||
assetId = asStruct.get<Any?>("assetId")?.let(::bindMultiLocation)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.fee.types.hydra
|
||||
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novasama.substrate_sdk_android.runtime.extrinsic.builder.ExtrinsicBuilder
|
||||
import java.math.BigInteger
|
||||
|
||||
interface HydrationFeeInjector {
|
||||
|
||||
class SetFeesMode(
|
||||
val setMode: SetMode,
|
||||
val resetMode: ResetMode
|
||||
)
|
||||
|
||||
sealed class SetMode {
|
||||
|
||||
/**
|
||||
* Always sets the fee to the required token, regardless of whether fees are already in the needed state or not
|
||||
*/
|
||||
object Always : SetMode()
|
||||
|
||||
/**
|
||||
* Sets the fee token to the required one only the current fee payment asset is different
|
||||
*/
|
||||
class Lazy(val currentlySetFeeAsset: BigInteger) : SetMode()
|
||||
}
|
||||
|
||||
sealed class ResetMode {
|
||||
|
||||
/**
|
||||
* Always resets the fee to the native token, regardless of whether fees are already in the needed state or not
|
||||
*/
|
||||
object ToNative : ResetMode()
|
||||
|
||||
/**
|
||||
* Resets the fee to the native one only the current fee payment asset is different
|
||||
*/
|
||||
class ToNativeLazily(val feeAssetBeforeTransaction: BigInteger) : ResetMode()
|
||||
}
|
||||
|
||||
suspend fun setFees(
|
||||
extrinsicBuilder: ExtrinsicBuilder,
|
||||
paymentAsset: Chain.Asset,
|
||||
mode: SetFeesMode,
|
||||
)
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.mappers
|
||||
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.chain.ChainUi
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
|
||||
fun mapChainToUi(chain: Chain): ChainUi = with(chain) {
|
||||
ChainUi(
|
||||
id = id,
|
||||
name = name,
|
||||
icon = icon,
|
||||
)
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.mappers
|
||||
|
||||
import io.novafoundation.nova.core.model.Network
|
||||
import io.novafoundation.nova.core.model.Node
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
|
||||
fun stubNetwork(chainId: ChainId): Network {
|
||||
val networkType = Node.NetworkType.findByGenesis(chainId) ?: Node.NetworkType.POLKADOT
|
||||
|
||||
return Network(networkType)
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.model
|
||||
|
||||
import io.novafoundation.nova.common.address.AccountIdKey
|
||||
|
||||
@Deprecated("Use AccountIdKeyMap instead")
|
||||
typealias AccountIdMap<V> = Map<String, V>
|
||||
|
||||
@Deprecated("Use AccountIdKeyMap instead")
|
||||
typealias AccountAddressMap<V> = Map<String, V>
|
||||
|
||||
typealias AccountIdKeyMap<V> = Map<AccountIdKey, V>
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.model
|
||||
|
||||
import io.novafoundation.nova.common.utils.amountFromPlanks
|
||||
import io.novafoundation.nova.feature_account_api.data.extrinsic.SubmissionOrigin
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.mixin.addressInput.maxAction.MaxAvailableDeduction
|
||||
import io.novafoundation.nova.runtime.ext.fullId
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novasama.substrate_sdk_android.runtime.AccountId
|
||||
import java.math.BigDecimal
|
||||
import java.math.BigInteger
|
||||
|
||||
// TODO rename FeeBase -> Fee and use SubmissionFee everywhere Fee is currently used
|
||||
typealias Fee = SubmissionFee
|
||||
|
||||
interface SubmissionFee : FeeBase, MaxAvailableDeduction {
|
||||
|
||||
companion object
|
||||
|
||||
/**
|
||||
* Information about origin that is supposed to send the transaction fee was calculated against
|
||||
*/
|
||||
val submissionOrigin: SubmissionOrigin
|
||||
|
||||
/**
|
||||
* Submission fee deducts fee amount from max balance only when executing account pays fees
|
||||
* When signing account is different from executing one, executing account balance remains unaffected by submission fee payment
|
||||
*/
|
||||
override fun maxAmountDeductionFor(amountAsset: Chain.Asset): BigInteger {
|
||||
return getAmountByExecutingAccount(amountAsset)
|
||||
}
|
||||
}
|
||||
|
||||
val SubmissionFee.submissionFeesPayer: AccountId
|
||||
get() = submissionOrigin.signingAccount
|
||||
|
||||
/**
|
||||
* Fee that doesn't have a particular origin
|
||||
* For example, fees paid during cross chain transfers do not have a specific account that pays them
|
||||
*/
|
||||
interface FeeBase {
|
||||
|
||||
val amount: BigInteger
|
||||
|
||||
val asset: Chain.Asset
|
||||
}
|
||||
|
||||
val FeeBase.decimalAmount: BigDecimal
|
||||
get() = amount.amountFromPlanks(asset.precision)
|
||||
|
||||
data class EvmFee(
|
||||
val gasLimit: BigInteger,
|
||||
val gasPrice: BigInteger,
|
||||
override val submissionOrigin: SubmissionOrigin,
|
||||
override val asset: Chain.Asset
|
||||
) : Fee {
|
||||
|
||||
override val amount = gasLimit * gasPrice
|
||||
}
|
||||
|
||||
class SubstrateFee(
|
||||
override val amount: BigInteger,
|
||||
override val submissionOrigin: SubmissionOrigin,
|
||||
override val asset: Chain.Asset
|
||||
) : Fee
|
||||
|
||||
class SubstrateFeeBase(
|
||||
override val amount: BigInteger,
|
||||
override val asset: Chain.Asset
|
||||
) : FeeBase
|
||||
|
||||
val Fee.amountByExecutingAccount: BigInteger
|
||||
get() = getAmount(asset, submissionOrigin.executingAccount)
|
||||
|
||||
val Fee.decimalAmountByExecutingAccount: BigDecimal
|
||||
get() = amountByExecutingAccount.amountFromPlanks(asset.precision)
|
||||
|
||||
fun FeeBase.addPlanks(extraPlanks: BigInteger): FeeBase {
|
||||
return SubstrateFeeBase(amount + extraPlanks, asset)
|
||||
}
|
||||
|
||||
fun List<FeeBase>.totalAmount(chainAsset: Chain.Asset): BigInteger {
|
||||
return sumOf { it.getAmount(chainAsset) }
|
||||
}
|
||||
|
||||
fun List<SubmissionFee>.totalAmount(chainAsset: Chain.Asset, origin: AccountId): BigInteger {
|
||||
return sumOf { it.getAmount(chainAsset, origin) }
|
||||
}
|
||||
|
||||
fun List<FeeBase>.totalPlanksEnsuringAsset(requireAsset: Chain.Asset): BigInteger {
|
||||
return sumOf {
|
||||
require(it.asset.fullId == requireAsset.fullId) {
|
||||
"Fees contain fee in different assets: ${it.asset.fullId}"
|
||||
}
|
||||
|
||||
it.amount
|
||||
}
|
||||
}
|
||||
|
||||
fun SubmissionFee.getAmount(chainAsset: Chain.Asset, origin: AccountId): BigInteger {
|
||||
return if (asset.fullId == chainAsset.fullId && submissionFeesPayer.contentEquals(origin)) {
|
||||
amount
|
||||
} else {
|
||||
BigInteger.ZERO
|
||||
}
|
||||
}
|
||||
|
||||
fun SubmissionFee.getAmountByExecutingAccount(chainAsset: Chain.Asset): BigInteger {
|
||||
return getAmount(chainAsset, submissionOrigin.executingAccount)
|
||||
}
|
||||
|
||||
fun FeeBase.getAmount(expectedAsset: Chain.Asset): BigInteger {
|
||||
return if (expectedAsset.fullId == asset.fullId) amount else BigInteger.ZERO
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.model
|
||||
|
||||
import io.novasama.substrate_sdk_android.runtime.AccountId
|
||||
|
||||
interface OnChainIdentity {
|
||||
val display: String?
|
||||
val legal: String?
|
||||
val web: String?
|
||||
val matrix: String?
|
||||
val email: String?
|
||||
val pgpFingerprint: String?
|
||||
val image: String?
|
||||
val twitter: String?
|
||||
}
|
||||
|
||||
class RootIdentity(
|
||||
override val display: String?,
|
||||
override val legal: String?,
|
||||
override val web: String?,
|
||||
override val matrix: String?,
|
||||
override val email: String?,
|
||||
override val pgpFingerprint: String?,
|
||||
override val image: String?,
|
||||
override val twitter: String?,
|
||||
) : OnChainIdentity
|
||||
|
||||
class ChildIdentity(
|
||||
val childName: String?,
|
||||
val parentIdentity: OnChainIdentity,
|
||||
) : OnChainIdentity by parentIdentity {
|
||||
|
||||
override val display: String = "${parentIdentity.display} / ${childName.orEmpty()}"
|
||||
}
|
||||
|
||||
class SuperOf(
|
||||
val parentId: AccountId,
|
||||
val childName: String?,
|
||||
)
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.multisig
|
||||
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.WeightV2
|
||||
import io.novafoundation.nova.common.utils.Modules
|
||||
import io.novafoundation.nova.common.utils.composeCall
|
||||
import io.novafoundation.nova.feature_account_api.data.multisig.model.MultisigTimePoint
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.MultisigMetaAccount
|
||||
import io.novafoundation.nova.feature_account_api.domain.multisig.CallHash
|
||||
import io.novasama.substrate_sdk_android.runtime.RuntimeSnapshot
|
||||
import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.GenericCall
|
||||
|
||||
fun RuntimeSnapshot.composeMultisigAsMulti(
|
||||
multisigMetaAccount: MultisigMetaAccount,
|
||||
maybeTimePoint: MultisigTimePoint?,
|
||||
call: GenericCall.Instance,
|
||||
maxWeight: WeightV2
|
||||
): GenericCall.Instance {
|
||||
return composeCall(
|
||||
moduleName = Modules.MULTISIG,
|
||||
callName = "as_multi",
|
||||
arguments = mapOf(
|
||||
"threshold" to multisigMetaAccount.threshold.toBigInteger(),
|
||||
"other_signatories" to multisigMetaAccount.otherSignatories.map { it.value },
|
||||
"maybe_timepoint" to maybeTimePoint?.toEncodableInstance(),
|
||||
"call" to call,
|
||||
"max_weight" to maxWeight.toEncodableInstance()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun RuntimeSnapshot.composeMultisigAsMultiThreshold1(
|
||||
multisigMetaAccount: MultisigMetaAccount,
|
||||
call: GenericCall.Instance,
|
||||
): GenericCall.Instance {
|
||||
return composeCall(
|
||||
moduleName = Modules.MULTISIG,
|
||||
callName = "as_multi_threshold_1",
|
||||
arguments = mapOf(
|
||||
"other_signatories" to multisigMetaAccount.otherSignatories.map { it.value },
|
||||
"call" to call,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun RuntimeSnapshot.composeMultisigCancelAsMulti(
|
||||
multisigMetaAccount: MultisigMetaAccount,
|
||||
maybeTimePoint: MultisigTimePoint,
|
||||
callHash: CallHash,
|
||||
): GenericCall.Instance {
|
||||
return composeCall(
|
||||
moduleName = Modules.MULTISIG,
|
||||
callName = "cancel_as_multi",
|
||||
arguments = mapOf(
|
||||
"threshold" to multisigMetaAccount.threshold.toBigInteger(),
|
||||
"other_signatories" to multisigMetaAccount.otherSignatories.map { it.value },
|
||||
"timepoint" to maybeTimePoint.toEncodableInstance(),
|
||||
"call_hash" to callHash.value
|
||||
)
|
||||
)
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.multisig
|
||||
|
||||
import io.novafoundation.nova.common.address.AccountIdKey
|
||||
import io.novafoundation.nova.feature_account_api.domain.multisig.CallHash
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
|
||||
interface MultisigDetailsRepository {
|
||||
|
||||
suspend fun hasMultisigOperation(
|
||||
chain: Chain,
|
||||
accountIdKey: AccountIdKey,
|
||||
callHash: CallHash
|
||||
): Boolean
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.multisig
|
||||
|
||||
import io.novafoundation.nova.common.data.memory.ComputationalScope
|
||||
import io.novafoundation.nova.feature_account_api.data.multisig.model.PendingMultisigOperation
|
||||
import io.novafoundation.nova.feature_account_api.data.multisig.model.PendingMultisigOperationId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface MultisigPendingOperationsService {
|
||||
|
||||
context(ComputationalScope)
|
||||
fun performMultisigOperationsSync(): Flow<Unit>
|
||||
|
||||
context(ComputationalScope)
|
||||
fun pendingOperationsCountFlow(): Flow<Int>
|
||||
|
||||
context(ComputationalScope)
|
||||
suspend fun getPendingOperationsCount(): Int
|
||||
|
||||
context(ComputationalScope)
|
||||
fun pendingOperations(): Flow<List<PendingMultisigOperation>>
|
||||
|
||||
context(ComputationalScope)
|
||||
fun pendingOperationFlow(id: PendingMultisigOperationId): Flow<PendingMultisigOperation?>
|
||||
|
||||
context(ComputationalScope)
|
||||
suspend fun pendingOperation(id: PendingMultisigOperationId): PendingMultisigOperation?
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.multisig.model
|
||||
|
||||
sealed class MultisigAction {
|
||||
|
||||
data object Signed : MultisigAction()
|
||||
|
||||
data object CanReject : MultisigAction()
|
||||
|
||||
data class CanApprove(val isFinalApproval: Boolean) : MultisigAction()
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.multisig.model
|
||||
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.BlockNumber
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.bindBlockNumber
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.bindInt
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.castToStruct
|
||||
import io.novafoundation.nova.common.utils.scale.ToDynamicScaleInstance
|
||||
import io.novafoundation.nova.common.utils.structOf
|
||||
import io.novasama.substrate_sdk_android.runtime.definitions.types.composite.Struct
|
||||
|
||||
class MultisigTimePoint(
|
||||
val height: BlockNumber,
|
||||
val extrinsicIndex: Int
|
||||
) : ToDynamicScaleInstance {
|
||||
|
||||
companion object {
|
||||
|
||||
fun bind(decoded: Any?): MultisigTimePoint {
|
||||
val asStruct = decoded.castToStruct()
|
||||
|
||||
return MultisigTimePoint(
|
||||
height = bindBlockNumber(asStruct["height"]),
|
||||
extrinsicIndex = bindInt(asStruct["index"])
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun toEncodableInstance(): Struct.Instance {
|
||||
return structOf(
|
||||
"height" to height,
|
||||
"index" to extrinsicIndex.toBigInteger()
|
||||
)
|
||||
}
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.multisig.model
|
||||
|
||||
import io.novafoundation.nova.common.address.AccountIdKey
|
||||
import io.novafoundation.nova.common.address.toHex
|
||||
import io.novafoundation.nova.common.utils.Identifiable
|
||||
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_account_api.domain.multisig.CallHash
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
import io.novasama.substrate_sdk_android.extensions.toHexString
|
||||
import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.GenericCall
|
||||
import java.math.BigInteger
|
||||
import kotlin.time.Duration
|
||||
|
||||
class PendingMultisigOperation(
|
||||
val multisigMetaId: Long,
|
||||
val call: GenericCall.Instance?,
|
||||
val callHash: CallHash,
|
||||
val chain: Chain,
|
||||
val timePoint: MultisigTimePoint,
|
||||
val approvals: List<AccountIdKey>,
|
||||
val depositor: AccountIdKey,
|
||||
val deposit: BigInteger,
|
||||
val signatoryAccountId: AccountIdKey,
|
||||
val signatoryMetaId: Long,
|
||||
val threshold: Int,
|
||||
val timestamp: Duration,
|
||||
) : Identifiable {
|
||||
|
||||
val operationId = PendingMultisigOperationId(multisigMetaId, chain.id, callHash.toHex())
|
||||
|
||||
override val identifier: String = operationId.identifier()
|
||||
|
||||
override fun toString(): String {
|
||||
val callFormatted = if (call != null) {
|
||||
"${call.module.name}.${call.function.name}"
|
||||
} else {
|
||||
callHash.toHex()
|
||||
}
|
||||
|
||||
return "Call: $callFormatted, Chain: ${chain.name}, Approvals: ${approvals.size}/$threshold, User action: ${userAction()}"
|
||||
}
|
||||
|
||||
companion object
|
||||
}
|
||||
|
||||
data class PendingMultisigOperationId(
|
||||
val metaId: Long,
|
||||
val chainId: ChainId,
|
||||
val callHash: String,
|
||||
) {
|
||||
companion object;
|
||||
}
|
||||
|
||||
fun PendingMultisigOperation.userAction(): MultisigAction {
|
||||
return when (signatoryAccountId) {
|
||||
depositor -> MultisigAction.CanReject
|
||||
!in approvals -> MultisigAction.CanApprove(
|
||||
isFinalApproval = approvals.size == threshold - 1
|
||||
)
|
||||
|
||||
else -> MultisigAction.Signed
|
||||
}
|
||||
}
|
||||
|
||||
fun PendingMultisigOperationId.identifier() = toString()
|
||||
|
||||
/**
|
||||
* operation hash is based on address in chain and ignored meta account id
|
||||
*/
|
||||
fun PendingMultisigOperation.Companion.createOperationHash(metaAccount: MetaAccount, chain: Chain, callHash: String): String {
|
||||
return "${metaAccount.addressIn(chain)}:${chain.id}:$callHash"
|
||||
.toByteArray()
|
||||
.toHexString(withPrefix = true)
|
||||
}
|
||||
|
||||
fun PendingMultisigOperationId.Companion.create(metaAccount: MetaAccount, chain: Chain, callHash: String): PendingMultisigOperationId {
|
||||
return PendingMultisigOperationId(metaAccount.id, chain.id, callHash)
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.multisig.repository
|
||||
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.SavedMultisigOperationCall
|
||||
import io.novafoundation.nova.feature_account_api.domain.multisig.CallHash
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface MultisigOperationLocalCallRepository {
|
||||
|
||||
suspend fun setMultisigCall(operation: SavedMultisigOperationCall)
|
||||
|
||||
fun callsFlow(): Flow<List<SavedMultisigOperationCall>>
|
||||
|
||||
suspend fun removeCallHashesExclude(metaId: Long, chainId: ChainId, excludedCallHashes: Set<CallHash>)
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.multisig.repository
|
||||
|
||||
import io.novafoundation.nova.common.address.AccountIdKey
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.BalanceOf
|
||||
import io.novafoundation.nova.common.utils.times
|
||||
import io.novafoundation.nova.feature_account_api.domain.multisig.CallHash
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
|
||||
interface MultisigValidationsRepository {
|
||||
|
||||
suspend fun getMultisigDepositBase(chainId: ChainId): BalanceOf
|
||||
|
||||
suspend fun getMultisigDepositFactor(chainId: ChainId): BalanceOf
|
||||
|
||||
suspend fun hasPendingCallHash(chainId: ChainId, accountIdKey: AccountIdKey, callHash: CallHash): Boolean
|
||||
}
|
||||
|
||||
suspend fun MultisigValidationsRepository.getMultisigDeposit(chainId: ChainId, threshold: Int): BalanceOf {
|
||||
if (threshold == 1) return BalanceOf.ZERO
|
||||
|
||||
val base = getMultisigDepositBase(chainId)
|
||||
val factor = getMultisigDepositFactor(chainId)
|
||||
|
||||
return base + factor * threshold
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.multisig.validation
|
||||
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.MultisigMetaAccount
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import java.math.BigInteger
|
||||
|
||||
sealed interface MultisigExtrinsicValidationFailure {
|
||||
|
||||
class NotEnoughSignatoryBalance(
|
||||
val signatory: MetaAccount,
|
||||
val asset: Chain.Asset,
|
||||
val deposit: BigInteger?,
|
||||
val fee: BigInteger?,
|
||||
val balanceToAdd: BigInteger
|
||||
) : MultisigExtrinsicValidationFailure
|
||||
|
||||
class OperationAlreadyExists(
|
||||
val multisigAccount: MultisigMetaAccount
|
||||
) : MultisigExtrinsicValidationFailure
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.multisig.validation
|
||||
|
||||
import io.novafoundation.nova.common.address.AccountIdKey
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.MultisigMetaAccount
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.requireAccountIdIn
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.requireAccountIdKeyIn
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novasama.substrate_sdk_android.runtime.AccountId
|
||||
import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.GenericCall
|
||||
|
||||
class MultisigExtrinsicValidationPayload(
|
||||
val multisig: MultisigMetaAccount,
|
||||
val signatory: MetaAccount,
|
||||
val chain: Chain,
|
||||
val signatoryFeePaymentMode: SignatoryFeePaymentMode,
|
||||
// Call that is passed to as_multi. Might be both the actual call (in case multisig is a the root signer) or be wrapped by some other signer
|
||||
val callInsideAsMulti: GenericCall.Instance,
|
||||
)
|
||||
|
||||
sealed class SignatoryFeePaymentMode {
|
||||
|
||||
data object PaysSubmissionFee : SignatoryFeePaymentMode()
|
||||
|
||||
data object NothingToPay : SignatoryFeePaymentMode()
|
||||
}
|
||||
|
||||
fun MultisigExtrinsicValidationPayload.signatoryAccountId(): AccountId {
|
||||
return signatory.requireAccountIdIn(chain)
|
||||
}
|
||||
|
||||
fun MultisigExtrinsicValidationPayload.multisigAccountId(): AccountIdKey {
|
||||
return multisig.requireAccountIdKeyIn(chain)
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.multisig.validation
|
||||
|
||||
import io.novafoundation.nova.common.utils.bus.BaseRequestBus
|
||||
import io.novafoundation.nova.common.utils.bus.RequestBus
|
||||
import io.novafoundation.nova.common.validation.ValidationStatus
|
||||
import io.novafoundation.nova.feature_account_api.data.multisig.validation.MultisigExtrinsicValidationRequestBus.Request
|
||||
import io.novafoundation.nova.feature_account_api.data.multisig.validation.MultisigExtrinsicValidationRequestBus.ValidationResponse
|
||||
|
||||
class MultisigExtrinsicValidationRequestBus() : BaseRequestBus<Request, ValidationResponse>() {
|
||||
|
||||
class Request(val validationPayload: MultisigExtrinsicValidationPayload) : RequestBus.Request
|
||||
|
||||
class ValidationResponse(val validationResult: Result<ValidationStatus<MultisigExtrinsicValidationFailure>>) : RequestBus.Response
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.multisig.validation
|
||||
|
||||
import io.novafoundation.nova.common.validation.Validation
|
||||
import io.novafoundation.nova.common.validation.ValidationStatus
|
||||
import io.novafoundation.nova.common.validation.ValidationSystem
|
||||
import io.novafoundation.nova.common.validation.ValidationSystemBuilder
|
||||
|
||||
typealias MultisigExtrinsicValidationBuilder = ValidationSystemBuilder<MultisigExtrinsicValidationPayload, MultisigExtrinsicValidationFailure>
|
||||
typealias MultisigExtrinsicValidation = Validation<MultisigExtrinsicValidationPayload, MultisigExtrinsicValidationFailure>
|
||||
typealias MultisigExtrinsicValidationStatus = ValidationStatus<MultisigExtrinsicValidationFailure>
|
||||
typealias MultisigExtrinsicValidationSystem = ValidationSystem<MultisigExtrinsicValidationPayload, MultisigExtrinsicValidationFailure>
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.proxy
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface MetaAccountsUpdatesRegistry {
|
||||
|
||||
fun addMetaIds(ids: List<Long>)
|
||||
|
||||
fun observeUpdates(): Flow<Set<Long>>
|
||||
|
||||
fun getUpdates(): Set<Long>
|
||||
|
||||
fun remove(ids: List<Long>)
|
||||
|
||||
fun clear()
|
||||
|
||||
fun hasUpdates(): Boolean
|
||||
|
||||
fun observeUpdatesExist(): Flow<Boolean>
|
||||
|
||||
fun observeLastConsumedUpdatesMetaIds(): Flow<Set<Long>>
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.proxy.validation
|
||||
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.ProxiedMetaAccount
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.requireAccountIdIn
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainWithAsset
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novasama.substrate_sdk_android.runtime.AccountId
|
||||
import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.GenericCall
|
||||
import java.math.BigInteger
|
||||
|
||||
class ProxiedExtrinsicValidationPayload(
|
||||
val proxiedMetaAccount: ProxiedMetaAccount,
|
||||
val proxyMetaAccount: MetaAccount,
|
||||
val chainWithAsset: ChainWithAsset,
|
||||
val proxiedCall: GenericCall.Instance
|
||||
)
|
||||
|
||||
val ProxiedExtrinsicValidationPayload.proxyAccountId: AccountId
|
||||
get() = proxyMetaAccount.requireAccountIdIn(chainWithAsset.chain)
|
||||
|
||||
sealed interface ProxiedExtrinsicValidationFailure {
|
||||
|
||||
class ProxyNotEnoughFee(
|
||||
val proxy: MetaAccount,
|
||||
val asset: Chain.Asset,
|
||||
val fee: BigInteger,
|
||||
val availableBalance: BigInteger
|
||||
) : ProxiedExtrinsicValidationFailure
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.proxy.validation
|
||||
|
||||
import io.novafoundation.nova.common.utils.bus.BaseRequestBus
|
||||
import io.novafoundation.nova.common.utils.bus.RequestBus
|
||||
import io.novafoundation.nova.common.validation.ValidationStatus
|
||||
import io.novafoundation.nova.feature_account_api.data.proxy.validation.ProxyExtrinsicValidationRequestBus.Request
|
||||
import io.novafoundation.nova.feature_account_api.data.proxy.validation.ProxyExtrinsicValidationRequestBus.ValidationResponse
|
||||
|
||||
class ProxyExtrinsicValidationRequestBus() : BaseRequestBus<Request, ValidationResponse>() {
|
||||
|
||||
class Request(val validationPayload: ProxiedExtrinsicValidationPayload) : RequestBus.Request
|
||||
|
||||
class ValidationResponse(val validationResult: Result<ValidationStatus<ProxiedExtrinsicValidationFailure>>) : RequestBus.Response
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.repository
|
||||
|
||||
import io.novafoundation.nova.common.data.secrets.v2.ChainAccountSecrets
|
||||
import io.novafoundation.nova.core.model.CryptoType
|
||||
import io.novasama.substrate_sdk_android.scale.EncodableStruct
|
||||
|
||||
interface CreateSecretsRepository {
|
||||
|
||||
suspend fun createSecretsWithSeed(
|
||||
seed: ByteArray,
|
||||
cryptoType: CryptoType,
|
||||
derivationPath: String?,
|
||||
isEthereum: Boolean,
|
||||
): EncodableStruct<ChainAccountSecrets>
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.repository
|
||||
|
||||
import io.novafoundation.nova.common.address.AccountIdKey
|
||||
import io.novafoundation.nova.feature_account_api.data.model.AccountAddressMap
|
||||
import io.novafoundation.nova.feature_account_api.data.model.AccountIdKeyMap
|
||||
import io.novafoundation.nova.feature_account_api.data.model.AccountIdMap
|
||||
import io.novafoundation.nova.feature_account_api.data.model.OnChainIdentity
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
import io.novasama.substrate_sdk_android.runtime.AccountId
|
||||
|
||||
interface OnChainIdentityRepository {
|
||||
|
||||
@Deprecated("Use getIdentitiesFromIds instead to avoid extra from/to hex conversions")
|
||||
suspend fun getIdentitiesFromIdsHex(chainId: ChainId, accountIdsHex: Collection<String>): AccountIdMap<OnChainIdentity?>
|
||||
|
||||
suspend fun getIdentitiesFromIds(accountIds: Collection<AccountId>, chainId: ChainId): AccountIdKeyMap<OnChainIdentity?>
|
||||
|
||||
suspend fun getIdentityFromId(chainId: ChainId, accountId: AccountId): OnChainIdentity?
|
||||
|
||||
suspend fun getMultiChainIdentities(accountIds: Collection<AccountIdKey>): AccountIdKeyMap<OnChainIdentity?>
|
||||
|
||||
@Deprecated("Use getIdentitiesFromIds instead to avoid extra from/to address conversions")
|
||||
suspend fun getIdentitiesFromAddresses(chain: Chain, accountAddresses: List<String>): AccountAddressMap<OnChainIdentity?>
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.repository.addAccount
|
||||
|
||||
import io.novafoundation.nova.feature_account_api.data.events.MetaAccountChangesEventBus.Event
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount
|
||||
|
||||
interface AddAccountRepository<T> {
|
||||
|
||||
suspend fun addAccount(payload: T): AddAccountResult
|
||||
}
|
||||
|
||||
sealed interface AddAccountResult {
|
||||
|
||||
sealed interface HadEffect : AddAccountResult
|
||||
|
||||
interface SingleAccountChange {
|
||||
|
||||
val metaId: Long
|
||||
}
|
||||
|
||||
class AccountAdded(override val metaId: Long, val type: LightMetaAccount.Type) : HadEffect, SingleAccountChange
|
||||
|
||||
class AccountChanged(override val metaId: Long, val type: LightMetaAccount.Type) : HadEffect, SingleAccountChange
|
||||
|
||||
class Batch(val updates: List<HadEffect>) : HadEffect
|
||||
|
||||
data object NoOp : AddAccountResult
|
||||
}
|
||||
|
||||
fun AddAccountResult.toAccountBusEvent(): Event? {
|
||||
return when (this) {
|
||||
is AddAccountResult.HadEffect -> toAccountBusEvent()
|
||||
is AddAccountResult.NoOp -> null
|
||||
}
|
||||
}
|
||||
|
||||
fun AddAccountResult.HadEffect.toAccountBusEvent(): Event {
|
||||
return when (this) {
|
||||
is AddAccountResult.AccountAdded -> Event.AccountAdded(metaId, type)
|
||||
is AddAccountResult.AccountChanged -> Event.AccountStructureChanged(metaId, type)
|
||||
is AddAccountResult.Batch -> Event.BatchUpdate(updates.map { it.toAccountBusEvent() })
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun <T> AddAccountRepository<T>.addAccountWithSingleChange(payload: T): AddAccountResult.SingleAccountChange {
|
||||
val result = addAccount(payload)
|
||||
require(result is AddAccountResult.SingleAccountChange)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
fun List<AddAccountResult>.batchIfNeeded(): AddAccountResult {
|
||||
val updatesThatHadEffect = filterIsInstance<AddAccountResult.HadEffect>()
|
||||
|
||||
return when (updatesThatHadEffect.size) {
|
||||
0 -> AddAccountResult.NoOp
|
||||
1 -> updatesThatHadEffect.single()
|
||||
else -> AddAccountResult.Batch(updatesThatHadEffect)
|
||||
}
|
||||
}
|
||||
|
||||
fun AddAccountResult.visit(
|
||||
onAdd: (AddAccountResult.AccountAdded) -> Unit
|
||||
) {
|
||||
when (this) {
|
||||
is AddAccountResult.AccountAdded -> onAdd(this)
|
||||
is AddAccountResult.AccountChanged -> Unit
|
||||
is AddAccountResult.Batch -> updates.onEach { it.visit(onAdd) }
|
||||
AddAccountResult.NoOp -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
fun AddAccountResult.collectAddedIds(): List<Long> {
|
||||
return buildList {
|
||||
visit {
|
||||
add(it.metaId)
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.repository.addAccount.ledger
|
||||
|
||||
import io.novafoundation.nova.feature_account_api.data.repository.addAccount.AddAccountRepository
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.application.substrate.LedgerEvmAccount
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.application.substrate.LedgerSubstrateAccount
|
||||
|
||||
interface GenericLedgerAddAccountRepository : AddAccountRepository<GenericLedgerAddAccountRepository.Payload> {
|
||||
|
||||
sealed interface Payload {
|
||||
|
||||
class NewWallet(
|
||||
val name: String,
|
||||
val substrateAccount: LedgerSubstrateAccount,
|
||||
val evmAccount: LedgerEvmAccount?,
|
||||
) : Payload
|
||||
|
||||
class AddEvmAccount(
|
||||
val metaId: Long,
|
||||
val evmAccount: LedgerEvmAccount
|
||||
) : Payload
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.repository.addAccount.ledger
|
||||
|
||||
import io.novafoundation.nova.feature_account_api.data.repository.addAccount.AddAccountRepository
|
||||
import io.novafoundation.nova.feature_ledger_api.sdk.application.substrate.LedgerSubstrateAccount
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
|
||||
interface LegacyLedgerAddAccountRepository : AddAccountRepository<LegacyLedgerAddAccountRepository.Payload> {
|
||||
|
||||
sealed interface Payload {
|
||||
class MetaAccount(
|
||||
val name: String,
|
||||
val ledgerChainAccounts: Map<ChainId, LedgerSubstrateAccount>
|
||||
) : Payload
|
||||
|
||||
class ChainAccount(
|
||||
val metaId: Long,
|
||||
val chainId: ChainId,
|
||||
val ledgerChainAccount: LedgerSubstrateAccount
|
||||
) : Payload
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.repository.addAccount.multisig
|
||||
|
||||
import io.novafoundation.nova.common.address.AccountIdKey
|
||||
import io.novafoundation.nova.feature_account_api.data.repository.addAccount.AddAccountRepository
|
||||
import io.novafoundation.nova.feature_account_api.data.repository.addAccount.multisig.MultisigAddAccountRepository.Payload
|
||||
import io.novafoundation.nova.feature_account_api.domain.account.identity.Identity
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
|
||||
interface MultisigAddAccountRepository : AddAccountRepository<Payload> {
|
||||
|
||||
class Payload(
|
||||
val accounts: List<AccountPayload>
|
||||
)
|
||||
|
||||
class AccountPayload(
|
||||
val chain: Chain,
|
||||
val multisigAccountId: AccountIdKey,
|
||||
val otherSignatories: List<AccountIdKey>,
|
||||
val threshold: Int,
|
||||
val signatoryMetaId: Long,
|
||||
val signatoryAccountId: AccountIdKey,
|
||||
val identity: Identity?
|
||||
)
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.repository.addAccount.proxied
|
||||
|
||||
import io.novafoundation.nova.feature_account_api.data.repository.addAccount.AddAccountRepository
|
||||
import io.novafoundation.nova.feature_account_api.domain.account.identity.Identity
|
||||
import io.novafoundation.nova.feature_proxy_api.domain.model.ProxyType
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
import io.novasama.substrate_sdk_android.runtime.AccountId
|
||||
|
||||
interface ProxiedAddAccountRepository : AddAccountRepository<ProxiedAddAccountRepository.Payload> {
|
||||
|
||||
class Payload(
|
||||
val chainId: ChainId,
|
||||
val proxiedAccountId: AccountId,
|
||||
val proxyType: ProxyType,
|
||||
val proxyMetaId: Long,
|
||||
val identity: Identity?
|
||||
)
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.repository.addAccount.secrets
|
||||
|
||||
import io.novafoundation.nova.feature_account_api.data.repository.addAccount.AddAccountRepository
|
||||
import io.novafoundation.nova.feature_account_api.domain.account.advancedEncryption.AdvancedEncryption
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.AddAccountType
|
||||
|
||||
interface MnemonicAddAccountRepository : AddAccountRepository<MnemonicAddAccountRepository.Payload> {
|
||||
|
||||
class Payload(
|
||||
val mnemonic: String,
|
||||
val advancedEncryption: AdvancedEncryption,
|
||||
val addAccountType: AddAccountType
|
||||
)
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.secrets
|
||||
|
||||
import io.novafoundation.nova.common.data.secrets.v2.AccountSecrets
|
||||
import io.novafoundation.nova.common.data.secrets.v2.ChainAccountSecrets
|
||||
import io.novafoundation.nova.common.data.secrets.v2.MetaAccountSecrets
|
||||
import io.novafoundation.nova.common.data.secrets.v2.SecretStoreV2
|
||||
import io.novafoundation.nova.common.data.secrets.v2.getAccountSecrets
|
||||
import io.novafoundation.nova.common.data.secrets.v2.mapChainAccountSecretsToKeypair
|
||||
import io.novafoundation.nova.common.data.secrets.v2.mapMetaAccountSecretsToDerivationPath
|
||||
import io.novafoundation.nova.common.data.secrets.v2.mapMetaAccountSecretsToKeypair
|
||||
import io.novafoundation.nova.common.utils.fold
|
||||
import io.novasama.substrate_sdk_android.encrypt.keypair.Keypair
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novasama.substrate_sdk_android.scale.EncodableStruct
|
||||
|
||||
suspend fun SecretStoreV2.getAccountSecrets(
|
||||
metaAccount: MetaAccount,
|
||||
chain: Chain
|
||||
): AccountSecrets {
|
||||
val accountId = metaAccount.accountIdIn(chain) ?: error("No account for chain $chain in meta account ${metaAccount.name}")
|
||||
|
||||
return getAccountSecrets(metaAccount.id, accountId)
|
||||
}
|
||||
|
||||
fun AccountSecrets.keypair(chain: Chain): Keypair {
|
||||
return fold(
|
||||
left = { mapMetaAccountSecretsToKeypair(it, ethereum = chain.isEthereumBased) },
|
||||
right = { mapChainAccountSecretsToKeypair(it) }
|
||||
)
|
||||
}
|
||||
|
||||
fun AccountSecrets.derivationPath(chain: Chain): String? {
|
||||
return fold(
|
||||
left = { mapMetaAccountSecretsToDerivationPath(it, ethereum = chain.isEthereumBased) },
|
||||
right = { it[ChainAccountSecrets.DerivationPath] }
|
||||
)
|
||||
}
|
||||
|
||||
fun EncodableStruct<MetaAccountSecrets>.keypair(ethereum: Boolean): Keypair {
|
||||
return mapMetaAccountSecretsToKeypair(this, ethereum)
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.signer
|
||||
|
||||
import io.novafoundation.nova.feature_account_api.data.signer.CallExecutionType.DELAYED
|
||||
import io.novafoundation.nova.feature_account_api.data.signer.CallExecutionType.IMMEDIATE
|
||||
|
||||
/**
|
||||
* Specifies whether the actual transaction call (e.g. transfer) will be executed immediately or delayed
|
||||
*/
|
||||
enum class CallExecutionType {
|
||||
|
||||
/**
|
||||
* Actual call is executed immediately, together with the transaction itself
|
||||
* This is the most common case
|
||||
*/
|
||||
IMMEDIATE,
|
||||
|
||||
/**
|
||||
* Actual call's executed is delayed - transaction only executes preparation step
|
||||
* Examples: multisig or delayed proxies operations
|
||||
*/
|
||||
DELAYED
|
||||
}
|
||||
|
||||
fun CallExecutionType.isImmediate(): Boolean {
|
||||
return this == IMMEDIATE
|
||||
}
|
||||
|
||||
fun CallExecutionType.intersect(other: CallExecutionType): CallExecutionType {
|
||||
return if (isImmediate() && other.isImmediate()) {
|
||||
IMMEDIATE
|
||||
} else {
|
||||
DELAYED
|
||||
}
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.signer
|
||||
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
|
||||
import io.novafoundation.nova.runtime.extrinsic.signer.SignerPayloadRawWithChain
|
||||
import io.novafoundation.nova.runtime.extrinsic.signer.withChain
|
||||
import io.novafoundation.nova.runtime.extrinsic.signer.withoutChain
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
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.signer.SignedRaw
|
||||
import io.novasama.substrate_sdk_android.runtime.extrinsic.signer.Signer
|
||||
import io.novasama.substrate_sdk_android.runtime.extrinsic.signer.SignerPayloadRaw
|
||||
|
||||
interface NovaSigner : Signer {
|
||||
|
||||
/**
|
||||
* Determines execution type of the actual call (e.g. transfer)
|
||||
* Implementation node: signers that delegate signing to nested signers should intersect their execution type with the nested one
|
||||
*/
|
||||
suspend fun callExecutionType(): CallExecutionType
|
||||
|
||||
/**
|
||||
* Meta account this signer was created for
|
||||
* This is the same value that was passed to [SignerProvider.rootSignerFor] or [SignerProvider.nestedSignerFor]
|
||||
*/
|
||||
val metaAccount: MetaAccount
|
||||
|
||||
/**
|
||||
* Returns full signing hierarchy for root and nested signers
|
||||
*/
|
||||
suspend fun getSigningHierarchy(): SubmissionHierarchy
|
||||
|
||||
/**
|
||||
* Modify the extrinsic to enrich it with the signing data relevant for this type
|
||||
* In all situations, at least nonce and signature will be required
|
||||
* However some signers may even modify the call (e.g. Proxied signer will wrap the current call into proxy call)
|
||||
*
|
||||
* This should only be called after all other extrinsic information has been set, including all non-signer related extensions and calls
|
||||
* So, this should be the final operation that modifies the extrinsic, followed just by [ExtrinsicBuilder.buildExtrinsic]
|
||||
*
|
||||
* Note for nested signers:
|
||||
*
|
||||
* Since signers delegation work in top-down approach(root signer is the executing account),
|
||||
* but the wrapping should be done in the bottom-up way (the actual call is the inner-most one),
|
||||
* nested signers should perform call wrapping themselves, and only after that perform nested [setSignerDataForSubmission] call.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* With Secrets Wallet -> Proxy -> Multisig setup, the signing sequence will be MultisigSigner -> ProxiedSigner -> SecretsSigner.
|
||||
* The final call should be proxy(as_multi(actual)) from Secrets origin.
|
||||
* So, the wrapping should be done in the following sequence: actual -> wrap in as_multi -> wrap in proxy.
|
||||
* So, the top-most signer (MultisigSigner) should first wrap the actual call into as_multi and only then delegate to ProxiedSigner.
|
||||
*/
|
||||
context(ExtrinsicBuilder)
|
||||
suspend fun setSignerDataForSubmission(context: SigningContext)
|
||||
|
||||
/**
|
||||
* Same as [setSignerDataForSubmission] but should use fake signature so signed extrinsic can be safely used for fee calculation
|
||||
* This may also apply certain optimizations like hard-coding the nonce or other values to speedup the extrinsic construction
|
||||
* and thus, fee calculation
|
||||
*
|
||||
* This should only be called after all other extrinsic information has been set, including all non-signer related extensions and calls
|
||||
* So, this should be the final operation that modifies the extrinsic, followed just by [ExtrinsicBuilder.buildExtrinsic]
|
||||
*
|
||||
* You can find notes about nested signers in [setSignerDataForSubmission]
|
||||
*/
|
||||
context(ExtrinsicBuilder)
|
||||
suspend fun setSignerDataForFee(context: SigningContext)
|
||||
|
||||
/**
|
||||
* Return accountId of a signer that will actually sign this extrinsic
|
||||
* For example, for Proxied account the actual signer is its Proxy
|
||||
*/
|
||||
suspend fun submissionSignerAccountId(chain: Chain): AccountId
|
||||
|
||||
/**
|
||||
* Determines whether this particular instance of signer imposes additional limits to the number of calls
|
||||
* it is possible to add to a single transaction.
|
||||
* This is useful for signers that run in resource-constrained environment and thus cannot handle large transactions, e.g. Ledger
|
||||
*/
|
||||
suspend fun maxCallsPerTransaction(): Int?
|
||||
|
||||
// TODO this is a temp solution to workaround Polkadot Vault requiring chain id to sign a raw message
|
||||
// This method should be removed once Vault behavior is improved
|
||||
suspend fun signRawWithChain(payload: SignerPayloadRawWithChain): SignedRaw {
|
||||
return signRaw(payload.withoutChain())
|
||||
}
|
||||
}
|
||||
|
||||
context(ExtrinsicBuilder)
|
||||
suspend fun NovaSigner.setSignerData(context: SigningContext, mode: SigningMode) {
|
||||
when (mode) {
|
||||
SigningMode.FEE -> setSignerDataForFee(context)
|
||||
SigningMode.SUBMISSION -> setSignerDataForSubmission(context)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun NovaSigner.signRaw(payloadRaw: SignerPayloadRaw, chainId: ChainId?): SignedRaw {
|
||||
return if (chainId != null) {
|
||||
signRawWithChain(payloadRaw.withChain(chainId))
|
||||
} else {
|
||||
signRaw(payloadRaw)
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.signer
|
||||
|
||||
import io.novafoundation.nova.common.utils.MutableSharedState
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
|
||||
import io.novafoundation.nova.runtime.extrinsic.signer.SignerPayloadRawWithChain
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
import io.novasama.substrate_sdk_android.extensions.toHexString
|
||||
import io.novasama.substrate_sdk_android.runtime.AccountId
|
||||
import io.novasama.substrate_sdk_android.runtime.extrinsic.v5.transactionExtension.InheritedImplication
|
||||
import io.novasama.substrate_sdk_android.runtime.extrinsic.v5.transactionExtension.getGenesisHashOrThrow
|
||||
import io.novasama.substrate_sdk_android.runtime.extrinsic.v5.transactionExtension.signingPayload
|
||||
|
||||
typealias SigningSharedState = MutableSharedState<SeparateFlowSignerState>
|
||||
|
||||
class SeparateFlowSignerState(val payload: SignerPayload, val metaAccount: MetaAccount)
|
||||
|
||||
sealed class SignerPayload {
|
||||
|
||||
class Extrinsic(val extrinsic: InheritedImplication, val accountId: AccountId) : SignerPayload()
|
||||
|
||||
class Raw(val raw: SignerPayloadRawWithChain) : SignerPayload()
|
||||
}
|
||||
|
||||
fun SignerPayload.chainId(): ChainId {
|
||||
return when (this) {
|
||||
is SignerPayload.Extrinsic -> extrinsic.getGenesisHashOrThrow().toHexString()
|
||||
is SignerPayload.Raw -> raw.chainId
|
||||
}
|
||||
}
|
||||
|
||||
fun SignerPayload.accountId(): AccountId {
|
||||
return when (this) {
|
||||
is SignerPayload.Extrinsic -> accountId
|
||||
is SignerPayload.Raw -> raw.accountId
|
||||
}
|
||||
}
|
||||
|
||||
fun SignerPayload.signaturePayload(): ByteArray {
|
||||
return when (this) {
|
||||
is SignerPayload.Extrinsic -> extrinsic.signingPayload()
|
||||
is SignerPayload.Raw -> raw.message
|
||||
}
|
||||
}
|
||||
|
||||
fun SeparateFlowSignerState.requireExtrinsic(): InheritedImplication {
|
||||
require(payload is SignerPayload.Extrinsic)
|
||||
return payload.extrinsic
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.signer
|
||||
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
|
||||
|
||||
interface SignerProvider {
|
||||
|
||||
fun rootSignerFor(metaAccount: MetaAccount): NovaSigner
|
||||
|
||||
fun nestedSignerFor(metaAccount: MetaAccount): NovaSigner
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.signer
|
||||
|
||||
import io.novafoundation.nova.common.address.AccountIdKey
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novasama.substrate_sdk_android.runtime.extrinsic.Nonce
|
||||
|
||||
interface SigningContext {
|
||||
|
||||
interface Factory {
|
||||
|
||||
fun default(chain: Chain): SigningContext
|
||||
}
|
||||
|
||||
val chain: Chain
|
||||
|
||||
suspend fun getNonce(accountId: AccountIdKey): Nonce
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.signer
|
||||
|
||||
enum class SigningMode {
|
||||
FEE, SUBMISSION
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package io.novafoundation.nova.feature_account_api.data.signer
|
||||
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
|
||||
|
||||
/**
|
||||
* A signing chain of accounts.
|
||||
* Contains at least 1 item in path.
|
||||
* Ordering of accounts is built in the following order:
|
||||
* - path[0] always contains account for Leaf Signer
|
||||
* - path[1] Nested account
|
||||
* ...
|
||||
* - path[n - 1] Nested account
|
||||
* - path[n] is always Selected account
|
||||
*
|
||||
*/
|
||||
class SubmissionHierarchy(
|
||||
val path: List<Node>
|
||||
) {
|
||||
|
||||
class Node(
|
||||
val account: MetaAccount,
|
||||
val callExecutionType: CallExecutionType
|
||||
)
|
||||
|
||||
constructor(metaAccount: MetaAccount, callExecutionType: CallExecutionType) : this(listOf(Node(metaAccount, callExecutionType)))
|
||||
|
||||
operator fun plus(submissionHierarchy: SubmissionHierarchy): SubmissionHierarchy {
|
||||
return SubmissionHierarchy(path + submissionHierarchy.path)
|
||||
}
|
||||
}
|
||||
|
||||
fun SubmissionHierarchy.isDelayed() = path.any { it.callExecutionType == CallExecutionType.DELAYED }
|
||||
|
||||
fun SubmissionHierarchy.selectedAccount() = path.last().account
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
package io.novafoundation.nova.feature_account_api.di
|
||||
|
||||
import io.novafoundation.nova.common.sequrity.TwoFactorVerificationExecutor
|
||||
import io.novafoundation.nova.common.sequrity.biometry.BiometricServiceFactory
|
||||
import io.novafoundation.nova.feature_account_api.data.cloudBackup.LocalAccountsCloudBackupFacade
|
||||
import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.EvmTransactionService
|
||||
import io.novafoundation.nova.feature_account_api.data.events.MetaAccountChangesEventBus
|
||||
import io.novafoundation.nova.feature_account_api.data.externalAccounts.ExternalAccountsSyncService
|
||||
import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService
|
||||
import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicSplitter
|
||||
import io.novafoundation.nova.feature_account_api.data.fee.FeePaymentProviderRegistry
|
||||
import io.novafoundation.nova.feature_account_api.data.fee.capability.CustomFeeCapabilityFacade
|
||||
import io.novafoundation.nova.feature_account_api.data.fee.types.hydra.HydrationFeeInjector
|
||||
import io.novafoundation.nova.feature_account_api.data.multisig.MultisigDetailsRepository
|
||||
import io.novafoundation.nova.feature_account_api.data.multisig.MultisigPendingOperationsService
|
||||
import io.novafoundation.nova.feature_account_api.data.multisig.repository.MultisigOperationLocalCallRepository
|
||||
import io.novafoundation.nova.feature_account_api.data.multisig.repository.MultisigValidationsRepository
|
||||
import io.novafoundation.nova.feature_account_api.data.multisig.validation.MultisigExtrinsicValidationRequestBus
|
||||
import io.novafoundation.nova.feature_account_api.data.proxy.MetaAccountsUpdatesRegistry
|
||||
import io.novafoundation.nova.feature_account_api.data.proxy.validation.ProxyExtrinsicValidationRequestBus
|
||||
import io.novafoundation.nova.feature_account_api.data.repository.CreateSecretsRepository
|
||||
import io.novafoundation.nova.feature_account_api.data.repository.OnChainIdentityRepository
|
||||
import io.novafoundation.nova.feature_account_api.data.repository.addAccount.ledger.GenericLedgerAddAccountRepository
|
||||
import io.novafoundation.nova.feature_account_api.data.repository.addAccount.ledger.LegacyLedgerAddAccountRepository
|
||||
import io.novafoundation.nova.feature_account_api.data.repository.addAccount.secrets.MnemonicAddAccountRepository
|
||||
import io.novafoundation.nova.feature_account_api.data.signer.SignerProvider
|
||||
import io.novafoundation.nova.feature_account_api.data.signer.SigningContext
|
||||
import io.novafoundation.nova.feature_account_api.data.signer.SigningSharedState
|
||||
import io.novafoundation.nova.feature_account_api.di.deeplinks.AccountDeepLinks
|
||||
import io.novafoundation.nova.feature_account_api.domain.account.common.EncryptionDefaults
|
||||
import io.novafoundation.nova.feature_account_api.domain.account.identity.IdentityProvider
|
||||
import io.novafoundation.nova.feature_account_api.domain.account.identity.LocalIdentity
|
||||
import io.novafoundation.nova.feature_account_api.domain.account.identity.LocalWithOnChainIdentity
|
||||
import io.novafoundation.nova.feature_account_api.domain.account.identity.OnChainIdentity
|
||||
import io.novafoundation.nova.feature_account_api.domain.cloudBackup.ApplyLocalSnapshotToCloudBackupUseCase
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountInteractor
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountUIUseCase
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.CreateGiftMetaAccountUseCase
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.MetaAccountGroupingInteractor
|
||||
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.common.listing.MetaAccountTypePresentationMapper
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.account.common.listing.delegeted.MultisigFormatter
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.account.common.listing.delegeted.ProxyFormatter
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.account.copyAddress.CopyAddressMixin
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.account.polkadotVault.config.PolkadotVaultVariantConfigProvider
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.WalletUiUseCase
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.account.watchOnly.WatchOnlyMissingKeysPresenter
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.actions.ExternalActions
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.addressActions.AddressActionsMixin
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.cloudBackup.createPassword.SyncWalletsBackupPasswordCommunicator
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.language.LanguageUseCase
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.mixin.addressInput.AddressInputMixinFactory
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.mixin.identity.IdentityMixin
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.mixin.importType.ImportTypeChooserMixin
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectAddress.SelectAddressMixin
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectSingleWallet.SelectSingleWalletMixin
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectWallet.SelectWalletMixin
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.navigation.ExtrinsicNavigationWrapper
|
||||
|
||||
interface AccountFeatureApi {
|
||||
|
||||
val addressInputMixinFactory: AddressInputMixinFactory
|
||||
|
||||
val walletUiUseCase: WalletUiUseCase
|
||||
|
||||
val signerProvider: SignerProvider
|
||||
|
||||
val watchOnlyMissingKeysPresenter: WatchOnlyMissingKeysPresenter
|
||||
|
||||
val signSharedState: SigningSharedState
|
||||
|
||||
val onChainIdentityRepository: OnChainIdentityRepository
|
||||
|
||||
val metaAccountTypePresentationMapper: MetaAccountTypePresentationMapper
|
||||
|
||||
val legacyLedgerAddAccountRepository: LegacyLedgerAddAccountRepository
|
||||
|
||||
val genericLedgerAddAccountRepository: GenericLedgerAddAccountRepository
|
||||
|
||||
val evmTransactionService: EvmTransactionService
|
||||
|
||||
val identityMixinFactory: IdentityMixin.Factory
|
||||
|
||||
val languageUseCase: LanguageUseCase
|
||||
|
||||
val selectWalletMixinFactory: SelectWalletMixin.Factory
|
||||
|
||||
val polkadotVaultVariantConfigProvider: PolkadotVaultVariantConfigProvider
|
||||
|
||||
val selectAddressMixinFactory: SelectAddressMixin.Factory
|
||||
|
||||
val metaAccountChangesEventBus: MetaAccountChangesEventBus
|
||||
|
||||
val applyLocalSnapshotToCloudBackupUseCase: ApplyLocalSnapshotToCloudBackupUseCase
|
||||
|
||||
val feePaymentProviderRegistry: FeePaymentProviderRegistry
|
||||
|
||||
val customFeeCapabilityFacade: CustomFeeCapabilityFacade
|
||||
|
||||
val hydrationFeeInjector: HydrationFeeInjector
|
||||
|
||||
val addressActionsMixinFactory: AddressActionsMixin.Factory
|
||||
|
||||
val accountDeepLinks: AccountDeepLinks
|
||||
|
||||
val mnemonicAddAccountRepository: MnemonicAddAccountRepository
|
||||
|
||||
val multisigPendingOperationsService: MultisigPendingOperationsService
|
||||
|
||||
val signingContextFactory: SigningContext.Factory
|
||||
|
||||
val extrinsicSplitter: ExtrinsicSplitter
|
||||
|
||||
val externalAccountsSyncService: ExternalAccountsSyncService
|
||||
|
||||
val multisigValidationsRepository: MultisigValidationsRepository
|
||||
|
||||
val multisigExtrinsicValidationRequestBus: MultisigExtrinsicValidationRequestBus
|
||||
|
||||
val extrinsicNavigationWrapper: ExtrinsicNavigationWrapper
|
||||
|
||||
val multisigOperationLocalCallRepository: MultisigOperationLocalCallRepository
|
||||
|
||||
val multisigFormatter: MultisigFormatter
|
||||
|
||||
val proxyFormatter: ProxyFormatter
|
||||
|
||||
val accountUIUseCase: AccountUIUseCase
|
||||
|
||||
val multisigDetailsRepository: MultisigDetailsRepository
|
||||
|
||||
val metaAccountsUpdatesRegistry: MetaAccountsUpdatesRegistry
|
||||
|
||||
val createSecretsRepository: CreateSecretsRepository
|
||||
|
||||
val createGiftMetaAccountUseCase: CreateGiftMetaAccountUseCase
|
||||
|
||||
val selectSingleWalletMixin: SelectSingleWalletMixin.Factory
|
||||
|
||||
@LocalIdentity
|
||||
fun localIdentityProvider(): IdentityProvider
|
||||
|
||||
@OnChainIdentity
|
||||
fun onChainIdentityProvider(): IdentityProvider
|
||||
|
||||
@LocalWithOnChainIdentity
|
||||
fun localWithOnChainIdentityProvider(): IdentityProvider
|
||||
|
||||
fun metaAccountGroupingInteractor(): MetaAccountGroupingInteractor
|
||||
|
||||
fun accountInteractor(): AccountInteractor
|
||||
|
||||
fun provideAccountRepository(): AccountRepository
|
||||
|
||||
fun externalAccountActions(): ExternalActions.Presentation
|
||||
|
||||
fun accountUpdateScope(): AccountUpdateScope
|
||||
|
||||
fun addressDisplayUseCase(): AddressDisplayUseCase
|
||||
|
||||
fun accountUseCase(): SelectedAccountUseCase
|
||||
|
||||
fun extrinsicService(): ExtrinsicService
|
||||
|
||||
fun extrinsicServiceFactory(): ExtrinsicService.Factory
|
||||
|
||||
fun importTypeChooserMixin(): ImportTypeChooserMixin.Presentation
|
||||
|
||||
fun twoFactorVerificationExecutor(): TwoFactorVerificationExecutor
|
||||
|
||||
fun biometricServiceFactory(): BiometricServiceFactory
|
||||
|
||||
fun encryptionDefaults(): EncryptionDefaults
|
||||
|
||||
fun proxyExtrinsicValidationRequestBus(): ProxyExtrinsicValidationRequestBus
|
||||
|
||||
fun cloudBackupFacade(): LocalAccountsCloudBackupFacade
|
||||
|
||||
fun syncWalletsBackupPasswordCommunicator(): SyncWalletsBackupPasswordCommunicator
|
||||
|
||||
fun copyAddressMixin(): CopyAddressMixin
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package io.novafoundation.nova.feature_account_api.di.deeplinks
|
||||
|
||||
import io.novafoundation.nova.feature_deep_linking.presentation.handling.DeepLinkHandler
|
||||
|
||||
class AccountDeepLinks(val deepLinkHandlers: List<DeepLinkHandler>)
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package io.novafoundation.nova.feature_account_api.domain.account.advancedEncryption
|
||||
|
||||
import io.novafoundation.nova.common.utils.input.Input
|
||||
import io.novafoundation.nova.core.model.CryptoType
|
||||
import io.novafoundation.nova.feature_account_api.domain.account.common.EncryptionDefaults
|
||||
|
||||
class AdvancedEncryptionInput(
|
||||
val substrateCryptoType: Input<CryptoType>,
|
||||
val substrateDerivationPath: Input<String>,
|
||||
val ethereumCryptoType: Input<CryptoType>,
|
||||
val ethereumDerivationPath: Input<String>
|
||||
)
|
||||
|
||||
data class AdvancedEncryption(
|
||||
val substrateCryptoType: CryptoType?,
|
||||
val ethereumCryptoType: CryptoType?,
|
||||
val derivationPaths: DerivationPaths
|
||||
) {
|
||||
|
||||
companion object;
|
||||
|
||||
data class DerivationPaths(
|
||||
val substrate: String?,
|
||||
val ethereum: String?
|
||||
) {
|
||||
companion object {
|
||||
fun empty() = DerivationPaths(null, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun EncryptionDefaults.recommended() = AdvancedEncryption(
|
||||
substrateCryptoType = substrateCryptoType,
|
||||
ethereumCryptoType = ethereumCryptoType,
|
||||
derivationPaths = AdvancedEncryption.DerivationPaths(
|
||||
substrate = substrateDerivationPath,
|
||||
ethereum = ethereumDerivationPath
|
||||
)
|
||||
)
|
||||
|
||||
fun AdvancedEncryption.Companion.substrate(
|
||||
cryptoType: CryptoType,
|
||||
substrateDerivationPaths: String?
|
||||
) = AdvancedEncryption(
|
||||
substrateCryptoType = cryptoType,
|
||||
ethereumCryptoType = null,
|
||||
derivationPaths = AdvancedEncryption.DerivationPaths(
|
||||
substrate = substrateDerivationPaths,
|
||||
ethereum = null
|
||||
)
|
||||
)
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package io.novafoundation.nova.feature_account_api.domain.account.common
|
||||
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
|
||||
class ChainWithAccountId(
|
||||
val chain: Chain,
|
||||
val accountId: ByteArray
|
||||
)
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package io.novafoundation.nova.feature_account_api.domain.account.common
|
||||
|
||||
import io.novafoundation.nova.core.model.CryptoType
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
|
||||
class EncryptionDefaults(
|
||||
val substrateCryptoType: CryptoType,
|
||||
val ethereumCryptoType: CryptoType,
|
||||
val substrateDerivationPath: String,
|
||||
val ethereumDerivationPath: String
|
||||
)
|
||||
|
||||
class ChainEncryptionDefaults(
|
||||
val cryptoType: CryptoType,
|
||||
val derivationPath: String
|
||||
)
|
||||
|
||||
fun EncryptionDefaults.forChain(chain: Chain): ChainEncryptionDefaults {
|
||||
return if (chain.isEthereumBased) {
|
||||
ChainEncryptionDefaults(
|
||||
cryptoType = ethereumCryptoType,
|
||||
derivationPath = ethereumDerivationPath
|
||||
)
|
||||
} else {
|
||||
ChainEncryptionDefaults(
|
||||
cryptoType = substrateCryptoType,
|
||||
derivationPath = substrateDerivationPath
|
||||
)
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package io.novafoundation.nova.feature_account_api.domain.account.identity
|
||||
|
||||
import io.novafoundation.nova.feature_account_api.data.model.OnChainIdentity
|
||||
|
||||
data class Identity(val name: String)
|
||||
|
||||
fun Identity(onChainIdentity: OnChainIdentity): Identity? {
|
||||
return onChainIdentity.display?.let { Identity(it) }
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package io.novafoundation.nova.feature_account_api.domain.account.identity
|
||||
|
||||
import io.novafoundation.nova.common.address.AccountIdKey
|
||||
import io.novafoundation.nova.common.presentation.ellipsizeAddress
|
||||
import io.novafoundation.nova.runtime.ext.addressOf
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
|
||||
suspend fun IdentityProvider.getNameOrAddress(accountId: AccountIdKey, chain: Chain): String {
|
||||
return identityFor(accountId.value, chain.id)?.name ?: chain.addressOf(accountId).ellipsizeAddress()
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package io.novafoundation.nova.feature_account_api.domain.account.identity
|
||||
|
||||
import io.novafoundation.nova.common.address.AccountIdKey
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
import io.novasama.substrate_sdk_android.runtime.AccountId
|
||||
|
||||
interface IdentityProvider {
|
||||
|
||||
companion object;
|
||||
|
||||
/**
|
||||
* Returns, if present, an identity for the given [accountId] inside specified [chainId]
|
||||
*/
|
||||
suspend fun identityFor(accountId: AccountId, chainId: ChainId): Identity?
|
||||
|
||||
/**
|
||||
* Bulk version of [identityFor]. Default implementation is unoptimized and just performs N single requests to [identityFor].
|
||||
*/
|
||||
suspend fun identitiesFor(accountIds: Collection<AccountId>, chainId: ChainId): Map<AccountIdKey, Identity?> {
|
||||
return accountIds.associateBy(
|
||||
keySelector = ::AccountIdKey,
|
||||
valueTransform = { identityFor(it, chainId) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun IdentityProvider.Companion.oneOf(vararg delegates: IdentityProvider): IdentityProvider {
|
||||
return OneOfIdentityProvider(delegates.toList())
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package io.novafoundation.nova.feature_account_api.domain.account.identity
|
||||
|
||||
import io.novafoundation.nova.common.address.AccountIdKey
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
import io.novasama.substrate_sdk_android.extensions.tryFindNonNull
|
||||
import io.novasama.substrate_sdk_android.runtime.AccountId
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
internal class OneOfIdentityProvider(
|
||||
private val delegates: List<IdentityProvider>
|
||||
) : IdentityProvider {
|
||||
|
||||
override suspend fun identityFor(accountId: AccountId, chainId: ChainId): Identity? = withContext(Dispatchers.IO) {
|
||||
delegates.tryFindNonNull {
|
||||
it.identityFor(accountId, chainId)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun identitiesFor(accountIds: Collection<AccountId>, chainId: ChainId): Map<AccountIdKey, Identity?> = withContext(Dispatchers.IO) {
|
||||
delegates.tryFindNonNull {
|
||||
it.identitiesFor(accountIds, chainId)
|
||||
}.orEmpty()
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package io.novafoundation.nova.feature_account_api.domain.account.identity
|
||||
|
||||
import javax.inject.Qualifier
|
||||
|
||||
@Qualifier
|
||||
annotation class OnChainIdentity
|
||||
|
||||
@Qualifier
|
||||
annotation class LocalIdentity
|
||||
|
||||
@Qualifier
|
||||
annotation class LocalWithOnChainIdentity
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package io.novafoundation.nova.feature_account_api.domain.account.system
|
||||
|
||||
import io.novafoundation.nova.common.address.AccountIdKey
|
||||
import io.novasama.substrate_sdk_android.runtime.AccountId
|
||||
|
||||
class AccountSystemAccountMatcher(private val accountIdKey: AccountIdKey) : SystemAccountMatcher {
|
||||
|
||||
override fun isSystemAccount(accountId: AccountId): Boolean {
|
||||
return accountIdKey.value.contentEquals(accountId)
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package io.novafoundation.nova.feature_account_api.domain.account.system
|
||||
|
||||
import io.novasama.substrate_sdk_android.runtime.AccountId
|
||||
|
||||
class CompoundSystemAccountMatcher(
|
||||
private val delegates: List<SystemAccountMatcher>
|
||||
) : SystemAccountMatcher {
|
||||
|
||||
constructor(vararg delegates: SystemAccountMatcher) : this(delegates.toList())
|
||||
|
||||
override fun isSystemAccount(accountId: AccountId): Boolean {
|
||||
return delegates.any { it.isSystemAccount(accountId) }
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package io.novafoundation.nova.feature_account_api.domain.account.system
|
||||
|
||||
import io.novafoundation.nova.common.utils.startsWith
|
||||
import io.novasama.substrate_sdk_android.runtime.AccountId
|
||||
|
||||
class PrefixSystemAccountMatcher(private val prefix: ByteArray) : SystemAccountMatcher {
|
||||
|
||||
constructor(utf8Prefix: String) : this(utf8Prefix.encodeToByteArray())
|
||||
|
||||
override fun isSystemAccount(accountId: AccountId): Boolean {
|
||||
return accountId.startsWith(prefix)
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package io.novafoundation.nova.feature_account_api.domain.account.system
|
||||
|
||||
import io.novasama.substrate_sdk_android.runtime.AccountId
|
||||
|
||||
interface SystemAccountMatcher {
|
||||
|
||||
companion object
|
||||
|
||||
fun isSystemAccount(accountId: AccountId): Boolean
|
||||
}
|
||||
|
||||
fun SystemAccountMatcher.Companion.default(): SystemAccountMatcher {
|
||||
return CompoundSystemAccountMatcher(
|
||||
// Pallet-specific technical accounts, e.g. crowdloan-fund, nomination pool,
|
||||
PrefixSystemAccountMatcher("modl"),
|
||||
// Parachain sovereign accounts on relaychain
|
||||
PrefixSystemAccountMatcher("para"),
|
||||
// Relaychain sovereign account on parachains
|
||||
PrefixSystemAccountMatcher("Parent"),
|
||||
// Sibling parachain soveregin accounts
|
||||
PrefixSystemAccountMatcher("sibl")
|
||||
)
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package io.novafoundation.nova.feature_account_api.domain.cloudBackup
|
||||
|
||||
interface ApplyLocalSnapshotToCloudBackupUseCase {
|
||||
|
||||
suspend fun applyLocalSnapshotToCloudBackupIfSyncEnabled(): Result<Unit>
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package io.novafoundation.nova.feature_account_api.domain.filter.selectAddress
|
||||
|
||||
import io.novafoundation.nova.common.utils.Filter
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.isControllableWallet
|
||||
|
||||
sealed interface SelectAccountFilter : Filter<MetaAccount> {
|
||||
|
||||
class Everything : SelectAccountFilter {
|
||||
|
||||
override fun shouldInclude(model: MetaAccount): Boolean {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
class ControllableWallets() : SelectAccountFilter {
|
||||
|
||||
override fun shouldInclude(model: MetaAccount): Boolean {
|
||||
return model.type.isControllableWallet()
|
||||
}
|
||||
}
|
||||
|
||||
class ExcludeMetaAccounts(val metaIds: List<Long>) : SelectAccountFilter {
|
||||
|
||||
override fun shouldInclude(model: MetaAccount): Boolean {
|
||||
return !metaIds.contains(model.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
package io.novafoundation.nova.feature_account_api.domain.interfaces
|
||||
|
||||
import io.novafoundation.nova.core.model.CryptoType
|
||||
import io.novafoundation.nova.core.model.Language
|
||||
import io.novafoundation.nova.core.model.Node
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.Account
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.PreferredCryptoType
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
import io.novasama.substrate_sdk_android.encrypt.mnemonic.Mnemonic
|
||||
import io.novasama.substrate_sdk_android.runtime.AccountId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface AccountInteractor {
|
||||
|
||||
suspend fun getActiveMetaAccounts(): List<MetaAccount>
|
||||
|
||||
suspend fun generateMnemonic(): Mnemonic
|
||||
|
||||
fun getCryptoTypes(): List<CryptoType>
|
||||
|
||||
suspend fun getPreferredCryptoType(chainId: ChainId? = null): PreferredCryptoType
|
||||
|
||||
suspend fun isCodeSet(): Boolean
|
||||
|
||||
suspend fun savePin(code: String)
|
||||
|
||||
suspend fun isPinCorrect(code: String): Boolean
|
||||
|
||||
suspend fun getMetaAccount(metaId: Long): MetaAccount
|
||||
|
||||
suspend fun selectMetaAccount(metaId: Long)
|
||||
|
||||
suspend fun selectedMetaAccount(): MetaAccount
|
||||
|
||||
suspend fun deleteAccount(metaId: Long): Boolean
|
||||
|
||||
suspend fun updateMetaAccountPositions(idsInNewOrder: List<Long>)
|
||||
|
||||
fun chainFlow(chainId: ChainId): Flow<Chain>
|
||||
|
||||
fun nodesFlow(): Flow<List<Node>>
|
||||
|
||||
suspend fun getNode(nodeId: Int): Node
|
||||
|
||||
fun getLanguages(): List<Language>
|
||||
|
||||
suspend fun getSelectedLanguage(): Language
|
||||
|
||||
suspend fun changeSelectedLanguage(language: Language)
|
||||
|
||||
suspend fun addNode(nodeName: String, nodeHost: String): Result<Unit>
|
||||
|
||||
suspend fun updateNode(nodeId: Int, newName: String, newHost: String): Result<Unit>
|
||||
|
||||
suspend fun getAccountsByNetworkTypeWithSelectedNode(networkType: Node.NetworkType): Pair<List<Account>, Node>
|
||||
|
||||
suspend fun selectNodeAndAccount(nodeId: Int, accountAddress: String)
|
||||
|
||||
suspend fun selectNode(nodeId: Int)
|
||||
|
||||
suspend fun deleteNode(nodeId: Int)
|
||||
|
||||
suspend fun getChainAddress(metaId: Long, chainId: ChainId): String?
|
||||
|
||||
suspend fun removeDeactivatedMetaAccounts()
|
||||
|
||||
suspend fun switchToNotDeactivatedAccountIfNeeded()
|
||||
|
||||
suspend fun hasSecretsAccounts(): Boolean
|
||||
|
||||
suspend fun hasCustomChainAccounts(metaId: Long): Boolean
|
||||
|
||||
suspend fun deleteProxiedMetaAccountsByChain(chainId: String)
|
||||
|
||||
suspend fun findMetaAccount(chain: Chain, value: AccountId): MetaAccount?
|
||||
}
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
package io.novafoundation.nova.feature_account_api.domain.interfaces
|
||||
|
||||
import io.novafoundation.nova.core.model.CryptoType
|
||||
import io.novafoundation.nova.core.model.Language
|
||||
import io.novafoundation.nova.core.model.Node
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.Account
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountAssetBalance
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountOrdering
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
import io.novasama.substrate_sdk_android.encrypt.mnemonic.Mnemonic
|
||||
import io.novasama.substrate_sdk_android.runtime.AccountId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class AccountAlreadyExistsException : Exception()
|
||||
|
||||
interface AccountRepository {
|
||||
|
||||
fun getEncryptionTypes(): List<CryptoType>
|
||||
|
||||
suspend fun getNode(nodeId: Int): Node
|
||||
|
||||
suspend fun getSelectedNodeOrDefault(): Node
|
||||
|
||||
suspend fun selectNode(node: Node)
|
||||
|
||||
suspend fun getDefaultNode(networkType: Node.NetworkType): Node
|
||||
|
||||
suspend fun selectAccount(account: Account, newNode: Node? = null)
|
||||
|
||||
suspend fun getSelectedMetaAccount(): MetaAccount
|
||||
|
||||
suspend fun getMetaAccount(metaId: Long): MetaAccount
|
||||
|
||||
fun metaAccountFlow(metaId: Long): Flow<MetaAccount>
|
||||
|
||||
fun selectedMetaAccountFlow(): Flow<MetaAccount>
|
||||
|
||||
suspend fun findMetaAccount(accountId: ByteArray, chainId: ChainId): MetaAccount?
|
||||
|
||||
suspend fun accountNameFor(accountId: AccountId, chainId: ChainId): String?
|
||||
|
||||
suspend fun hasActiveMetaAccounts(): Boolean
|
||||
|
||||
fun allMetaAccountsFlow(): Flow<List<MetaAccount>>
|
||||
|
||||
fun activeMetaAccountsFlow(): Flow<List<MetaAccount>>
|
||||
|
||||
fun metaAccountsByTypeFlow(type: LightMetaAccount.Type): Flow<List<MetaAccount>>
|
||||
|
||||
fun metaAccountBalancesFlow(): Flow<List<MetaAccountAssetBalance>>
|
||||
|
||||
fun metaAccountBalancesFlow(metaId: Long): Flow<List<MetaAccountAssetBalance>>
|
||||
|
||||
suspend fun selectMetaAccount(metaId: Long)
|
||||
|
||||
suspend fun updateMetaAccountName(metaId: Long, newName: String)
|
||||
|
||||
suspend fun isAccountSelected(): Boolean
|
||||
|
||||
suspend fun deleteAccount(metaId: Long)
|
||||
|
||||
suspend fun getAccounts(): List<Account>
|
||||
|
||||
suspend fun getAccount(address: String): Account
|
||||
|
||||
suspend fun getAccountOrNull(address: String): Account?
|
||||
|
||||
suspend fun getMyAccounts(query: String, chainId: String): Set<Account>
|
||||
|
||||
suspend fun isCodeSet(): Boolean
|
||||
|
||||
suspend fun savePinCode(code: String)
|
||||
|
||||
suspend fun getPinCode(): String?
|
||||
|
||||
suspend fun generateMnemonic(): Mnemonic
|
||||
|
||||
fun isBiometricEnabledFlow(): Flow<Boolean>
|
||||
|
||||
fun isBiometricEnabled(): Boolean
|
||||
|
||||
fun setBiometricOn()
|
||||
|
||||
fun setBiometricOff()
|
||||
|
||||
fun nodesFlow(): Flow<List<Node>>
|
||||
|
||||
suspend fun updateAccountsOrdering(accountOrdering: List<MetaAccountOrdering>)
|
||||
|
||||
fun getLanguages(): List<Language>
|
||||
|
||||
suspend fun selectedLanguage(): Language
|
||||
|
||||
suspend fun changeLanguage(language: Language)
|
||||
|
||||
suspend fun addNode(nodeName: String, nodeHost: String, networkType: Node.NetworkType)
|
||||
|
||||
suspend fun updateNode(nodeId: Int, newName: String, newHost: String, networkType: Node.NetworkType)
|
||||
|
||||
suspend fun checkNodeExists(nodeHost: String): Boolean
|
||||
|
||||
/**
|
||||
* @throws NovaException
|
||||
* @throws NovaException
|
||||
*/
|
||||
suspend fun getNetworkName(nodeHost: String): String
|
||||
|
||||
suspend fun getAccountsByNetworkType(networkType: Node.NetworkType): List<Account>
|
||||
|
||||
suspend fun deleteNode(nodeId: Int)
|
||||
|
||||
suspend fun createQrAccountContent(chain: Chain, account: MetaAccount): String
|
||||
|
||||
suspend fun generateRestoreJson(
|
||||
metaAccount: MetaAccount,
|
||||
chain: Chain,
|
||||
password: String
|
||||
): String
|
||||
|
||||
suspend fun isAccountExists(accountId: AccountId, chainId: ChainId): Boolean
|
||||
|
||||
suspend fun removeDeactivatedMetaAccounts()
|
||||
|
||||
suspend fun getActiveMetaAccounts(): List<MetaAccount>
|
||||
|
||||
suspend fun getAllMetaAccounts(): List<MetaAccount>
|
||||
|
||||
suspend fun getActiveMetaAccountsQuantity(): Int
|
||||
|
||||
fun hasMetaAccountsCountOfTypeFlow(type: LightMetaAccount.Type): Flow<Boolean>
|
||||
|
||||
suspend fun hasMetaAccountsByType(type: LightMetaAccount.Type): Boolean
|
||||
|
||||
suspend fun hasMetaAccountsByType(metaIds: Set<Long>, type: LightMetaAccount.Type): Boolean
|
||||
|
||||
suspend fun generateRestoreJson(metaAccount: MetaAccount, password: String): String
|
||||
|
||||
suspend fun hasSecretsAccounts(): Boolean
|
||||
|
||||
suspend fun deleteProxiedMetaAccountsByChain(chainId: String)
|
||||
|
||||
suspend fun getMetaAccountsByIds(metaIds: List<Long>): List<MetaAccount>
|
||||
|
||||
suspend fun getAvailableMetaIdsFromSet(metaIds: Set<Long>): Set<Long>
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package io.novafoundation.nova.feature_account_api.domain.interfaces
|
||||
|
||||
import io.novafoundation.nova.common.address.AccountIdKey
|
||||
import io.novafoundation.nova.common.address.intoKey
|
||||
import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.TransactionOrigin
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.requireAccountIdIn
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
import io.novasama.substrate_sdk_android.extensions.toHexString
|
||||
import io.novasama.substrate_sdk_android.runtime.AccountId
|
||||
|
||||
suspend fun AccountRepository.findMetaAccountOrThrow(accountId: AccountId, chainId: ChainId) = findMetaAccount(accountId, chainId)
|
||||
?: error("No meta account found for accountId: ${accountId.toHexString()}")
|
||||
|
||||
suspend fun AccountRepository.requireIdOfSelectedMetaAccountIn(chain: Chain): AccountId {
|
||||
val metaAccount = getSelectedMetaAccount()
|
||||
|
||||
return metaAccount.requireAccountIdIn(chain)
|
||||
}
|
||||
|
||||
suspend fun AccountRepository.requireIdKeyOfSelectedMetaAccountIn(chain: Chain): AccountIdKey {
|
||||
return requireIdOfSelectedMetaAccountIn(chain).intoKey()
|
||||
}
|
||||
|
||||
suspend fun AccountRepository.getIdOfSelectedMetaAccountIn(chain: Chain): AccountId? {
|
||||
val metaAccount = getSelectedMetaAccount()
|
||||
|
||||
return metaAccount.accountIdIn(chain)
|
||||
}
|
||||
|
||||
suspend fun AccountRepository.requireMetaAccountFor(transactionOrigin: TransactionOrigin, chainId: ChainId): MetaAccount {
|
||||
return when (transactionOrigin) {
|
||||
TransactionOrigin.SelectedWallet -> getSelectedMetaAccount()
|
||||
is TransactionOrigin.WalletWithAccount -> findMetaAccountOrThrow(transactionOrigin.accountId, chainId)
|
||||
is TransactionOrigin.Wallet -> transactionOrigin.metaAccount
|
||||
is TransactionOrigin.WalletWithId -> getMetaAccount(transactionOrigin.metaId)
|
||||
}
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
package io.novafoundation.nova.feature_account_api.domain.interfaces
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import io.novafoundation.nova.common.address.AccountIdKey
|
||||
import io.novafoundation.nova.common.address.AddressIconGenerator
|
||||
import io.novafoundation.nova.common.address.AddressIconGenerator.Companion.BACKGROUND_TRANSPARENT
|
||||
import io.novafoundation.nova.common.address.AddressIconGenerator.Companion.SIZE_MEDIUM
|
||||
import io.novafoundation.nova.common.address.AddressModel
|
||||
import io.novafoundation.nova.feature_account_api.domain.account.identity.Identity
|
||||
import io.novafoundation.nova.feature_account_api.domain.account.identity.IdentityProvider
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.accountIdKeyIn
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.account.icon.createAddressModel
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.WalletModel
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.WalletUiUseCase
|
||||
import io.novafoundation.nova.runtime.ext.addressOf
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
|
||||
sealed interface AccountModel {
|
||||
|
||||
fun address(): String
|
||||
|
||||
fun drawable(): Drawable?
|
||||
|
||||
fun nameOrAddress(): String
|
||||
|
||||
class Wallet(
|
||||
metaId: Long,
|
||||
name: String,
|
||||
icon: Drawable?,
|
||||
private val address: String
|
||||
) : WalletModel(metaId, name, icon), AccountModel {
|
||||
|
||||
constructor(walletModel: WalletModel, address: String) : this(walletModel.metaId, walletModel.name, walletModel.icon, address)
|
||||
|
||||
override fun address() = address
|
||||
override fun drawable() = icon
|
||||
override fun nameOrAddress() = name
|
||||
}
|
||||
|
||||
class Address(
|
||||
address: String,
|
||||
image: Drawable,
|
||||
name: String? = null
|
||||
) : AddressModel(address, image, name), AccountModel {
|
||||
|
||||
constructor(addressModel: AddressModel) : this(addressModel.address, addressModel.image, addressModel.name)
|
||||
|
||||
override fun address() = address
|
||||
override fun drawable() = image
|
||||
override fun nameOrAddress() = nameOrAddress
|
||||
}
|
||||
}
|
||||
|
||||
interface AccountUIUseCase {
|
||||
|
||||
suspend fun getAccountModel(accountId: AccountIdKey, chain: Chain): AccountModel
|
||||
|
||||
suspend fun getAccountModels(accountIds: Set<AccountIdKey>, chain: Chain): Map<AccountIdKey, AccountModel>
|
||||
}
|
||||
|
||||
class RealAccountUIUseCase(
|
||||
private val accountRepository: AccountRepository,
|
||||
private val walletUiUseCase: WalletUiUseCase,
|
||||
private val addressIconGenerator: AddressIconGenerator,
|
||||
private val identityProvider: IdentityProvider
|
||||
) : AccountUIUseCase {
|
||||
|
||||
override suspend fun getAccountModel(accountId: AccountIdKey, chain: Chain): AccountModel {
|
||||
return getAccountModelInternal(
|
||||
accountId,
|
||||
chain,
|
||||
accountRepository.findMetaAccount(accountId.value, chain.id),
|
||||
identityProvider.identityFor(accountId.value, chain.id)
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getAccountModels(accountIds: Set<AccountIdKey>, chain: Chain): Map<AccountIdKey, AccountModel> {
|
||||
val identities = identityProvider.identitiesFor(accountIds.map { it.value }, chain.id)
|
||||
val metaAccounts = accountRepository.getActiveMetaAccounts().associateBy { it.accountIdKeyIn(chain) }
|
||||
|
||||
return accountIds.associateWith { accountId ->
|
||||
val metaAccount = metaAccounts[accountId]
|
||||
val identity = identities[accountId]
|
||||
getAccountModelInternal(accountId, chain, metaAccount, identity)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getAccountModelInternal(accountId: AccountIdKey, chain: Chain, metaAccount: MetaAccount?, identity: Identity?): AccountModel {
|
||||
return when (metaAccount) {
|
||||
null -> {
|
||||
val addressModel = addressIconGenerator.createAddressModel(
|
||||
chain,
|
||||
chain.addressOf(accountId),
|
||||
SIZE_MEDIUM,
|
||||
accountName = identity?.name,
|
||||
background = BACKGROUND_TRANSPARENT
|
||||
)
|
||||
AccountModel.Address(addressModel)
|
||||
}
|
||||
|
||||
else -> {
|
||||
val walletModel = walletUiUseCase.walletUiFor(metaAccount)
|
||||
AccountModel.Wallet(walletModel, chain.addressOf(accountId))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package io.novafoundation.nova.feature_account_api.domain.interfaces
|
||||
|
||||
import io.novafoundation.nova.common.data.secrets.v2.ChainAccountSecrets
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novasama.substrate_sdk_android.scale.EncodableStruct
|
||||
|
||||
interface CreateGiftMetaAccountUseCase {
|
||||
|
||||
fun createTemporaryGiftMetaAccount(chain: Chain, chainSecrets: EncodableStruct<ChainAccountSecrets>): MetaAccount
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package io.novafoundation.nova.feature_account_api.domain.interfaces
|
||||
|
||||
import io.novafoundation.nova.common.list.GroupedList
|
||||
import io.novafoundation.nova.common.utils.Filter
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.AccountDelegation
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountListingItem
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface MetaAccountGroupingInteractor {
|
||||
|
||||
fun metaAccountsWithTotalBalanceFlow(): Flow<GroupedList<LightMetaAccount.Type, MetaAccountListingItem>>
|
||||
|
||||
fun metaAccountWithTotalBalanceFlow(metaId: Long): Flow<MetaAccountListingItem>
|
||||
|
||||
fun getMetaAccountsWithFilter(metaAccountFilter: Filter<MetaAccount>): Flow<GroupedList<LightMetaAccount.Type, MetaAccount>>
|
||||
|
||||
fun updatedDelegates(): Flow<GroupedList<LightMetaAccount.Status, AccountDelegation>>
|
||||
|
||||
suspend fun hasAvailableMetaAccountsForChain(
|
||||
chainId: ChainId,
|
||||
metaAccountFilter: Filter<MetaAccount>
|
||||
): Boolean
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package io.novafoundation.nova.feature_account_api.domain.interfaces
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import io.novafoundation.nova.common.address.AddressModel
|
||||
import io.novafoundation.nova.common.view.TintedIcon
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class SelectedWalletModel(
|
||||
val typeIcon: TintedIcon?,
|
||||
val walletIcon: Drawable,
|
||||
val name: String,
|
||||
val hasUpdates: Boolean,
|
||||
)
|
||||
|
||||
interface SelectedAccountUseCase {
|
||||
|
||||
fun selectedMetaAccountFlow(): Flow<MetaAccount>
|
||||
|
||||
fun selectedAddressModelFlow(chain: suspend () -> Chain): Flow<AddressModel>
|
||||
|
||||
fun selectedWalletModelFlow(): Flow<SelectedWalletModel>
|
||||
|
||||
suspend fun getSelectedMetaAccount(): MetaAccount
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package io.novafoundation.nova.feature_account_api.domain.model
|
||||
|
||||
import io.novafoundation.nova.core.model.CryptoType
|
||||
import io.novafoundation.nova.core.model.Network
|
||||
import io.novasama.substrate_sdk_android.extensions.fromHex
|
||||
|
||||
data class Account(
|
||||
val address: String,
|
||||
val name: String?,
|
||||
val accountIdHex: String,
|
||||
val cryptoType: CryptoType, // TODO make optional
|
||||
val position: Int,
|
||||
val network: Network, // TODO remove when account management will be rewritten,
|
||||
) {
|
||||
|
||||
val accountId = accountIdHex.fromHex()
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package io.novafoundation.nova.feature_account_api.domain.model
|
||||
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
|
||||
sealed class AddAccountType {
|
||||
|
||||
class MetaAccount(val name: String) : AddAccountType()
|
||||
|
||||
class ChainAccount(val chainId: ChainId, val metaId: Long) : AddAccountType()
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package io.novafoundation.nova.feature_account_api.domain.model
|
||||
|
||||
enum class AuthType {
|
||||
PINCODE,
|
||||
BIOMETRY
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package io.novafoundation.nova.feature_account_api.domain.model
|
||||
|
||||
fun metaAccountTypeComparator() = compareBy<LightMetaAccount.Type> {
|
||||
when (it) {
|
||||
LightMetaAccount.Type.SECRETS -> 0
|
||||
LightMetaAccount.Type.POLKADOT_VAULT -> 1
|
||||
LightMetaAccount.Type.PARITY_SIGNER -> 2
|
||||
LightMetaAccount.Type.LEDGER -> 3
|
||||
LightMetaAccount.Type.LEDGER_LEGACY -> 4
|
||||
LightMetaAccount.Type.PROXIED -> 5
|
||||
LightMetaAccount.Type.MULTISIG -> 6
|
||||
LightMetaAccount.Type.WATCH_ONLY -> 7
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package io.novafoundation.nova.feature_account_api.domain.model
|
||||
|
||||
import io.novafoundation.nova.core.model.CryptoType
|
||||
|
||||
class ImportJsonMetaData(
|
||||
val name: String?,
|
||||
val chainId: String?,
|
||||
val encryptionType: CryptoType
|
||||
)
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package io.novafoundation.nova.feature_account_api.domain.model
|
||||
|
||||
enum class LedgerVariant {
|
||||
LEGACY, GENERIC
|
||||
}
|
||||
+298
@@ -0,0 +1,298 @@
|
||||
package io.novafoundation.nova.feature_account_api.domain.model
|
||||
|
||||
import io.novafoundation.nova.common.address.AccountIdKey
|
||||
import io.novafoundation.nova.common.address.format.AddressScheme
|
||||
import io.novafoundation.nova.common.address.intoKey
|
||||
import io.novafoundation.nova.common.data.mappers.mapCryptoTypeToEncryption
|
||||
import io.novafoundation.nova.common.data.mappers.mapEncryptionToCryptoType
|
||||
import io.novafoundation.nova.common.utils.DEFAULT_PREFIX
|
||||
import io.novafoundation.nova.core.model.CryptoType
|
||||
import io.novafoundation.nova.runtime.ext.addressOf
|
||||
import io.novafoundation.nova.runtime.ext.toEthereumAddress
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
import io.novasama.substrate_sdk_android.encrypt.MultiChainEncryption
|
||||
import io.novasama.substrate_sdk_android.extensions.asEthereumPublicKey
|
||||
import io.novasama.substrate_sdk_android.extensions.toAccountId
|
||||
import io.novasama.substrate_sdk_android.runtime.AccountId
|
||||
import io.novasama.substrate_sdk_android.ss58.SS58Encoder
|
||||
import io.novasama.substrate_sdk_android.ss58.SS58Encoder.toAddress
|
||||
import kotlin.contracts.ExperimentalContracts
|
||||
import kotlin.contracts.contract
|
||||
|
||||
class MetaIdWithType(
|
||||
val metaId: Long,
|
||||
val type: LightMetaAccount.Type
|
||||
)
|
||||
|
||||
class MetaAccountOrdering(
|
||||
val id: Long,
|
||||
val position: Int,
|
||||
)
|
||||
|
||||
interface LightMetaAccount {
|
||||
|
||||
val id: Long
|
||||
|
||||
/**
|
||||
* In contrast to [id] which should only be unique **locally**, [globallyUniqueId] should be unique **globally**,
|
||||
* meaning it should be unique across all application instances. This is useful to compare meta accounts from different application instances
|
||||
*/
|
||||
val globallyUniqueId: String
|
||||
|
||||
val substratePublicKey: ByteArray?
|
||||
val substrateCryptoType: CryptoType?
|
||||
val substrateAccountId: ByteArray?
|
||||
val ethereumAddress: ByteArray?
|
||||
val ethereumPublicKey: ByteArray?
|
||||
val isSelected: Boolean
|
||||
val name: String
|
||||
val type: Type
|
||||
val status: Status
|
||||
|
||||
val parentMetaId: Long?
|
||||
|
||||
enum class Type {
|
||||
SECRETS,
|
||||
WATCH_ONLY,
|
||||
PARITY_SIGNER,
|
||||
LEDGER_LEGACY,
|
||||
LEDGER,
|
||||
POLKADOT_VAULT,
|
||||
PROXIED,
|
||||
MULTISIG
|
||||
}
|
||||
|
||||
enum class Status {
|
||||
ACTIVE, DEACTIVATED
|
||||
}
|
||||
}
|
||||
|
||||
fun LightMetaAccount(
|
||||
id: Long,
|
||||
globallyUniqueId: String,
|
||||
substratePublicKey: ByteArray?,
|
||||
substrateCryptoType: CryptoType?,
|
||||
substrateAccountId: ByteArray?,
|
||||
ethereumAddress: ByteArray?,
|
||||
ethereumPublicKey: ByteArray?,
|
||||
isSelected: Boolean,
|
||||
name: String,
|
||||
type: LightMetaAccount.Type,
|
||||
status: LightMetaAccount.Status,
|
||||
parentMetaId: Long?,
|
||||
) = object : LightMetaAccount {
|
||||
override val id: Long = id
|
||||
override val globallyUniqueId: String = globallyUniqueId
|
||||
override val substratePublicKey: ByteArray? = substratePublicKey
|
||||
override val substrateCryptoType: CryptoType? = substrateCryptoType
|
||||
override val substrateAccountId: ByteArray? = substrateAccountId
|
||||
override val ethereumAddress: ByteArray? = ethereumAddress
|
||||
override val ethereumPublicKey: ByteArray? = ethereumPublicKey
|
||||
override val isSelected: Boolean = isSelected
|
||||
override val name: String = name
|
||||
override val type: LightMetaAccount.Type = type
|
||||
override val status: LightMetaAccount.Status = status
|
||||
override val parentMetaId: Long? = parentMetaId
|
||||
}
|
||||
|
||||
interface MetaAccount : LightMetaAccount {
|
||||
|
||||
// TODO this should not be exposed as its a implementation detail
|
||||
// We should rather use something like
|
||||
// fun iterateAccounts(): Iterable<(AccountId, ChainId?, MultiChainEncryption?)>
|
||||
val chainAccounts: Map<ChainId, ChainAccount>
|
||||
|
||||
class ChainAccount(
|
||||
val metaId: Long,
|
||||
val chainId: ChainId,
|
||||
val publicKey: ByteArray?,
|
||||
val accountId: ByteArray,
|
||||
// TODO this should be MultiChainEncryption
|
||||
val cryptoType: CryptoType?,
|
||||
)
|
||||
|
||||
suspend fun supportsAddingChainAccount(chain: Chain): Boolean
|
||||
|
||||
fun hasAccountIn(chain: Chain): Boolean
|
||||
|
||||
fun accountIdIn(chain: Chain): AccountId?
|
||||
|
||||
fun publicKeyIn(chain: Chain): ByteArray?
|
||||
}
|
||||
|
||||
interface SecretsMetaAccount : MetaAccount {
|
||||
|
||||
fun multiChainEncryptionIn(chain: Chain): MultiChainEncryption?
|
||||
}
|
||||
|
||||
interface ProxiedMetaAccount : MetaAccount {
|
||||
|
||||
val proxy: ProxyAccount
|
||||
}
|
||||
|
||||
interface MultisigMetaAccount : MetaAccount {
|
||||
|
||||
val signatoryMetaId: Long
|
||||
|
||||
val signatoryAccountId: AccountIdKey
|
||||
|
||||
/**
|
||||
* A **sorted** list of other signatories signatories of the account
|
||||
*/
|
||||
val otherSignatories: List<AccountIdKey>
|
||||
|
||||
val threshold: Int
|
||||
|
||||
val availability: MultisigAvailability
|
||||
}
|
||||
|
||||
sealed class MultisigAvailability {
|
||||
|
||||
class Universal(val addressScheme: AddressScheme) : MultisigAvailability()
|
||||
|
||||
class SingleChain(val chainId: ChainId) : MultisigAvailability()
|
||||
}
|
||||
|
||||
fun MetaAccount.isUniversal(): Boolean {
|
||||
return substrateAccountId != null || ethereumAddress != null
|
||||
}
|
||||
|
||||
fun MultisigAvailability.singleChainId(): ChainId? {
|
||||
return when (this) {
|
||||
is MultisigAvailability.SingleChain -> chainId
|
||||
is MultisigAvailability.Universal -> null
|
||||
}
|
||||
}
|
||||
|
||||
fun MultisigMetaAccount.isThreshold1(): Boolean {
|
||||
return threshold == 1
|
||||
}
|
||||
|
||||
fun MetaAccount.requireMultisigAccount() = this as MultisigMetaAccount
|
||||
|
||||
fun MetaAccount.hasChainAccountIn(chainId: ChainId) = chainId in chainAccounts
|
||||
|
||||
fun MetaAccount.addressIn(chain: Chain): String? {
|
||||
return accountIdIn(chain)?.let(chain::addressOf)
|
||||
}
|
||||
|
||||
fun MetaAccount.accountIdKeyIn(chain: Chain): AccountIdKey? {
|
||||
return accountIdIn(chain)?.let(::AccountIdKey)
|
||||
}
|
||||
|
||||
fun MetaAccount.mainEthereumAddress() = ethereumAddress?.toEthereumAddress()
|
||||
|
||||
fun MetaAccount.requireAddressIn(chain: Chain): String = addressIn(chain) ?: throw NoSuchElementException("No chain account found for ${chain.name} in $name")
|
||||
|
||||
val MetaAccount.defaultSubstrateAddress: String?
|
||||
get() = substrateAccountId?.toDefaultSubstrateAddress()
|
||||
|
||||
fun ByteArray.toDefaultSubstrateAddress(): String {
|
||||
return toAddress(SS58Encoder.DEFAULT_PREFIX)
|
||||
}
|
||||
|
||||
fun MetaAccount.substrateMultiChainEncryption(): MultiChainEncryption? {
|
||||
return substrateCryptoType?.let(MultiChainEncryption.Companion::substrateFrom)
|
||||
}
|
||||
|
||||
fun MetaAccount.requireAccountIdIn(chain: Chain): ByteArray {
|
||||
return requireNotNull(accountIdIn(chain))
|
||||
}
|
||||
|
||||
fun MetaAccount.requireAccountIdKeyIn(chain: Chain): AccountIdKey {
|
||||
return requireAccountIdIn(chain).intoKey()
|
||||
}
|
||||
|
||||
fun MetaAccount.multiChainEncryptionIn(chain: Chain): MultiChainEncryption? {
|
||||
return (this as? SecretsMetaAccount)?.multiChainEncryptionIn(chain)
|
||||
}
|
||||
|
||||
fun MetaAccount.cryptoTypeIn(chain: Chain): CryptoType? {
|
||||
return multiChainEncryptionIn(chain)?.toCryptoType()
|
||||
}
|
||||
|
||||
private fun MultiChainEncryption.toCryptoType(): CryptoType {
|
||||
return when (this) {
|
||||
is MultiChainEncryption.Substrate -> mapEncryptionToCryptoType(encryptionType)
|
||||
MultiChainEncryption.Ethereum -> CryptoType.ECDSA
|
||||
}
|
||||
}
|
||||
|
||||
fun MultiChainEncryption.Companion.substrateFrom(cryptoType: CryptoType): MultiChainEncryption.Substrate {
|
||||
return MultiChainEncryption.Substrate(mapCryptoTypeToEncryption(cryptoType))
|
||||
}
|
||||
|
||||
fun MetaAccount.ethereumAccountId() = ethereumPublicKey?.asEthereumPublicKey()?.toAccountId()?.value
|
||||
|
||||
fun MetaAccount.chainAccountFor(chainId: ChainId) = chainAccounts.getValue(chainId)
|
||||
|
||||
fun LightMetaAccount.Type.asPolkadotVaultVariantOrNull(): PolkadotVaultVariant? {
|
||||
return when (this) {
|
||||
LightMetaAccount.Type.PARITY_SIGNER -> PolkadotVaultVariant.PARITY_SIGNER
|
||||
LightMetaAccount.Type.POLKADOT_VAULT -> PolkadotVaultVariant.POLKADOT_VAULT
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
fun LightMetaAccount.Type.asPolkadotVaultVariantOrThrow(): PolkadotVaultVariant {
|
||||
return requireNotNull(asPolkadotVaultVariantOrNull()) {
|
||||
"Not a Polkadot Vault compatible account type"
|
||||
}
|
||||
}
|
||||
|
||||
fun LightMetaAccount.Type.requestedAccountPaysFees(): Boolean {
|
||||
return when (this) {
|
||||
LightMetaAccount.Type.SECRETS,
|
||||
LightMetaAccount.Type.WATCH_ONLY,
|
||||
LightMetaAccount.Type.PARITY_SIGNER,
|
||||
LightMetaAccount.Type.LEDGER_LEGACY,
|
||||
LightMetaAccount.Type.LEDGER,
|
||||
LightMetaAccount.Type.POLKADOT_VAULT -> true
|
||||
|
||||
LightMetaAccount.Type.PROXIED,
|
||||
LightMetaAccount.Type.MULTISIG -> false
|
||||
}
|
||||
}
|
||||
|
||||
fun LightMetaAccount.Type.isControllableWallet(): Boolean {
|
||||
return when (this) {
|
||||
LightMetaAccount.Type.SECRETS,
|
||||
LightMetaAccount.Type.PARITY_SIGNER,
|
||||
LightMetaAccount.Type.LEDGER_LEGACY,
|
||||
LightMetaAccount.Type.LEDGER,
|
||||
LightMetaAccount.Type.POLKADOT_VAULT -> true
|
||||
|
||||
LightMetaAccount.Type.WATCH_ONLY,
|
||||
LightMetaAccount.Type.PROXIED,
|
||||
LightMetaAccount.Type.MULTISIG -> false
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalContracts::class)
|
||||
fun LightMetaAccount.isProxied(): Boolean {
|
||||
contract {
|
||||
returns(true) implies (this@isProxied is ProxiedMetaAccount)
|
||||
}
|
||||
|
||||
return this is ProxiedMetaAccount
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalContracts::class)
|
||||
fun LightMetaAccount.isMultisig(): Boolean {
|
||||
contract {
|
||||
returns(true) implies (this@isMultisig is MultisigMetaAccount)
|
||||
}
|
||||
|
||||
return this is MultisigMetaAccount
|
||||
}
|
||||
|
||||
fun LightMetaAccount.asProxied(): ProxiedMetaAccount = this as ProxiedMetaAccount
|
||||
fun LightMetaAccount.asMultisig(): MultisigMetaAccount = this as MultisigMetaAccount
|
||||
|
||||
fun MultisigMetaAccount.signatoriesCount() = 1 + otherSignatories.size
|
||||
|
||||
fun MultisigMetaAccount.allSignatories() = buildSet {
|
||||
add(signatoryAccountId)
|
||||
addAll(otherSignatories.toSet())
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
package io.novafoundation.nova.feature_account_api.domain.model
|
||||
|
||||
import io.novafoundation.nova.common.utils.Precision
|
||||
import io.novafoundation.nova.feature_currency_api.domain.model.Currency
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import java.math.BigDecimal
|
||||
import java.math.BigInteger
|
||||
|
||||
class MetaAccountAssetBalance(
|
||||
val metaId: Long,
|
||||
val freeInPlanks: BigInteger,
|
||||
val reservedInPlanks: BigInteger,
|
||||
val offChainBalance: BigInteger?,
|
||||
val precision: Precision,
|
||||
val rate: BigDecimal?
|
||||
)
|
||||
|
||||
sealed interface MetaAccountListingItem {
|
||||
|
||||
val metaAccount: MetaAccount
|
||||
|
||||
val hasUpdates: Boolean
|
||||
|
||||
val totalBalance: BigDecimal
|
||||
|
||||
val currency: Currency
|
||||
|
||||
class Proxied(
|
||||
val proxyMetaAccount: MetaAccount,
|
||||
val proxyChain: Chain,
|
||||
override val totalBalance: BigDecimal,
|
||||
override val currency: Currency,
|
||||
override val metaAccount: ProxiedMetaAccount,
|
||||
override val hasUpdates: Boolean
|
||||
) : MetaAccountListingItem
|
||||
|
||||
class Multisig(
|
||||
val signatory: MetaAccount,
|
||||
val singleChain: Chain?, // null in case multisig is universal
|
||||
override val totalBalance: BigDecimal,
|
||||
override val currency: Currency,
|
||||
override val metaAccount: MultisigMetaAccount,
|
||||
override val hasUpdates: Boolean
|
||||
) : MetaAccountListingItem
|
||||
|
||||
class TotalBalance(
|
||||
override val totalBalance: BigDecimal,
|
||||
override val currency: Currency,
|
||||
override val metaAccount: MetaAccount,
|
||||
override val hasUpdates: Boolean
|
||||
) : MetaAccountListingItem
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package io.novafoundation.nova.feature_account_api.domain.model
|
||||
|
||||
enum class PolkadotVaultVariant {
|
||||
|
||||
POLKADOT_VAULT, PARITY_SIGNER
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package io.novafoundation.nova.feature_account_api.domain.model
|
||||
|
||||
import io.novafoundation.nova.core.model.CryptoType
|
||||
|
||||
data class PreferredCryptoType(
|
||||
val cryptoType: CryptoType,
|
||||
val frozen: Boolean
|
||||
)
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package io.novafoundation.nova.feature_account_api.domain.model
|
||||
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
|
||||
sealed interface AccountDelegation {
|
||||
|
||||
val delegator: MetaAccount
|
||||
|
||||
class Proxy(
|
||||
val proxied: ProxiedMetaAccount,
|
||||
val proxy: MetaAccount,
|
||||
val chain: Chain
|
||||
) : AccountDelegation {
|
||||
|
||||
override val delegator = proxied
|
||||
}
|
||||
|
||||
class Multisig(
|
||||
val metaAccount: MultisigMetaAccount,
|
||||
val signatory: MetaAccount,
|
||||
val singleChain: Chain?, // null in case multisig is universal
|
||||
) : AccountDelegation {
|
||||
|
||||
override val delegator = metaAccount
|
||||
}
|
||||
}
|
||||
|
||||
fun AccountDelegation.getChainOrNull(): Chain? {
|
||||
return when (this) {
|
||||
is AccountDelegation.Multisig -> singleChain
|
||||
is AccountDelegation.Proxy -> chain
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package io.novafoundation.nova.feature_account_api.domain.model
|
||||
|
||||
import io.novafoundation.nova.feature_proxy_api.domain.model.ProxyType
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
|
||||
class ProxyAccount(
|
||||
val proxyMetaId: Long,
|
||||
val chainId: ChainId,
|
||||
val proxyType: ProxyType,
|
||||
)
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package io.novafoundation.nova.feature_account_api.domain.model
|
||||
|
||||
class SavedMultisigOperationCall(
|
||||
val metaId: Long,
|
||||
val chainId: String,
|
||||
val callHash: ByteArray,
|
||||
val callInstance: String
|
||||
)
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package io.novafoundation.nova.feature_account_api.domain.multisig
|
||||
|
||||
import io.novafoundation.nova.common.address.AccountIdKey
|
||||
import io.novafoundation.nova.common.address.intoKey
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.bindAccountIdKey
|
||||
import io.novasama.substrate_sdk_android.extensions.fromHex
|
||||
|
||||
// TODO multisig: we are using `AccountIdKey` as it logically represents the `DataByteArray`
|
||||
// We need to create DataByteArray class that AccountIdKey will typealias to
|
||||
typealias CallHash = AccountIdKey
|
||||
|
||||
fun String.intoCallHash() = fromHex().intoCallHash()
|
||||
|
||||
fun ByteArray.intoCallHash() = intoKey()
|
||||
|
||||
fun bindCallHash(decoded: Any?) = bindAccountIdKey(decoded)
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package io.novafoundation.nova.feature_account_api.domain.updaters
|
||||
|
||||
import io.novafoundation.nova.core.updater.UpdateScope
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class AccountUpdateScope(
|
||||
private val accountRepository: AccountRepository
|
||||
) : UpdateScope<MetaAccount> {
|
||||
|
||||
override fun invalidationFlow(): Flow<MetaAccount> {
|
||||
return accountRepository.selectedMetaAccountFlow()
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package io.novafoundation.nova.feature_account_api.domain.updaters
|
||||
|
||||
import io.novafoundation.nova.core.updater.UpdateScope
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class ChainUpdateScope(
|
||||
private val chainFlow: Flow<Chain>
|
||||
) : UpdateScope<Chain> {
|
||||
|
||||
override fun invalidationFlow(): Flow<Chain> {
|
||||
return chainFlow
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user