feat: Bitcoin key derivation, secrets storage, and chain-family plumbing

Derives a BIP84 (native SegWit) keypair alongside the existing substrate/
ethereum/tron ones, stores it in MetaAccountSecrets/CloudBackup, persists
bitcoinPublicKey/bitcoinAddress on meta_accounts (DB migration 74->75), and
threads isBitcoinBased through Chain/ChainLocal/mappers/ChainExt/ChainRegistry
the same way isTronBased was wired. Adds BitcoinAddressBackfillMigration,
mirroring TronAddressBackfillMigration, so existing wallets get a Bitcoin
address without re-import.
This commit is contained in:
2026-07-12 14:47:07 -07:00
parent 2a0ccffbe4
commit 36b5a17644
29 changed files with 365 additions and 29 deletions
@@ -7,6 +7,8 @@ import io.novafoundation.nova.common.data.secrets.v2.KeyPairSchema
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.derivationPath
import io.novafoundation.nova.common.data.secrets.v2.bitcoinDerivationPath
import io.novafoundation.nova.common.data.secrets.v2.bitcoinKeypair
import io.novafoundation.nova.common.data.secrets.v2.entropy
import io.novafoundation.nova.common.data.secrets.v2.ethereumDerivationPath
import io.novafoundation.nova.common.data.secrets.v2.ethereumKeypair
@@ -90,6 +92,7 @@ class RealLocalAccountsCloudBackupFacade(
substrate = baseSecrets.getSubstrateBackupSecrets(),
ethereum = baseSecrets.getEthereumBackupSecrets(),
chainAccounts = emptyList(),
bitcoin = baseSecrets.getBitcoinBackupSecrets(),
)
return CloudBackup(
@@ -357,6 +360,7 @@ class RealLocalAccountsCloudBackupFacade(
substrate = prepareSubstrateBackupSecrets(baseSecrets, joinedMetaAccountInfo),
ethereum = baseSecrets.getEthereumBackupSecrets(),
chainAccounts = chainAccountsFromChainSecrets + chainAccountFromAdditionalSecrets,
bitcoin = baseSecrets.getBitcoinBackupSecrets(),
)
}
@@ -466,7 +470,9 @@ class RealLocalAccountsCloudBackupFacade(
substrateKeyPair = substrate?.keypair?.toLocalKeyPair() ?: return null,
substrateDerivationPath = substrate?.derivationPath,
ethereumKeypair = ethereum?.keypair?.toLocalKeyPair(),
ethereumDerivationPath = ethereum?.derivationPath
ethereumDerivationPath = ethereum?.derivationPath,
bitcoinKeypair = bitcoin?.keypair?.toLocalKeyPair(),
bitcoinDerivationPath = bitcoin?.derivationPath
)
}
@@ -479,6 +485,15 @@ class RealLocalAccountsCloudBackupFacade(
)
}
private fun EncodableStruct<MetaAccountSecrets>?.getBitcoinBackupSecrets(): CloudBackup.WalletPrivateInfo.BitcoinSecrets? {
if (this == null) return null
return CloudBackup.WalletPrivateInfo.BitcoinSecrets(
keypair = bitcoinKeypair?.toBackupKeypairSecrets() ?: return null,
derivationPath = bitcoinDerivationPath
)
}
private fun EncodableStruct<MetaAccountSecrets>?.getSubstrateBackupSecrets(): CloudBackup.WalletPrivateInfo.SubstrateSecrets? {
if (this == null) return null
@@ -520,7 +535,9 @@ class RealLocalAccountsCloudBackupFacade(
ethereumPublicKey = metaAccount.ethereumPublicKey,
name = metaAccount.name,
type = metaAccount.type.toBackupWalletType() ?: return null,
chainAccounts = chainAccounts.mapToSet { chainAccount -> chainAccount.toBackupPublicChainAccount(chainsById) }
chainAccounts = chainAccounts.mapToSet { chainAccount -> chainAccount.toBackupPublicChainAccount(chainsById) },
bitcoinAddress = metaAccount.bitcoinAddress,
bitcoinPublicKey = metaAccount.bitcoinPublicKey
)
}
@@ -542,7 +559,9 @@ class RealLocalAccountsCloudBackupFacade(
isSelected = isSelected,
position = accountPosition,
status = MetaAccountLocal.Status.ACTIVE,
typeExtras = null
typeExtras = null,
bitcoinAddress = bitcoinAddress,
bitcoinPublicKey = bitcoinPublicKey
).also {
if (localIdOverwrite != null) {
it.id = localIdOverwrite
@@ -65,6 +65,8 @@ class AccountMappers(
ethereumPublicKey = ethereumPublicKey,
tronAddress = tronAddress,
tronPublicKey = tronPublicKey,
bitcoinAddress = bitcoinAddress,
bitcoinPublicKey = bitcoinPublicKey,
isSelected = isSelected,
name = name,
status = mapMetaAccountStateFromLocal(status),
@@ -82,6 +84,8 @@ class AccountMappers(
ethereumPublicKey = ethereumPublicKey,
tronAddress = tronAddress,
tronPublicKey = tronPublicKey,
bitcoinAddress = bitcoinAddress,
bitcoinPublicKey = bitcoinPublicKey,
isSelected = isSelected,
name = name,
type = type,
@@ -101,6 +105,8 @@ class AccountMappers(
ethereumPublicKey = ethereumPublicKey,
tronAddress = tronAddress,
tronPublicKey = tronPublicKey,
bitcoinAddress = bitcoinAddress,
bitcoinPublicKey = bitcoinPublicKey,
isSelected = isSelected,
name = name,
type = type,
@@ -119,6 +125,8 @@ class AccountMappers(
ethereumPublicKey = ethereumPublicKey,
tronAddress = tronAddress,
tronPublicKey = tronPublicKey,
bitcoinAddress = bitcoinAddress,
bitcoinPublicKey = bitcoinPublicKey,
isSelected = isSelected,
name = name,
type = type,
@@ -138,6 +146,8 @@ class AccountMappers(
ethereumPublicKey = ethereumPublicKey,
tronAddress = tronAddress,
tronPublicKey = tronPublicKey,
bitcoinAddress = bitcoinAddress,
bitcoinPublicKey = bitcoinPublicKey,
isSelected = isSelected,
name = name,
type = type,
@@ -165,6 +175,8 @@ class AccountMappers(
ethereumPublicKey = ethereumPublicKey,
tronAddress = tronAddress,
tronPublicKey = tronPublicKey,
bitcoinAddress = bitcoinAddress,
bitcoinPublicKey = bitcoinPublicKey,
isSelected = isSelected,
name = name,
status = mapMetaAccountStateFromLocal(status),
@@ -183,6 +195,8 @@ class AccountMappers(
ethereumPublicKey = ethereumPublicKey,
tronAddress = tronAddress,
tronPublicKey = tronPublicKey,
bitcoinAddress = bitcoinAddress,
bitcoinPublicKey = bitcoinPublicKey,
chainAccounts = chainAccounts,
isSelected = isSelected,
name = name,
@@ -30,6 +30,7 @@ import io.novafoundation.nova.feature_account_impl.data.mappers.AccountMappers
import io.novafoundation.nova.feature_account_impl.data.mappers.mapMetaAccountTypeToLocal
import io.novafoundation.nova.feature_account_impl.data.mappers.mapMetaAccountWithBalanceFromLocal
import io.novafoundation.nova.feature_account_impl.data.repository.datasource.migration.AccountDataMigration
import io.novafoundation.nova.feature_account_impl.data.repository.datasource.migration.BitcoinAddressBackfillMigration
import io.novafoundation.nova.feature_account_impl.data.repository.datasource.migration.model.ChainAccountInsertionData
import io.novafoundation.nova.feature_account_impl.data.repository.datasource.migration.model.MetaAccountInsertionData
import io.novafoundation.nova.runtime.ext.accountIdOf
@@ -64,10 +65,12 @@ class AccountDataSourceImpl(
private val secretsMetaAccountLocalFactory: SecretsMetaAccountLocalFactory,
secretStoreV1: SecretStoreV1,
accountDataMigration: AccountDataMigration,
private val bitcoinAddressBackfillMigration: BitcoinAddressBackfillMigration,
) : AccountDataSource, SecretStoreV1 by secretStoreV1 {
init {
migrateIfNeeded(accountDataMigration)
async { bitcoinAddressBackfillMigration.migrate() }
}
private fun migrateIfNeeded(migration: AccountDataMigration) = async {
@@ -2,6 +2,7 @@ package io.novafoundation.nova.feature_account_impl.data.repository.datasource
import io.novafoundation.nova.common.data.secrets.v2.KeyPairSchema
import io.novafoundation.nova.common.data.secrets.v2.MetaAccountSecrets
import io.novafoundation.nova.common.utils.bitcoinPublicKeyToAccountId
import io.novafoundation.nova.common.utils.substrateAccountId
import io.novafoundation.nova.common.utils.tronPublicKeyToAccountId
import io.novafoundation.nova.core.model.CryptoType
@@ -31,6 +32,7 @@ class RealSecretsMetaAccountLocalFactory : SecretsMetaAccountLocalFactory {
val substratePublicKey = secrets[MetaAccountSecrets.SubstrateKeypair][KeyPairSchema.PublicKey]
val ethereumPublicKey = secrets[MetaAccountSecrets.EthereumKeypair]?.get(KeyPairSchema.PublicKey)
val tronPublicKey = secrets[MetaAccountSecrets.TronKeypair]?.get(KeyPairSchema.PublicKey)
val bitcoinPublicKey = secrets[MetaAccountSecrets.BitcoinKeypair]?.get(KeyPairSchema.PublicKey)
return MetaAccountLocal(
substratePublicKey = substratePublicKey,
@@ -47,7 +49,13 @@ class RealSecretsMetaAccountLocalFactory : SecretsMetaAccountLocalFactory {
globallyUniqueId = MetaAccountLocal.generateGloballyUniqueId(),
typeExtras = null,
tronPublicKey = tronPublicKey,
tronAddress = tronPublicKey?.tronPublicKeyToAccountId()
tronAddress = tronPublicKey?.tronPublicKeyToAccountId(),
bitcoinPublicKey = bitcoinPublicKey,
// Bip32EcdsaKeypairFactory already yields a compressed (33-byte) public key (confirmed against
// substrate-sdk-android's own source: ECDSAUtils.derivePublicKey -> compressedPublicKeyFromPrivate),
// which is exactly the format both BIP143 (P2WPKH) and bitcoinPublicKeyToAccountId() require - no
// extra compression/decompression step needed here, unlike Ethereum's uncompressed-key derivation.
bitcoinAddress = bitcoinPublicKey?.bitcoinPublicKeyToAccountId()
)
}
}
@@ -0,0 +1,119 @@
package io.novafoundation.nova.feature_account_impl.data.repository.datasource.migration
import android.util.Log
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.bitcoinDerivationPath
import io.novafoundation.nova.common.data.secrets.v2.bitcoinKeypair
import io.novafoundation.nova.common.data.secrets.v2.entropy
import io.novafoundation.nova.common.data.secrets.v2.ethereumDerivationPath
import io.novafoundation.nova.common.data.secrets.v2.ethereumKeypair
import io.novafoundation.nova.common.data.secrets.v2.mapKeypairStructToKeypair
import io.novafoundation.nova.common.data.secrets.v2.seed
import io.novafoundation.nova.common.data.secrets.v2.substrateDerivationPath
import io.novafoundation.nova.common.data.secrets.v2.substrateKeypair
import io.novafoundation.nova.common.data.secrets.v2.tronDerivationPath
import io.novafoundation.nova.common.data.secrets.v2.tronKeypair
import io.novafoundation.nova.common.utils.bitcoinPublicKeyToAccountId
import io.novafoundation.nova.core_db.dao.MetaAccountDao
import io.novafoundation.nova.core_db.dao.updateMetaAccount
import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal
import io.novafoundation.nova.feature_account_impl.data.secrets.AccountSecretsFactory
import io.novafoundation.nova.feature_account_impl.data.secrets.BITCOIN_DEFAULT_DERIVATION_PATH
import io.novasama.substrate_sdk_android.encrypt.mnemonic.MnemonicCreator
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
private const val TAG = "BitcoinAddressBackfill"
/**
* Idempotent, per-account backfill for accounts that don't yet have a Bitcoin keypair - mirrors
* [TronAddressBackfillMigration]'s exact design (see that class for the full rationale): any account created
* before Bitcoin support existed has no `MetaAccountSecrets.BitcoinKeypair`/`meta_accounts.bitcoinAddress` yet,
* and this fills it in from the account's own mnemonic without requiring re-import.
*
* Deliberately has NO "have I already run once" flag, for the same reason as the Tron migration: a one-shot
* flag that gets set even when backfill legitimately still didn't produce a key (e.g. a since-fixed bug kept
* re-losing it) would permanently strand that account. This is cheap to call for an account that doesn't need
* it, so it just runs unconditionally on every app start.
*
* Only touches accounts that are `Type.SECRETS` (mnemonic-derived) and still hold their `Entropy` in
* [SecretStoreV2] - same restriction as the Tron migration, for the same reason (watch-only/Ledger/Json/
* multisig/proxied accounts and raw-seed imports never had a Bitcoin-capable mnemonic to derive from).
*/
class BitcoinAddressBackfillMigration(
private val secretStoreV2: SecretStoreV2,
private val metaAccountDao: MetaAccountDao,
private val accountSecretsFactory: AccountSecretsFactory,
) {
suspend fun migrate() = withContext(Dispatchers.Default) {
val secretsAccounts = metaAccountDao.getMetaAccounts().filter { it.type == MetaAccountLocal.Type.SECRETS }
Log.d(TAG, "migrate() starting - ${secretsAccounts.size} SECRETS-type account(s): ${secretsAccounts.map { it.id }}")
secretsAccounts.forEach { account ->
try {
backfillIfNeeded(account)
} catch (e: Throwable) {
Log.e(TAG, "backfill failed for metaId=${account.id}, continuing with remaining accounts", e)
}
}
Log.d(TAG, "migrate() done")
}
private suspend fun backfillIfNeeded(account: MetaAccountLocal) {
val secrets = secretStoreV2.getMetaAccountSecrets(account.id)
if (secrets == null) {
Log.d(TAG, "metaId=${account.id}: no stored secrets at all (watch-only/Ledger/etc) - skipping")
return
}
val entropy = secrets.entropy
if (entropy == null) {
Log.d(TAG, "metaId=${account.id}: no entropy (raw-seed import, not a mnemonic) - skipping")
return
}
if (secrets.bitcoinKeypair != null) {
Log.d(TAG, "metaId=${account.id}: already has a BitcoinKeypair - skipping")
return
}
val substrateCryptoType = account.substrateCryptoType
if (substrateCryptoType == null) {
Log.d(TAG, "metaId=${account.id}: substrateCryptoType is null - skipping")
return
}
Log.d(TAG, "metaId=${account.id}: deriving Bitcoin keypair")
val mnemonic = MnemonicCreator.fromEntropy(entropy).words
val bitcoinChainSecrets = accountSecretsFactory.chainAccountSecrets(
derivationPath = BITCOIN_DEFAULT_DERIVATION_PATH,
accountSource = AccountSecretsFactory.AccountSource.Mnemonic(substrateCryptoType, mnemonic),
isEthereum = true
).secrets
val bitcoinKeypair = mapKeypairStructToKeypair(bitcoinChainSecrets[ChainAccountSecrets.Keypair])
val updatedSecrets = MetaAccountSecrets(
substrateKeyPair = mapKeypairStructToKeypair(secrets.substrateKeypair),
entropy = secrets.entropy,
substrateSeed = secrets.seed,
substrateDerivationPath = secrets.substrateDerivationPath,
ethereumKeypair = secrets.ethereumKeypair?.let(::mapKeypairStructToKeypair),
ethereumDerivationPath = secrets.ethereumDerivationPath,
tronKeypair = secrets.tronKeypair?.let(::mapKeypairStructToKeypair),
tronDerivationPath = secrets.tronDerivationPath,
bitcoinKeypair = bitcoinKeypair,
bitcoinDerivationPath = BITCOIN_DEFAULT_DERIVATION_PATH
)
secretStoreV2.putMetaAccountSecrets(account.id, updatedSecrets)
val bitcoinAccountId = bitcoinKeypair.publicKey.bitcoinPublicKeyToAccountId()
metaAccountDao.updateMetaAccount(account.id) { it.addBitcoinAccount(bitcoinKeypair.publicKey, bitcoinAccountId) }
Log.d(TAG, "metaId=${account.id}: backfilled successfully, bitcoinAddress set (${bitcoinAccountId.size} bytes)")
}
}
@@ -34,6 +34,14 @@ import kotlinx.coroutines.withContext
*/
const val TRON_DEFAULT_DERIVATION_PATH = "//44//195//0/0/0"
/**
* BIP84 purpose (native SegWit), SLIP-44 coin type 0 (Bitcoin). Deliberately `//84//...`, not `//44//...` -
* this app only supports native SegWit (bech32 `bc1q...`) addresses, not legacy/P2SH-SegWit, so the derivation
* path signals that choice the same way real Bitcoin wallets do. Not user-configurable yet - always derived at
* this fixed path (single address, no HD address-index rotation).
*/
const val BITCOIN_DEFAULT_DERIVATION_PATH = "//84//0//0/0/0"
class AccountSecretsFactory(
private val JsonDecoder: JsonDecoder
) {
@@ -131,6 +139,7 @@ class AccountSecretsFactory(
ethereumDerivationPath: String?,
accountSource: AccountSource,
tronDerivationPath: String? = TRON_DEFAULT_DERIVATION_PATH,
bitcoinDerivationPath: String? = BITCOIN_DEFAULT_DERIVATION_PATH,
): Result<MetaAccountSecrets> = withContext(Dispatchers.Default) {
val (substrateSecrets, substrateCryptoType) = chainAccountSecrets(
derivationPath = substrateDerivationPath,
@@ -157,6 +166,16 @@ class AccountSecretsFactory(
Bip32EcdsaKeypairFactory.generate(seed = seed, junctions = decodedTronDerivationPath?.junctions.orEmpty())
}
// Bitcoin (native SegWit) also reuses the exact same secp256k1/BIP32 keypair generation as Ethereum/Tron -
// only the SLIP-44 coin type (0) and BIP84 purpose differ. See BITCOIN_DEFAULT_DERIVATION_PATH's doc.
val bitcoinKeypair = accountSource.castOrNull<AccountSource.Mnemonic>()?.let {
val decodedBitcoinDerivationPath = decodeDerivationPath(bitcoinDerivationPath, ethereum = true)
val seed = deriveSeed(it.mnemonic, password = decodedBitcoinDerivationPath?.password, ethereum = true).seed
Bip32EcdsaKeypairFactory.generate(seed = seed, junctions = decodedBitcoinDerivationPath?.junctions.orEmpty())
}
val secrets = MetaAccountSecrets(
entropy = substrateSecrets[ChainAccountSecrets.Entropy],
substrateSeed = substrateSecrets[ChainAccountSecrets.Seed],
@@ -165,7 +184,9 @@ class AccountSecretsFactory(
ethereumKeypair = ethereumKeypair,
ethereumDerivationPath = ethereumDerivationPath,
tronKeypair = tronKeypair,
tronDerivationPath = tronDerivationPath
tronDerivationPath = tronDerivationPath,
bitcoinKeypair = bitcoinKeypair,
bitcoinDerivationPath = bitcoinDerivationPath,
)
Result(secrets = secrets, cryptoType = substrateCryptoType)
@@ -104,6 +104,7 @@ import io.novafoundation.nova.feature_account_impl.data.repository.datasource.Ac
import io.novafoundation.nova.feature_account_impl.data.repository.datasource.RealSecretsMetaAccountLocalFactory
import io.novafoundation.nova.feature_account_impl.data.repository.datasource.SecretsMetaAccountLocalFactory
import io.novafoundation.nova.feature_account_impl.data.repository.datasource.migration.AccountDataMigration
import io.novafoundation.nova.feature_account_impl.data.repository.datasource.migration.BitcoinAddressBackfillMigration
import io.novafoundation.nova.feature_account_impl.data.secrets.AccountSecretsFactory
import io.novafoundation.nova.feature_account_impl.data.signer.signingContext.SigningContextFactory
import io.novafoundation.nova.feature_account_impl.di.AccountFeatureModule.BindsModule
@@ -406,6 +407,7 @@ class AccountFeatureModule {
secretsMetaAccountLocalFactory: SecretsMetaAccountLocalFactory,
secretStoreV2: SecretStoreV2,
accountMappers: AccountMappers,
bitcoinAddressBackfillMigration: BitcoinAddressBackfillMigration,
): AccountDataSource {
return AccountDataSourceImpl(
preferences,
@@ -416,10 +418,21 @@ class AccountFeatureModule {
secretStoreV2,
secretsMetaAccountLocalFactory,
secretStoreV1,
accountDataMigration
accountDataMigration,
bitcoinAddressBackfillMigration
)
}
@Provides
@FeatureScope
fun provideBitcoinAddressBackfillMigration(
secretStoreV2: SecretStoreV2,
metaAccountDao: MetaAccountDao,
accountSecretsFactory: AccountSecretsFactory,
): BitcoinAddressBackfillMigration {
return BitcoinAddressBackfillMigration(secretStoreV2, metaAccountDao, accountSecretsFactory)
}
@Provides
fun provideNodeHostValidator() = NodeHostValidator()
@@ -24,6 +24,8 @@ open class DefaultMetaAccount(
override val parentMetaId: Long?,
override val tronAddress: ByteArray? = null,
override val tronPublicKey: ByteArray? = null,
override val bitcoinAddress: ByteArray? = null,
override val bitcoinPublicKey: ByteArray? = null,
) : MetaAccount {
override suspend fun supportsAddingChainAccount(chain: Chain): Boolean {
@@ -34,6 +36,7 @@ open class DefaultMetaAccount(
return when {
hasChainAccountIn(chain.id) -> true
chain.isTronBased -> tronAddress != null
chain.isBitcoinBased -> bitcoinAddress != null
chain.isEthereumBased -> ethereumAddress != null
else -> substrateAccountId != null
}
@@ -43,6 +46,7 @@ open class DefaultMetaAccount(
return when {
hasChainAccountIn(chain.id) -> chainAccounts.getValue(chain.id).accountId
chain.isTronBased -> tronAddress
chain.isBitcoinBased -> bitcoinAddress
chain.isEthereumBased -> ethereumAddress
else -> substrateAccountId
}
@@ -52,6 +56,7 @@ open class DefaultMetaAccount(
return when {
hasChainAccountIn(chain.id) -> chainAccounts.getValue(chain.id).publicKey
chain.isTronBased -> tronPublicKey
chain.isBitcoinBased -> bitcoinPublicKey
chain.isEthereumBased -> ethereumPublicKey
else -> substratePublicKey
}
@@ -24,6 +24,8 @@ class GenericLedgerMetaAccount(
private val supportedGenericLedgerChains: Set<ChainId>,
tronAddress: ByteArray? = null,
tronPublicKey: ByteArray? = null,
bitcoinAddress: ByteArray? = null,
bitcoinPublicKey: ByteArray? = null,
) : DefaultMetaAccount(
id = id,
globallyUniqueId = globallyUniqueId,
@@ -39,7 +41,9 @@ class GenericLedgerMetaAccount(
chainAccounts = chainAccounts,
parentMetaId = parentMetaId,
tronAddress = tronAddress,
tronPublicKey = tronPublicKey
tronPublicKey = tronPublicKey,
bitcoinAddress = bitcoinAddress,
bitcoinPublicKey = bitcoinPublicKey
) {
override suspend fun supportsAddingChainAccount(chain: Chain): Boolean {
@@ -24,6 +24,8 @@ class LegacyLedgerMetaAccount(
parentMetaId: Long?,
tronAddress: ByteArray? = null,
tronPublicKey: ByteArray? = null,
bitcoinAddress: ByteArray? = null,
bitcoinPublicKey: ByteArray? = null,
) : DefaultMetaAccount(
id = id,
globallyUniqueId = globallyUniqueId,
@@ -39,7 +41,9 @@ class LegacyLedgerMetaAccount(
chainAccounts = chainAccounts,
parentMetaId = parentMetaId,
tronAddress = tronAddress,
tronPublicKey = tronPublicKey
tronPublicKey = tronPublicKey,
bitcoinAddress = bitcoinAddress,
bitcoinPublicKey = bitcoinPublicKey
) {
override suspend fun supportsAddingChainAccount(chain: Chain): Boolean {
@@ -30,6 +30,8 @@ class RealMultisigMetaAccount(
parentMetaId: Long?,
tronAddress: ByteArray? = null,
tronPublicKey: ByteArray? = null,
bitcoinAddress: ByteArray? = null,
bitcoinPublicKey: ByteArray? = null,
) : DefaultMetaAccount(
id = id,
globallyUniqueId = globallyUniqueId,
@@ -45,7 +47,9 @@ class RealMultisigMetaAccount(
chainAccounts = chainAccounts,
parentMetaId = parentMetaId,
tronAddress = tronAddress,
tronPublicKey = tronPublicKey
tronPublicKey = tronPublicKey,
bitcoinAddress = bitcoinAddress,
bitcoinPublicKey = bitcoinPublicKey
),
MultisigMetaAccount {
@@ -41,6 +41,6 @@ class PolkadotVaultMetaAccount(
) {
override suspend fun supportsAddingChainAccount(chain: Chain): Boolean {
return !chain.isEthereumBased && !chain.isTronBased
return !chain.isEthereumBased && !chain.isTronBased && !chain.isBitcoinBased
}
}
@@ -24,6 +24,8 @@ internal class RealProxiedMetaAccount(
parentMetaId: Long?,
tronAddress: ByteArray? = null,
tronPublicKey: ByteArray? = null,
bitcoinAddress: ByteArray? = null,
bitcoinPublicKey: ByteArray? = null,
) : DefaultMetaAccount(
id = id,
globallyUniqueId = globallyUniqueId,
@@ -39,7 +41,9 @@ internal class RealProxiedMetaAccount(
chainAccounts = chainAccounts,
parentMetaId = parentMetaId,
tronAddress = tronAddress,
tronPublicKey = tronPublicKey
tronPublicKey = tronPublicKey,
bitcoinAddress = bitcoinAddress,
bitcoinPublicKey = bitcoinPublicKey
),
ProxiedMetaAccount {
@@ -25,6 +25,8 @@ class RealSecretsMetaAccount(
parentMetaId: Long?,
tronAddress: ByteArray? = null,
tronPublicKey: ByteArray? = null,
bitcoinAddress: ByteArray? = null,
bitcoinPublicKey: ByteArray? = null,
) : DefaultMetaAccount(
id = id,
globallyUniqueId = globallyUniqueId,
@@ -40,7 +42,9 @@ class RealSecretsMetaAccount(
chainAccounts = chainAccounts,
parentMetaId = parentMetaId,
tronAddress = tronAddress,
tronPublicKey = tronPublicKey
tronPublicKey = tronPublicKey,
bitcoinAddress = bitcoinAddress,
bitcoinPublicKey = bitcoinPublicKey
),
SecretsMetaAccount {