Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions android/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
1 change: 1 addition & 0 deletions android/app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
77 changes: 77 additions & 0 deletions android/app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
plugins {
alias(libs.plugins.androidApplication)
alias(libs.plugins.jetbrainsKotlinAndroid)
alias(libs.plugins.daggerHilt)
alias(libs.plugins.kotlinKapt)
}

android {
namespace = "com.zk.android.app"
compileSdk = 34

defaultConfig {
applicationId = "com.zk.android.app"
minSdk = 24
targetSdk = 33
versionCode = 1
versionName = "1.0"
//TODO Insert your key here
//buildConfigField("String", "PRIVATE_KEY", "\"<WALLET_PRIVATE_KEY>\"")
}
flavorDimensions.add("default")

productFlavors {
create("mainnet") {
applicationIdSuffix = ".debug"
buildConfigField("String", "RPC_ETH_URL", "\"https://rpc.ankr.com/eth\"")
buildConfigField("String", "ZK_ERA_URL", "\"https://mainnet.era.zksync.io\"")
buildConfigField("String", "ZK_ERA_SOCKET", "\"wss://mainnet.era.zksync.io/ws\"")
}
create("sepolia") {
buildConfigField("String", "RPC_ETH_URL", "\"https://rpc.ankr.com/eth_sepolia\"")
buildConfigField("String", "ZK_ERA_URL", "\"https://sepolia.era.zksync.dev\"")
buildConfigField("String", "ZK_ERA_SOCKET", "\"wss://sepolia.era.zksync.dev/ws\"")
}
}

buildTypes {
release {
isMinifyEnabled = false
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
kotlinOptions {
jvmTarget = "1.8"
}
buildFeatures {
viewBinding = true
buildConfig = true
}
}

dependencies {

//Zk
implementation(libs.zksync)
implementation(libs.web3j.core)
implementation(libs.web3j.crypto)

implementation(libs.androidx.core.ktx)
implementation(libs.androidx.core.splashscreen)
implementation(libs.androidx.appcompat)
implementation(libs.androidx.constraintlayout)
implementation(libs.androidx.navigation.fragment.ktx)
implementation(libs.androidx.navigation.ui.ktx)
implementation(libs.lifecycle.viewmodel.ktx)
implementation(libs.lifecycle.runtime.ktx)

implementation(libs.material)
implementation(libs.orbit.viewmodel)
implementation(libs.lottie)

// Dependency injection
implementation(libs.hilt.android)
kapt(libs.hilt.compiler)
kapt(libs.androidx.hilt.compiler)
implementation(libs.hilt.navigation.fragment)
}
37 changes: 37 additions & 0 deletions android/app/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

# JSR 305 annotations are for embedding nullability information.
-dontwarn javax.annotation.**

# A resource is loaded with a relative path so the package of this class must be preserved.
-keeppackagenames okhttp3.internal.publicsuffix.*
-adaptresourcefilenames okhttp3/internal/publicsuffix/PublicSuffixDatabase.gz

# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java.
-dontwarn org.codehaus.mojo.animal_sniffer.*

# OkHttp platform used only on JVM and when Conscrypt and other security providers are available.
-dontwarn okhttp3.internal.platform.**
-dontwarn org.conscrypt.**
-dontwarn org.bouncycastle.**
-dontwarn org.openjsse.**
29 changes: 29 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.INTERNET" />

<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:name=".ZkSynApp"
android:theme="@style/AppTheme.Starting"
tools:targetApi="33">
<activity
android:name=".presentation.MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>
Binary file added android/app/src/main/ic_launcher-playstore.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions android/app/src/main/java/com/zk/android/app/ZkSynApp.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.zk.android.app

import android.app.Application
import dagger.hilt.android.HiltAndroidApp

@HiltAndroidApp
class ZkSynApp : Application()
48 changes: 48 additions & 0 deletions android/app/src/main/java/com/zk/android/app/di/ZkModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.zk.android.app.di

import com.zk.android.app.BuildConfig
import com.zk.android.app.network.ZkConfig
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import io.zksync.protocol.ZkSync
import org.web3j.crypto.Credentials
import org.web3j.protocol.Web3j
import org.web3j.protocol.http.HttpService
import javax.inject.Singleton


@Module
@InstallIn(SingletonComponent::class)
class ZkModule {

@Provides
@Singleton
fun provideConfig(): ZkConfig {
return ZkConfig(
BuildConfig.RPC_ETH_URL,
BuildConfig.ZK_ERA_URL,
BuildConfig.ZK_ERA_SOCKET,
BuildConfig.PRIVATE_KEY
)
}

@Provides
@Singleton
fun provideWeb3j(config: ZkConfig): Web3j {
return Web3j.build(HttpService(config.rpc))
}

@Provides
@Singleton
fun provideZkSync(config: ZkConfig): ZkSync {
return ZkSync.build(HttpService(config.url))
}

@Provides
@Singleton
fun provideCredentials(config: ZkConfig): Credentials {
return Credentials.create(config.privateKey)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.zk.android.app.domain.balance

import java.math.BigInteger


data class BalanceModelDomain (
val l1Balance: BigInteger,
val l2Balance: BigInteger,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.zk.android.app.domain.balance

import com.zk.android.app.network.WalletProvider
import com.zk.android.app.utils.sendSafe
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import java.math.BigInteger
import javax.inject.Inject


class GetBalanceUseCase @Inject constructor(
private val walletProvider: WalletProvider
) {

suspend fun getBalance(): Result<BalanceModelDomain> = withContext(Dispatchers.IO) {
val wallet = walletProvider.create().getOrElse {
return@withContext Result.failure(it)
}

val l1Balance: BigInteger = wallet.balanceL1.sendSafe().getOrElse {
return@withContext Result.failure(it)
}
val l2Balance: BigInteger = wallet.balance.sendSafe().getOrElse {
return@withContext Result.failure(it)
}

return@withContext Result.success(
BalanceModelDomain(
l1Balance,
l2Balance
)
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.zk.android.app.domain.deposit

import com.zk.android.app.domain.balance.BalanceModelDomain


data class DepositModelDomain (
val newBalance: BalanceModelDomain
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.zk.android.app.domain.deposit

import com.zk.android.app.domain.balance.GetBalanceUseCase
import com.zk.android.app.network.WalletProvider
import com.zk.android.app.utils.toWei
import com.zk.android.app.utils.waitForTransactionReceiptSafe
import io.zksync.transaction.type.DepositTransaction
import io.zksync.utils.ZkSyncAddresses
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.math.BigInteger
import javax.inject.Inject


class DepositUseCase @Inject constructor(
private val walletProvider: WalletProvider,
private val balanceUseCase: GetBalanceUseCase
) {

suspend fun deposit(amount: Float): Result<DepositModelDomain> = withContext(Dispatchers.IO) {
val wallet = walletProvider.create().getOrElse {
return@withContext Result.failure(it)
}

val sendAmount: BigInteger = amount.toWei()

val transaction = DepositTransaction(ZkSyncAddresses.ETH_ADDRESS, sendAmount)
val hash = wallet.deposit(transaction).sendAsync().join().result
wallet.transactionReceiptProcessorL1.waitForTransactionReceiptSafe(hash)

val newBalance = balanceUseCase.getBalance().getOrElse {
return@withContext Result.failure(it)
}

return@withContext Result.success(DepositModelDomain(newBalance))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.zk.android.app.network

import androidx.annotation.WorkerThread
import io.zksync.protocol.ZkSync
import io.zksync.protocol.account.Wallet
import org.web3j.crypto.Credentials
import org.web3j.protocol.Web3j
import javax.inject.Inject


class WalletProvider @Inject constructor(
private val web3: Web3j,
private val zkSync: ZkSync,
private val credentials: Credentials
) {

@WorkerThread
//TODO can we use single Wallet instance?
fun create(): Result<Wallet> {
return runCatching { Wallet(web3, zkSync, credentials) }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.zk.android.app.network


data class ZkConfig(
val rpc: String,
val url: String,
val socket: String,
val privateKey: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.zk.android.app.presentation

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration
import com.zk.android.app.R
import com.zk.android.app.databinding.ActivityMainBinding
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

private lateinit var appBarConfiguration: AppBarConfiguration
private lateinit var binding: ActivityMainBinding

override fun onCreate(savedInstanceState: Bundle?) {
setTheme(R.style.Theme_ZkSyncApp)
super.onCreate(savedInstanceState)

binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)

val navController = findNavController(R.id.nav_host_fragment_content_main)
appBarConfiguration = AppBarConfiguration(navController.graph)
}
}
Loading