Skip to content

Commit

Permalink
feat: adding transfer funds (#122)
Browse files Browse the repository at this point in the history
* feat: adding transfer funds

* removing local.properties

* adding local.properties to .gitignore

* minor fix

* rollback local.properties file

* minor fix

* adding dropdown for eth and strk

* sending eth
  • Loading branch information
jrmncos authored Nov 5, 2024
1 parent a6a959a commit 9372f6b
Show file tree
Hide file tree
Showing 8 changed files with 380 additions and 83 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@ lightclientservice/.idea
**/*.so

**/.idea/*

wallet_app/local.properties
4 changes: 2 additions & 2 deletions wallet_app/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@
</intent-filter>
</activity>
<activity
android:name=".ui.transfer.SendActivity"
android:name=".ui.transfer.SendScreen"
android:exported="true"
android:theme="@style/Theme.Walletapp"/>
<activity
android:name=".ui.transfer.ReceiverActivity"
android:name=".ui.transfer.ReceiverScreen"
android:exported="true"
android:theme="@style/Theme.Walletapp"/>
<activity android:name=".ui.account.AddTokenActivity"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ package com.example.walletapp.ui

import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.example.walletapp.BuildConfig
import com.example.walletapp.ui.account.AddTokenScreen
import com.example.walletapp.ui.account.TokenViewModel
import com.example.walletapp.ui.account.WalletScreen
import com.example.walletapp.ui.account.WalletViewModel
import com.example.walletapp.ui.activity.FinalizeAccountCreationScreen
import com.example.walletapp.ui.onboarding.CreateAccountScreen
import com.example.walletapp.ui.onboarding.CreatePinScreen
Expand Down Expand Up @@ -44,6 +47,7 @@ object Receive

@Composable
fun WalletApp(tokenViewModel: TokenViewModel) {
val walletViewModel: WalletViewModel = viewModel()
WalletappTheme {

// TODO(#109): get this information from a data store
Expand Down Expand Up @@ -95,7 +99,8 @@ fun WalletApp(tokenViewModel: TokenViewModel) {
onNewTokenPress = { navController.navigate( route = AddToken ) },
onReceivePress = { navController.navigate( route = Receive ) },
onSendPress = { navController.navigate( route = Send ) },
tokenViewModel = tokenViewModel
tokenViewModel = tokenViewModel,
walletViewModel = walletViewModel
)
}
composable<AddToken> {
Expand All @@ -106,7 +111,7 @@ fun WalletApp(tokenViewModel: TokenViewModel) {
}

composable<Send> {
SendScreen()
SendScreen(walletViewModel)
}
composable<Receive> {
ReceiveScreen(modifier = Modifier)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,15 +80,17 @@ fun WalletScreen(
onNewTokenPress: () -> Unit,
onSendPress: () -> Unit,
onReceivePress: () -> Unit,
tokenViewModel: TokenViewModel
tokenViewModel: TokenViewModel,
walletViewModel: WalletViewModel
) {
Surface(modifier = Modifier.fillMaxSize()) {
Wallet(
modifier = Modifier.padding(10.dp),
onNewTokenPress = onNewTokenPress,
onSendPress = onSendPress,
onReceivePress = onReceivePress,
tokenViewModel = tokenViewModel
tokenViewModel = tokenViewModel,
walletViewModel = walletViewModel
)
}
}
Expand All @@ -97,60 +99,32 @@ fun WalletScreen(

@SuppressLint("MutableCollectionMutableState")
@Composable
fun Wallet(modifier: Modifier, onNewTokenPress: () -> Unit, onReceivePress: () -> Unit, onSendPress: () -> Unit,tokenViewModel: TokenViewModel) {
fun Wallet(modifier: Modifier, onNewTokenPress: () -> Unit, onReceivePress: () -> Unit, onSendPress: () -> Unit,tokenViewModel: TokenViewModel, walletViewModel: WalletViewModel) {
val networkList = listOf("Starknet Mainnet", "Test Networks")
var selectedNetworkIndex by remember { mutableStateOf(0) }
val coinViewModel: CoinViewModel = viewModel()
val tokens by tokenViewModel.tokens.observeAsState(initial = emptyList())
val context = (LocalContext.current as Activity)
val address= BuildConfig.ACCOUNT_ADDRESS
val accountAddress = Felt.fromHex(address)
val starknetClient = StarknetClient(BuildConfig.RPC_URL)

var tokenImages by rememberSaveable { mutableStateOf<HashMap<String, String>>(hashMapOf()) }
var balances by remember { mutableStateOf<List<HashMap<String, Double>>>(emptyList()) }
val tokenIds = remember { mutableStateListOf<String>() }
val balances by walletViewModel.balances.collectAsState()
val coinsPrices by rememberSaveable { mutableStateOf<HashMap<String, Double>>(hashMapOf()) }
var prices by rememberSaveable { mutableStateOf(mapOf<String, Double>()) }


prices = coinViewModel.prices.value
tokenImages=coinViewModel.tokenImages.value
val errorMessage by coinViewModel.errorMessage

LaunchedEffect(tokens) {
if(tokens.isNotEmpty()){
tokenIds.addAll(tokens.map { it.tokenId })
coinViewModel.fetchTokenImages(tokenIds)
try {
coinViewModel.getTokenPrices(ids = tokenIds.joinToString(",") { it }, vsCurrencies = "usd")
val balanceDeferred: List<Deferred<HashMap<String, Double>>> = tokens.map { token ->
async(Dispatchers.IO) {
try {
val balanceInWei = starknetClient.getBalance(accountAddress, token.contactAddress)
val balanceInEther = weiToEther(balanceInWei).toDoubleWithTwoDecimal()
hashMapOf(token.name to balanceInEther)
} catch (e: RpcRequestFailedException) {
withContext(Dispatchers.Main) {
Toast.makeText(context, "${e.code}: ${e.message}", Toast.LENGTH_LONG).show()
}
hashMapOf(token.name to 0.0)
}
}
}
// Wait for all balance fetching to complete
balances = balanceDeferred.awaitAll()
} catch (e: Exception) {
withContext(Dispatchers.Main) {
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
}
}
}
val errorMessageCoinViewModel by coinViewModel.errorMessage
val errorMessageWalletViewModel by walletViewModel.errorMessage;

LaunchedEffect(tokens) {
walletViewModel.fetchBalance(accountAddress, tokens, coinViewModel)
}
if (errorMessage.isNotEmpty()) {

if (errorMessageCoinViewModel.isNotEmpty()) {
Text(
text = errorMessage,
text = errorMessageCoinViewModel,
color = MaterialTheme.colorScheme.error,
modifier = Modifier.padding(16.dp)
)
Expand All @@ -163,6 +137,10 @@ fun Wallet(modifier: Modifier, onNewTokenPress: () -> Unit, onReceivePress: () -
}
}

if(errorMessageWalletViewModel.isNotEmpty()) {
Toast.makeText(context, errorMessageWalletViewModel, Toast.LENGTH_LONG).show()
}

Column(
modifier = Modifier
.fillMaxSize()
Expand All @@ -179,7 +157,7 @@ fun Wallet(modifier: Modifier, onNewTokenPress: () -> Unit, onReceivePress: () -
selectedNetworkIndex,
modifier = Modifier,
onItemClick = { index ->
selectedNetworkIndex = index
selectedNetworkIndex = index
}
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package com.example.walletapp.ui.account

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateListOf
import com.example.walletapp.BuildConfig
import com.example.walletapp.model.Token
import com.example.walletapp.utils.StarknetClient
import com.example.walletapp.utils.weiToEther
import com.example.walletapp.utils.toDoubleWithTwoDecimal
import com.swmansion.starknet.account.Account
import com.swmansion.starknet.data.types.Felt
import com.swmansion.starknet.data.types.Uint256
import com.swmansion.starknet.provider.exceptions.RpcRequestFailedException
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.withContext


class WalletViewModel : ViewModel() {
private val _balances = MutableStateFlow(emptyList<HashMap<String, Double>>())
val balances: StateFlow<List<HashMap<String, Double>>> get() = _balances
val tokenIds = mutableStateListOf<String>()

private val starknetClient = StarknetClient(BuildConfig.RPC_URL)

private val _errorMessage = mutableStateOf("")
val errorMessage: State<String> get() = _errorMessage

fun fetchBalance(accountAddress: Felt, tokens: List<Token>, coinViewModel: CoinViewModel) {
viewModelScope.launch {
if (tokens.isNotEmpty()) {
tokenIds.addAll(tokens.map { it.tokenId })
coinViewModel.fetchTokenImages(tokenIds)
try {
coinViewModel.getTokenPrices(
ids = tokenIds.joinToString(",") { it },
vsCurrencies = "usd"
)
val balanceDeferred: List<Deferred<HashMap<String, Double>>> = tokens.map { token ->
async(Dispatchers.IO) {
try {
val balanceInWei =
starknetClient.getBalance(accountAddress, token.contactAddress)
val balanceInEther = weiToEther(balanceInWei).toDoubleWithTwoDecimal()
hashMapOf(token.name to balanceInEther)
} catch (e: RpcRequestFailedException) {
withContext(Dispatchers.Main) {
_errorMessage.value = "${e.code}: ${e.message}"
}
hashMapOf(token.name to 0.0)
}
}
}
// Wait for all balance fetching to complete
_balances.value = balanceDeferred.awaitAll()
} catch (e: Exception) {
withContext(Dispatchers.Main) {
_errorMessage.value = e.message.toString()
}
}
}
}
}

fun transferFunds(account: Account, toAddress: Felt, amount: Uint256) {
viewModelScope.launch {
try {
//TODO Handle the transaction hash if transaction was successfully
starknetClient.transferFunds(account, toAddress, amount)
} catch (e: Exception) {
println(e)
}
}

}
}
Loading

0 comments on commit 9372f6b

Please sign in to comment.