From 0ca82e4f8fa85aa9918416606468b845be0008d5 Mon Sep 17 00:00:00 2001 From: Rui Date: Mon, 21 Jul 2025 20:45:28 -0700 Subject: [PATCH 1/8] WIP --- app/build.gradle | 10 +++++----- .../dydx/carteraExample/WalletListViewModel.kt | 2 +- build.gradle | 8 ++++---- cartera/build.gradle | 7 +++++-- .../exchange/dydx/cartera}/solana/SolanaInteractor.kt | 2 +- .../exchange/dydx/cartera}/solana/SystemProgram.kt | 2 +- 6 files changed, 17 insertions(+), 14 deletions(-) rename {app/src/main/java/exchange/dydx/carteraExample => cartera/src/main/java/exchange/dydx/cartera}/solana/SolanaInteractor.kt (99%) rename {app/src/main/java/exchange/dydx/carteraExample => cartera/src/main/java/exchange/dydx/cartera}/solana/SystemProgram.kt (96%) diff --git a/app/build.gradle b/app/build.gradle index 34e2325..42e2b43 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -65,8 +65,8 @@ dependencies { 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' @@ -86,9 +86,9 @@ dependencies { 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("io.ktor:ktor-client-core:3.2.2") + implementation("io.ktor:ktor-client-android:3.2.2") - implementation("com.squareup.okhttp3:okhttp:4.12.0") + implementation("com.squareup.okhttp3:okhttp:5.1.0") implementation("com.google.code.gson:gson:2.13.1") } \ No newline at end of file diff --git a/app/src/main/java/exchange/dydx/carteraExample/WalletListViewModel.kt b/app/src/main/java/exchange/dydx/carteraExample/WalletListViewModel.kt index 35e1eb3..5d82db4 100644 --- a/app/src/main/java/exchange/dydx/carteraExample/WalletListViewModel.kt +++ b/app/src/main/java/exchange/dydx/carteraExample/WalletListViewModel.kt @@ -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 @@ -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 diff --git a/build.gradle b/build.gradle index a52f4a6..83c11be 100644 --- a/build.gradle +++ b/build.gradle @@ -10,12 +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 '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 '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 '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 "2.2.0" apply false } diff --git a/cartera/build.gradle b/cartera/build.gradle index 3206d49..5281293 100644 --- a/cartera/build.gradle +++ b/cartera/build.gradle @@ -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' // diff --git a/app/src/main/java/exchange/dydx/carteraExample/solana/SolanaInteractor.kt b/cartera/src/main/java/exchange/dydx/cartera/solana/SolanaInteractor.kt similarity index 99% rename from app/src/main/java/exchange/dydx/carteraExample/solana/SolanaInteractor.kt rename to cartera/src/main/java/exchange/dydx/cartera/solana/SolanaInteractor.kt index be14f0e..ce94a06 100644 --- a/app/src/main/java/exchange/dydx/carteraExample/solana/SolanaInteractor.kt +++ b/cartera/src/main/java/exchange/dydx/cartera/solana/SolanaInteractor.kt @@ -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 diff --git a/app/src/main/java/exchange/dydx/carteraExample/solana/SystemProgram.kt b/cartera/src/main/java/exchange/dydx/cartera/solana/SystemProgram.kt similarity index 96% rename from app/src/main/java/exchange/dydx/carteraExample/solana/SystemProgram.kt rename to cartera/src/main/java/exchange/dydx/cartera/solana/SystemProgram.kt index e2a16e5..907d33b 100644 --- a/app/src/main/java/exchange/dydx/carteraExample/solana/SystemProgram.kt +++ b/cartera/src/main/java/exchange/dydx/cartera/solana/SystemProgram.kt @@ -1,4 +1,4 @@ -package exchange.dydx.carteraexample.solana +package exchange.dydx.cartera.solana import com.solana.publickey.SolanaPublicKey import com.solana.transaction.AccountMeta From d00a21cf19be8ba6b2f065fbe46226c3e254d994 Mon Sep 17 00:00:00 2001 From: Rui Date: Mon, 21 Jul 2025 20:50:07 -0700 Subject: [PATCH 2/8] Update libs --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 42e2b43..b7f2d54 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -89,6 +89,6 @@ dependencies { implementation("io.ktor:ktor-client-core:3.2.2") implementation("io.ktor:ktor-client-android:3.2.2") - implementation("com.squareup.okhttp3:okhttp:5.1.0") + implementation("com.squareup.okhttp3:okhttp:4.12.0") implementation("com.google.code.gson:gson:2.13.1") } \ No newline at end of file From aef4329a8575637d402b2689aab1fac7ffc20a1b Mon Sep 17 00:00:00 2001 From: Rui Date: Tue, 22 Jul 2025 10:56:44 -0700 Subject: [PATCH 3/8] WIP --- app/build.gradle | 46 +++++++++++-------- build.gradle | 13 +++--- cartera/build.gradle | 4 +- .../dydx/cartera/solana/SolanaInteractor.kt | 32 +++++++++++++ gradle.properties | 2 +- 5 files changed, 69 insertions(+), 28 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index b7f2d54..95962ab 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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") } @@ -20,6 +19,10 @@ android { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } + composeOptions { + kotlinCompilerExtensionVersion = "1.5.14" + } + buildTypes { release { minifyEnabled true @@ -39,34 +42,34 @@ 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.core:core-ktx:1.15.0' + implementation platform('org.jetbrains.kotlin:kotlin-bom:1.9.24') + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.8.7' 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 platform('androidx.compose:compose-bom:2025.03.00') + implementation 'com.google.accompanist:accompanist-navigation-material:0.34.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.2' - implementation 'androidx.navigation:navigation-compose:2.9.2' + implementation 'androidx.navigation:navigation-runtime-ktx:2.8.9' + implementation 'androidx.navigation:navigation-compose:2.8.9' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.2.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' @@ -74,6 +77,13 @@ dependencies { debugImplementation 'androidx.compose.ui:ui-tooling' debugImplementation 'androidx.compose.ui:ui-test-manifest' +// implementation platform("androidx.compose:compose-bom:2024.05.00") // Example BOM version +// implementation "androidx.compose.ui:ui" +// implementation "androidx.compose.material:material" +// implementation "androidx.compose.ui:ui-tooling-preview" +// debugImplementation "androidx.compose.ui:ui-tooling" +// debugImplementation "androidx.compose.ui:ui-test-manifest" + implementation 'com.github.kenglxn.QRGen:android:3.0.1' implementation platform('com.walletconnect:android-bom:1.35.2') @@ -82,12 +92,12 @@ 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 '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:3.2.2") - implementation("io.ktor:ktor-client-android:3.2.2") +// implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0") +// implementation("io.ktor:ktor-client-core:3.2.2") +// implementation("io.ktor:ktor-client-android:3.2.2") implementation("com.squareup.okhttp3:okhttp:4.12.0") implementation("com.google.code.gson:gson:2.13.1") diff --git a/build.gradle b/build.gradle index 83c11be..935fe8c 100644 --- a/build.gradle +++ b/build.gradle @@ -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.1' apply false - id 'com.android.library' version '8.11.1' 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.57' apply false - id "com.diffplug.spotless" version "7.2.1" // apply false - id "org.jetbrains.kotlin.plugin.serialization" version "2.2.0" apply false + id 'com.android.application' version '8.9.0' apply false + id 'com.android.library' version '8.9.0' apply false + id 'org.jetbrains.kotlin.android' version '1.9.24' apply false + id 'com.google.dagger.hilt.android' version '2.41' apply false + id "com.diffplug.spotless" version "6.22.0" // apply false + id "org.jetbrains.kotlin.plugin.serialization" version "1.9.24" apply false } allprojects { diff --git a/cartera/build.gradle b/cartera/build.gradle index 5281293..337c1af 100644 --- a/cartera/build.gradle +++ b/cartera/build.gradle @@ -98,5 +98,5 @@ task sourceJar(type: Jar) { // !!!! Switch between local and remote publishing here: !!!! // IMPORTANT: Be sure to switch back to "publishRemote" before committing. Otherwise, the deployment will fail. -//apply from: 'publishLocal.gradle' -apply from: 'publishRemote.gradle' \ No newline at end of file +apply from: 'publishLocal.gradle' +//apply from: 'publishRemote.gradle' \ No newline at end of file diff --git a/cartera/src/main/java/exchange/dydx/cartera/solana/SolanaInteractor.kt b/cartera/src/main/java/exchange/dydx/cartera/solana/SolanaInteractor.kt index ce94a06..d21696e 100644 --- a/cartera/src/main/java/exchange/dydx/cartera/solana/SolanaInteractor.kt +++ b/cartera/src/main/java/exchange/dydx/cartera/solana/SolanaInteractor.kt @@ -1,4 +1,5 @@ package exchange.dydx.cartera.solana +import android.content.Context import com.google.gson.Gson import com.google.gson.annotations.SerializedName import com.solana.publickey.SolanaPublicKey @@ -130,6 +131,37 @@ class SolanaInteractor( } } + suspend fun sendRawTransaction(base64Tx: 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(base64Tx, mapOf("encoding" to "base64")), + ) + + val requestBody = RequestBody.create( + "application/json; charset=utf-8".toMediaTypeOrNull(), + gson.toJson(json), + ) + + val request = Request.Builder() + .url(rpcUrl) + .post(requestBody) + .build() + + val response = client.newCall(request).execute() + if (!response.isSuccessful) { + println("Request failed: ${response.code}") + return@withContext null + } + + val body = response.body?.string() ?: return@withContext null + return@withContext body + } + fun buildTestMemoTransaction(address: SolanaPublicKey, memo: String) = TransactionInstruction( programId = SystemProgram.programId, diff --git a/gradle.properties b/gradle.properties index 52cb355..1b99ea2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -26,6 +26,6 @@ android.nonTransitiveRClass=true LIBRARY_GROUP=dydxprotocol LIBRARY_ARTIFACT_ID=cartera-android -LIBRARY_VERSION_NAME=0.1.23 +LIBRARY_VERSION_NAME=0.1.24 android.enableR8.fullMode = false \ No newline at end of file From 2de6937f0f0def2013041267d039c6d1fe7fee2b Mon Sep 17 00:00:00 2001 From: Rui Date: Tue, 22 Jul 2025 11:02:45 -0700 Subject: [PATCH 4/8] Clean up --- app/build.gradle | 25 ++++--------------- .../dydx/carteraExample/MainActivity.kt | 3 ++- .../dydx/carteraExample/WalletList.kt | 3 +++ 3 files changed, 10 insertions(+), 21 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 95962ab..cebe6ca 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -56,20 +56,18 @@ android { dependencies { implementation project(path: ':cartera') - implementation 'androidx.core:core-ktx:1.15.0' - implementation platform('org.jetbrains.kotlin:kotlin-bom:1.9.24') - implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.8.7' + implementation 'androidx.core:core-ktx:1.16.0' + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.9.2' implementation 'androidx.activity:activity-compose:1.10.1' - implementation platform('androidx.compose:compose-bom:2025.03.00') - implementation 'com.google.accompanist:accompanist-navigation-material:0.34.0' + 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.8.9' - implementation 'androidx.navigation:navigation-compose:2.8.9' + 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' @@ -77,13 +75,6 @@ dependencies { debugImplementation 'androidx.compose.ui:ui-tooling' debugImplementation 'androidx.compose.ui:ui-test-manifest' -// implementation platform("androidx.compose:compose-bom:2024.05.00") // Example BOM version -// implementation "androidx.compose.ui:ui" -// implementation "androidx.compose.material:material" -// implementation "androidx.compose.ui:ui-tooling-preview" -// debugImplementation "androidx.compose.ui:ui-tooling" -// debugImplementation "androidx.compose.ui:ui-test-manifest" - implementation 'com.github.kenglxn.QRGen:android:3.0.1' implementation platform('com.walletconnect:android-bom:1.35.2') @@ -92,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:3.2.2") -// implementation("io.ktor:ktor-client-android:3.2.2") implementation("com.squareup.okhttp3:okhttp:4.12.0") implementation("com.google.code.gson:gson:2.13.1") diff --git a/app/src/main/java/exchange/dydx/carteraExample/MainActivity.kt b/app/src/main/java/exchange/dydx/carteraExample/MainActivity.kt index 36037f4..d2e1fda 100644 --- a/app/src/main/java/exchange/dydx/carteraExample/MainActivity.kt +++ b/app/src/main/java/exchange/dydx/carteraExample/MainActivity.kt @@ -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 @@ -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) diff --git a/app/src/main/java/exchange/dydx/carteraExample/WalletList.kt b/app/src/main/java/exchange/dydx/carteraExample/WalletList.kt index 3822218..c22ed9e 100644 --- a/app/src/main/java/exchange/dydx/carteraExample/WalletList.kt +++ b/app/src/main/java/exchange/dydx/carteraExample/WalletList.kt @@ -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 @@ -75,6 +76,7 @@ object WalletList { var useWcModal: Boolean by mutableStateOf(false) } + @OptIn(ExperimentalMaterialApi::class) @SuppressLint("CoroutineCreationDuringComposition") @Composable fun Content() { @@ -119,6 +121,7 @@ object WalletList { } } + @OptIn(ExperimentalMaterialApi::class) @Composable fun WalletListContent( viewState: WalletList.WalletListState, From 12c1b5427a61f220493a29d866c65da9d5257a22 Mon Sep 17 00:00:00 2001 From: Rui Date: Tue, 22 Jul 2025 13:26:04 -0700 Subject: [PATCH 5/8] Working --- .../dydx/cartera/solana/SolanaInteractor.kt | 110 ++++++++++++------ .../providers/PhantomWalletProvider.kt | 64 +++++++--- 2 files changed, 124 insertions(+), 50 deletions(-) diff --git a/cartera/src/main/java/exchange/dydx/cartera/solana/SolanaInteractor.kt b/cartera/src/main/java/exchange/dydx/cartera/solana/SolanaInteractor.kt index d21696e..5ae0e73 100644 --- a/cartera/src/main/java/exchange/dydx/cartera/solana/SolanaInteractor.kt +++ b/cartera/src/main/java/exchange/dydx/cartera/solana/SolanaInteractor.kt @@ -1,5 +1,4 @@ package exchange.dydx.cartera.solana -import android.content.Context import com.google.gson.Gson import com.google.gson.annotations.SerializedName import com.solana.publickey.SolanaPublicKey @@ -11,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" @@ -42,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) { @@ -73,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) { @@ -111,27 +126,33 @@ class SolanaInteractor( .post(requestBody) .build() - val response = client.newCall(request).execute() - if (!response.isSuccessful) { - println("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) + 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 } - 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 } } - suspend fun sendRawTransaction(base64Tx: String): String? = withContext(Dispatchers.IO) { + suspend fun sendRawTransaction(base58Tx: String): String? = withContext(Dispatchers.IO) { val client = OkHttpClient() val gson = Gson() @@ -139,7 +160,7 @@ class SolanaInteractor( "jsonrpc" to "2.0", "id" to 1, "method" to "sendTransaction", - "params" to listOf(base64Tx, mapOf("encoding" to "base64")), + "params" to listOf(base58Tx, mapOf("encoding" to "base58")), ) val requestBody = RequestBody.create( @@ -152,14 +173,27 @@ 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) { + 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 + } + } catch (e: Exception) { + Timber.tag(TAG).e("Request failed: ${e.message}") return@withContext null } - - val body = response.body?.string() ?: return@withContext null - return@withContext body } fun buildTestMemoTransaction(address: SolanaPublicKey, memo: String) = @@ -245,3 +279,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 +) \ No newline at end of file diff --git a/cartera/src/main/java/exchange/dydx/cartera/walletprovider/providers/PhantomWalletProvider.kt b/cartera/src/main/java/exchange/dydx/cartera/walletprovider/providers/PhantomWalletProvider.kt index b4375ba..13daa8f 100644 --- a/cartera/src/main/java/exchange/dydx/cartera/walletprovider/providers/PhantomWalletProvider.kt +++ b/cartera/src/main/java/exchange/dydx/cartera/walletprovider/providers/PhantomWalletProvider.kt @@ -13,6 +13,7 @@ import exchange.dydx.cartera.PhantomWalletConfig import exchange.dydx.cartera.decodeBase58 import exchange.dydx.cartera.encodeToBase58String import exchange.dydx.cartera.entities.Wallet +import exchange.dydx.cartera.solana.SolanaInteractor import exchange.dydx.cartera.tag import exchange.dydx.cartera.typeddata.WalletTypedDataProviderProtocol import exchange.dydx.cartera.typeddata.typedDataAsString @@ -362,6 +363,29 @@ class PhantomWalletProvider( ) } + private fun doSignTransaction( + transaction: String, + completion: WalletOperationCompletion + ) { + val request = SignTransactionRequest( + session = session, + transaction = transaction, + ) + val uri = createRequestUri( + request = Gson().toJson(request), + action = CallbackAction.onSignTransaction, + ) + if (uri != null) { + if (openPeerDeeplink(uri)) { + operationCompletion = completion + } else { + completion(null, WalletError(CarteraErrorCode.UNEXPECTED_RESPONSE, "Failed to open Phantom app")) + } + } else { + completion(null, WalletError(CarteraErrorCode.UNEXPECTED_RESPONSE, "Failed to create request URI")) + } + } + override fun send( request: WalletTransactionRequest, connected: WalletConnectedCompletion?, @@ -388,22 +412,32 @@ class PhantomWalletProvider( return } - val sendRequest = SendTransactionRequest( - session = session, - transaction = data.encodeToBase58String(), - ) - val uri = createRequestUri( - request = Gson().toJson(sendRequest), - action = CallbackAction.onSendTransaction, - ) - if (uri != null) { - if (openPeerDeeplink(uri)) { - operationCompletion = completion - } else { - completion(null, WalletError(CarteraErrorCode.UNEXPECTED_RESPONSE, "Failed to open Phantom app")) + doSignTransaction(transaction = data.encodeToBase58String()) { signed, error -> + if (error != null || signed == null) { + completion(null, error) + return@doSignTransaction + } + + val isMainnet = request.walletRequest.chainId == "1" + val solanaInteractor = SolanaInteractor( + rpcUrl = if (isMainnet) { + SolanaInteractor.mainnetUrl + } else { + SolanaInteractor.devnetUrl + } + ) + + val scope = CoroutineScope(Dispatchers.Unconfined) + scope.launch { + val hash = solanaInteractor.sendRawTransaction(signed) + CoroutineScope(Dispatchers.Main).launch { + if (hash != null) { + completion(hash, null) + } else { + completion(null, WalletError(CarteraErrorCode.UNEXPECTED_RESPONSE, "Failed to send transaction")) + } + } } - } else { - completion(null, WalletError(CarteraErrorCode.UNEXPECTED_RESPONSE, "Failed to create request URI")) } } From 7c999c684b4e0895e54d541ff42824e38fbda859 Mon Sep 17 00:00:00 2001 From: Rui Date: Tue, 22 Jul 2025 13:35:53 -0700 Subject: [PATCH 6/8] Update lib versions --- build.gradle | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index 935fe8c..3d2c20e 100644 --- a/build.gradle +++ b/build.gradle @@ -10,11 +10,11 @@ buildscript { // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id 'com.android.application' version '8.9.0' apply false - id 'com.android.library' version '8.9.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.41' apply false - id "com.diffplug.spotless" version "6.22.0" // 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 } From 54bcd1dccd2bc71997957200e66072035b2ccc95 Mon Sep 17 00:00:00 2001 From: Rui Date: Tue, 22 Jul 2025 13:37:25 -0700 Subject: [PATCH 7/8] Lint --- cartera/build.gradle | 4 ++-- .../java/exchange/dydx/cartera/solana/SolanaInteractor.kt | 5 ++--- .../walletprovider/providers/PhantomWalletProvider.kt | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/cartera/build.gradle b/cartera/build.gradle index 337c1af..5281293 100644 --- a/cartera/build.gradle +++ b/cartera/build.gradle @@ -98,5 +98,5 @@ task sourceJar(type: Jar) { // !!!! Switch between local and remote publishing here: !!!! // IMPORTANT: Be sure to switch back to "publishRemote" before committing. Otherwise, the deployment will fail. -apply from: 'publishLocal.gradle' -//apply from: 'publishRemote.gradle' \ No newline at end of file +//apply from: 'publishLocal.gradle' +apply from: 'publishRemote.gradle' \ No newline at end of file diff --git a/cartera/src/main/java/exchange/dydx/cartera/solana/SolanaInteractor.kt b/cartera/src/main/java/exchange/dydx/cartera/solana/SolanaInteractor.kt index 5ae0e73..5cef238 100644 --- a/cartera/src/main/java/exchange/dydx/cartera/solana/SolanaInteractor.kt +++ b/cartera/src/main/java/exchange/dydx/cartera/solana/SolanaInteractor.kt @@ -174,7 +174,6 @@ class SolanaInteractor( .build() try { - val response = client.newCall(request).execute() if (!response.isSuccessful) { println("Request failed: ${response.code}") @@ -190,7 +189,7 @@ class SolanaInteractor( Timber.tag(TAG).e("Failed to parse response: ${e.message}") return@withContext null } - } catch (e: Exception) { + } catch (e: Exception) { Timber.tag(TAG).e("Request failed: ${e.message}") return@withContext null } @@ -284,4 +283,4 @@ data class SendTransactionResponse( val jsonrpc: String, val result: String, // the transaction signature (base58) val id: Int -) \ No newline at end of file +) diff --git a/cartera/src/main/java/exchange/dydx/cartera/walletprovider/providers/PhantomWalletProvider.kt b/cartera/src/main/java/exchange/dydx/cartera/walletprovider/providers/PhantomWalletProvider.kt index 13daa8f..dcc02bc 100644 --- a/cartera/src/main/java/exchange/dydx/cartera/walletprovider/providers/PhantomWalletProvider.kt +++ b/cartera/src/main/java/exchange/dydx/cartera/walletprovider/providers/PhantomWalletProvider.kt @@ -424,7 +424,7 @@ class PhantomWalletProvider( SolanaInteractor.mainnetUrl } else { SolanaInteractor.devnetUrl - } + }, ) val scope = CoroutineScope(Dispatchers.Unconfined) From c7e7a801f7cdd5a25cb026be3314e0adffc7f8b8 Mon Sep 17 00:00:00 2001 From: Rui Date: Tue, 22 Jul 2025 13:44:10 -0700 Subject: [PATCH 8/8] Lint --- .../providers/PhantomWalletProvider.kt | 33 ------------------- 1 file changed, 33 deletions(-) diff --git a/cartera/src/main/java/exchange/dydx/cartera/walletprovider/providers/PhantomWalletProvider.kt b/cartera/src/main/java/exchange/dydx/cartera/walletprovider/providers/PhantomWalletProvider.kt index dcc02bc..9e2e777 100644 --- a/cartera/src/main/java/exchange/dydx/cartera/walletprovider/providers/PhantomWalletProvider.kt +++ b/cartera/src/main/java/exchange/dydx/cartera/walletprovider/providers/PhantomWalletProvider.kt @@ -48,7 +48,6 @@ class PhantomWalletProvider( onDisconnect("disconnect"), onSignMessage("signMessage"), onSignTransaction("signTransaction"), - onSendTransaction("signAndSendTransaction") } private var _walletStatus = WalletStatusImp() @@ -196,38 +195,6 @@ class PhantomWalletProvider( } } } - - CallbackAction.onSendTransaction.name -> { - if (operationCompletion != null) { - if (errorCode != null) { - CoroutineScope(Dispatchers.Main).launch { - operationCompletion?.invoke(null, WalletError(CarteraErrorCode.UNEXPECTED_RESPONSE, errorMessage)) - operationCompletion = null - } - } else { - val data = decryptPayload( - payload = uri.getQueryParameter("data"), - nonce = uri.getQueryParameter("nonce"), - ) - val response = try { - Gson().fromJson(data?.decodeToString(), SendTransactionResponse::class.java) - } catch (e: Exception) { - null - } - if (response != null) { - CoroutineScope(Dispatchers.Main).launch { - operationCompletion?.invoke(response.signature, null) - operationCompletion = null - } - } else { - CoroutineScope(Dispatchers.Main).launch { - operationCompletion?.invoke(null, WalletError(CarteraErrorCode.UNEXPECTED_RESPONSE, "Failed to decrypt payload")) - operationCompletion = null - } - } - } - } - } } return true