Import MetaMask SDK into your native Android dapp to enable your users to easily connect with their MetaMask Mobile wallet.
See the following for more information:
You can also see the JavaScript SDK repository and the iOS SDK repository.
To add the SDK from Maven Central as a dependency to your project, in your app/build.gradle
file,
add the following entry to the dependencies
block:
dependencies {
implementation 'io.metamask.androidsdk:metamask-android-sdk'
}
Then, sync your project with the Gradle settings. Once the syncing completes, you can set up the rest of your project.
Import the SDK by adding the following line to the top of your project file:
import io.metamask.androidsdk.Ethereum
We have provided a convenient way to make rpc requests without having to first make a connect request. Please refer to Connect With Request for examples. Otherwise you can connect your dapp to MetaMask in one of two ways:
ethereum
provider object directly.
We recommend using this method in a pure model layer.ethereum
provider object.
We recommend using this method at the app level, because it provides a single instance that
survives configuration changes and can be shared across all views.Note: By default, MetaMask logs three SDK events:
connection_request
,connected
, anddisconnected
. This allows MetaMask to monitor any SDK connection issues. To disable this, setethereum.enableDebug = false
.
The SDK supports both callbacks and coroutines. If using callbacks use Ethereum
object and if using coroutines use EthereumFlow
object. Use the Ethereum
or EthereumFlow
provider object directly to connect your dapp to MetaMask by adding the following
code to your project file:
@AndroidEntryPoint
class SomeModel(context: Context) {
val dappMetadata = DappMetadata("Droid Dapp", "https://www.droiddapp.io")
// To use the Infura API to make read-only requests, specify your Infura API key using the `infuraAPIKey` option in `SDKOptions`
val infuraAPIKey = "1234567890"
// To use your own node (for example, with Hardhat) to make read-only requests, specify your node's chain ID and RPC URL
val readonlyRPCMap = mapOf("0x1" to "hptts://www.testrpc.com") using the `readonlyRPCMap` option
// A) Using callbacks
val ethereum = Ethereum(context, dappMetadata, SDKOptions(infuraAPIKey, readonlyRPCMap))
// This is the same as calling eth_requestAccounts
ethereum.connect() { result ->
when (result) {
is Result.Error -> {
Logger.log("Ethereum connection error: ${result.error.message}")
}
is Result.Success.Item -> {
Logger.log("Ethereum connection result: ${result.value}")
}
}
}
// B) Using coroutines
val coroutineScope = rememberCoroutineScope()
// This is the same as calling eth_requestAccounts
coroutineScope.launch {
when (val result = ethereum.connect()) {
is Result.Error -> {
Logger.log("Ethereum connection error: ${result.error.message}")
}
is Result.Success.Item -> {
Logger.log("Ethereum connection result: ${result.value}")
}
}
}
}
To connect your dapp to MetaMask using a ViewModel, create a ViewModel that injects the
Ethereum/EthereumFlow
provider object, then add wrapper functions for each Ethereum method you wish to call. The example dapp uses EthereumViewModel
for the callback API and EthereumFlowViewModel
for the coroutine API. The rest of the examples use the coroutine option
You can use a dependency manager such as Hilt to initialize the ViewModel and maintain its state across configuration changes. If you use Hilt, your setup might look like the following:
@HiltViewModel
class EthereumViewModel @Inject constructor(
private val ethereum: Ethereum
): ViewModel() {
val ethereumState = MediatorLiveData<EthereumState>().apply {
addSource(ethereum.ethereumState) { newEthereumState ->
value = newEthereumState
}
}
// Wrapper function to connect the dapp
fun connect(callback: ((Result) -> Unit)?) {
ethereum.connect(callback)
}
// Wrapper function call all RPC methods
fun sendRequest(request: EthereumRequest, callback: ((Result) -> Unit)?) {
ethereum.sendRequest(request, callback)
}
}
@HiltViewModel
class EthereumFlowViewModel @Inject constructor(
private val ethereum: EthereumFlowWrapper
): ViewModel() {
val ethereumFlow: Flow<EthereumState> get() = ethereum.ethereumState
suspend fun connect(): Result {
return ethereum.connect()
}
suspend fun sendRequest(request: EthereumRequest): Result {
return ethereum.sendRequest(request)
}
}
To use the ViewModel, add the following code to your project file:
val ethereumViewModel: EthereumFlowViewModel by viewModels()
// This is the same as calling eth_requestAccounts
ethereumViewModel.connect()
See the example dapp's
EthereumViewModel.kt
file for more information.
You can now call any JSON-RPC API method
using ethereum.sendRequest()
. We also have convenience methods for most common RPC calls so that you don't have to manually construct requests.
The following example gets the user's account balance by calling
eth_getBalance
.
This is a read-only rpc ("direct call"), which uses the Infura API if an infuraAPIKey is provided in the SDKOptions - which we highly recommend as it provides a seamless use experience.
val balance = ethereum.getEthBalance(ethereum.selectedAddress, "latest")
// Make request
when (balance) {
is Result.Success.Item -> {
Logger.log("Ethereum account balance: ${result.value}")
balance = result.value
}
is Result.Error -> {
Logger.log("Ethereum request balance error: ${result.error.message}")
}
}
The following example requests the user sign a message by calling
eth_signTypedData_v4
.
val message = "{\"domain\":{\"chainId\":\"${ethereum.chainId}\",\"name\":\"Ether Mail\",\"verifyingContract\":\"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC\",\"version\":\"1\"},\"message\":{\"contents\":\"Hello, Busa!\",\"from\":{\"name\":\"Kinno\",\"wallets\":[\"0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826\",\"0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF\"]},\"to\":[{\"name\":\"Busa\",\"wallets\":[\"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB\",\"0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57\",\"0xB0B0b0b0b0b0B000000000000000000000000000\"]}]},\"primaryType\":\"Mail\",\"types\":{\"EIP712Domain\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"version\",\"type\":\"string\"},{\"name\":\"chainId\",\"type\":\"uint256\"},{\"name\":\"verifyingContract\",\"type\":\"address\"}],\"Group\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"members\",\"type\":\"Person[]\"}],\"Mail\":[{\"name\":\"from\",\"type\":\"Person\"},{\"name\":\"to\",\"type\":\"Person[]\"},{\"name\":\"contents\",\"type\":\"string\"}],\"Person\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"wallets\",\"type\":\"address[]\"}]}}"
val address = ethereum.selectedAddress
when (val result = ethereum.ethSignTypedDataV4(message, address)) {
is Result.Error -> {
Logger.log("Ethereum sign error: ${result.error.message}")
}
is Result.Success.Item -> {
Logger.log("Ethereum sign result: ${result.value}")
}
}
The following example requests the user to personal sign a batch of messages each of
personal_sign
using metamask_batch
rpc.
val ethereumRequest1 = EthereumRequest(
method = EthereumMethod.PERSONAL_SIGN.value,
params = listOf(address, "hello world")
)
val ethereumRequest2 = EthereumRequest(
method = EthereumMethod.PERSONAL_SIGN.value,
params = listOf(address, "second message")
)
when (val result = ethereum.sendRequestBatch(listOf(ethereumRequest1, ethereumRequest2))) {
is Result.Error -> {
Logger.log("Ethereum batch sign error: ${result.error.message}")
}
is Result.Success.Items -> {
Logger.log("Ethereum batch sign result: ${result.value}")
}
}
The following example sends a transaction by calling
eth_sendTransaction
.
// Create parameters
val from = ethereum.selectedAddress
val to = "0x0000000000000000000000000000000000000000"
val value = "0x8ac7230489e80000"
when (val result = ethereum.sendTransaction(from, to, value)) {
is Result.Success.Item -> {
Logger.log("Ethereum transaction result: ${result.value}")
balance = result.value
}
is Result.Error -> {
// handle error
}
}
The following example switches to a new Ethereum chain by calling
wallet_switchEthereumChain
.
when(val result = ethereum.switchEthereumChain(chainId)) {
is Result.Success.Item -> {
// successfully switched to chainId
}
is Result.Error -> {
// handle error
}
}
We have provided a convenience method that enables you to connect and make any request in one rpc request without having to call connect()
first.
val params: Map<String, Any> = mutableMapOf(
"from" to "", // this will be populated with selected address once connected
"to" to "0x0000000000000000000000000000000000000000",
"value" to "0x8ac7230489e80000"
)
val sendTransactionRequest = EthereumRequest(
method = EthereumMethod.ETH_SEND_TRANSACTION.value,
params = listOf(params)
)
when (val result = ethereum.connectWith(sendTransactionRequest)) {
is Result.Error -> {
// handle error
}
is Result.Success.Item -> {
// transaction hash ${result.value}
}
}
We have further provided a specific convenience method that enables you to connect and make a personal sign rpc request. In this case you do not need to construct a request, you only provide the message to personal sign.
val message = "This is the message to sign"
when (val result = ethereum.connectSign(message)) {
is Result.Error -> {
Logger.log("Ethereum connectSign error: ${result.error.message}")
}
is Result.Success.Item -> {
Logger.log("Ethereum connectSign result: ${result.value}")
}
}