Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@ dependencies {
api fileTree(dir: "libs", include: ["*.aar", "*.jar"], exclude: ["trustwalletcore.aar"])

// Fetch Flow Wallet Kit from GitHub Packages
implementation 'com.github.onflow.flow-wallet-kit:flow-wallet-android:0.2.5'
implementation 'com.github.onflow.flow-wallet-kit:flow-wallet-android:0.2.6'
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.9.22"
/** Android Architecture **/
implementation 'androidx.core:core-ktx:1.13.1'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ fun instabugInitialize(application: Application) {
childAccounts.toString()
)
report.setUserAttribute("COA", EVMWalletManager.getEVMAddress().orEmpty())
report.setUserAttribute("EOA", WalletManager.getEOAAddress().orEmpty())
report.setUserAttribute("EOA", WalletManager.getEOAAddresses().toString())
report.setUserAttribute("Network", chainNetWorkString())
}
val configuration = ProactiveReportingConfigs.Builder()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.flowfoundation.wallet.manager.evm

import com.flowfoundation.wallet.manager.account.AccountManager
import com.flowfoundation.wallet.manager.flowjvm.cadenceQueryCOATokenBalance
import com.flowfoundation.wallet.manager.wallet.WalletManager
import com.flowfoundation.wallet.manager.walletdata.EOAWallet
import com.flowfoundation.wallet.utils.formatPrice
import com.flowfoundation.wallet.utils.Env
import com.flowfoundation.wallet.utils.ioScope
Expand Down Expand Up @@ -107,12 +109,12 @@ object DAppEVMConnectionManager {
}
}

// Load EOA account
val eoaAddress = WalletManager.getEOAAddress()
if (!eoaAddress.isNullOrEmpty()) {
logd(TAG, "Found EOA account: $eoaAddress")
// EOA balance is hidden as per requirements
accounts.add(DAppEVMAccount(eoaAddress, DAppEVMAccountType.EOA, null))
// Load all EOA accounts
val eoaWallets = AccountManager.walletNodes()
?.filterIsInstance<EOAWallet>() ?: emptyList()
eoaWallets.forEach { eoaWallet ->
logd(TAG, "Found EOA account: ${eoaWallet.address} (index=${eoaWallet.index})")
accounts.add(DAppEVMAccount(eoaWallet.address, DAppEVMAccountType.EOA, null))
}

_availableAccounts.value = accounts
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.flowfoundation.wallet.manager.evm

import com.flowfoundation.wallet.manager.account.AccountManager
import com.flowfoundation.wallet.manager.walletdata.EOAWallet
import com.flowfoundation.wallet.manager.flowjvm.CadenceScript
import com.flowfoundation.wallet.manager.flowjvm.cadenceBridgeChildFTFromCOA
import com.flowfoundation.wallet.manager.flowjvm.cadenceBridgeChildFTToCOA
Expand Down Expand Up @@ -119,7 +120,10 @@ object EVMWalletManager {
}

fun isEOAAddress(address: String): Boolean {
val result = address.equals(WalletManager.getEOAAddress(), ignoreCase = true)
val result = AccountManager.walletNodes()
?.filterIsInstance<EOAWallet>()
?.any { it.address.equals(address, ignoreCase = true) }
?: false
logd(TAG, "isEOAAddress result: $result")
return result
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ suspend fun loadInitJS(): String {
}
}

WalletManager.getEOAAddress()?.let { eoaAddress ->
WalletManager.getEOAAddresses().forEach { eoaAddress ->
if (eoaAddress.isNotEmpty()) {
addressList.add(eoaAddress)
}
Expand Down Expand Up @@ -257,11 +257,13 @@ Unit) {
return@ioScope
}
val singer = cryptoProvider.getSigner(HashingAlgorithm.SHA2_256)
val eoaIndex = WalletManager.getSelectedEOAIndex()
val result = WalletManager.wallet()?.ethSignTransactionAndSendByCadence(
input = input,
fromAddress = address,
signers = listOf(singer),
flowAddress = FlowAddress(WalletManager.getCurrentFlowWalletAddress().orEmpty())
flowAddress = FlowAddress(WalletManager.getCurrentFlowWalletAddress().orEmpty()),
index = eoaIndex
)
if (result == null) {
logd("EOATransaction", "ERROR: Failed to sign transaction")
Expand All @@ -275,7 +277,8 @@ Unit) {
callback.invoke(txHash)

} else {
val output = WalletManager.wallet()?.ethSignTransaction(input)
val eoaIndex = WalletManager.getSelectedEOAIndex()
val output = WalletManager.wallet()?.ethSignTransaction(input, index = eoaIndex)

if (output == null) {
logd("EOATransaction", "ERROR: Failed to sign transaction")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ object WalletManager {
fun canDeriveEoa() = _canDeriveEoa

/**
* Get EOA address
* Get the first EOA address (for backward compatibility / single-address scenarios)
*/
fun getEOAAddress(): String? {
val eoaWallet = AccountManager.walletNodes()
Expand All @@ -55,6 +55,28 @@ object WalletManager {
return null
}

/**
* Get all EOA addresses
*/
fun getEOAAddresses(): List<String> {
return AccountManager.walletNodes()
?.filterIsInstance<EOAWallet>()
?.map { it.address }
?: emptyList()
}

/**
* Get the BIP44 derivation index for the currently selected EOA wallet.
* Returns 0 if no EOA is selected or found.
*/
fun getSelectedEOAIndex(): Int {
val selectedAddress = selectedWalletAddress()
return AccountManager.walletNodes()
?.filterIsInstance<EOAWallet>()
?.firstOrNull { it.address.equals(selectedAddress, ignoreCase = true) }
?.index ?: 0
}


private fun initializeWallet(): Boolean {
logd(TAG, "initializeWallet() called")
Expand Down Expand Up @@ -143,7 +165,9 @@ object WalletManager {
}

fun isEVMAccountSelected(): Boolean {
return selectedWalletAddress().toAddress().equals(EVMWalletManager.getEVMAddress()?.toAddress(), ignoreCase = true) || selectedWalletAddress().toAddress().equals(getEOAAddress(), ignoreCase = true)
val selected = selectedWalletAddress().toAddress()
return selected.equals(EVMWalletManager.getEVMAddress()?.toAddress(), ignoreCase = true)
|| EVMWalletManager.isEOAAddress(selected)
}

fun isSelfFlowAddress(address: String): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,8 @@ suspend fun WCRequest.evmSignTypedData() {
EVMSignTypedDataDialog.observe { isApprove ->
ioScope {
val result = if (DAppEVMConnectionManager.isCurrentEOAAccount()) {
val data = WalletManager.wallet()?.ethSignTypedData(message)
val eoaIndex = WalletManager.getSelectedEOAIndex()
val data = WalletManager.wallet()?.ethSignTypedData(message, index = eoaIndex)
Numeric.toHexString(data)
} else {
signTypedData(hashData)
Expand Down Expand Up @@ -290,7 +291,8 @@ private suspend fun WCRequest.evmSignMessage() {
val result = if (EVMWalletManager.isEVMWalletAddress(fromAddress)) {
signEthereumMessage(message)
} else {
val data = WalletManager.wallet()?.ethSignPersonalMessage(hexMessage.hexToBytes())
val eoaIndex = WalletManager.getSelectedEOAIndex()
val data = WalletManager.wallet()?.ethSignPersonalMessage(hexMessage.hexToBytes(), index = eoaIndex)
Numeric.toHexString(data)
}
if (isApprove) approve(result) else reject()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ data class EOAWallet(
@SerializedName("name")
override val name: String,
@SerializedName("emojiId")
override val emojiId: Int
override val emojiId: Int,
@SerializedName("index")
val index: Int = 0
) : MainWallet()

// Linked Wallets (Children / COA)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,16 +272,32 @@ object WalletDataManager {
// EOA Wallet - canDeriveEoa is determined per-wallet by WalletCreationHelper
// based on key type (Secure Enclave / private key = false, seed phrase = true)
if (canDeriveEoa) {
val eoa = deriveEoaAddress(wallet)
logd(TAG, "Generated EOA address: $eoa")
if (eoa.isNotEmpty()) {
logd(TAG, "Adding EOA for account: $eoa")
val eoaEmojiInfo = getEmojiInfo(eoa)
nodes.add(EOAWallet(
address = eoa,
name = eoaEmojiInfo.emojiName,
emojiId = eoaEmojiInfo.emojiId
))
val existingEOAs = account.walletNodes.filterIsInstance<EOAWallet>()
if (existingEOAs.isNotEmpty()) {
// Preserve all existing EOA wallets with their indices
existingEOAs.forEach { existingEoa ->
val eoaEmojiInfo = getEmojiInfo(existingEoa.address)
nodes.add(EOAWallet(
address = existingEoa.address,
name = eoaEmojiInfo.emojiName,
emojiId = eoaEmojiInfo.emojiId,
index = existingEoa.index
))
}
logd(TAG, "Preserved ${existingEOAs.size} existing EOA wallets")
} else {
val eoa = deriveEoaAddress(wallet)
logd(TAG, "Generated EOA address: $eoa")
if (eoa.isNotEmpty()) {
logd(TAG, "Adding EOA for account: $eoa")
val eoaEmojiInfo = getEmojiInfo(eoa)
nodes.add(EOAWallet(
address = eoa,
name = eoaEmojiInfo.emojiName,
emojiId = eoaEmojiInfo.emojiId,
index = 0
))
}
}
}

Expand Down Expand Up @@ -392,15 +408,30 @@ object WalletDataManager {
// EOA - canDeriveEoa is determined per-wallet by WalletCreationHelper
// based on key type (Secure Enclave / private key = false, seed phrase = true)
if (canDeriveEoa) {
val eoa = deriveEoaAddress(wallet)
if (eoa.isNotEmpty()) {
logd(TAG, "Adding EOA for non-current account: $eoa")
val eoaEmojiInfo = getEmojiInfo(eoa)
nodes.add(EOAWallet(
address = eoa,
name = eoaEmojiInfo.emojiName,
emojiId = eoaEmojiInfo.emojiId
))
val existingEOAs = account.walletNodes.filterIsInstance<EOAWallet>()
if (existingEOAs.isNotEmpty()) {
existingEOAs.forEach { existingEoa ->
val eoaEmojiInfo = getEmojiInfo(existingEoa.address)
nodes.add(EOAWallet(
address = existingEoa.address,
name = eoaEmojiInfo.emojiName,
emojiId = eoaEmojiInfo.emojiId,
index = existingEoa.index
))
}
logd(TAG, "Preserved ${existingEOAs.size} existing EOA wallets for non-current account")
} else {
val eoa = deriveEoaAddress(wallet)
if (eoa.isNotEmpty()) {
logd(TAG, "Adding EOA for non-current account: $eoa")
val eoaEmojiInfo = getEmojiInfo(eoa)
nodes.add(EOAWallet(
address = eoa,
name = eoaEmojiInfo.emojiName,
emojiId = eoaEmojiInfo.emojiId,
index = 0
))
}
}
} else {
logd(TAG, "Skipping EOA for non-current account (canDeriveEoa=false)")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.flowfoundation.wallet.manager.app.chainNetWorkString
import com.flowfoundation.wallet.manager.emoji.AccountEmojiManager
import com.flowfoundation.wallet.manager.key.CryptoProviderManager
import com.flowfoundation.wallet.manager.wallet.WalletManager
import com.flowfoundation.wallet.manager.walletdata.EOAWallet
import com.flowfoundation.wallet.manager.walletdata.FlowWallet
import com.flowfoundation.wallet.network.model.ManualAddressRequest
import com.flowfoundation.wallet.utils.logd
Expand Down Expand Up @@ -91,3 +92,50 @@ suspend fun addNewFlowAccount(): String? {
null
}
}

/**
* Add a new EOA (Externally Owned Account) wallet by deriving the next BIP44 index.
* This is a purely local operation (no network request needed).
*
* @return the new EOA address, or null on failure
*/
suspend fun addNewEOAAccount(): String? {
return try {
val wallet = WalletManager.wallet()
if (wallet == null) {
loge(TAG, "No wallet available from WalletManager")
return null
}

val existingEOAs = AccountManager.walletNodes()
?.filterIsInstance<EOAWallet>() ?: emptyList()
val nextIndex = if (existingEOAs.isEmpty()) 0 else existingEOAs.maxOf { it.index } + 1

logd(TAG, "Deriving EOA address at index $nextIndex")
val address = wallet.ethAddress(nextIndex)
if (address.isBlank()) {
loge(TAG, "Derived empty EOA address at index $nextIndex")
return null
}

val emojiInfo = AccountEmojiManager.getEmojiByAddress(address)

val eoaWallet = EOAWallet(
address = address,
name = emojiInfo.emojiName,
emojiId = emojiInfo.emojiId,
index = nextIndex
)

AccountManager.updateCurrentAccount { account ->
account.copy(walletNodes = account.walletNodes + eoaWallet)
}

logd(TAG, "New EOAWallet added to account: $address (index=$nextIndex)")
address
} catch (e: Exception) {
loge(TAG, "Failed to add new EOA account: ${e.message}")
e.printStackTrace()
null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.viewmodel.compose.viewModel
import com.flowfoundation.wallet.R
import com.flowfoundation.wallet.base.activity.BaseActivity
import com.flowfoundation.wallet.page.dialog.common.AddNewAccountDialog
import com.flowfoundation.wallet.manager.wallet.WalletManager
import com.flowfoundation.wallet.page.wallet.view.AccountItemSection
import com.flowfoundation.wallet.page.profile.subpage.wallet.WalletSettingActivity
Expand Down Expand Up @@ -107,6 +108,7 @@ fun AccountListScreen(
val hiddenAccounts by viewModel.hiddenAccounts.collectAsState()
val isAddingAccount by viewModel.isAddingAccount.collectAsState()
val canAddAccount by viewModel.canAddAccount.collectAsState()
val canAddEOAAccount by viewModel.canAddEOAAccount.collectAsState()
val context = LocalContext.current
val activity = remember { getActivityFromContext(context) as FragmentActivity }
val lifecycleOwner = androidx.lifecycle.compose.LocalLifecycleOwner.current
Expand Down Expand Up @@ -154,10 +156,19 @@ fun AccountListScreen(
}
},
actions = {
val canAdd = canAddAccount || canAddEOAAccount
IconButton(
onClick = { viewModel.addAccount() },
enabled = canAddAccount,
modifier = Modifier.alpha(if (canAddAccount) 1f else 0f)
onClick = {
AddNewAccountDialog.show(
activity.supportFragmentManager,
canAddCadence = canAddAccount,
canAddEOA = canAddEOAAccount,
onCadence = { viewModel.addCadenceAccount() },
onEOA = { viewModel.addEOAAccount() }
)
},
enabled = canAdd,
modifier = Modifier.alpha(if (canAdd) 1f else 0f)
) {
Icon(
imageVector = Icons.Default.Add,
Expand Down
Loading
Loading