├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── minijoy │ │ └── particle_connect_android │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── connect │ │ │ └── demo │ │ │ ├── App.kt │ │ │ ├── base │ │ │ └── BaseActivity.kt │ │ │ ├── controller │ │ │ ├── main │ │ │ │ ├── AccountAdapter.kt │ │ │ │ └── MainActivity.kt │ │ │ ├── manage │ │ │ │ ├── ConnectAdapter.kt │ │ │ │ ├── ManageActivity.kt │ │ │ │ └── WalletConnectFragment.kt │ │ │ ├── reference │ │ │ │ └── ReferenceActivity.kt │ │ │ └── secret │ │ │ │ └── ImportWalletActivity.kt │ │ │ ├── custom_connectadapter │ │ │ └── Coin98ConnectAdapter.kt │ │ │ ├── model │ │ │ ├── RpcRequest.kt │ │ │ ├── RpcResponse.kt │ │ │ ├── SerializeTransaction.kt │ │ │ ├── TransactionAddressData.kt │ │ │ └── WalletAccount.kt │ │ │ ├── transaction │ │ │ ├── SolanaRpcRepository.kt │ │ │ └── SolanaTransactionManager.kt │ │ │ └── utils │ │ │ ├── BarcodeEncoder.kt │ │ │ ├── ChainUtils.kt │ │ │ ├── CoilLoader.kt │ │ │ ├── ContextExt.kt │ │ │ ├── MockManger.kt │ │ │ ├── QrParams.kt │ │ │ ├── SolanaRpcApi.kt │ │ │ └── StreamUtils.kt │ └── res │ │ ├── drawable-xxhdpi │ │ ├── arbitrum.png │ │ ├── aurora.png │ │ ├── avalanche.png │ │ ├── bsc.png │ │ ├── ethereum.png │ │ ├── fantom.png │ │ ├── harmony.png │ │ ├── heco.png │ │ ├── ic_logo.png │ │ ├── kcc.png │ │ ├── moonbeam.png │ │ ├── moonriver.png │ │ ├── optimism.png │ │ ├── polygon.png │ │ └── solana.png │ │ ├── drawable │ │ ├── edit_bg.xml │ │ ├── ic_create_wallet.xml │ │ ├── ic_delete.xml │ │ ├── ic_edit.xml │ │ ├── ic_left.xml │ │ ├── ic_more.xml │ │ ├── ic_right.xml │ │ ├── ic_wallet.xml │ │ ├── list_item_divider.xml │ │ └── popup_window_transparent.xml │ │ ├── layout │ │ ├── activity_import_wallet.xml │ │ ├── activity_main.xml │ │ ├── activity_manage.xml │ │ ├── activity_reference.xml │ │ ├── edit_wallet_name_layout.xml │ │ ├── fragment_wallet_connect.xml │ │ ├── item_account.xml │ │ └── item_adapter.xml │ │ ├── menu │ │ ├── delete_action.xml │ │ └── toolbar_action.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── raw │ │ └── typed_data.json │ │ └── values │ │ ├── colors.xml │ │ ├── ic_launcher_background.xml │ │ ├── strings.xml │ │ └── themes.xml │ └── test │ └── java │ └── com │ └── minijoy │ └── particle_connect_android │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/network.particle/connect/badge.svg?style=flat)](https://search.maven.org/artifact/network.particle/connect) 2 | 3 | # Particle Connect Android 4 | The best way to connect a wallet. Support multi chains and multi wallet. Learn more visit [Particle Network](https://docs.particle.network/). 5 | 6 | 7 | 8 | 9 | ## Summary 10 | 11 | Modular Kotlin wallet adapters and components for EVM & Solana chains. Manage wallet and custom RPC request. 12 | 13 | ![Particle Connect](https://static.particle.network/docs-images/particle-connect.jpeg) 14 | 15 | ## Quick Start 16 | 17 | ``` 18 | dependencies { 19 | //required dependencies 20 | implementation("network.particle:auth-service:{latest-version}") 21 | implementation("network.particle:connect-common:${latest-version}") 22 | implementation 'network.particle:connect:{latest-version}' 23 | 24 | //Optional: support evm chain Generate & Import wallet 25 | implementation 'network.particle:connect-evm-adapter:{latest-version}' 26 | 27 | //Optional: support solana chain Generate & Import wallet 28 | implementation 'network.particle:connect-solana-adapter:{latest-version}' 29 | 30 | //Optional: support connect Phantom wallet 31 | implementation 'network.particle:connect-phantom-adapter:{latest-version}' 32 | 33 | //Optional: support WalletConnect Protocol, include MetaMask, Rainbow, Trust, imToken etc. 34 | implementation 'network.particle:connect-wallet-connect-adapter:{latest-version}' 35 | } 36 | ``` 37 | 38 | Add below config to AndroidManifest.xml 39 | 40 | ``` 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 78 | 82 | 86 | 87 | 88 | ``` 89 | 90 | Init Particle Connect in `Application#Create()` 91 | 92 | ``` 93 | ParticleConnect.init( 94 | this, 95 | Env.DEV, //debug mode 96 | EthereumChain(EthereumChainId.Kovan), //chain info 97 | DAppMetadata( 98 | "Particle Connect", 99 | "https://static.particle.network/wallet-icons/Particle.png", 100 | "https://particle.network" 101 | ) //DApp or Wallet info 102 | ) { 103 | listOf( 104 | ParticleConnectAdapter(), 105 | MetaMaskConnectAdapter(), 106 | RainbowConnectAdapter(), 107 | TrustConnectAdapter(), 108 | ImTokenConnectAdapter(), 109 | BitKeepConnectAdapter(), 110 | WalletConnectAdapter(), 111 | PhantomConnectAdapter(), 112 | EVMConnectAdapter(), 113 | SolanaConnectAdapter(), 114 | ) //list all support adapters, lazy create. 115 | } 116 | ``` 117 | 118 | Switch chain. 119 | 120 | ``` 121 | ParticleConnect.setChain(chain) 122 | ``` 123 | 124 | Get all wallet adapters. 125 | 126 | ``` 127 | var adapters = ParticleConnect.getAdapters(chainTypes) 128 | //or 129 | var adapters = ParticleConnect.getAdapterByAddress(address) 130 | ``` 131 | 132 | Get all connected accounts. 133 | 134 | ``` 135 | val accounts = ParticleConnect.getAccounts(chainTypes) 136 | ``` 137 | 138 | Connect wallet. (For `EVMConnectAdapter` or `SolanaConnectAdapter` will generate new wallet) 139 | 140 | ``` 141 | connectAdapter.connect(callback) 142 | ``` 143 | 144 | Disconnect wallet. 145 | 146 | ``` 147 | connectAdapter.disconnect(address, callback) 148 | ``` 149 | 150 | Check whether the account is connected. 151 | 152 | ``` 153 | val result = connectAdapter.connected(address) 154 | ``` 155 | 156 | Import wallet. (Only `EVMConnectAdapter` and `SolanaConnectAdapter` support this method) 157 | 158 | ``` 159 | // import wallet with private key 160 | val account = connectAdapter.importWalletFromPrivateKey(privateKey) 161 | 162 | // import wallet with mnemonic(Split with space). 163 | val account = connectAdapter.importWalletFromMnemonic(mnemonic) 164 | ``` 165 | 166 | Export wallet. (Only `EVMConnectAdapter` and `SolanaConnectAdapter` support this method) 167 | 168 | ``` 169 | val privateKey = connectAdapter.exportWalletPrivateKey(address) 170 | ``` 171 | 172 | Sign and send transaction. 173 | 174 | ``` 175 | // todo: check connected before sign 176 | connectAdapter.signAndSendTransaction(address, transaction, callback) 177 | ``` 178 | 179 | Sign transaction. (Only Solana chain support this method) 180 | 181 | ``` 182 | connectAdapter.signTransaction(address, transaction, callback) 183 | ``` 184 | 185 | Sign all transactions. (Only Solana chain support this method) 186 | 187 | ``` 188 | connectAdapter.signAllTransactions(address, transactions, callback) 189 | ``` 190 | 191 | Sign message. (EVM call `personal_sign`) 192 | 193 | ``` 194 | connectAdapter.signMessage(address, message, callback) 195 | ``` 196 | 197 | Sign typed data. (Only EVM chains support this method) 198 | 199 | ``` 200 | connectAdapter.signTypedData(address, data, callback) 201 | ``` 202 | 203 | ## Give Feedback 204 | 205 | You can join our [Discord](https://discord.gg/2y44qr6CR2). 206 | 207 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") 3 | id("kotlin-android") 4 | id("kotlin-kapt") 5 | id("kotlin-parcelize") 6 | id("org.jetbrains.kotlin.android") 7 | } 8 | 9 | android { 10 | compileSdk = libs.versions.compileSdk.get().toInt() 11 | 12 | defaultConfig { 13 | applicationId = "com.connect.demo" 14 | minSdk = libs.versions.minSdk.get().toInt() 15 | targetSdk = libs.versions.targetSdk.get().toInt() 16 | versionCode = 1 17 | versionName = "1.0" 18 | 19 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 20 | manifestPlaceholders["PN_PROJECT_ID"] = "34c6b829-5b89-44e8-90a9-6d982787b9c9" 21 | manifestPlaceholders["PN_PROJECT_CLIENT_KEY"] = "c6Z44Ml4TQeNhctvwYgdSv6DBzfjf6t6CB0JDscR" 22 | manifestPlaceholders["PN_APP_ID"] = "e35f880b-5d71-425f-97aa-5f26de7bc4d7" 23 | buildConfigField( 24 | "String", 25 | "PN_API_BASE_URL", 26 | "\"https://api.particle.network\"" 27 | ) 28 | proguardFiles( 29 | getDefaultProguardFile("proguard-android-optimize.txt"), 30 | "proguard-rules.pro" 31 | ) 32 | } 33 | 34 | 35 | 36 | buildTypes { 37 | debug { 38 | isMinifyEnabled = false 39 | proguardFiles( 40 | getDefaultProguardFile("proguard-android-optimize.txt"), 41 | "proguard-rules.pro" 42 | ) 43 | } 44 | release { 45 | isMinifyEnabled = true 46 | proguardFiles( 47 | getDefaultProguardFile("proguard-android-optimize.txt"), 48 | "proguard-rules.pro" 49 | ) 50 | } 51 | } 52 | compileOptions { 53 | sourceCompatibility(JavaVersion.VERSION_11) 54 | targetCompatibility(JavaVersion.VERSION_11) 55 | } 56 | 57 | kotlinOptions { 58 | jvmTarget = JavaVersion.VERSION_11.toString() 59 | } 60 | dataBinding { 61 | isEnabled = true 62 | } 63 | namespace = "com.connect.demo" 64 | } 65 | 66 | 67 | dependencies { 68 | modules { 69 | module("org.bouncycastle:bcprov-jdk15to18") { 70 | replacedBy("org.bouncycastle:bcprov-jdk15on") 71 | } 72 | module("org.bouncycastle:bcprov-jdk18on") { 73 | replacedBy("org.bouncycastle:bcprov-jdk15on") 74 | } 75 | } 76 | //required dependencies 77 | implementation(libs.particle.auth) // deprecated use auth-core-service instead 78 | implementation(libs.particle.auth.core) 79 | implementation(libs.particle.api) 80 | implementation(libs.connect.common) 81 | implementation(libs.connect) 82 | implementation(libs.connect.kit) 83 | //optional dependencies 84 | implementation(libs.connect.auth.adapter)// deprecated use auth-core-adapter instead 85 | implementation(libs.connect.auth.core.adapter) 86 | implementation(libs.connect.evm.adapter) 87 | implementation(libs.connect.sol.adapter) 88 | implementation(libs.connect.phantom.adapter) 89 | implementation(libs.connect.wallet.connect.adapter) 90 | 91 | implementation(libs.appcompat) 92 | implementation(libs.core.ktx) 93 | implementation(libs.material) 94 | implementation(libs.utilcodex) 95 | implementation(libs.constraintlayout) 96 | implementation(libs.recyclerview) 97 | implementation(libs.bundles.retrofit) 98 | implementation(libs.bundles.okhttp3) 99 | implementation(libs.recyclerview.adapter) 100 | implementation(libs.coil) 101 | implementation(libs.coil.svg) 102 | implementation(libs.coil.gif) 103 | implementation(libs.zxing.barcodescanner) 104 | 105 | testImplementation(libs.junit) 106 | androidTestImplementation(libs.junit.ext) 107 | androidTestImplementation(libs.espresso.core) 108 | 109 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -keepclassmembers enum com.connect.demo.** { *; } 23 | -keep class com.connect.demo.** { *; } 24 | -dontwarn com.connect.demo.** 25 | -repackageclasses com.connect.demo 26 | 27 | -keep class androidx.databinding.DataBindingComponent {*;} 28 | -keepclassmembers class **.R$* { 29 | public static ; 30 | } 31 | 32 | -keepattributes Signature 33 | -keepattributes SourceFile,LineNumberTable 34 | -renamesourcefileattribute SourceFile 35 | 36 | -keep class com.chad.library.adapter.** { 37 | *; 38 | } 39 | -keep public class * extends com.chad.library.adapter.base.BaseQuickAdapter 40 | -keep public class * extends com.chad.library.adapter.base.viewholder.BaseViewHolder 41 | -keepclassmembers class **$** extends com.chad.library.adapter.base.viewholder.BaseViewHolder { 42 | (...); 43 | } 44 | -keepattributes InnerClasses 45 | 46 | -keep class androidx.** {*;} 47 | -keep public class * extends androidx.** 48 | -keep interface androidx.** {*;} 49 | -dontwarn androidx.** 50 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/minijoy/particle_connect_android/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.minijoy.particle_connect_android 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.minijoy.particle_connect_android", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | ... 7 | 8 | 9 | 10 | 20 | 25 | 29 | 33 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | > 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 95 | 98 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /app/src/main/java/com/connect/demo/App.kt: -------------------------------------------------------------------------------- 1 | package com.connect.demo 2 | 3 | import android.app.Application 4 | import auth.core.adapter.AuthCoreAdapter 5 | import com.connect.demo.utils.CoilLoader 6 | import com.evm.adapter.EVMConnectAdapter 7 | import com.particle.base.Env 8 | import com.particle.base.model.DAppMetadata 9 | import com.particle.connect.ParticleConnect 10 | import com.phantom.adapter.PhantomConnectAdapter 11 | import com.solana.adapter.SolanaConnectAdapter 12 | import com.wallet.connect.adapter.BitGetConnectAdapter 13 | import com.wallet.connect.adapter.ImTokenConnectAdapter 14 | import com.wallet.connect.adapter.MetaMaskConnectAdapter 15 | import com.wallet.connect.adapter.OKXConnectAdapter 16 | import com.wallet.connect.adapter.RainbowConnectAdapter 17 | import com.wallet.connect.adapter.TrustConnectAdapter 18 | import com.wallet.connect.adapter.WalletConnectAdapter 19 | import network.particle.chains.ChainInfo.Companion.EthereumSepolia 20 | import particle.auth.adapter.ParticleConnectAdapter 21 | 22 | /** 23 | * Created by chaichuanfa on 2022/7/15 24 | */ 25 | class App : Application() { 26 | 27 | private lateinit var instance: App 28 | 29 | override fun onCreate() { 30 | super.onCreate() 31 | instance = this 32 | CoilLoader.init(this) 33 | 34 | ParticleConnect.init( 35 | this, Env.DEV, EthereumSepolia, DAppMetadata( 36 | walletConnectProjectId = "f431aaea6e4dea6a669c0496f9c009c1", 37 | name = "Particle Connect", 38 | icon = "https://connect.particle.network/icons/512.png", 39 | url = "https://particle.network", 40 | description = "Particle Connect is a decentralized wallet connection solution that allows users to connect to DApps with their wallets.", 41 | redirect = "redirect://", 42 | verifyUrl = "verifyUrl", 43 | ) 44 | ) { 45 | listOf( 46 | AuthCoreAdapter(), 47 | MetaMaskConnectAdapter(), 48 | RainbowConnectAdapter(), 49 | TrustConnectAdapter(), 50 | ImTokenConnectAdapter(), 51 | BitGetConnectAdapter(), 52 | WalletConnectAdapter(), 53 | PhantomConnectAdapter(), 54 | EVMConnectAdapter(), 55 | SolanaConnectAdapter(), 56 | OKXConnectAdapter() 57 | ) 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /app/src/main/java/com/connect/demo/base/BaseActivity.kt: -------------------------------------------------------------------------------- 1 | package com.connect.demo.base 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | import androidx.databinding.DataBindingUtil 6 | import androidx.databinding.ViewDataBinding 7 | 8 | /** 9 | * Created by chaichuanfa on 2022/7/25 10 | */ 11 | open class BaseActivity(private val layoutId: Int) : AppCompatActivity() { 12 | 13 | internal lateinit var binding: BD 14 | private set 15 | 16 | override fun onCreate(savedInstanceState: Bundle?) { 17 | super.onCreate(savedInstanceState) 18 | binding = DataBindingUtil.setContentView(this, layoutId) 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/main/java/com/connect/demo/controller/main/AccountAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.connect.demo.controller.main 2 | 3 | import coil.load 4 | import com.chad.library.adapter.base.BaseQuickAdapter 5 | import com.chad.library.adapter.base.viewholder.BaseDataBindingHolder 6 | import com.connect.demo.R 7 | import com.connect.demo.databinding.ItemAccountBinding 8 | import com.connect.demo.model.WalletAccount 9 | 10 | /** 11 | * Created by chaichuanfa on 2022/7/25 12 | */ 13 | class AccountAdapter : 14 | BaseQuickAdapter>(R.layout.item_account) { 15 | 16 | init { 17 | addChildClickViewIds(R.id.edit_account) 18 | } 19 | 20 | override fun convert(holder: BaseDataBindingHolder, item: WalletAccount) { 21 | try { 22 | holder.dataBinding?.apply { 23 | name.text = item.name 24 | address.text = item.account.publicAddress 25 | icon.load(item.account.icons?.get(0)) 26 | } 27 | } catch (e: Exception) { 28 | e.printStackTrace() 29 | } 30 | 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /app/src/main/java/com/connect/demo/controller/main/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.connect.demo.controller.main 2 | 3 | import android.content.ClipData 4 | import android.content.ClipboardManager 5 | import android.content.Intent 6 | import android.os.Bundle 7 | import android.util.Log 8 | import android.widget.EditText 9 | import androidx.appcompat.app.AlertDialog 10 | import androidx.lifecycle.lifecycleScope 11 | import androidx.recyclerview.widget.DividerItemDecoration 12 | import androidx.recyclerview.widget.LinearLayoutManager 13 | import com.connect.common.ConnectKitCallback 14 | import com.connect.common.DisconnectCallback 15 | import com.connect.common.ILocalAdapter 16 | import com.connect.common.model.Account 17 | import com.connect.common.model.ConnectError 18 | import com.connect.common.utils.PrefUtils 19 | import com.connect.demo.R 20 | import com.connect.demo.base.BaseActivity 21 | import com.connect.demo.controller.manage.ManageActivity 22 | import com.connect.demo.controller.reference.ReferenceActivity 23 | import com.connect.demo.databinding.ActivityMainBinding 24 | import com.connect.demo.model.WalletAccount 25 | import com.connect.demo.utils.ChainUtils 26 | import com.connect.demo.utils.MockManger 27 | import com.connect.demo.utils.toast 28 | import com.particle.connect.ParticleConnect 29 | import com.particle.connectkit.AdditionalLayoutOptions 30 | import com.particle.connectkit.ConnectKitConfig 31 | import com.particle.connectkit.ConnectOption 32 | import com.particle.connectkit.EnableSocialProvider 33 | import com.particle.connectkit.EnableWallet 34 | import com.particle.connectkit.EnableWalletLabel 35 | import com.particle.connectkit.EnableWalletProvider 36 | import com.particle.connectkit.ParticleConnectKit 37 | import kotlinx.coroutines.launch 38 | import network.particle.chains.ChainInfo 39 | 40 | 41 | class MainActivity : BaseActivity(R.layout.activity_main) { 42 | 43 | private var selectChain = 1 44 | 45 | private val adapter: AccountAdapter = AccountAdapter() 46 | 47 | override fun onCreate(savedInstanceState: Bundle?) { 48 | super.onCreate(savedInstanceState) 49 | setupData() 50 | setupToolbar() 51 | setupChangeChain() 52 | setupRv() 53 | } 54 | 55 | override fun onResume() { 56 | super.onResume() 57 | refreshAccount() 58 | } 59 | 60 | private fun setupData() { 61 | selectChain = PrefUtils.getSettingInt("current_selected_chain", 1) 62 | } 63 | 64 | private fun setupToolbar() { 65 | binding.toolbar.inflateMenu(R.menu.toolbar_action) 66 | binding.toolbar.setOnMenuItemClickListener { item -> 67 | when (item.itemId) { 68 | R.id.connect -> { 69 | startActivity(Intent(this, ManageActivity::class.java)) 70 | true 71 | } 72 | R.id.connectKit->{ 73 | val config = ConnectKitConfig( 74 | logo = "", 75 | connectOptions = listOf( 76 | ConnectOption.EMAIL, 77 | ConnectOption.PHONE, 78 | ConnectOption.SOCIAL, 79 | ConnectOption.WALLET), 80 | socialProviders = listOf( 81 | EnableSocialProvider.GOOGLE, 82 | EnableSocialProvider.APPLE, 83 | EnableSocialProvider.DISCORD, 84 | EnableSocialProvider.TWITTER, 85 | EnableSocialProvider.FACEBOOK, 86 | EnableSocialProvider.GITHUB, 87 | EnableSocialProvider.MICROSOFT, 88 | EnableSocialProvider.TWITCH, 89 | EnableSocialProvider.LINKEDIN), 90 | walletProviders = listOf( 91 | EnableWalletProvider(EnableWallet.MetaMask, EnableWalletLabel.RECOMMENDED), 92 | EnableWalletProvider(EnableWallet.OKX), 93 | EnableWalletProvider(EnableWallet.Phantom), 94 | EnableWalletProvider(EnableWallet.Trust), 95 | EnableWalletProvider(EnableWallet.Bitget), 96 | EnableWalletProvider(EnableWallet.WalletConnect), 97 | ), 98 | additionalLayoutOptions = AdditionalLayoutOptions( 99 | isCollapseWalletList = false, 100 | isSplitEmailAndSocial = false, 101 | isSplitEmailAndPhone = false, 102 | isHideContinueButton = false 103 | ) 104 | ) 105 | ParticleConnectKit.connect(config,connectCallback = object : 106 | ConnectKitCallback { 107 | override fun onConnected(walletName: String, account: Account) { 108 | refreshAccount() 109 | } 110 | 111 | override fun onError(error: ConnectError) { 112 | } 113 | 114 | }); 115 | true 116 | } 117 | else -> false 118 | } 119 | } 120 | 121 | updateCurrentChain(ChainUtils.getAllChains()[selectChain]) 122 | } 123 | 124 | private fun setupRv() { 125 | binding.accountRv.layoutManager = LinearLayoutManager(this) 126 | binding.accountRv.addItemDecoration( 127 | DividerItemDecoration( 128 | this, 129 | DividerItemDecoration.VERTICAL 130 | ) 131 | ) 132 | binding.accountRv.adapter = adapter 133 | adapter.setOnItemClickListener { _, _, position -> 134 | val walletAccount = adapter.data[position] 135 | if (walletAccount.connectAdapter.supportChains.contains(ParticleConnect.chainType)) { 136 | MockManger.walletAccount = walletAccount 137 | startActivity(Intent(this, ReferenceActivity::class.java)) 138 | } else { 139 | toast("The wallet not support current chain") 140 | } 141 | } 142 | adapter.setOnItemChildClickListener { _, _, position -> 143 | val walletAccount = adapter.data[position] 144 | showAccountMenu(walletAccount) 145 | } 146 | } 147 | 148 | private fun showAccountMenu(walletAccount: WalletAccount) { 149 | AlertDialog.Builder(this).apply { 150 | setIcon(R.drawable.ic_logo) 151 | setTitle(walletAccount.name) 152 | val items = mutableListOf("Copy address", "Rename wallet", "Disconnect") 153 | if (!walletAccount.account.mnemonic.isNullOrEmpty() || walletAccount.connectAdapter is ILocalAdapter) { 154 | items.add("Export Wallet") 155 | } 156 | 157 | setItems(items.toTypedArray()) { _, which -> 158 | when (which) { 159 | 0 -> copyWalletAddress(walletAccount) 160 | 1 -> renameWallet(walletAccount) 161 | 2 -> disconnectWallet(walletAccount) 162 | 3 -> exportWallet(walletAccount) 163 | } 164 | } 165 | setNegativeButton("Cancel", null) 166 | create().show() 167 | } 168 | } 169 | 170 | private fun copyWalletAddress(walletAccount: WalletAccount) { 171 | val cm = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager 172 | cm.setPrimaryClip(ClipData.newPlainText(packageName, walletAccount.account.publicAddress)) 173 | toast("Copied to clipboard") 174 | } 175 | 176 | private fun renameWallet(walletAccount: WalletAccount) { 177 | AlertDialog.Builder(this).apply { 178 | setIcon(R.drawable.ic_logo) 179 | setTitle("Wallet Name") 180 | val view = layoutInflater.inflate(R.layout.edit_wallet_name_layout, null) 181 | val editView = view.findViewById(R.id.edit) 182 | editView.setText(walletAccount.name) 183 | setView(view) 184 | setNegativeButton("Cancel", null) 185 | setPositiveButton("OK") { _, _ -> 186 | val name = editView.text.trim() 187 | if (name.isNotEmpty()) { 188 | PrefUtils.setSettingString( 189 | "${walletAccount.account.publicAddress}_wallet_name", 190 | name.toString() 191 | ) 192 | refreshAccount() 193 | } 194 | } 195 | create().show() 196 | } 197 | } 198 | 199 | private fun disconnectWallet(walletAccount: WalletAccount) { 200 | val address = walletAccount.account.publicAddress 201 | walletAccount.connectAdapter.disconnect( 202 | address, 203 | object : DisconnectCallback { 204 | override fun onDisconnected() { 205 | toast("Wallet Disconnected") 206 | PrefUtils.remove("${address}_wallet_name") 207 | refreshAccount() 208 | } 209 | 210 | override fun onError(error: ConnectError) { 211 | toast(error.message) 212 | } 213 | }) 214 | } 215 | 216 | private fun exportWallet(walletAccount: WalletAccount) { 217 | lifecycleScope.launch { 218 | var message = "" 219 | val mnemonic = walletAccount.account.mnemonic 220 | if (!mnemonic.isNullOrEmpty()) { 221 | message += "Mnemonic:\n$mnemonic\n\n" 222 | } 223 | if (walletAccount.connectAdapter is ILocalAdapter) { 224 | val privateKey = 225 | walletAccount.connectAdapter.exportWalletPrivateKey(walletAccount.account.publicAddress) 226 | if (privateKey.isNullOrEmpty()) { 227 | toast("Export Private Key Error") 228 | } else { 229 | message += "Private Key:\n$privateKey\n" 230 | Log.d("privateKey", privateKey) 231 | } 232 | } 233 | AlertDialog.Builder(this@MainActivity).apply { 234 | setIcon(R.drawable.ic_logo) 235 | setTitle(walletAccount.name) 236 | setMessage(message) 237 | setPositiveButton("OK", null) 238 | create().show() 239 | } 240 | } 241 | } 242 | 243 | private fun refreshAccount() { 244 | val adapterAccounts = ParticleConnect.getAccounts() 245 | val walletAccounts = mutableListOf() 246 | adapterAccounts.forEach { adapterAccount -> 247 | adapterAccount.accounts.forEach { 248 | val name = 249 | PrefUtils.getSettingString( 250 | "${it.publicAddress}_wallet_name", 251 | "${it.name} Wallet" 252 | )!! 253 | walletAccounts.add(WalletAccount(name, it, adapterAccount.connectAdapter)) 254 | } 255 | } 256 | adapter.setList(walletAccounts) 257 | } 258 | 259 | private fun updateCurrentChain(chain: ChainInfo) { 260 | val name = chain.name 261 | binding.chainName.text = name 262 | binding.chainId.text = chain.id.toString() 263 | binding.chainName.setCompoundDrawablesRelativeWithIntrinsicBounds( 264 | resources.getIdentifier( 265 | name.lowercase(), 266 | "drawable", 267 | packageName 268 | ), 0, 0, 0 269 | ) 270 | 271 | ParticleConnect.setChain(chain) 272 | refreshAccount() 273 | } 274 | 275 | private fun setupChangeChain() { 276 | binding.changeChain.setOnClickListener { 277 | val alertDialog = AlertDialog.Builder(this) 278 | alertDialog.setIcon(R.drawable.ic_logo) 279 | alertDialog.setTitle("Choose Chain") 280 | val listItems = 281 | ChainUtils.getAllChains().map { 282 | it.fullname + "-" + it.id.toString() 283 | }.toTypedArray() 284 | 285 | alertDialog.setSingleChoiceItems(listItems, selectChain) { dialog, which -> 286 | selectChain = which 287 | updateCurrentChain(ChainUtils.getAllChains()[which]) 288 | PrefUtils.setSettingInt("current_selected_chain", selectChain) 289 | dialog.dismiss() 290 | } 291 | alertDialog.setNegativeButton("Cancel", null) 292 | alertDialog.create().show() 293 | } 294 | } 295 | 296 | } -------------------------------------------------------------------------------- /app/src/main/java/com/connect/demo/controller/manage/ConnectAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.connect.demo.controller.manage 2 | 3 | import coil.load 4 | import com.chad.library.adapter.base.BaseQuickAdapter 5 | import com.chad.library.adapter.base.viewholder.BaseDataBindingHolder 6 | import com.connect.common.IConnectAdapter 7 | import com.connect.demo.R 8 | import com.connect.demo.databinding.ItemAdapterBinding 9 | 10 | /** 11 | * Created by chaichuanfa on 2022/7/25 12 | */ 13 | class ConnectAdapter : 14 | BaseQuickAdapter>(R.layout.item_adapter) { 15 | 16 | override fun convert(holder: BaseDataBindingHolder, item: IConnectAdapter) { 17 | holder.dataBinding?.apply { 18 | icon.load(item.icon) 19 | name.text = item.name 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/connect/demo/controller/manage/ManageActivity.kt: -------------------------------------------------------------------------------- 1 | package com.connect.demo.controller.manage 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import androidx.appcompat.app.AlertDialog 6 | import androidx.recyclerview.widget.DividerItemDecoration 7 | import androidx.recyclerview.widget.LinearLayoutManager 8 | import auth.core.adapter.ConnectConfigPhone 9 | import com.blankj.utilcode.util.LogUtils 10 | import com.connect.common.ConnectCallback 11 | import com.connect.common.IConnectAdapter 12 | import com.connect.common.model.Account 13 | import com.connect.common.model.ConnectError 14 | import com.connect.common.utils.PrefUtils 15 | import com.connect.demo.R 16 | import com.connect.demo.base.BaseActivity 17 | import com.connect.demo.controller.secret.ImportWalletActivity 18 | import com.connect.demo.databinding.ActivityManageBinding 19 | import com.connect.demo.utils.ChainUtils 20 | import com.connect.demo.utils.MockManger 21 | import com.connect.demo.utils.toast 22 | import com.evm.adapter.EVMConnectAdapter 23 | import com.particle.connect.ParticleConnect 24 | import com.phantom.adapter.PhantomConnectAdapter 25 | import com.solana.adapter.SolanaConnectAdapter 26 | import com.wallet.connect.adapter.TrustConnectAdapter 27 | import com.wallet.connect.adapter.WalletConnectAdapter 28 | import network.particle.chains.ChainInfo 29 | 30 | class ManageActivity : BaseActivity(R.layout.activity_manage) { 31 | 32 | private val adapter = ConnectAdapter() 33 | 34 | override fun onCreate(savedInstanceState: Bundle?) { 35 | super.onCreate(savedInstanceState) 36 | setupRv() 37 | setupToolbar() 38 | } 39 | 40 | private fun setupToolbar() { 41 | binding.toolbar.setNavigationOnClickListener { finish() } 42 | setSubTitle() 43 | } 44 | 45 | lateinit var chainInfo: ChainInfo 46 | private fun setSubTitle() { 47 | val selectChain = PrefUtils.getSettingInt("current_selected_chain", 1) 48 | chainInfo = ChainUtils.getAllChains()[selectChain] 49 | val name = chainInfo.fullname 50 | binding.toolbar.subtitle = "Current Chain: $name" 51 | } 52 | 53 | private fun setupRv() { 54 | binding.adapterRv.layoutManager = LinearLayoutManager(this) 55 | binding.adapterRv.addItemDecoration( 56 | DividerItemDecoration( 57 | this, 58 | DividerItemDecoration.VERTICAL 59 | ) 60 | ) 61 | binding.adapterRv.setHasFixedSize(true) 62 | 63 | adapter.setOnItemClickListener { _, _, position -> 64 | val connectAdapter = adapter.data[position] 65 | connectWallet(connectAdapter) 66 | } 67 | binding.adapterRv.adapter = adapter 68 | adapter.setList(ParticleConnect.getAdapters()) 69 | } 70 | 71 | private var qrDialog: WalletConnectFragment? = null 72 | private fun connectWallet(connectAdapter: IConnectAdapter) { 73 | when (connectAdapter) { 74 | is SolanaConnectAdapter -> { 75 | showImportMenu(connectAdapter, "solana") 76 | } 77 | 78 | is EVMConnectAdapter -> { 79 | showImportMenu(connectAdapter, "evm") 80 | } 81 | 82 | is WalletConnectAdapter -> { 83 | connectAdapter.connect(null, object : ConnectCallback { 84 | override fun onConnected(account: Account) { 85 | toast("connect success") 86 | qrDialog?.dismissAllowingStateLoss() 87 | finish() 88 | } 89 | 90 | override fun onError(error: ConnectError) { 91 | toast(error.message) 92 | qrDialog?.dismissAllowingStateLoss() 93 | } 94 | }) 95 | val url = connectAdapter.qrCodeUri() 96 | url?.let { 97 | qrDialog = WalletConnectFragment.show(supportFragmentManager, it) 98 | } 99 | } 100 | 101 | else -> { 102 | if (connectCheck(connectAdapter)) { 103 | return 104 | } 105 | connectAdapter.connect(ConnectConfigPhone(), object : ConnectCallback { 106 | override fun onConnected(account: Account) { 107 | LogUtils.d("connect success account: $account") 108 | toast("connect success") 109 | finish() 110 | } 111 | 112 | override fun onError(error: ConnectError) { 113 | LogUtils.d("connect error",Thread.currentThread()) 114 | LogUtils.d("connect error: $error") 115 | } 116 | }) 117 | } 118 | } 119 | } 120 | 121 | private fun connectCheck(connectAdapter: IConnectAdapter): Boolean { 122 | 123 | if (connectAdapter is PhantomConnectAdapter) { 124 | if (chainInfo.isEvmChain()) { 125 | toast("Phantom only support Solana Chain,current is ${chainInfo.name} ${chainInfo.id}") 126 | return true 127 | } 128 | } 129 | if (connectAdapter is TrustConnectAdapter) { 130 | return if (chainInfo.isEvmChain()) { 131 | if (chainInfo.isMainnet()) { 132 | false 133 | } else { 134 | toast("Trust only support EVM Mainnet Chain,current is ${chainInfo.name} ${chainInfo.id}") 135 | true 136 | } 137 | } else { 138 | toast("Trust only support EVM Chain,current is ${chainInfo.name} ${chainInfo.id}") 139 | true 140 | } 141 | } 142 | return false 143 | } 144 | 145 | private fun showImportMenu(connectAdapter: IConnectAdapter, chainType: String) { 146 | val alertDialog = AlertDialog.Builder(this) 147 | alertDialog.setIcon(R.drawable.ic_logo) 148 | alertDialog.setTitle("Particle Connect") 149 | 150 | alertDialog.setItems( 151 | arrayOf( 152 | "Import ${chainType.uppercase()} Wallet", 153 | "Create ${chainType.uppercase()} Wallet" 154 | ) 155 | ) { _, which -> 156 | if (which == 0) { 157 | val intent = Intent(this, ImportWalletActivity::class.java) 158 | intent.putExtra("chainType", chainType) 159 | startActivity(intent) 160 | } else { 161 | createWallet(connectAdapter) 162 | } 163 | } 164 | 165 | alertDialog.setNegativeButton("Cancel", null) 166 | alertDialog.create().show() 167 | } 168 | 169 | private fun createWallet(connectAdapter: IConnectAdapter) { 170 | connectAdapter.connect(null, object : ConnectCallback { 171 | override fun onConnected(account: Account) { 172 | toast("Create wallet success") 173 | finish() 174 | } 175 | 176 | override fun onError(error: ConnectError) { 177 | toast(error.message) 178 | } 179 | }) 180 | } 181 | } -------------------------------------------------------------------------------- /app/src/main/java/com/connect/demo/controller/manage/WalletConnectFragment.kt: -------------------------------------------------------------------------------- 1 | package com.connect.demo.controller.manage 2 | 3 | import android.content.Intent 4 | import android.graphics.Bitmap 5 | import android.net.Uri 6 | import android.os.Bundle 7 | import android.os.Parcelable 8 | import android.view.LayoutInflater 9 | import android.view.View 10 | import android.view.ViewGroup 11 | import androidx.annotation.Keep 12 | import androidx.core.content.ContextCompat 13 | import androidx.fragment.app.DialogFragment 14 | import androidx.fragment.app.FragmentManager 15 | import androidx.lifecycle.lifecycleScope 16 | import com.connect.common.ConnectManager 17 | 18 | import com.connect.demo.R 19 | import com.connect.demo.databinding.FragmentWalletConnectBinding 20 | import com.connect.demo.utils.BarcodeEncoder 21 | import com.connect.demo.utils.QrParams 22 | import com.google.zxing.BarcodeFormat 23 | import com.particle.base.ParticleNetwork 24 | import kotlinx.coroutines.Dispatchers 25 | import kotlinx.coroutines.launch 26 | import kotlinx.coroutines.withContext 27 | import kotlinx.parcelize.Parcelize 28 | 29 | @Keep 30 | @Parcelize 31 | data class ReceiveData( 32 | val tokenAddress: String?, 33 | ) : Parcelable 34 | 35 | internal class WalletConnectFragment : DialogFragment() { 36 | 37 | companion object { 38 | private const val DATA_KEY: String = "DATA_KEY" 39 | 40 | fun show(fm: FragmentManager, data: String): WalletConnectFragment { 41 | val frag = WalletConnectFragment() 42 | val bundle = Bundle() 43 | bundle.putString(DATA_KEY, data) 44 | frag.arguments = bundle 45 | frag.show(fm, WalletConnectFragment::javaClass.name) 46 | return frag 47 | } 48 | 49 | 50 | } 51 | 52 | lateinit var binding: FragmentWalletConnectBinding 53 | override fun onCreateView( 54 | inflater: LayoutInflater, 55 | container: ViewGroup?, 56 | savedInstanceState: Bundle? 57 | ): View? { 58 | binding = FragmentWalletConnectBinding.inflate(inflater, container, false) 59 | return binding.root 60 | } 61 | 62 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 63 | super.onViewCreated(view, savedInstanceState) 64 | requireArguments().getString(DATA_KEY)?.let { uri-> 65 | binding.connect.setOnClickListener { 66 | val intent = Intent(Intent.ACTION_VIEW) 67 | intent.data = Uri.parse(uri) 68 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 69 | ConnectManager.context.startActivity(intent) 70 | } 71 | lifecycleScope.launch { 72 | try { 73 | val qr = generateQrCode(uri) 74 | binding.ivQrCode.setImageBitmap(qr) 75 | } catch (e: Throwable) { 76 | e.printStackTrace() 77 | } 78 | } 79 | } 80 | } 81 | 82 | suspend fun generateQrCode(address: String): Bitmap = withContext(Dispatchers.Default) { 83 | BarcodeEncoder.encodeBitmap( 84 | address, 85 | BarcodeFormat.QR_CODE, 86 | 900, 87 | 900, 88 | QrParams( 89 | ContextCompat.getColor(ParticleNetwork.context, R.color.black), 90 | ContextCompat.getColor(ParticleNetwork.context, R.color.white) 91 | ) 92 | ) 93 | } 94 | 95 | } -------------------------------------------------------------------------------- /app/src/main/java/com/connect/demo/controller/reference/ReferenceActivity.kt: -------------------------------------------------------------------------------- 1 | package com.connect.demo.controller.reference 2 | 3 | import android.annotation.SuppressLint 4 | import android.os.Bundle 5 | import android.text.method.ScrollingMovementMethod 6 | import android.util.Log 7 | import androidx.lifecycle.lifecycleScope 8 | import com.blankj.utilcode.util.GsonUtils 9 | import com.blankj.utilcode.util.LogUtils 10 | import com.connect.common.* 11 | import com.connect.common.eip4361.Eip4361Message 12 | import com.connect.common.eip4361.Web3jSignatureVerifier 13 | import com.connect.common.model.* 14 | import com.connect.common.utils.PrefUtils 15 | import com.connect.demo.R 16 | import com.connect.demo.base.BaseActivity 17 | import com.connect.demo.databinding.ActivityReferenceBinding 18 | import com.connect.demo.model.WalletAccount 19 | import com.connect.demo.utils.ChainUtils 20 | import com.connect.demo.utils.MockManger 21 | import com.connect.demo.utils.StreamUtils 22 | import com.connect.demo.utils.toast 23 | import com.particle.api.evm 24 | import com.particle.api.service.data.ContractParams 25 | import com.particle.base.ParticleNetwork 26 | import com.particle.base.model.ChainType 27 | import com.particle.base.model.EVMTransactionUtil 28 | import com.particle.base.model.ITxData 29 | import com.particle.base.model.LegacyTransactionData 30 | import com.particle.connect.ParticleConnect 31 | import kotlinx.coroutines.launch 32 | import network.particle.chains.NativeCurrency 33 | 34 | class ReferenceActivity : BaseActivity(R.layout.activity_reference) { 35 | 36 | private lateinit var walletAccount: WalletAccount 37 | 38 | 39 | override fun onCreate(savedInstanceState: Bundle?) { 40 | super.onCreate(savedInstanceState) 41 | walletAccount = MockManger.walletAccount!! 42 | setupUI() 43 | } 44 | 45 | override fun onDestroy() { 46 | super.onDestroy() 47 | MockManger.walletAccount = null 48 | } 49 | 50 | @SuppressLint("SetTextI18n") 51 | private fun setupUI() { 52 | binding.toolbar.setNavigationOnClickListener { 53 | finish() 54 | } 55 | binding.toolbar.title = walletAccount.name 56 | binding.toolbar.inflateMenu(R.menu.delete_action) 57 | binding.toolbar.setOnMenuItemClickListener { 58 | walletAccount.connectAdapter.disconnect(walletAccount.account.publicAddress, 59 | object : DisconnectCallback { 60 | override fun onDisconnected() { 61 | finish() 62 | } 63 | 64 | override fun onError(error: ConnectError) { 65 | toast(error.message) 66 | } 67 | 68 | }) 69 | true 70 | } 71 | 72 | binding.address.text = "Address: ${walletAccount.account.publicAddress}" 73 | binding.signAndSendTransaction.setOnClickListener { 74 | if (walletAccount.connectAdapter.connected(walletAccount.account.publicAddress)) { 75 | lifecycleScope.launch { 76 | try { 77 | signAndSendTransaction() 78 | } catch (e: Exception) { 79 | e.printStackTrace() 80 | } 81 | } 82 | } else { 83 | connectWallet() 84 | } 85 | } 86 | binding.signTransaction.setOnClickListener { 87 | if (walletAccount.connectAdapter.connected(walletAccount.account.publicAddress)) { 88 | lifecycleScope.launch { 89 | try { 90 | signTransaction() 91 | } catch (e: Exception) { 92 | e.printStackTrace() 93 | } 94 | } 95 | } else { 96 | connectWallet() 97 | } 98 | } 99 | 100 | binding.signAllTransactions.setOnClickListener { 101 | if (walletAccount.connectAdapter.connected(walletAccount.account.publicAddress)) { 102 | lifecycleScope.launch { 103 | try { 104 | signAllTransactions() 105 | } catch (e: Exception) { 106 | e.printStackTrace() 107 | } 108 | } 109 | } else { 110 | connectWallet() 111 | } 112 | } 113 | binding.signMessage.setOnClickListener { 114 | if (walletAccount.connectAdapter.connected(walletAccount.account.publicAddress)) { 115 | signMessage() 116 | } else { 117 | connectWallet() 118 | } 119 | } 120 | binding.signTypedData.setOnClickListener { 121 | if (walletAccount.connectAdapter.connected(walletAccount.account.publicAddress)) { 122 | signTypedData() 123 | } else { 124 | connectWallet() 125 | } 126 | } 127 | binding.request.movementMethod = ScrollingMovementMethod.getInstance() 128 | binding.result.movementMethod = ScrollingMovementMethod.getInstance() 129 | binding.signMessageVerify.setOnClickListener { 130 | val message = createMessage() 131 | if (walletAccount.connectAdapter.connected(walletAccount.account.publicAddress)) { 132 | login(message) 133 | } else { 134 | connectWallet() 135 | } 136 | 137 | } 138 | 139 | binding.writeContract.setOnClickListener { 140 | lifecycleScope.launch { 141 | val params = ContractParams.customAbiEncodeFunctionCall( 142 | contractAddress = "0xd000f000aa1f8accbd5815056ea32a54777b2fc4", 143 | methodName = "mint", 144 | params = listOf("1") 145 | ) 146 | val txData: ITxData? = ParticleNetwork.evm.writeContract( 147 | walletAccount.account.publicAddress, 148 | params 149 | ) 150 | walletAccount.connectAdapter.signAndSendTransaction( 151 | walletAccount.account.publicAddress, 152 | txData!!.serialize(), 153 | object : TransactionCallback { 154 | override fun onTransaction(transactionId: String?) { 155 | binding.result.text = transactionId ?: "" 156 | toast("signAndSendTransaction success") 157 | } 158 | 159 | override fun onError(error: ConnectError) { 160 | 161 | Log.e("signAndSendTransaction", error.message) 162 | toast(error.message) 163 | } 164 | }) 165 | } 166 | 167 | } 168 | } 169 | 170 | 171 | private fun connectWallet() { 172 | walletAccount.connectAdapter.connect(null, object : ConnectCallback { 173 | override fun onConnected(account: Account) { 174 | LogUtils.d("onConnected: $account") 175 | if (!account.publicAddress.equals(walletAccount.account.publicAddress, true)) { 176 | toast("Please connect correct account") 177 | } 178 | 179 | } 180 | 181 | override fun onError(error: ConnectError) { 182 | toast(error.message) 183 | } 184 | }) 185 | } 186 | 187 | // val testTransactionStr ="7b22616374696f6e223a226e6f726d616c222c22636861696e4964223a2230783261222c2264617461223a223078613037313264363830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303031222c2266726f6d223a22307835303466383364363530323966623630376663616134336562643062373032326162313631623063222c226761734c6576656c223a22222c226761734c696d6974223a2230783230633835222c226d6178466565506572476173223a2230783737333539343063222c226d61785072696f72697479466565506572476173223a2230783737333539343030222c226e6f6e6365223a22307830222c2272223a6e756c6c2c2273223a6e756c6c2c22746f223a22307864303030663030306161316638616363626435383135303536656133326135343737376232666334222c2274797065223a22307832222c2276223a6e756c6c2c2276616c7565223a6e756c6c7d" 188 | 189 | private suspend fun signAndSendTransaction() { 190 | val transaction = MockManger.mockCreateTransaction(walletAccount.account.publicAddress) 191 | 192 | 193 | val transNativeWithApi = ParticleNetwork.evm.createTransaction(walletAccount.account.publicAddress,"0x504F83D65029fB607fcAa43ebD0b7022ab161B0C","0x9184e72a000",) 194 | 195 | val contractParams = ContractParams.erc20Transfer("0xfE642A6EABfAc01b758829661f01eA92824c6807","0x504F83D65029fB607fcAa43ebD0b7022ab161B0C","10000000000000") 196 | val transTokenWithApi = ParticleNetwork.evm.createTransaction(walletAccount.account.publicAddress,"0xfE642A6EABfAc01b758829661f01eA92824c6807",contractParams = contractParams) 197 | 198 | binding.request.text = GsonUtils.toJson(transNativeWithApi) 199 | binding.result.text = "" 200 | walletAccount.connectAdapter.signAndSendTransaction( 201 | walletAccount.account.publicAddress, 202 | transNativeWithApi!!.serialize(), 203 | object : TransactionCallback { 204 | override fun onTransaction(transactionId: String?) { 205 | binding.result.text = transactionId ?: "" 206 | toast("signAndSendTransaction success") 207 | } 208 | 209 | override fun onError(error: ConnectError) { 210 | 211 | Log.e("signAndSendTransaction", error.message) 212 | toast(error.message) 213 | } 214 | }) 215 | } 216 | 217 | private suspend fun signTransaction() { 218 | val transaction = MockManger.mockCreateTransaction(walletAccount.account.publicAddress) 219 | binding.request.text = GsonUtils.toJson(transaction) 220 | binding.result.text = "" 221 | walletAccount.connectAdapter.signTransaction( 222 | walletAccount.account.publicAddress, 223 | transaction, 224 | object : SignCallback { 225 | override fun onSigned(signature: String) { 226 | binding.result.text = signature 227 | toast("signTransaction success") 228 | } 229 | 230 | override fun onError(error: ConnectError) { 231 | toast(error.message) 232 | } 233 | }) 234 | } 235 | 236 | private suspend fun signAllTransactions() { 237 | // val transactions = listOf( 238 | // MockManger.mockCreateTransaction(walletAccount.account.publicAddress), 239 | // MockManger.mockCreateTransaction(walletAccount.account.publicAddress) 240 | // ) 241 | // binding.request.text = GsonUtils.toJson(transactions) 242 | val transactions = Array(1) { 243 | "3igAAYeQWiGxrTXP47oHcCqgbLeYt9uHEmzhR7ZXNfffL9kGxgoCpw4qsBwby6y7kCJC2JDRopPvFtqs4C377ygMb6z4rcodDfB5Qt1Wz6iTXBVGht462fGWcumarJx6J5tnMpzRN8DffZfwuaJFxuE9WSora2cXWWyRW2Ps7n2A5NGv2ULPMGumkzPaw3mUyLfDQDndrgxHPYaWzogMxWdwTQ76t44ZBBqhswoTx1KkqYku1tL1p2qnhtPZcS73czrBuhprqwLNSo5tP5ijRvUM8x9qeMhtUvsSZ9Le5y27GaGFHznB4ET85KfyujVtWbSWeHTRyDkzoR2EEHpfm6gw6DHj9o7f72c5rWrtjaDe7CFdUuKYcJcQDWwHnKgn3A2s1iHnv4ZN4sm5HXuGNTYMdxuLZdbvZzmHN3Ai4dn2vVKFks7uYcv15AKnfj9mv2RLXSLkubrP5GCymPyhZ1HUxk7qjVHJFQ89w2q71fzvPmhnMU6vzTz2LTBoXttnTRWxBk818Pg1J2sUHaqKgvp77N9LGhf6aEE2d5XQ68dzRpm6NQRkRX4jF5oyDDWZ9x95q6WQTFeENdUZxPJFYy4WUqGqqRPMkphFbpDCPQAAKkmoNCbkehwPULMBQw3p4BRW7pQLs3cvDcPgxjtBUMQtkCqPNRAk7tvxPLM6EhcFYB9d9TV3BuQ9yxJvyorD9rCzyk8qCkCuNJc7S2NPsCDcMYDRSD8GhcMbjnuWdYwdcMj2xw6Gz5zB1rK9wGcAMJmefDFv89VoBDnktKVa4sWp9Y519YmpPRuN2CMaaiuwAqQyZJaeE5V7WZXZAMppNYntyevu4P4Ws2HPqG8wM49fuDf1au6ehhDRDrihTh3wdwouyVACEuAomjqMEYtYaSJMA6YChm6Y8vXposU4Svog7cCenpoHLY3rYPGaR5orqJS7rfNDmRLp8UKqQ4XLwdKkjoFXBPB9DeptAFstpRPGGsSpk4hqCAEy6Z59AJWaKfD51PcVaN88z53uwfhfoWTKAecqAx9u2YctpueWrD9KMscXxjLbNgz7USSN3CFCtBXERiawEEepgRPKHiUhejFfpECoXhMDR36TmBusQ5TZ4fyoffwQDpf1L3iNcxcVv23YXnHqgC3dJunJQmkajUYS9qMokjJUBTtwbi9ZUcTGDtAGwKmsvsDetwn3WYVhLVtmSPF33URKQ3zH7CKAVEEdmUfVEboDB3mv5CuV8aJ8f23qzuqA8qUDbF3MYNTYDgPyN2Z8vQStxKQSbtu3uvzuW6rPKHjSwvm6RmHziVPsrNqVabyean9Nx1Gdf1fN6BYSN9DcW9EoFaTb2ucFFwz3yFgVePzEjezLi346xYZDwuYH5unkFA8eczYwSVpy7rNy6uKMkWR4REVubeLuHnkMTYucGkjFoyP6JCow6P2xb7cv2YA5ngXeQ5xQ1enAnqgKMTjxDfRSX4Wj5sBPJhyZ47gTkuT5GKZYpo5DuJ8ksvB91hPq3U4YbvKmVrvdEp" 244 | } 245 | binding.result.text = "" 246 | walletAccount.connectAdapter.signAllTransactions( 247 | walletAccount.account.publicAddress, 248 | transactions, 249 | object : SignAllCallback { 250 | override fun onSigned(signatures: List) { 251 | binding.result.text = GsonUtils.toJson(signatures) 252 | toast("signAllTransactions success") 253 | } 254 | 255 | override fun onError(error: ConnectError) { 256 | toast(error.message) 257 | } 258 | }) 259 | } 260 | 261 | private fun signMessage(message: String = "Hello Particle Connect!") { 262 | binding.request.text = message 263 | binding.result.text = "" 264 | walletAccount.connectAdapter.signMessage( 265 | walletAccount.account.publicAddress, 266 | MockManger.encode(message), 267 | object : SignCallback { 268 | override fun onSigned(signature: String) { 269 | binding.result.text = signature 270 | toast("signMessage success") 271 | LogUtils.d( 272 | "signMessage", 273 | signature 274 | ) 275 | if (ParticleConnect.chainType == ChainType.EVM) { 276 | val address = Web3jSignatureVerifier.recoverAddressFromSignature( 277 | signature, 278 | message 279 | ) 280 | LogUtils.d( 281 | "recoverAddressFromSignature", 282 | address, 283 | walletAccount.account.publicAddress 284 | ) 285 | } 286 | } 287 | 288 | override fun onError(error: ConnectError) { 289 | LogUtils.d("signMessage", error.message) 290 | toast(error.message) 291 | } 292 | }) 293 | } 294 | 295 | private fun signTypedData() { 296 | val message = StreamUtils.getRawString(resources, R.raw.typed_data) 297 | binding.request.text = message 298 | binding.result.text = "" 299 | walletAccount.connectAdapter.signTypedData( 300 | walletAccount.account.publicAddress, 301 | MockManger.encode(message), 302 | object : SignCallback { 303 | override fun onSigned(signature: String) { 304 | binding.result.text = signature 305 | toast("signTypedData success") 306 | } 307 | 308 | override fun onError(error: ConnectError) { 309 | toast(error.message) 310 | } 311 | }) 312 | } 313 | 314 | private fun login(eip4361Message: Eip4361Message) { 315 | val message = eip4361Message.toString() 316 | binding.request.text = eip4361Message.toString() 317 | binding.result.text = "" 318 | walletAccount.connectAdapter.login( 319 | walletAccount.account.publicAddress, 320 | eip4361Message, 321 | object : SignCallback { 322 | override fun onSigned(signature: String) { 323 | val result = walletAccount.connectAdapter.verify( 324 | walletAccount.account.publicAddress, 325 | signature, 326 | message 327 | ) 328 | val displayTxt = "signature:$signature\n\n verify:$result" 329 | LogUtils.d("login verify", result) 330 | binding.result.text = displayTxt 331 | } 332 | 333 | override fun onError(error: ConnectError) { 334 | toast(error.message) 335 | } 336 | }) 337 | } 338 | 339 | private fun createMessage(): Eip4361Message { 340 | // Message example from 341 | // evm-> https://eips.ethereum.org/EIPS/eip-4361 342 | // sol-> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-74.md 343 | /* val msg = Eip4361Message.fromString( 344 | """ 345 | particle.network wants you to sign in with your Ethereum account: 346 | 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc21 347 | 348 | I accept the ServiceOrg Terms of Service: https://service.org/tos 349 | 350 | URI: https://service.org/login 351 | Version: 1 352 | Chain ID: 1 353 | Nonce: 32891756 354 | Issued At: 2021-09-30T16:25:24Z 355 | Resources: 356 | - ipfs://bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq/ 357 | - https://example.com/my-web2-claim.json 358 | """.trimIndent() 359 | )*/ 360 | //you can use val msg = Eip4361Message("xx"...) 361 | val msg = Eip4361Message.createWithRequiredParameter( 362 | "particle.network", 363 | "https://service.org/login", 364 | "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc21" 365 | ) 366 | return msg 367 | } 368 | 369 | 370 | } -------------------------------------------------------------------------------- /app/src/main/java/com/connect/demo/controller/secret/ImportWalletActivity.kt: -------------------------------------------------------------------------------- 1 | package com.connect.demo.controller.secret 2 | 3 | import android.os.Bundle 4 | import androidx.lifecycle.lifecycleScope 5 | import com.connect.common.ILocalAdapter 6 | import com.connect.demo.R 7 | import com.connect.demo.base.BaseActivity 8 | import com.connect.demo.databinding.ActivityImportWalletBinding 9 | import com.connect.demo.utils.toast 10 | import com.evm.adapter.EVMConnectAdapter 11 | import com.particle.connect.ParticleConnect 12 | import com.solana.adapter.SolanaConnectAdapter 13 | import kotlinx.coroutines.launch 14 | 15 | class ImportWalletActivity : 16 | BaseActivity(R.layout.activity_import_wallet) { 17 | 18 | private var chainType: String = "evm" 19 | 20 | override fun onCreate(savedInstanceState: Bundle?) { 21 | super.onCreate(savedInstanceState) 22 | chainType = intent.getStringExtra("chainType") ?: "evm" 23 | binding.toolbar.setNavigationOnClickListener {finish() } 24 | 25 | binding.btImport.setOnClickListener { 26 | val secret = binding.secret.text.trim() 27 | if (secret.isNotEmpty()) { 28 | importWallet(secret.toString()) 29 | } else { 30 | toast("Please input private key or mnemonic") 31 | } 32 | } 33 | } 34 | 35 | private fun importWallet(secret: String) { 36 | 37 | lifecycleScope.launch { 38 | try { 39 | val account = if (secret.contains(" ")) { 40 | // import mnemonic 41 | getAdapter().importWalletFromMnemonic(secret) 42 | } else { 43 | // import private key 44 | getAdapter().importWalletFromPrivateKey(secret) 45 | } 46 | if (account != null) { 47 | toast("Import wallet success") 48 | } else { 49 | toast("import wallet fail") 50 | } 51 | finish() 52 | } catch (e: Exception) { 53 | e.printStackTrace() 54 | toast("import wallet fail") 55 | } 56 | } 57 | 58 | } 59 | 60 | private fun getAdapter(): ILocalAdapter { 61 | if (chainType == "evm") { 62 | return ParticleConnect.getAdapters().first { it is EVMConnectAdapter } as ILocalAdapter 63 | } 64 | return ParticleConnect.getAdapters().first { it is SolanaConnectAdapter } as ILocalAdapter 65 | } 66 | } -------------------------------------------------------------------------------- /app/src/main/java/com/connect/demo/custom_connectadapter/Coin98ConnectAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.connect.demo.custom_connectadapter 2 | 3 | import com.connect.common.ConnectManager 4 | import com.connect.common.model.WalletReadyState 5 | import com.connect.common.utils.AppUtils 6 | import com.particle.base.model.IconUrl 7 | import com.particle.base.model.MobileWCWallet 8 | import com.particle.base.model.WalletName 9 | import com.particle.base.model.WebsiteUrl 10 | import com.wallet.connect.adapter.BaseWalletConnectAdapter 11 | 12 | 13 | class Coin98ConnectAdapter : BaseWalletConnectAdapter() { 14 | 15 | val coin98 = MobileWCWallet("Coin98", "coin98.crypto.finance.media", "coin98") 16 | 17 | override val name: WalletName = coin98.name 18 | 19 | override val icon: IconUrl = "https://registry.walletconnect.com/v2/logo/md/dee547be-936a-4c92-9e3f-7a2350a62e00" 20 | 21 | override val url: WebsiteUrl = "https://coin98.com" 22 | 23 | 24 | override val readyState: WalletReadyState 25 | get() { 26 | if (supportChains.contains(ConnectManager.chainType)) { 27 | return if (AppUtils.isAppInstalled( 28 | ConnectManager.context, coin98.packageName 29 | ) 30 | ) WalletReadyState.Installed else WalletReadyState.NotDetected 31 | } 32 | return WalletReadyState.Unsupported 33 | } 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/connect/demo/model/RpcRequest.kt: -------------------------------------------------------------------------------- 1 | package com.connect.demo.model 2 | 3 | /** 4 | * Created by chaichuanfa on 2022/7/27 5 | */ 6 | data class RpcRequest( 7 | val chainId: Long, 8 | val id: String, 9 | val jsonrpc: String = "2.0", 10 | val method: String, 11 | val params: List? = null, 12 | ) 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/connect/demo/model/RpcResponse.kt: -------------------------------------------------------------------------------- 1 | package com.connect.demo.model 2 | 3 | /** 4 | * Created by chaichuanfa on 2022/7/27 5 | */ 6 | data class RpcResponse( 7 | val chainId: Long, 8 | val jsonrpc: String, 9 | val id: String, 10 | val result: T, 11 | ) 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/connect/demo/model/SerializeTransaction.kt: -------------------------------------------------------------------------------- 1 | package com.connect.demo.model 2 | 3 | /** 4 | * Created by chaichuanfa on 2022/7/27 5 | */ 6 | data class SerializeTransaction( 7 | val transaction: Transaction 8 | ) 9 | 10 | data class Transaction( 11 | val hasPartialSign: Boolean, 12 | val serialized: String 13 | ) 14 | 15 | data class SOLTransfer( 16 | val sender: String, 17 | val receiver: String, 18 | val lamports: Long, 19 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/connect/demo/model/TransactionAddressData.kt: -------------------------------------------------------------------------------- 1 | package com.connect.demo.model 2 | 3 | import org.p2p.solanaj.core.PublicKey 4 | 5 | /** 6 | * Created by chaichuanfa on 2022/7/28 7 | */ 8 | data class TransactionAddressData( 9 | val associatedAddress: PublicKey, 10 | val shouldCreateAssociatedInstruction: Boolean 11 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/connect/demo/model/WalletAccount.kt: -------------------------------------------------------------------------------- 1 | package com.connect.demo.model 2 | 3 | import com.connect.common.IConnectAdapter 4 | import com.connect.common.model.Account 5 | 6 | /** 7 | * Created by chaichuanfa on 2022/7/25 8 | */ 9 | data class WalletAccount( 10 | val name: String, 11 | val account: Account, 12 | val connectAdapter: IConnectAdapter, 13 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/connect/demo/transaction/SolanaRpcRepository.kt: -------------------------------------------------------------------------------- 1 | package com.connect.demo.transaction 2 | 3 | import com.connect.common.provider.NetworkProvider 4 | import com.connect.demo.model.RpcRequest 5 | import com.connect.demo.model.TransactionAddressData 6 | import com.connect.demo.utils.SolanaRpcApi 7 | import com.particle.connect.ParticleConnect 8 | import org.p2p.solanaj.core.PublicKey 9 | import org.p2p.solanaj.kits.TokenTransaction 10 | import org.p2p.solanaj.model.types.* 11 | import org.p2p.solanaj.programs.TokenProgram 12 | import java.util.* 13 | 14 | /** 15 | * Created by chaichuanfa on 2022/7/29 16 | */ 17 | object SolanaRpcRepository { 18 | 19 | private val solanaRpcApi: SolanaRpcApi = 20 | NetworkProvider.createRetrofit("${com.connect.common.BuildConfig.PN_API_BASE_URL}/solana/") 21 | .create(SolanaRpcApi::class.java) 22 | 23 | suspend fun getRecentBlockhash(commitment: String = "finalized"): String { 24 | val response = solanaRpcApi.getRecentBlockhash( 25 | RpcRequest( 26 | ParticleConnect.chainId, 27 | UUID.randomUUID().toString(), 28 | method = "getRecentBlockhash", 29 | params = listOf(ConfigObjects.Commitment(commitment)) 30 | ) 31 | ) 32 | return response.result.recentBlockhash 33 | } 34 | 35 | suspend fun findSplTokenAddressData( 36 | destinationAddress: PublicKey, 37 | mintAddress: String, 38 | ): TransactionAddressData { 39 | val associatedAddress = try { 40 | findSplTokenAddress(destinationAddress, mintAddress) 41 | } catch (e: IllegalStateException) { 42 | throw IllegalStateException("Invalid owner address") 43 | } 44 | 45 | /* If account is not found, create one */ 46 | val accountInfo = getAccountInfo(associatedAddress.toBase58()) 47 | val value = accountInfo?.value 48 | val accountExists = value?.owner == TokenProgram.PROGRAM_ID.toString() && value.data != null 49 | return TransactionAddressData( 50 | associatedAddress, 51 | !accountExists 52 | ) 53 | } 54 | 55 | suspend fun getAccountInfo(account: String): AccountInfo? { 56 | val response = solanaRpcApi.getAccountInfo( 57 | RpcRequest( 58 | ParticleConnect.chainId, 59 | UUID.randomUUID().toString(), 60 | method = "getAccountInfo", 61 | params = listOf( 62 | account, 63 | RequestConfiguration(encoding = Encoding.BASE64.encoding) 64 | ) 65 | ) 66 | ) 67 | return response.result 68 | } 69 | 70 | suspend fun findSplTokenAddress( 71 | destinationAddress: PublicKey, 72 | mintAddress: String 73 | ): PublicKey { 74 | val accountInfo = getAccountInfo(destinationAddress.toBase58()) 75 | val info = TokenTransaction.parseAccountInfoData(accountInfo, TokenProgram.PROGRAM_ID) 76 | 77 | // create associated token address 78 | val value = accountInfo?.value 79 | if (value == null || value.data?.get(0).isNullOrEmpty()) { 80 | return TokenTransaction.getAssociatedTokenAddress( 81 | PublicKey(mintAddress), 82 | destinationAddress 83 | ) 84 | } 85 | 86 | // detect if destination address is already a SPLToken address 87 | if (info?.mint == destinationAddress) { 88 | return destinationAddress 89 | } 90 | 91 | // detect if destination address is a SOL address 92 | if (info?.owner?.toBase58() == TokenProgram.PROGRAM_ID.toBase58()) { 93 | // create associated token address 94 | return TokenTransaction.getAssociatedTokenAddress( 95 | PublicKey(mintAddress), 96 | destinationAddress 97 | ) 98 | } 99 | 100 | throw IllegalStateException("Wallet address is not valid") 101 | } 102 | 103 | suspend fun getTokenAccountsByOwner(owner: String): TokenAccounts { 104 | val programId = TokenProgram.PROGRAM_ID 105 | val programIdParam = HashMap() 106 | programIdParam["programId"] = programId.toBase58() 107 | 108 | val encoding = HashMap() 109 | encoding["encoding"] = "jsonParsed" 110 | encoding["commitment"] = "confirmed" 111 | 112 | val params = listOf( 113 | owner, 114 | programIdParam, 115 | encoding 116 | ) 117 | 118 | val rpcRequest = RpcRequest( 119 | ParticleConnect.chainId, 120 | UUID.randomUUID().toString(), 121 | method = "getTokenAccountsByOwner", 122 | params = params, 123 | ) 124 | return solanaRpcApi.getTokenAccountsByOwner(rpcRequest).result 125 | } 126 | 127 | } -------------------------------------------------------------------------------- /app/src/main/java/com/connect/demo/transaction/SolanaTransactionManager.kt: -------------------------------------------------------------------------------- 1 | package com.connect.demo.transaction 2 | 3 | import org.p2p.solanaj.core.Transaction 4 | import org.p2p.solanaj.core.PublicKey 5 | import org.p2p.solanaj.core.TransactionInstruction 6 | import org.p2p.solanaj.kits.TokenTransaction 7 | import org.p2p.solanaj.programs.SystemProgram 8 | import org.p2p.solanaj.programs.TokenProgram 9 | import java.math.BigInteger 10 | 11 | /** 12 | * Created by chaichuanfa on 2022/7/29 13 | */ 14 | object SolanaTransactionManager { 15 | 16 | suspend fun transferNativeToken( 17 | fromAddress: String, 18 | destinationAddress: String, 19 | lamports: BigInteger, 20 | recentBlockhash: String? = null, 21 | feePayerPublicKey: String? = null, 22 | ): Transaction { 23 | if (fromAddress == destinationAddress) { 24 | error("You can not send tokens to yourself") 25 | } 26 | 27 | val transaction = Transaction() 28 | transaction.addInstruction( 29 | SystemProgram.transfer( 30 | PublicKey(fromAddress), 31 | PublicKey(destinationAddress), 32 | lamports 33 | ) 34 | ) 35 | transaction.feePayer = PublicKey(feePayerPublicKey ?: fromAddress) 36 | transaction.recentBlockhash = recentBlockhash ?: SolanaRpcRepository.getRecentBlockhash() 37 | return transaction 38 | } 39 | 40 | suspend fun transferSplToken( 41 | fromAddress: String, 42 | destinationAddress: String, 43 | mintAddress: String, 44 | lamports: BigInteger, 45 | recentBlockhash: String? = null, 46 | feePayerAddress: String? = null, 47 | ): Transaction { 48 | val transaction = Transaction() 49 | val destinationPublicKey = PublicKey(destinationAddress) 50 | val feePayer = PublicKey(feePayerAddress ?: fromAddress) 51 | val senderPublicKey = PublicKey(fromAddress) 52 | 53 | val senderAta = TokenTransaction.getAssociatedTokenAddress( 54 | PublicKey(mintAddress), 55 | senderPublicKey 56 | ) 57 | 58 | val splDestinationAddress = SolanaRpcRepository.findSplTokenAddressData( 59 | destinationAddress = destinationPublicKey, 60 | mintAddress = mintAddress 61 | ) 62 | // get address 63 | val toPublicKey = splDestinationAddress.associatedAddress 64 | val instructions = mutableListOf() 65 | 66 | // create associated token address 67 | if (splDestinationAddress.shouldCreateAssociatedInstruction) { 68 | val createAccount = TokenProgram.createAssociatedTokenAccountInstruction( 69 | TokenProgram.ASSOCIATED_TOKEN_PROGRAM_ID, 70 | TokenProgram.PROGRAM_ID, 71 | PublicKey(mintAddress), 72 | toPublicKey, 73 | destinationPublicKey, 74 | feePayer 75 | ) 76 | 77 | instructions += createAccount 78 | } 79 | 80 | instructions += TokenProgram.transferInstruction( 81 | TokenProgram.PROGRAM_ID, 82 | senderAta, 83 | toPublicKey, 84 | senderPublicKey, 85 | lamports 86 | ) 87 | 88 | transaction.addInstruction(*instructions.toTypedArray()) 89 | transaction.recentBlockhash = recentBlockhash ?: SolanaRpcRepository.getRecentBlockhash() 90 | transaction.feePayer = feePayer 91 | 92 | return transaction 93 | } 94 | } -------------------------------------------------------------------------------- /app/src/main/java/com/connect/demo/utils/BarcodeEncoder.kt: -------------------------------------------------------------------------------- 1 | package com.connect.demo.utils 2 | 3 | import android.graphics.Bitmap 4 | import android.graphics.Canvas 5 | import android.graphics.Matrix 6 | import com.google.zxing.BarcodeFormat 7 | import com.google.zxing.EncodeHintType 8 | import com.google.zxing.MultiFormatWriter 9 | import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel 10 | 11 | /** 12 | * Helper class for encoding barcodes as a Bitmap. 13 | * 14 | * Adapted from QRCodeEncoder, from the zxing project: 15 | * https://github.com/zxing/zxing 16 | * 17 | * Licensed under the Apache License, Version 2.0. 18 | */ 19 | 20 | object BarcodeEncoder { 21 | 22 | fun encodeBitmap( 23 | contents: String, 24 | format: BarcodeFormat, 25 | width: Int, 26 | height: Int, 27 | qrParams: QrParams 28 | ): Bitmap { 29 | val matrix = MultiFormatWriter() 30 | .encode( 31 | contents, 32 | format, 33 | width, 34 | height, 35 | mapOf( 36 | EncodeHintType.CHARACTER_SET to "UTF-8", 37 | EncodeHintType.MARGIN to 0, 38 | EncodeHintType.ERROR_CORRECTION to ErrorCorrectionLevel.Q 39 | ) 40 | ) 41 | val pixels = IntArray(matrix.width * matrix.height) 42 | for (y in 0 until matrix.height) { 43 | val offset = y * matrix.width 44 | for (x in 0 until matrix.width) { 45 | pixels[offset + x] = 46 | if (matrix.get(x, y)) qrParams.contentColor else qrParams.backgroundColor 47 | } 48 | } 49 | val bitmap = Bitmap.createBitmap(matrix.width, matrix.height, Bitmap.Config.ARGB_8888) 50 | bitmap.setPixels(pixels, 0, matrix.width, 0, 0, matrix.width, matrix.height) 51 | return bitmap 52 | } 53 | 54 | fun createQRcode( 55 | overlay: Bitmap, 56 | qrCodeData: String, 57 | width: Int, 58 | height: Int, 59 | qrParams: QrParams 60 | ): Bitmap { 61 | val hintMap = mapOf(EncodeHintType.ERROR_CORRECTION to ErrorCorrectionLevel.H) 62 | // generating qr code in bitmatrix type 63 | val charsetData = charset("UTF-8") 64 | val matrix = MultiFormatWriter().encode( 65 | String(qrCodeData.toByteArray(charsetData), charsetData), 66 | BarcodeFormat.QR_CODE, width, height, hintMap 67 | ) 68 | 69 | // converting bitmatrix to bitmap 70 | val newWidth = matrix.width 71 | val newHeight = matrix.height 72 | val pixels = IntArray(newWidth * newHeight) 73 | // All are 0, or black, by default 74 | for (y in 0 until newHeight) { 75 | val offset = y * newWidth 76 | for (x in 0 until newWidth) { 77 | pixels[offset + x] = 78 | if (matrix.get(x, y)) qrParams.contentColor else qrParams.backgroundColor 79 | } 80 | } 81 | val bitmap = Bitmap.createBitmap(newWidth, newHeight, Bitmap.Config.ARGB_8888) 82 | bitmap.setPixels(pixels, 0, newWidth, 0, 0, newWidth, newHeight) 83 | return mergeBitmaps(overlay, bitmap) 84 | } 85 | 86 | fun mergeBitmaps(overlay: Bitmap, bitmap: Bitmap): Bitmap { 87 | val height = bitmap.height 88 | val width = bitmap.width 89 | val combined = Bitmap.createBitmap(width, height, bitmap.config) 90 | val canvas = Canvas(combined) 91 | val canvasWidth: Int = canvas.width 92 | val canvasHeight: Int = canvas.height 93 | canvas.drawBitmap(bitmap, Matrix(), null) 94 | val centreX = (canvasWidth - overlay.width) / 2 95 | val centreY = (canvasHeight - overlay.height) / 2 96 | canvas.drawBitmap(overlay, centreX.toFloat(), centreY.toFloat(), null) 97 | return combined 98 | } 99 | } -------------------------------------------------------------------------------- /app/src/main/java/com/connect/demo/utils/ChainUtils.kt: -------------------------------------------------------------------------------- 1 | package com.connect.demo.utils 2 | 3 | import network.particle.chains.ChainInfo 4 | 5 | /** 6 | * Created by chaichuanfa on 2022/7/25 7 | */ 8 | object ChainUtils { 9 | 10 | fun getAllChains(): List { 11 | return ChainInfo.ParticleChains.values.toList() 12 | } 13 | 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/connect/demo/utils/CoilLoader.kt: -------------------------------------------------------------------------------- 1 | package com.connect.demo.utils 2 | 3 | import android.content.Context 4 | import android.os.Build 5 | import coil.Coil 6 | import coil.ImageLoader 7 | import coil.decode.ImageDecoderDecoder 8 | import coil.decode.SvgDecoder 9 | import coil.disk.DiskCache 10 | import coil.memory.MemoryCache 11 | import coil.util.DebugLogger 12 | import com.particle.base.ParticleNetwork 13 | import okhttp3.Dispatcher 14 | import okhttp3.OkHttpClient 15 | 16 | object CoilLoader { 17 | 18 | fun init(context: Context) { 19 | Coil.setImageLoader(newImageLoader(context.applicationContext)) 20 | } 21 | 22 | private fun newImageLoader(context: Context): ImageLoader { 23 | return ImageLoader.Builder(context) 24 | .components { 25 | // GIFs 26 | if (Build.VERSION.SDK_INT >= 28) { 27 | add(ImageDecoderDecoder.Factory()) 28 | } else { 29 | add(coil.decode.GifDecoder.Factory()) 30 | } 31 | // SVGs 32 | add(SvgDecoder.Factory()) 33 | } 34 | .memoryCache { 35 | MemoryCache.Builder(context) 36 | // Set the max size to 25% of the app's available memory. 37 | .maxSizePercent(0.25) 38 | .build() 39 | } 40 | .diskCache { 41 | DiskCache.Builder() 42 | .directory(context.filesDir.resolve("image_cache")) 43 | .maxSizeBytes(512L * 1024 * 1024) //512MB 44 | .build() 45 | } 46 | .okHttpClient { 47 | // Don't limit concurrent network requests by host. 48 | val dispatcher = Dispatcher().apply { maxRequestsPerHost = maxRequests } 49 | 50 | // Lazily create the OkHttpClient that is used for network operations. 51 | OkHttpClient.Builder() 52 | .dispatcher(dispatcher) 53 | .build() 54 | } 55 | // Show a short crossfade when loading images asynchronously. 56 | .crossfade(true) 57 | // Ignore the network cache headers and always read from/write to the disk cache. 58 | .respectCacheHeaders(false) 59 | // Enable logging to the standard Android log if this is a debug build. 60 | .apply { if (ParticleNetwork.isDebug()) logger(DebugLogger()) } 61 | .build() 62 | } 63 | } -------------------------------------------------------------------------------- /app/src/main/java/com/connect/demo/utils/ContextExt.kt: -------------------------------------------------------------------------------- 1 | package com.connect.demo.utils 2 | 3 | import android.content.Context 4 | import android.widget.Toast 5 | 6 | /** 7 | * Created by chaichuanfa on 2022/7/25 8 | */ 9 | fun Context.toast(msg: String) { 10 | Toast.makeText(this, msg, Toast.LENGTH_SHORT).show() 11 | } 12 | 13 | fun Context.toast(resId: Int) { 14 | Toast.makeText(this, getString(resId), Toast.LENGTH_SHORT).show() 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/connect/demo/utils/MockManger.kt: -------------------------------------------------------------------------------- 1 | package com.connect.demo.utils 2 | 3 | import com.connect.common.ConnectManager 4 | import com.connect.common.provider.NetworkProvider 5 | import com.connect.demo.model.RpcRequest 6 | import com.connect.demo.model.SOLTransfer 7 | import com.connect.demo.model.WalletAccount 8 | import com.connect.demo.transaction.SolanaTransactionManager 9 | import com.particle.base.model.ChainType 10 | import com.particle.base.model.EIP1559TransactionData 11 | import com.particle.base.model.LegacyTransactionData 12 | import com.particle.base.utils.Base58Utils 13 | import com.particle.base.utils.HexUtils 14 | import com.particle.connect.ParticleConnect 15 | import org.p2p.solanaj.core.ITransactionData 16 | import org.p2p.solanaj.core.Transaction 17 | import java.math.BigInteger 18 | import java.util.UUID 19 | 20 | /** 21 | * Created by chaichuanfa on 2022/7/26 22 | */ 23 | object MockManger { 24 | 25 | private val solanaRpcApi: SolanaRpcApi = 26 | NetworkProvider.createRetrofit("${com.connect.common.BuildConfig.PN_API_BASE_URL}") 27 | .create(SolanaRpcApi::class.java) 28 | 29 | private var minBalanceForRentExemption: BigInteger? = null 30 | private var lamportsPerSignature: BigInteger? = null 31 | 32 | var walletAccount: WalletAccount? = null 33 | 34 | 35 | fun encode(message: String): String { 36 | return if (ParticleConnect.chainType == ChainType.Solana) { 37 | Base58Utils.encode(message.toByteArray(Charsets.UTF_8)) 38 | } else { 39 | HexUtils.encodeWithPrefix(message.toByteArray(Charsets.UTF_8)) 40 | } 41 | } 42 | 43 | suspend fun mockCreateTransaction(from: String): ITransactionData { 44 | return if (ParticleConnect.chainType == ChainType.Solana) { 45 | mockSendSolanaTransaction(from) 46 | } else { 47 | if (ConnectManager.chainInfo.isEIP1559Supported()) { 48 | EIP1559TransactionData( 49 | "0x${ParticleConnect.chainId.toString(16)}", 50 | from, 51 | "0x504F83D65029fB607fcAa43ebD0b7022ab161B0C", 52 | "0x9184e72a000", 53 | gasLimit = "0x${Integer.toHexString(25000)}", 54 | maxFeePerGas = "0x9502f90e", 55 | maxPriorityFeePerGas = "0x9502F900", 56 | ) 57 | } else { 58 | LegacyTransactionData( 59 | "0x${ParticleConnect.chainId.toString(16)}", 60 | from = from, 61 | to = "0x504F83D65029fB607fcAa43ebD0b7022ab161B0C", 62 | value = "0x2386f26fc10000", 63 | data = "0x", 64 | nonce = "0x0", 65 | gasPrice = "0x25cfcb580", 66 | gasLimit = "0x5208", 67 | type = "0x0", 68 | action = "normal", 69 | gasLevel = "medium" 70 | ) 71 | } 72 | 73 | } 74 | } 75 | 76 | private suspend fun mockSendSolanaTransaction(from: String): Transaction { 77 | val tx = SolanaTransactionManager.transferNativeToken( 78 | from, 79 | "DtbnGhuAzK1NhbgdBhX6DLLFzFJFFEpjtzrbxwVEZEAs", 80 | BigInteger("100000"), 81 | ) 82 | 83 | return tx 84 | } 85 | 86 | private suspend fun mockCreateSendSplTokenTransaction( 87 | from: String, 88 | ): Transaction { 89 | val tokenMintAddress = "GobzzzFQsFAHPvmwT42rLockfUCeV3iutEkK218BxT8K" 90 | val destinationAddress = "DtbnGhuAzK1NhbgdBhX6DLLFzFJFFEpjtzrbxwVEZEAs" 91 | 92 | return SolanaTransactionManager.transferSplToken( 93 | from, 94 | destinationAddress, 95 | tokenMintAddress, 96 | BigInteger("100000") 97 | ) 98 | } 99 | 100 | suspend fun createSolanaTransaction( 101 | sender: String, 102 | receiver: String, 103 | lamports: Long, 104 | ): String { 105 | val response = solanaRpcApi.enhancedSerializeTransaction( 106 | RpcRequest( 107 | ParticleConnect.chainId, 108 | UUID.randomUUID().toString(), 109 | method = "enhancedSerializeTransaction", 110 | params = listOf("transfer-sol", SOLTransfer(sender, receiver, lamports)) 111 | ) 112 | ) 113 | return response.result.transaction.serialized 114 | } 115 | 116 | } -------------------------------------------------------------------------------- /app/src/main/java/com/connect/demo/utils/QrParams.kt: -------------------------------------------------------------------------------- 1 | package com.connect.demo.utils 2 | 3 | import androidx.annotation.ColorInt 4 | 5 | data class QrParams( 6 | @ColorInt val contentColor: Int, 7 | @ColorInt val backgroundColor: Int, 8 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/connect/demo/utils/SolanaRpcApi.kt: -------------------------------------------------------------------------------- 1 | package com.connect.demo.utils 2 | 3 | import com.connect.demo.model.RpcRequest 4 | import com.connect.demo.model.RpcResponse 5 | import com.connect.demo.model.SerializeTransaction 6 | import org.p2p.solanaj.model.types.AccountInfo 7 | import org.p2p.solanaj.model.types.FeesResponse 8 | import org.p2p.solanaj.model.types.RecentBlockhash 9 | import org.p2p.solanaj.model.types.TokenAccounts 10 | import retrofit2.http.Body 11 | import retrofit2.http.POST 12 | import retrofit2.http.Url 13 | 14 | 15 | interface SolanaRpcApi { 16 | 17 | @POST 18 | suspend fun enhancedSerializeTransaction( 19 | @Body body: RpcRequest, 20 | @Url url: String = "" 21 | ): RpcResponse 22 | 23 | @POST 24 | suspend fun getRecentBlockhash( 25 | @Body body: RpcRequest, 26 | @Url url: String = "" 27 | ): RpcResponse 28 | 29 | @POST 30 | suspend fun getAccountInfo( 31 | @Body body: RpcRequest, 32 | @Url url: String = "" 33 | ): RpcResponse 34 | 35 | @POST 36 | suspend fun getMinimumBalanceForRentExemption( 37 | @Body rpcRequest: RpcRequest, 38 | @Url url: String = "" 39 | ): RpcResponse 40 | 41 | @POST 42 | suspend fun getFees( 43 | @Body rpcRequest: RpcRequest, 44 | @Url url: String = "" 45 | ): RpcResponse 46 | 47 | @POST 48 | suspend fun getTokenAccountsByOwner( 49 | @Body rpcRequest: RpcRequest, 50 | @Url url: String = "" 51 | ): RpcResponse 52 | } -------------------------------------------------------------------------------- /app/src/main/java/com/connect/demo/utils/StreamUtils.kt: -------------------------------------------------------------------------------- 1 | package com.connect.demo.utils 2 | 3 | import android.content.res.Resources 4 | import java.io.BufferedReader 5 | import java.io.InputStreamReader 6 | 7 | /** 8 | * Created by chaichuanfa on 2022/7/26 9 | */ 10 | object StreamUtils { 11 | 12 | fun getRawString(resource: Resources, rawId: Int): String { 13 | val stream = resource.openRawResource(rawId) 14 | try { 15 | val reader = BufferedReader(InputStreamReader(stream, "utf-8")) 16 | val sb = StringBuilder() 17 | var line: String? = null 18 | while (run { 19 | line = reader.readLine() 20 | line 21 | } != null) { 22 | sb.append(line + "\n") 23 | } 24 | return sb.toString() 25 | } catch (e: Exception) { 26 | e.printStackTrace() 27 | } finally { 28 | stream.close() 29 | } 30 | return "" 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/arbitrum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Particle-Network/particle-connect-android/f7022c0e45f9ca6197b0c71329db415e9f0dffc9/app/src/main/res/drawable-xxhdpi/arbitrum.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/aurora.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Particle-Network/particle-connect-android/f7022c0e45f9ca6197b0c71329db415e9f0dffc9/app/src/main/res/drawable-xxhdpi/aurora.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/avalanche.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Particle-Network/particle-connect-android/f7022c0e45f9ca6197b0c71329db415e9f0dffc9/app/src/main/res/drawable-xxhdpi/avalanche.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/bsc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Particle-Network/particle-connect-android/f7022c0e45f9ca6197b0c71329db415e9f0dffc9/app/src/main/res/drawable-xxhdpi/bsc.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ethereum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Particle-Network/particle-connect-android/f7022c0e45f9ca6197b0c71329db415e9f0dffc9/app/src/main/res/drawable-xxhdpi/ethereum.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/fantom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Particle-Network/particle-connect-android/f7022c0e45f9ca6197b0c71329db415e9f0dffc9/app/src/main/res/drawable-xxhdpi/fantom.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/harmony.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Particle-Network/particle-connect-android/f7022c0e45f9ca6197b0c71329db415e9f0dffc9/app/src/main/res/drawable-xxhdpi/harmony.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/heco.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Particle-Network/particle-connect-android/f7022c0e45f9ca6197b0c71329db415e9f0dffc9/app/src/main/res/drawable-xxhdpi/heco.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Particle-Network/particle-connect-android/f7022c0e45f9ca6197b0c71329db415e9f0dffc9/app/src/main/res/drawable-xxhdpi/ic_logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/kcc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Particle-Network/particle-connect-android/f7022c0e45f9ca6197b0c71329db415e9f0dffc9/app/src/main/res/drawable-xxhdpi/kcc.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/moonbeam.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Particle-Network/particle-connect-android/f7022c0e45f9ca6197b0c71329db415e9f0dffc9/app/src/main/res/drawable-xxhdpi/moonbeam.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/moonriver.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Particle-Network/particle-connect-android/f7022c0e45f9ca6197b0c71329db415e9f0dffc9/app/src/main/res/drawable-xxhdpi/moonriver.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/optimism.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Particle-Network/particle-connect-android/f7022c0e45f9ca6197b0c71329db415e9f0dffc9/app/src/main/res/drawable-xxhdpi/optimism.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/polygon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Particle-Network/particle-connect-android/f7022c0e45f9ca6197b0c71329db415e9f0dffc9/app/src/main/res/drawable-xxhdpi/polygon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/solana.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Particle-Network/particle-connect-android/f7022c0e45f9ca6197b0c71329db415e9f0dffc9/app/src/main/res/drawable-xxhdpi/solana.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/edit_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_create_wallet.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_delete.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_edit.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_left.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_more.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_right.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_wallet.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/list_item_divider.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/popup_window_transparent.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_import_wallet.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | 11 | 12 |