diff --git a/wallet_app/android/app/build.gradle.kts b/wallet_app/android/app/build.gradle.kts index 232f331b..f0b74905 100644 --- a/wallet_app/android/app/build.gradle.kts +++ b/wallet_app/android/app/build.gradle.kts @@ -1,6 +1,8 @@ plugins { alias(libs.plugins.android.application) alias(libs.plugins.jetbrains.kotlin.android) + id("com.google.dagger.hilt.android") + id("kotlin-kapt") } android { @@ -93,6 +95,16 @@ dependencies { implementation(libs.androidx.navigation.compose) + implementation (libs.retrofit) + implementation(libs.converter.gson) + implementation (libs.kotlinx.coroutines.core) + implementation (libs.kotlinx.coroutines.android) + + implementation("com.google.dagger:hilt-android:2.50") + kapt("com.google.dagger:hilt-android-compiler:2.50") + kapt("androidx.hilt:hilt-compiler:1.0.0") + implementation("androidx.hilt:hilt-navigation-fragment:1.0.0") + implementation("androidx.hilt:hilt-navigation-compose:1.0.0-alpha03") implementation(libs.androidx.core.ktx) implementation(libs.androidx.appcompat) diff --git a/wallet_app/android/app/src/main/AndroidManifest.xml b/wallet_app/android/app/src/main/AndroidManifest.xml index ce5f1264..31403cd6 100644 --- a/wallet_app/android/app/src/main/AndroidManifest.xml +++ b/wallet_app/android/app/src/main/AndroidManifest.xml @@ -2,6 +2,9 @@ + + + - diff --git a/wallet_app/android/app/src/main/java/com/example/walletapp/AccountBalanceActivity.kt b/wallet_app/android/app/src/main/java/com/example/walletapp/AccountBalanceActivity.kt deleted file mode 100644 index 8f3170d8..00000000 --- a/wallet_app/android/app/src/main/java/com/example/walletapp/AccountBalanceActivity.kt +++ /dev/null @@ -1,105 +0,0 @@ -package com.example.walletapp - -import android.os.Bundle -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import androidx.activity.enableEdgeToEdge -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.material.Surface -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.font.Font -import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.core.graphics.toColorInt -import androidx.core.view.WindowCompat -import org.web3j.protocol.Web3j -import org.web3j.protocol.http.HttpService -import org.web3j.utils.Convert -import java.math.BigDecimal -import com.example.walletapp.ui.theme.WalletappTheme - -class AccountBalanceActivity : ComponentActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - enableEdgeToEdge() - WindowCompat.setDecorFitsSystemWindows(window, true) - setContent { - WalletappTheme { - Surface(modifier = Modifier.fillMaxSize()) { - AccountBalanceScreenView( - ) - } - } - } - } - - @Composable - fun AccountBalanceScreenView(){ - - val rpcUrl = "https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID" // Replace with your RPC URL - val accountAddress = "0xYourHardcodedAccountAddress" // account Address - - - // Call the balance retrieval function - val balance = getAccountBalance(rpcUrl, accountAddress)?.toPlainString() ?: "0.0" - - Column(modifier = Modifier - .fillMaxSize() - .background(Color("#0C0C4F".toColorInt())) - .padding(16.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center){ - Spacer(modifier = Modifier.height(40.dp)) - Text( - text = "Your Account Balance", - fontFamily = FontFamily(Font(R.font.publicsans_regular)), - color = Color.White, - fontSize = 14.sp - ) - Spacer(modifier = Modifier.height(8.dp)) - Text( - text = "$$balance", - fontFamily = FontFamily(Font(R.font.inter_regular)), - color = Color.White, - fontSize = 28.sp, - modifier = Modifier - .align(Alignment.CenterHorizontally) - .padding(top = 70.dp) - ) - Spacer(modifier = Modifier.height(8.dp)) - Text( - text = accountAddress, - fontFamily = FontFamily(Font(R.font.inter_regular)), - color = Color.White, - fontSize = 16.sp, - modifier = Modifier.align(Alignment.CenterHorizontally) - ) - - - - } - } - - fun getAccountBalance(rpcUrl: String, accountAddress: String): BigDecimal? { - val web3j = Web3j.build(HttpService(rpcUrl)) - return try { - val ethGetBalance = web3j.ethGetBalance(accountAddress, org.web3j.protocol.core.DefaultBlockParameterName.LATEST).send() - val balanceInWei = ethGetBalance.balance - Convert.fromWei(balanceInWei.toString(), Convert.Unit.ETHER) - } catch (e: Exception) { - e.printStackTrace() - null - } - } -} \ No newline at end of file diff --git a/wallet_app/android/app/src/main/java/com/example/walletapp/MainActivity.kt b/wallet_app/android/app/src/main/java/com/example/walletapp/MainActivity.kt index f993e3ce..d9b25a74 100644 --- a/wallet_app/android/app/src/main/java/com/example/walletapp/MainActivity.kt +++ b/wallet_app/android/app/src/main/java/com/example/walletapp/MainActivity.kt @@ -38,6 +38,9 @@ import androidx.compose.ui.unit.sp import androidx.core.graphics.toColorInt import androidx.core.view.WindowCompat import com.example.walletapp.ui.theme.WalletappTheme +import dagger.hilt.android.AndroidEntryPoint +import dagger.hilt.android.HiltAndroidApp + class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -127,6 +130,25 @@ fun CreateAccount( modifier: Modifier) { fontSize = 17.sp ) } + Spacer(modifier = Modifier.height(10.dp)) + + + Button( + onClick = { val i = Intent(context, WalletActivity::class.java) + context.startActivity(i) }, + colors = ButtonDefaults.buttonColors(backgroundColor = Color("#EC796B".toColorInt())), + shape = RoundedCornerShape(10.dp), + modifier = Modifier + .fillMaxWidth() + .height(49.dp) + ) { + Text( + text = "My Starknet Wallet", + fontFamily = FontFamily(Font(R.font.inter_regular)), + color = Color.White, + fontSize = 17.sp + ) + } } Spacer(modifier = Modifier.height(15.dp)) diff --git a/wallet_app/android/app/src/main/java/com/example/walletapp/WalletActivity.kt b/wallet_app/android/app/src/main/java/com/example/walletapp/WalletActivity.kt index d7f9449c..bd6199ad 100644 --- a/wallet_app/android/app/src/main/java/com/example/walletapp/WalletActivity.kt +++ b/wallet_app/android/app/src/main/java/com/example/walletapp/WalletActivity.kt @@ -35,6 +35,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.graphics.toColorInt import androidx.core.view.WindowCompat +import com.example.walletapp.ui.activity.AccountBalanceActivity import com.example.walletapp.ui.theme.WalletappTheme class WalletActivity : ComponentActivity() { diff --git a/wallet_app/android/app/src/main/java/com/example/walletapp/data/datasource/RetrofitInstance.kt b/wallet_app/android/app/src/main/java/com/example/walletapp/data/datasource/RetrofitInstance.kt new file mode 100644 index 00000000..fa4ce814 --- /dev/null +++ b/wallet_app/android/app/src/main/java/com/example/walletapp/data/datasource/RetrofitInstance.kt @@ -0,0 +1,17 @@ +package com.example.walletapp.data.datasource + +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory + +object RetrofitInstance { + // Replace this with the actual StarkNet RPC URL + private const val BASE_URL = "https://starknet-mainnet.g.alchemy.com/starknet/version/rpc/v0_7/" + + val api: StarknetApiService by lazy { + Retrofit.Builder() + .baseUrl(BASE_URL) + .addConverterFactory(GsonConverterFactory.create()) + .build() + .create(StarknetApiService::class.java) + } +} diff --git a/wallet_app/android/app/src/main/java/com/example/walletapp/data/datasource/StarknetApiService.kt b/wallet_app/android/app/src/main/java/com/example/walletapp/data/datasource/StarknetApiService.kt new file mode 100644 index 00000000..77b9c670 --- /dev/null +++ b/wallet_app/android/app/src/main/java/com/example/walletapp/data/datasource/StarknetApiService.kt @@ -0,0 +1,14 @@ +package com.example.walletapp.data.datasource + +import com.example.walletapp.data.model.StarknetResponse +import com.example.walletapp.data.repository.StarknetCallRequest +import retrofit2.Call +import retrofit2.http.Body +import retrofit2.http.POST + +interface StarknetApiService { + + + @POST("rFAP8fkTAz9TmYw8_V5Fyzxi-WSoQdhk") // rpc end point + fun getBalance( @Body request: StarknetCallRequest): Call +} \ No newline at end of file diff --git a/wallet_app/android/app/src/main/java/com/example/walletapp/data/model/StarknetResponse.kt b/wallet_app/android/app/src/main/java/com/example/walletapp/data/model/StarknetResponse.kt new file mode 100644 index 00000000..4de494d3 --- /dev/null +++ b/wallet_app/android/app/src/main/java/com/example/walletapp/data/model/StarknetResponse.kt @@ -0,0 +1,8 @@ +package com.example.walletapp.data.model + +data class StarknetResponse( + val jsonrpc: String, + val id: Int, + val result: List +) + diff --git a/wallet_app/android/app/src/main/java/com/example/walletapp/data/repository/StarknetCallRequest.kt b/wallet_app/android/app/src/main/java/com/example/walletapp/data/repository/StarknetCallRequest.kt new file mode 100644 index 00000000..0160b2f9 --- /dev/null +++ b/wallet_app/android/app/src/main/java/com/example/walletapp/data/repository/StarknetCallRequest.kt @@ -0,0 +1,8 @@ +package com.example.walletapp.data.repository + +data class StarknetCallRequest( + val id: Int = 1, + val jsonrpc: String = "2.0", + val method: String = "starknet_call", + val params: List +) \ No newline at end of file diff --git a/wallet_app/android/app/src/main/java/com/example/walletapp/data/repository/StarknetRepository.kt b/wallet_app/android/app/src/main/java/com/example/walletapp/data/repository/StarknetRepository.kt new file mode 100644 index 00000000..cb7ac07c --- /dev/null +++ b/wallet_app/android/app/src/main/java/com/example/walletapp/data/repository/StarknetRepository.kt @@ -0,0 +1,26 @@ +package com.example.walletapp.data.repository + +import android.util.Log +import com.example.walletapp.data.datasource.RetrofitInstance +import com.example.walletapp.data.model.StarknetResponse +import retrofit2.Call + +class StarknetRepository() { + fun getAccountBalance( + contractAddress: String, + accountAddress: String + ): Call { + val calldata = listOf(accountAddress) + val params = listOf( + mapOf( + "contract_address" to "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", + "calldata" to listOf("0x02dc260794e4c2eeae87b1403a88385a72c18a5844d220b88117b2965a8cf3a5"), + "entry_point_selector" to "0x2e4263afad30923c891518314c3c95dbe830a16874e8abc5777a9a20b54c76e" + ), + "latest" + ) + val request = StarknetCallRequest(params = params) + + return RetrofitInstance.api.getBalance(request) + } +} diff --git a/wallet_app/android/app/src/main/java/com/example/walletapp/ui/activity/AccountBalanceActivity.kt b/wallet_app/android/app/src/main/java/com/example/walletapp/ui/activity/AccountBalanceActivity.kt new file mode 100644 index 00000000..f073fffa --- /dev/null +++ b/wallet_app/android/app/src/main/java/com/example/walletapp/ui/activity/AccountBalanceActivity.kt @@ -0,0 +1,155 @@ +package com.example.walletapp.ui.activity + +import android.app.Activity +import android.os.Bundle +import android.util.Log +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material.Button +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Surface +import androidx.compose.material.Text +import androidx.compose.material.TextField +import androidx.compose.material.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Color.Companion.Transparent +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.core.graphics.toColorInt +import androidx.core.view.WindowCompat +import androidx.lifecycle.viewmodel.compose.viewModel +import com.example.walletapp.data.repository.StarknetRepository +import com.example.walletapp.ui.theme.WalletappTheme +import java.math.BigDecimal +import java.math.RoundingMode + + +class AccountBalanceActivity : ComponentActivity() { + private lateinit var viewModelFactory: StarknetViewModelFactory + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enableEdgeToEdge() + // Create the repository and factory + val repository = StarknetRepository() // Initialize your repository here + viewModelFactory = StarknetViewModelFactory(repository) + + WindowCompat.setDecorFitsSystemWindows(window, true) + setContent { + WalletappTheme { + Surface(modifier = Modifier.fillMaxSize()) { + AccountBalanceScreenView(viewModelFactory = viewModelFactory + ) + } + } + } + } + + @Composable + fun AccountBalanceScreenView(viewModelFactory: StarknetViewModelFactory){ + val context = (LocalContext.current as Activity) + val viewModel: StarknetViewModel = viewModel(factory = viewModelFactory) + + val contractAddress by remember { mutableStateOf("0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7") } + var accountAddress by remember { mutableStateOf("0x02dc260794e4c2eeae87b1403a88385a72c18a5844d220b88117b2965a8cf3a5") } + + val balance by viewModel.balanceLiveData.observeAsState("...") + val error by viewModel.errorLiveData.observeAsState("") + + Column(modifier = Modifier + .fillMaxSize() + .background(Color("#0C0C4F".toColorInt())) + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center) + { + TextField( + value = accountAddress, + onValueChange = { accountAddress = it }, + label = { Text("Account Address", + fontSize = 16.sp, + color = Color.White, + modifier=Modifier.padding(10.dp), + fontWeight = FontWeight.Bold) }, + modifier=Modifier.padding(15.dp), + colors = TextFieldDefaults.textFieldColors( + textColor = Color("#ffffff".toColorInt()), + disabledTextColor = Transparent, + backgroundColor = Transparent, + focusedIndicatorColor = Transparent, + unfocusedIndicatorColor = Transparent, + disabledIndicatorColor = Transparent + ) + + ) + + Spacer(modifier = Modifier.height(16.dp)) + Row(){ + Button(onClick = { + viewModel.fetchAccountBalance(contractAddress, accountAddress) + }) { + Text("Get Balance") + } + Spacer(modifier = Modifier.width(10.dp)) + Button(onClick = { + context.finish() + }) { + Text("Back to home") + } + + } + + Spacer(modifier = Modifier.height(20.dp)) + + if (error.isNotEmpty()) { + Text("Error: $error",color= MaterialTheme.colors.error,fontSize = 20.sp,fontWeight = FontWeight.Bold) + } else { + Text("Balance : ${ConvertHexToBalance(balance)}",color= Color.White,fontSize = 20.sp,fontWeight = FontWeight.Bold) + } + + + } + } + + + fun ConvertHexToBalance(hex: String): String { + return try { + // Remove "0x" prefix if present + val cleanedHex = hex.removePrefix("0x") + + // Convert hex string to BigInteger + val balanceBigInt = cleanedHex.toBigInteger(16) + + // If StarkNet uses a base unit similar to Ethereum's wei, you might need to adjust the balance + // For this example, we'll assume the balance is in the smallest unit and needs to be converted + + // Convert balance to a decimal format (adjust the divisor based on StarkNet's unit) + val balanceDecimal = BigDecimal(balanceBigInt) + .divide(BigDecimal("1000000000000000000"), 18, RoundingMode.HALF_UP) + + balanceDecimal.stripTrailingZeros().toPlainString() // Return balance without scientific notation + } catch (e: Exception) { + "Invalid Balance" // Handle invalid hex strings or other exceptions + } + } + +} \ No newline at end of file diff --git a/wallet_app/android/app/src/main/java/com/example/walletapp/ui/activity/StarknetViewModel.kt b/wallet_app/android/app/src/main/java/com/example/walletapp/ui/activity/StarknetViewModel.kt new file mode 100644 index 00000000..a5621b72 --- /dev/null +++ b/wallet_app/android/app/src/main/java/com/example/walletapp/ui/activity/StarknetViewModel.kt @@ -0,0 +1,45 @@ +package com.example.walletapp.ui.activity + +import android.util.Log +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.example.walletapp.data.model.StarknetResponse +import com.example.walletapp.data.repository.StarknetRepository +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response + + +class StarknetViewModel(private val repository: StarknetRepository) : ViewModel() { + + val balanceLiveData = MutableLiveData() + val errorLiveData = MutableLiveData() + + // Function to trigger API call to fetch balance + fun fetchAccountBalance(contractAddress: String, accountAddress: String) { + viewModelScope.launch(Dispatchers.IO) { + repository.getAccountBalance(contractAddress, accountAddress) + .enqueue(object : Callback { + override fun onResponse( + call: Call, + response: Response + ) { + if (response.isSuccessful) { + balanceLiveData.postValue(response.body()?.result?.get(0) ?: "0") + + } else { + errorLiveData.postValue("Error fetching balance") + } + } + + override fun onFailure(call: Call, t: Throwable) { + errorLiveData.postValue(t.message) + } + }) + } + } +} + diff --git a/wallet_app/android/app/src/main/java/com/example/walletapp/ui/activity/StarknetViewModelFactory.kt b/wallet_app/android/app/src/main/java/com/example/walletapp/ui/activity/StarknetViewModelFactory.kt new file mode 100644 index 00000000..6a2f02c4 --- /dev/null +++ b/wallet_app/android/app/src/main/java/com/example/walletapp/ui/activity/StarknetViewModelFactory.kt @@ -0,0 +1,15 @@ +package com.example.walletapp.ui.activity + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import com.example.walletapp.data.repository.StarknetRepository + +class StarknetViewModelFactory(private val repository: StarknetRepository) : ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + if (modelClass.isAssignableFrom(StarknetViewModel::class.java)) { + @Suppress("UNCHECKED_CAST") + return StarknetViewModel(repository) as T + } + throw IllegalArgumentException("Unknown ViewModel class") + } +} diff --git a/wallet_app/android/build.gradle.kts b/wallet_app/android/build.gradle.kts index f74b04bf..be658d4c 100644 --- a/wallet_app/android/build.gradle.kts +++ b/wallet_app/android/build.gradle.kts @@ -2,4 +2,6 @@ plugins { alias(libs.plugins.android.application) apply false alias(libs.plugins.jetbrains.kotlin.android) apply false + + id("com.google.dagger.hilt.android") version "2.50" apply false } \ No newline at end of file diff --git a/wallet_app/android/gradle/libs.versions.toml b/wallet_app/android/gradle/libs.versions.toml index b8c80f01..a6fa483c 100644 --- a/wallet_app/android/gradle/libs.versions.toml +++ b/wallet_app/android/gradle/libs.versions.toml @@ -12,6 +12,8 @@ junit = "4.13.2" junitVersion = "1.2.1" espressoCore = "3.6.1" appcompat = "1.7.0" +kotlinxCoroutinesAndroid = "1.7.3" +kotlinxCoroutinesCore = "1.7.3" lifecycleViewmodelCompose = "2.8.4" loggingInterceptor = "4.11.0" material = "1.12.0" @@ -40,6 +42,8 @@ junit = { group = "junit", name = "junit", version.ref = "junit" } androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } +kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinxCoroutinesAndroid" } +kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutinesCore" } logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "loggingInterceptor" } material = { group = "com.google.android.material", name = "material", version.ref = "material" } androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }