Create an Ethereum Web3 wallet in Android
In this guide, we'll talk about how we can use Web3Auth to build your Ethereum Web3 wallet in Android. The wallet will only support the Ethereum ecosystem, but functionality can be extended with any blockchain ecosystem.
Check out Embedded Wallets management infrastructure for a high-level overview of the Embedded Wallets management infrastructure, architecture, and implementation. For those who want to skip straight to the code, find it on GitHub.
:::
This is what your application will look like:
Step 1: Set up the Embedded Wallets dashboard
If you haven't already, sign up on the Embedded Wallets platform. It's free and gives you access to the Embedded Wallets' base plan. After the basic setup, explore other features and functionalities offered by Embedded Wallets. It includes custom verifiers, whitelabeling, analytics, and more. Head to the Embedded Wallet documentation page for detailed instructions on setting up the dashboard.
Step 2: Integrate Embedded Wallets in Android
Next, integrate the Embedded Wallets/Web3Auth SDK in your Android application. For the implementation, we'll use the "web3auth-android-sdk" SDK to manage an Embedded Wallet in your Android application.
2.1 Installation
To install the web3auth-android-sdk SDK:
2.1.1 In your module-level build.gradle or settings.gradle file, add the JitPack repository.
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven { url "https://jitpack.io" } // <-- Add this line
}
}
2.1.2 In your app-level build.gradle dependencies section, add the web3auth-android-sdk.
dependencies {
// ...
implementation 'com.github.web3auth:web3auth-android-sdk:7.4.0'
}
2.2 Initialization
2.2.1 Next, initialize Web3Auth in your Android app. This sets up the necessary configurations using Client ID and prepares Web3Auth.
Since we are using the Model–View–ViewModel (MVVM) architecture for the wallet, along with dependency injection,
we have defined a Web3AuthHelper to interact with a Web3Auth instance, which also makes it easier to write
mocks for unit testing.
class Web3AuthHelperImpl(
private val web3Auth: Web3Auth
): Web3AuthHelper {
// Performs the login to authenticate the user with Web3Auth network.
override suspend fun login(loginParams: LoginParams): CompletableFuture<Web3AuthResponse> {
return web3Auth.login(loginParams)
}
// Logout of the current active session.
override suspend fun logOut(): CompletableFuture<Void> {
return web3Auth.logout()
}
// Returns the Ethereum compatible private key.
override fun getPrivateKey(): String {
return web3Auth.getPrivkey()
}
// Returns the user information such as name, email, profile image, and etc.
// For more details, please checkout UserInfo.
override fun getUserInfo(): UserInfo {
try {
return web3Auth.getUserInfo()!!
} catch (e: Exception) {
throw e
}
}
override suspend fun initialize(): CompletableFuture<Void> {
return web3Auth.initialize()
}
override suspend fun setResultUrl(uri: Uri?) {
return web3Auth.setResultUrl(uri)
}
override suspend fun isUserAuthenticated(): Boolean {
return web3Auth.getPrivkey().isNotEmpty()
}
}
2.2.2 Initialize the Web3Auth instance in the Kotlin module and make it a singleton component.
val appModule = module {
single {
getWeb3AuthHelper(get())
}
// Additional code
viewModel { MainViewModel(get()) }
}
private fun getWeb3AuthHelper(context: Context): Web3AuthHelper {
val web3Auth = Web3Auth(
Web3AuthOptions(
clientId = "WEB3AUTH_CLIENT_ID",
context = context,
network = Network.SAPPHIRE_MAINNET,
redirectUrl = Uri.parse("w3a://com.example.android_playground/auth")
)
)
return Web3AuthHelperImpl(web3Auth)
}
Learn more about Embedded Wallets initialization.
2.3 Session management
To check whether the user is authenticated, you can use the getPrivateKey or getEd25519PrivKey method.
For an authenticated user, the result would be a non-empty string. You can navigate to different
views based on the result. If the user is already authenticated, we'll generate and prepare the Credentials,
required to interact with the blockchain. Along with that, we'll retrieve user info, and navigate them
to HomeScreen. In case of no active session, we'll navigate to LoginScreen to authenticate again.
::: note
Learn more about Web3Auth session management.
:::
Since we are using the MVVM architecture, we'll create a ViewModel class to encapsulate the business
logic for Embedded Wallets and Ethereum chain interaction.
class MainViewModel(private val web3AuthHelper: Web3AuthHelper) : ViewModel() {
// _isLoggedIn can be used in the UI to know whether the user is logged.
private val _isLoggedIn: MutableStateFlow<Boolean> = MutableStateFlow(false)
val isLoggedIn: StateFlow<Boolean> = _isLoggedIn
lateinit var credentials: Credentials
lateinit var userInfo: UserInfo
// Additional code
// Function to retrieve private key.
private fun privateKey(): String {
return web3AuthHelper.getPrivateKey()
}
// prepareCredentials uses the private key to create Ethereum credentials which
// can be used to retrieve the EOA address, and sign the transactions.
private fun prepareCredentials() {
credentials = Credentials.create(privateKey())
}
private fun prepareUserInfo() {
userInfo = web3AuthHelper.getUserInfo()
}
// Additional code
fun initialise() {
viewModelScope.launch {
web3AuthHelper.initialize().await()
isUserLoggedIn()
}
}
private fun isUserLoggedIn() {
viewModelScope.launch {
try {
val isLoggedIn = web3AuthHelper.isUserAuthenticated()
if (isLoggedIn) {
prepareCredentials()
prepareUserInfo()
}
_isLoggedIn.emit(isLoggedIn)
} catch (e: Exception) {
_isLoggedIn.emit(false)
}
}
}
}
2.4 Authentication
If the user is not authenticated, we can utilize the login method to authenticate the user. For the
wallet, we will add an Email Passwordless login. We'll create a helper function, login inside
MainViewModel. The login method is pretty straightforward in Embedded Wallets and takes LoginParams as input.
After successfully logging in, we'll generate and prepare the Credentials, required to interact with the
blockchain. Along with that, we'll retrieve user info, and navigate them to HomeScreen.
Learn more about Embedded Wallets' LoginParams.
class MainViewModel(private val web3AuthHelper: Web3AuthHelper) : ViewModel() {
// Additional code
fun login(email: String) {
val loginParams = LoginParams(
loginProvider = Provider.EMAIL_PASSWORDLESS,
extraLoginOptions = ExtraLoginOptions(login_hint = email)
)
viewModelScope.launch {
try {
web3AuthHelper.login(loginParams = loginParams).await()
// Functions from Session Management code snippets
prepareCredentials()
prepareUserInfo()
// Emit true to navigate to HomeScreen
_isLoggedIn.emit(true)
} catch (error: Exception) {
_isLoggedIn.emit(false)
throw error
}
}
}
}
Step 3: Set up blockchain providers
Once we have successfully authenticated the user, the next step is to fetch the user details, retrieve the wallet address, and prepare blockchain providers for interactions. This guide supports the Ethereum ecosystem, but the general idea can be used for any blockchain ecosystem.
Given that the project follows MVVM architecture pattern, we'll want to create a use case to interact with the blockchain. This use case will help us expand the blockchain support while isolating it from the rest of the application.
For interacting with Ethereum chains, we'll use the web3j SDK.
To install the web3j SDK, in your module-level build.gradle or settings.gradle file, add web3j in your
app-level dependencies.
dependencies {
// ...
implementation 'org.web3j:core:4.8.7-android'
}
After successfully installing the SDK, it's time to set up our Ethereum use case. First, we'll create a new
class, EthereumUseCase interface, which will used as a base class for EthereumUseCaseImpl. If you wish
to support any additional ecosystem, you can create the chain-agnostic use case and implement the methods.
Learn more about integrating different blockchains with Embedded Wallets.
interface EthereumUseCase {
suspend fun getBalance(publicKey: String): String
suspend fun signMessage(message: String, sender: Credentials): String
suspend fun sendETH(amount: String, recipientAddress: String, sender: Credentials): String
suspend fun getBalanceOf(contractAddress: String, address: String, credentials: Credentials): String
suspend fun approve(contractAddress: String, spenderAddress: String, credentials: Credentials): String
}
Generally, for any blockchain provider, you'll only require the getBalance, sendTransaction, and
signMessage. The getBalance and approve can be used to interact with smart contracts. To interact
with smart contracts, we'll be required to generate smart contract function wrappers in Java from Solidity
ABI files.