mirror of
https://github.com/pezkuwichain/pezkuwi-wallet-android.git
synced 2026-07-22 13:45:48 +00:00
fix: backfill Tron address for pre-existing wallets - TRX was invisible for every account created before Tron support shipped
Found via manual device testing against a real, pre-Tron production wallet: TRX/USDT-TRC20 didn't appear anywhere in the Assets list, not even as a zero balance - unlike every other configured chain (KSM, USDC, ETH, BNB, AVAX, LINK all show up at 0). Root cause: MetaAccount.hasAccountIn() gates Tron balance sync on tronAddress != null, but tronAddress is only ever populated once, at fresh-mnemonic-creation time in AccountSecretsFactory.metaAccountSecrets(). The migration that added the tronPublicKey/tronAddress columns (73_74_AddTronSupport) is pure ALTER TABLE like every other migration in this codebase - it never derived a value for pre-existing rows. Net effect: BalancesUpdateSystem silently skips Tron entirely for every wallet that existed before this feature shipped, with zero error surfaced anywhere. No automated test catches this because every automated test creates a fresh (post-Tron) account - this is exactly the class of bug that only manual testing against a real, aged wallet can find. Adds TronAddressBackfillMigration: a one-time, flag-gated pass (mirroring the existing AccountDataMigration idiom) over SECRETS-type accounts that still hold their mnemonic entropy in SecretStoreV2 but have no TronKeypair yet. Derives the Tron keypair via AccountSecretsFactory.chainAccountSecrets (isEthereum=true, same BIP32/secp256k1 path Tron already uses everywhere else) at TRON_DEFAULT_DERIVATION_PATH - the same call fresh-account creation already makes - so a backfilled wallet ends up with byte-for-byte the same Tron address it would have gotten had it been created today, not a separately-reimplemented derivation. Watch-only/Ledger/Json/multisig/ proxied accounts (never had a Tron-capable mnemonic) and raw-seed imports (no entropy) are correctly left untouched, same as they already are for Ethereum. Includes a dedicated unit test asserting the backfill derives the exact same reference Tron address (TUEZSdKsoDHQMeZwihtdoBiN46zxhGWYdH) that TronDerivationTest already cross-validated against live TronGrid data for the standard BIP39 test mnemonic - this touches real wallets' key material, so it's pinned to a known-good vector rather than just asserting against its own output.
This commit is contained in:
+13
-5
@@ -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.TronAddressBackfillMigration
|
||||
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,15 +65,22 @@ class AccountDataSourceImpl(
|
||||
private val secretsMetaAccountLocalFactory: SecretsMetaAccountLocalFactory,
|
||||
secretStoreV1: SecretStoreV1,
|
||||
accountDataMigration: AccountDataMigration,
|
||||
tronAddressBackfillMigration: TronAddressBackfillMigration,
|
||||
) : AccountDataSource, SecretStoreV1 by secretStoreV1 {
|
||||
|
||||
init {
|
||||
migrateIfNeeded(accountDataMigration)
|
||||
}
|
||||
// Run sequentially in one coroutine, not as two independent migrateIfNeeded() launches - the Tron
|
||||
// backfill reads accounts/secrets that the legacy migration may still be in the middle of writing for
|
||||
// very old (pre-MetaAccount) installs, and two separate GlobalScope.launch calls give no ordering
|
||||
// guarantee relative to each other.
|
||||
async {
|
||||
if (accountDataMigration.migrationNeeded()) {
|
||||
accountDataMigration.migrate(::saveSecuritySource)
|
||||
}
|
||||
|
||||
private fun migrateIfNeeded(migration: AccountDataMigration) = async {
|
||||
if (migration.migrationNeeded()) {
|
||||
migration.migrate(::saveSecuritySource)
|
||||
if (tronAddressBackfillMigration.migrationNeeded()) {
|
||||
tronAddressBackfillMigration.migrate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
package io.novafoundation.nova.feature_account_impl.data.repository.datasource.migration
|
||||
|
||||
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.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.tronKeypair
|
||||
import io.novafoundation.nova.common.data.storage.Preferences
|
||||
import io.novafoundation.nova.common.utils.tronPublicKeyToAccountId
|
||||
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.TRON_DEFAULT_DERIVATION_PATH
|
||||
import io.novasama.substrate_sdk_android.encrypt.mnemonic.MnemonicCreator
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
private const val PREFS_TRON_ADDRESS_BACKFILL_DONE = "tron_address_backfill_1_1_3"
|
||||
|
||||
/**
|
||||
* One-time backfill for accounts created before Tron support existed. `73_74_AddTronSupport` (the migration
|
||||
* that added `meta_accounts.tronPublicKey`/`tronAddress`) is, like every other migration in this codebase, pure
|
||||
* `ALTER TABLE` - it never derives a value for pre-existing rows. `tronAddress` is otherwise only ever set once,
|
||||
* at fresh-mnemonic-creation time in [io.novafoundation.nova.feature_account_impl.data.secrets.AccountSecretsFactory.metaAccountSecrets],
|
||||
* so without this backfill `MetaAccount.hasAccountIn(tronChain)` (`tronAddress != null`) permanently returns
|
||||
* false for every pre-existing seed-derived wallet, which makes `BalancesUpdateSystem` skip Tron entirely for
|
||||
* that account - no address, no balance, no send, with no error surfaced anywhere. Found via manual testing
|
||||
* against a real pre-Tron production wallet; no automated test catches this because every automated test
|
||||
* creates a fresh (post-Tron) account.
|
||||
*
|
||||
* Only touches accounts that are:
|
||||
* - `Type.SECRETS` (mnemonic-derived) - watch-only/Ledger/Json/multisig/proxied accounts never had a
|
||||
* Tron-capable mnemonic and are correctly left with `tronAddress == null` forever, same as they already are
|
||||
* for Ethereum.
|
||||
* - still holding their `Entropy` in [SecretStoreV2] - an account imported from a raw seed/keypair rather than
|
||||
* a mnemonic has no entropy either and is likewise correctly left alone.
|
||||
* - missing a `TronKeypair` - i.e. not already backfilled and not created after Tron support shipped.
|
||||
*
|
||||
* The Tron keypair is derived via [AccountSecretsFactory.chainAccountSecrets] with `isEthereum = true` (Tron
|
||||
* reuses the exact same secp256k1/BIP32 derivation as Ethereum, just under its own SLIP-44 coin-type-195 path -
|
||||
* see that class's own doc comment) at [TRON_DEFAULT_DERIVATION_PATH], the same call this codebase's own
|
||||
* `metaAccountSecrets()` makes for a fresh account - so a backfilled account ends up with byte-for-byte the
|
||||
* same Tron address it would have gotten had it been created today, not a separately-reimplemented derivation.
|
||||
*/
|
||||
class TronAddressBackfillMigration(
|
||||
private val preferences: Preferences,
|
||||
private val secretStoreV2: SecretStoreV2,
|
||||
private val metaAccountDao: MetaAccountDao,
|
||||
private val accountSecretsFactory: AccountSecretsFactory,
|
||||
) {
|
||||
|
||||
suspend fun migrationNeeded(): Boolean = withContext(Dispatchers.Default) {
|
||||
!preferences.getBoolean(PREFS_TRON_ADDRESS_BACKFILL_DONE, false)
|
||||
}
|
||||
|
||||
suspend fun migrate() = withContext(Dispatchers.Default) {
|
||||
val secretsAccounts = metaAccountDao.getMetaAccounts().filter { it.type == MetaAccountLocal.Type.SECRETS }
|
||||
|
||||
secretsAccounts.forEach { account ->
|
||||
backfillIfNeeded(account)
|
||||
}
|
||||
|
||||
preferences.putBoolean(PREFS_TRON_ADDRESS_BACKFILL_DONE, true)
|
||||
}
|
||||
|
||||
private suspend fun backfillIfNeeded(account: MetaAccountLocal) {
|
||||
val secrets = secretStoreV2.getMetaAccountSecrets(account.id) ?: return
|
||||
val entropy = secrets.entropy ?: return
|
||||
if (secrets.tronKeypair != null) return
|
||||
val substrateCryptoType = account.substrateCryptoType ?: return
|
||||
|
||||
val mnemonic = MnemonicCreator.fromEntropy(entropy).words
|
||||
|
||||
val tronChainSecrets = accountSecretsFactory.chainAccountSecrets(
|
||||
derivationPath = TRON_DEFAULT_DERIVATION_PATH,
|
||||
accountSource = AccountSecretsFactory.AccountSource.Mnemonic(substrateCryptoType, mnemonic),
|
||||
isEthereum = true
|
||||
).secrets
|
||||
|
||||
val tronKeypair = mapKeypairStructToKeypair(tronChainSecrets[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 = tronKeypair,
|
||||
tronDerivationPath = TRON_DEFAULT_DERIVATION_PATH
|
||||
)
|
||||
|
||||
secretStoreV2.putMetaAccountSecrets(account.id, updatedSecrets)
|
||||
|
||||
val tronAccountId = tronKeypair.publicKey.tronPublicKeyToAccountId()
|
||||
metaAccountDao.updateMetaAccount(account.id) { it.addTronAccount(tronKeypair.publicKey, tronAccountId) }
|
||||
}
|
||||
}
|
||||
+15
-1
@@ -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.TronAddressBackfillMigration
|
||||
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
|
||||
@@ -402,6 +403,7 @@ class AccountFeatureModule {
|
||||
nodeDao: NodeDao,
|
||||
secretStoreV1: SecretStoreV1,
|
||||
accountDataMigration: AccountDataMigration,
|
||||
tronAddressBackfillMigration: TronAddressBackfillMigration,
|
||||
metaAccountDao: MetaAccountDao,
|
||||
secretsMetaAccountLocalFactory: SecretsMetaAccountLocalFactory,
|
||||
secretStoreV2: SecretStoreV2,
|
||||
@@ -416,7 +418,8 @@ class AccountFeatureModule {
|
||||
secretStoreV2,
|
||||
secretsMetaAccountLocalFactory,
|
||||
secretStoreV1,
|
||||
accountDataMigration
|
||||
accountDataMigration,
|
||||
tronAddressBackfillMigration
|
||||
)
|
||||
}
|
||||
|
||||
@@ -439,6 +442,17 @@ class AccountFeatureModule {
|
||||
return AccountDataMigration(preferences, encryptedPreferences, accountDao)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideTronAddressBackfillMigration(
|
||||
preferences: Preferences,
|
||||
secretStoreV2: SecretStoreV2,
|
||||
metaAccountDao: MetaAccountDao,
|
||||
accountSecretsFactory: AccountSecretsFactory,
|
||||
): TronAddressBackfillMigration {
|
||||
return TronAddressBackfillMigration(preferences, secretStoreV2, metaAccountDao, accountSecretsFactory)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideExternalAccountActions(
|
||||
|
||||
+199
@@ -0,0 +1,199 @@
|
||||
package io.novafoundation.nova.feature_account_impl.data.repository.datasource.migration
|
||||
|
||||
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.mapKeypairStructToKeypair
|
||||
import io.novafoundation.nova.common.data.secrets.v2.tronKeypair
|
||||
import io.novafoundation.nova.common.data.storage.Preferences
|
||||
import io.novafoundation.nova.common.utils.tronAddressToAccountId
|
||||
import io.novafoundation.nova.common.utils.tronPublicKeyToAccountId
|
||||
import io.novafoundation.nova.core.model.CryptoType
|
||||
import io.novafoundation.nova.core_db.dao.MetaAccountDao
|
||||
import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal
|
||||
import io.novafoundation.nova.feature_account_impl.data.secrets.AccountSecretsFactory
|
||||
import io.novasama.substrate_sdk_android.encrypt.json.JsonDecoder
|
||||
import io.novasama.substrate_sdk_android.encrypt.mnemonic.MnemonicCreator
|
||||
import io.novasama.substrate_sdk_android.scale.EncodableStruct
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentMatcher
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.Mockito.never
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.junit.MockitoJUnitRunner
|
||||
|
||||
// Same guaranteed-non-null-return wrappers as RealTronTransactionServiceTest, and for the same reason: the
|
||||
// shared test_shared eq()/any() helpers crash with "eq(...) must not be null" once Mockito's genuinely-null
|
||||
// runtime return flows into a Kotlin non-null-typed parameter - see that test's class doc for the full writeup.
|
||||
private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun <T> any(): T {
|
||||
Mockito.any<T>()
|
||||
return null as T
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun <T> argThat(matcher: (T) -> Boolean): T {
|
||||
Mockito.argThat(ArgumentMatcher<T> { matcher(it) })
|
||||
return null as T
|
||||
}
|
||||
|
||||
private fun <T> whenever(methodCall: T?) = Mockito.`when`(methodCall)
|
||||
|
||||
/**
|
||||
* This is the money-safety-critical half of the Tron backfill fix (see TronAddressBackfillMigration's class
|
||||
* doc for the full context): a wrong derivation here would silently give a pre-existing wallet a Tron address
|
||||
* it does NOT actually control, or fail to skip an account it shouldn't touch. Uses a real (non-mocked)
|
||||
* AccountSecretsFactory so the actual BIP32/secp256k1 derivation math runs for real, and asserts against the
|
||||
* same live-TronGrid-cross-validated reference address TronDerivationTest already established for the standard
|
||||
* BIP39 test mnemonic, so this test and that one are pinned to the same known-good vector.
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner::class)
|
||||
class TronAddressBackfillMigrationTest {
|
||||
|
||||
@Mock
|
||||
lateinit var preferences: Preferences
|
||||
|
||||
@Mock
|
||||
lateinit var secretStoreV2: SecretStoreV2
|
||||
|
||||
@Mock
|
||||
lateinit var metaAccountDao: MetaAccountDao
|
||||
|
||||
@Mock
|
||||
lateinit var jsonDecoder: JsonDecoder
|
||||
|
||||
private lateinit var subject: TronAddressBackfillMigration
|
||||
|
||||
private val testMnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
|
||||
private val expectedTronAccountId = "TUEZSdKsoDHQMeZwihtdoBiN46zxhGWYdH".tronAddressToAccountId()
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
// Real factory, not a mock - the whole point of this test is to exercise the actual derivation.
|
||||
val accountSecretsFactory = AccountSecretsFactory(jsonDecoder)
|
||||
subject = TronAddressBackfillMigration(preferences, secretStoreV2, metaAccountDao, accountSecretsFactory)
|
||||
|
||||
// any() would try to unbox a null placeholder into the primitive Boolean parameter and crash - Mockito's
|
||||
// own anyBoolean() returns a real `false` default instead, avoiding that entirely.
|
||||
whenever(preferences.getBoolean(any(), Mockito.anyBoolean())).thenReturn(false)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `migrate should derive and persist the well-known reference Tron address for a pre-existing mnemonic account`(): Unit = runBlocking {
|
||||
val account = secretsAccount(id = 42, substrateCryptoType = CryptoType.SR25519)
|
||||
val secrets = accountSecrets(entropy = MnemonicCreator.fromWords(testMnemonic).entropy, tronKeypair = null)
|
||||
|
||||
whenever(metaAccountDao.getMetaAccounts()).thenReturn(listOf(account))
|
||||
whenever(metaAccountDao.getMetaAccount(eq(42L))).thenReturn(account)
|
||||
whenever(secretStoreV2.getMetaAccountSecrets(eq(42L))).thenReturn(secrets)
|
||||
|
||||
subject.migrate()
|
||||
|
||||
verify(metaAccountDao).updateMetaAccount(
|
||||
argThat<MetaAccountLocal> { updated ->
|
||||
updated.id == 42L && updated.tronAddress.contentEquals(expectedTronAccountId)
|
||||
}
|
||||
)
|
||||
|
||||
verify(secretStoreV2).putMetaAccountSecrets(
|
||||
eq(42L),
|
||||
argThat<EncodableStruct<MetaAccountSecrets>> { updatedSecrets ->
|
||||
val tronKeypairStruct = updatedSecrets.tronKeypair
|
||||
tronKeypairStruct != null &&
|
||||
mapKeypairStructToKeypair(tronKeypairStruct).publicKey.tronPublicKeyToAccountId().contentEquals(expectedTronAccountId)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `migrate should skip an account that already has a Tron keypair`(): Unit = runBlocking {
|
||||
val account = secretsAccount(id = 7, substrateCryptoType = CryptoType.SR25519)
|
||||
val existingTronKeypair = KeyPairSchema { keypair ->
|
||||
keypair[KeyPairSchema.PublicKey] = ByteArray(33) { 9 }
|
||||
keypair[KeyPairSchema.PrivateKey] = ByteArray(32) { 8 }
|
||||
keypair[KeyPairSchema.Nonce] = null
|
||||
}
|
||||
val secrets = accountSecrets(entropy = MnemonicCreator.fromWords(testMnemonic).entropy, tronKeypair = existingTronKeypair)
|
||||
|
||||
whenever(metaAccountDao.getMetaAccounts()).thenReturn(listOf(account))
|
||||
whenever(secretStoreV2.getMetaAccountSecrets(eq(7L))).thenReturn(secrets)
|
||||
|
||||
subject.migrate()
|
||||
|
||||
verify(metaAccountDao, never()).updateMetaAccount(any())
|
||||
// metaId is a primitive Long parameter - same anyBoolean() reasoning applies, use anyLong().
|
||||
verify(secretStoreV2, never()).putMetaAccountSecrets(Mockito.anyLong(), any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `migrate should skip an account with no entropy (raw-seed import, not a mnemonic)`(): Unit = runBlocking {
|
||||
val account = secretsAccount(id = 11, substrateCryptoType = CryptoType.SR25519)
|
||||
val secrets = accountSecrets(entropy = null, tronKeypair = null)
|
||||
|
||||
whenever(metaAccountDao.getMetaAccounts()).thenReturn(listOf(account))
|
||||
whenever(secretStoreV2.getMetaAccountSecrets(eq(11L))).thenReturn(secrets)
|
||||
|
||||
subject.migrate()
|
||||
|
||||
verify(metaAccountDao, never()).updateMetaAccount(any())
|
||||
// metaId is a primitive Long parameter - same anyBoolean() reasoning applies, use anyLong().
|
||||
verify(secretStoreV2, never()).putMetaAccountSecrets(Mockito.anyLong(), any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `migrate should skip an account with no stored secrets at all (watch-only, Ledger, etc)`(): Unit = runBlocking {
|
||||
val account = secretsAccount(id = 13, substrateCryptoType = CryptoType.SR25519)
|
||||
|
||||
whenever(metaAccountDao.getMetaAccounts()).thenReturn(listOf(account))
|
||||
whenever(secretStoreV2.getMetaAccountSecrets(eq(13L))).thenReturn(null)
|
||||
|
||||
subject.migrate()
|
||||
|
||||
verify(metaAccountDao, never()).updateMetaAccount(any())
|
||||
// metaId is a primitive Long parameter - same anyBoolean() reasoning applies, use anyLong().
|
||||
verify(secretStoreV2, never()).putMetaAccountSecrets(Mockito.anyLong(), any())
|
||||
}
|
||||
|
||||
private fun secretsAccount(id: Long, substrateCryptoType: CryptoType): MetaAccountLocal {
|
||||
return MetaAccountLocal(
|
||||
substratePublicKey = ByteArray(32) { 1 },
|
||||
substrateCryptoType = substrateCryptoType,
|
||||
substrateAccountId = ByteArray(32) { 2 },
|
||||
ethereumPublicKey = null,
|
||||
ethereumAddress = null,
|
||||
name = "Test account",
|
||||
parentMetaId = null,
|
||||
isSelected = true,
|
||||
position = 0,
|
||||
type = MetaAccountLocal.Type.SECRETS,
|
||||
status = MetaAccountLocal.Status.ACTIVE,
|
||||
globallyUniqueId = "test-guid-$id",
|
||||
typeExtras = null
|
||||
).also { it.id = id }
|
||||
}
|
||||
|
||||
private fun accountSecrets(entropy: ByteArray?, tronKeypair: EncodableStruct<KeyPairSchema>?): EncodableStruct<MetaAccountSecrets> {
|
||||
val dummySubstrateKeypair = KeyPairSchema { keypair ->
|
||||
keypair[KeyPairSchema.PublicKey] = ByteArray(32) { 5 }
|
||||
keypair[KeyPairSchema.PrivateKey] = ByteArray(32) { 6 }
|
||||
keypair[KeyPairSchema.Nonce] = ByteArray(8) { 7 }
|
||||
}
|
||||
|
||||
return MetaAccountSecrets(
|
||||
substrateKeyPair = mapKeypairStructToKeypair(dummySubstrateKeypair),
|
||||
entropy = entropy,
|
||||
substrateSeed = null,
|
||||
substrateDerivationPath = null,
|
||||
ethereumKeypair = null,
|
||||
ethereumDerivationPath = null,
|
||||
tronKeypair = tronKeypair?.let(::mapKeypairStructToKeypair),
|
||||
tronDerivationPath = null
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user