Skip to content
This repository was archived by the owner on Oct 20, 2025. It is now read-only.
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
29 changes: 12 additions & 17 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'org.jetbrains.kotlin.plugin.compose'
id("kotlin-kapt")
id("kotlinx-serialization")
}
Expand All @@ -20,6 +19,10 @@ android {
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

composeOptions {
kotlinCompilerExtensionVersion = "1.5.14"
}

buildTypes {
release {
minifyEnabled true
Expand All @@ -39,34 +42,32 @@ android {
compose = true
}

configurations{
all*.exclude module: 'bcprov-jdk15on'
}

packagingOptions {
resources {
excludes += ['META-INF/AL2.0', 'META-INF/LGPL2.1', 'META-INF/versions/9/OSGI-INF/MANIFEST.MF']
}
}

configurations{
all*.exclude module: 'bcprov-jdk15on'
}
}

dependencies {
implementation project(path: ':cartera')

implementation 'androidx.core:core-ktx:1.16.0'
implementation platform('org.jetbrains.kotlin:kotlin-bom:2.0.21')
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.9.1'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.9.2'
implementation 'androidx.activity:activity-compose:1.10.1'
implementation platform('androidx.compose:compose-bom:2025.06.01')
implementation "androidx.compose.runtime:runtime"
implementation 'com.google.accompanist:accompanist-navigation-material:0.36.0'

implementation 'androidx.compose.ui:ui'
implementation 'androidx.compose.ui:ui-graphics'
implementation 'androidx.compose.ui:ui-tooling-preview'
implementation 'androidx.compose.material3:material3'
implementation 'androidx.compose.material:material'
implementation 'androidx.navigation:navigation-runtime-ktx:2.9.1'
implementation 'androidx.navigation:navigation-compose:2.9.1'
implementation 'androidx.navigation:navigation-runtime-ktx:2.9.2'
implementation 'androidx.navigation:navigation-compose:2.9.2'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
Expand All @@ -82,12 +83,6 @@ dependencies {

implementation 'com.solanamobile:web3-solana:0.2.5'
implementation 'com.solanamobile:rpc-core:0.2.8'
implementation 'io.github.funkatronics:kborsh:0.1.1'
implementation 'io.github.funkatronics:multimult:0.2.4'

implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0")
implementation("io.ktor:ktor-client-core:2.3.4")
implementation("io.ktor:ktor-client-android:2.3.4")

implementation("com.squareup.okhttp3:okhttp:4.12.0")
implementation("com.google.code.gson:gson:2.13.1")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.ModalBottomSheetValue
import androidx.compose.material.rememberModalBottomSheetState
import androidx.compose.material3.MaterialTheme
Expand All @@ -27,7 +28,7 @@ import exchange.dydx.cartera.walletprovider.providers.WalletConnectModalProvider

class MainActivity : ComponentActivity() {

@OptIn(ExperimentalMaterialNavigationApi::class)
@OptIn(ExperimentalMaterialNavigationApi::class, ExperimentalMaterialApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

Expand Down
3 changes: 3 additions & 0 deletions app/src/main/java/exchange/dydx/carteraExample/WalletList.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.Divider
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.MaterialTheme
import androidx.compose.material.ModalBottomSheetLayout
import androidx.compose.material.ModalBottomSheetState
Expand Down Expand Up @@ -75,6 +76,7 @@ object WalletList {
var useWcModal: Boolean by mutableStateOf(false)
}

@OptIn(ExperimentalMaterialApi::class)
@SuppressLint("CoroutineCreationDuringComposition")
@Composable
fun Content() {
Expand Down Expand Up @@ -119,6 +121,7 @@ object WalletList {
}
}

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun WalletListContent(
viewState: WalletList.WalletListState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import exchange.dydx.cartera.CarteraConfig
import exchange.dydx.cartera.CarteraConstants
import exchange.dydx.cartera.CarteraProvider
import exchange.dydx.cartera.entities.Wallet
import exchange.dydx.cartera.solana.SolanaInteractor
import exchange.dydx.cartera.tag
import exchange.dydx.cartera.typeddata.EIP712DomainTypedDataProvider
import exchange.dydx.cartera.typeddata.WalletTypedData
Expand All @@ -22,7 +23,6 @@ import exchange.dydx.cartera.walletprovider.WalletRequest
import exchange.dydx.cartera.walletprovider.WalletStatusDelegate
import exchange.dydx.cartera.walletprovider.WalletStatusProtocol
import exchange.dydx.cartera.walletprovider.WalletTransactionRequest
import exchange.dydx.carteraexample.solana.SolanaInteractor
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
Expand Down
13 changes: 6 additions & 7 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,12 @@ buildscript {

// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id 'com.android.application' version '8.11.0' apply false
id 'com.android.library' version '8.11.0' apply false
id 'org.jetbrains.kotlin.android' version '2.2.0' apply false
id 'org.jetbrains.kotlin.plugin.compose' version '2.2.0' apply false
id 'com.google.dagger.hilt.android' version '2.56.2' apply false
id "com.diffplug.spotless" version "7.0.4" // apply false
id "org.jetbrains.kotlin.plugin.serialization" version "2.2.0" apply false
id 'com.android.application' version '8.11.1' apply false
id 'com.android.library' version '8.11.1' apply false
id 'org.jetbrains.kotlin.android' version '1.9.24' apply false
id 'com.google.dagger.hilt.android' version '2.57' apply false
id "com.diffplug.spotless" version "7.2.1" // apply false
id "org.jetbrains.kotlin.plugin.serialization" version "1.9.24" apply false
}

allprojects {
Expand Down
7 changes: 5 additions & 2 deletions cartera/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,17 @@ android {
}

dependencies {
implementation 'androidx.core:core-ktx:1.15.0'
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'androidx.core:core-ktx:1.16.0'
implementation 'androidx.appcompat:appcompat:1.7.1'
implementation 'com.google.android.material:material:1.12.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
// implementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava'

implementation 'com.solanamobile:web3-solana:0.2.5'
implementation 'com.solanamobile:rpc-core:0.2.8'

implementation 'com.google.code.gson:gson:2.13.1'

//
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package exchange.dydx.carteraexample.solana
package exchange.dydx.cartera.solana
import com.google.gson.Gson
import com.google.gson.annotations.SerializedName
import com.solana.publickey.SolanaPublicKey
Expand All @@ -10,12 +10,15 @@ import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody
import timber.log.Timber
import kotlin.math.max
import kotlin.math.pow

class SolanaInteractor(
private val rpcUrl: String,
) {
private val TAG = "SolanaInteractor"

companion object {
val mainnetUrl = "https://api.mainnet-beta.solana.com"
val devnetUrl = "https://api.devnet.solana.com"
Expand All @@ -41,14 +44,22 @@ class SolanaInteractor(
.post(requestBody)
.build()

val response = client.newCall(request).execute()
if (!response.isSuccessful) {
println("Request failed: ${response.code}")
try {
val response = client.newCall(request).execute()
if (!response.isSuccessful) {
Timber.tag(TAG).e("Request failed: ${response.code}")
return@withContext null
}

val responseBody = response.body?.string() ?: return@withContext null
return@withContext gson.fromJson(
responseBody,
LatestBlockhashResponse::class.java,
).result
} catch (e: Exception) {
Timber.tag(TAG).e("Request failed: ${e.message}")
return@withContext null
}

val responseBody = response.body?.string() ?: return@withContext null
return@withContext gson.fromJson(responseBody, LatestBlockhashResponse::class.java).result
}

suspend fun getBalance(publicKey: String): Double? = withContext(Dispatchers.IO) {
Expand All @@ -72,15 +83,20 @@ class SolanaInteractor(
.post(requestBody)
.build()

val response = client.newCall(request).execute()
if (!response.isSuccessful) {
println("Request failed: ${response.code}")
try {
val response = client.newCall(request).execute()
if (!response.isSuccessful) {
Timber.tag(TAG).e("Request failed: ${response.code}")
return@withContext null
}

val body = response.body?.string() ?: return@withContext null
val parsed = gson.fromJson(body, BalanceResponse::class.java)
return@withContext parsed.result.value.toDouble() / 10.0.pow(9.0)
} catch (e: Exception) {
Timber.tag(TAG).e("Request failed: ${e.message}")
return@withContext null
}

val body = response.body?.string() ?: return@withContext null
val parsed = gson.fromJson(body, BalanceResponse::class.java)
return@withContext parsed.result.value.toDouble() / 10.0.pow(9.0)
}

suspend fun getTokenBalance(publicKey: String, tokenAddress: String): Double? = withContext(Dispatchers.IO) {
Expand Down Expand Up @@ -110,22 +126,71 @@ class SolanaInteractor(
.post(requestBody)
.build()

val response = client.newCall(request).execute()
if (!response.isSuccessful) {
println("Request failed: ${response.code}")
try {
val response = client.newCall(request).execute()
if (!response.isSuccessful) {
Timber.tag(TAG).e("Request failed: ${response.code}")
return@withContext null
}

try {
val parsed =
gson.fromJson(response.body?.string(), TokenAccountsResponse::class.java)
var balance = 0.0f
for (account in parsed.result.value) {
val tokenAmount = account.account.data.parsed.info.tokenAmount.uiAmount
balance = max(balance, tokenAmount)
}
return@withContext balance.toDouble()
} catch (e: Exception) {
Timber.tag(TAG).e("Failed to parse response: ${e.message}")
return@withContext null
}
} catch (e: Exception) {
Timber.tag(TAG).e("Request failed: ${e.message}")
return@withContext null
}
}

suspend fun sendRawTransaction(base58Tx: String): String? = withContext(Dispatchers.IO) {
val client = OkHttpClient()
val gson = Gson()

val json = mapOf(
"jsonrpc" to "2.0",
"id" to 1,
"method" to "sendTransaction",
"params" to listOf(base58Tx, mapOf("encoding" to "base58")),
)

val requestBody = RequestBody.create(
"application/json; charset=utf-8".toMediaTypeOrNull(),
gson.toJson(json),
)

val request = Request.Builder()
.url(rpcUrl)
.post(requestBody)
.build()

try {
val parsed = gson.fromJson(response.body?.string(), TokenAccountsResponse::class.java)
var balance = 0.0f
for (account in parsed.result.value) {
val tokenAmount = account.account.data.parsed.info.tokenAmount.uiAmount
balance = max(balance, tokenAmount)
val response = client.newCall(request).execute()
if (!response.isSuccessful) {
println("Request failed: ${response.code}")
return@withContext null
}

try {
val json = response.body?.string() ?: return@withContext null
val parsed =
gson.fromJson(json, SendTransactionResponse::class.java)
return@withContext parsed.result
} catch (e: Exception) {
Timber.tag(TAG).e("Failed to parse response: ${e.message}")
return@withContext null
}
return@withContext balance.toDouble()
} catch (e: Exception) {
println("Failed to parse response: ${e.message}")
Timber.tag(TAG).e("Request failed: ${e.message}")
return@withContext null
}
}
Expand Down Expand Up @@ -213,3 +278,9 @@ data class TokenAmount(
val decimals: Int,
val uiAmount: Float
)

data class SendTransactionResponse(
val jsonrpc: String,
val result: String, // the transaction signature (base58)
val id: Int
)
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package exchange.dydx.carteraexample.solana
package exchange.dydx.cartera.solana

import com.solana.publickey.SolanaPublicKey
import com.solana.transaction.AccountMeta
Expand Down
Loading
Loading