Android example demonstrating Grouped Connections (formerly Aggregate Verifiers) with MetaMask Embedded Wallets (formerly Web3Auth Plug and Play). Grouped connections allow multiple login methods — Google and Email Passwordless in this example — to resolve to the same wallet address for the same user.
- Grouped connections: Google Sign-In + Email Passwordless → same wallet address
- EVM wallet creation and management
- Get wallet address, Sepolia balance, sign messages, and send transactions
- Session persistence across app restarts
- Android Studio Hedgehog (2023.1.1) or later
- Android API 24+, Compile SDK 34
- JDK 17+
- MetaMask Embedded Wallets Dashboard account
- Google OAuth Client ID (for the Google login connection)
- Auth0 application (for the Email Passwordless connection)
git clone https://github.com/Web3Auth/web3auth-android-examples.git
cd web3auth-android-examples/android-aggregate-verifier-example- Create a project on the Embedded Wallets Dashboard.
- Go to Authentication → create a Grouped Connection with two sub-connections:
- Google sub-connection — Auth Connection ID: e.g.
w3a-google - Auth0 Email Passwordless sub-connection — Auth Connection ID: e.g.
w3a-a0-email-passwordless
- Google sub-connection — Auth Connection ID: e.g.
- Note the Grouped Auth Connection ID (e.g.
aggregate-sapphire). - Allowlist your redirect URI:
com.sbz.web3authdemoapp://auth
<string name="web3auth_project_id">YOUR_WEB3AUTH_CLIENT_ID</string>
<string name="web3auth_google_client_id">YOUR_GOOGLE_CLIENT_ID</string>
<string name="web3auth_auth0_client_id">YOUR_AUTH0_CLIENT_ID</string>private const val GROUPED_AUTH_CONNECTION_ID = "aggregate-sapphire"
private const val GOOGLE_AUTH_CONNECTION_ID = "w3a-google"
private const val AUTH0_AUTH_CONNECTION_ID = "w3a-a0-email-passwordless"
private const val AUTH0_DOMAIN = "https://web3auth.au.auth0.com"
val authConnections = listOf(
AuthConnectionConfig(
authConnectionId = GOOGLE_AUTH_CONNECTION_ID,
authConnection = AuthConnection.GOOGLE,
clientId = getString(R.string.web3auth_google_client_id),
groupedAuthConnectionId = GROUPED_AUTH_CONNECTION_ID
),
AuthConnectionConfig(
authConnectionId = AUTH0_AUTH_CONNECTION_ID,
authConnection = AuthConnection.CUSTOM,
clientId = getString(R.string.web3auth_auth0_client_id),
groupedAuthConnectionId = GROUPED_AUTH_CONNECTION_ID,
jwtParameters = ExtraLoginOptions(
domain = AUTH0_DOMAIN,
userIdField = "email",
isUserIdCaseSensitive = false
)
)
)
web3Auth = Web3Auth(
Web3AuthOptions(
clientId = getString(R.string.web3auth_project_id),
web3AuthNetwork = Web3AuthNetwork.SAPPHIRE_MAINNET,
redirectUrl = "com.sbz.web3authdemoapp://auth",
authConnectionConfig = authConnections
), this
)
web3Auth.setResultUrl(intent?.data)
val sessionResponse: CompletableFuture<Void> = web3Auth.initialize()
sessionResponse.whenComplete { _, error ->
if (error == null) {
val credentials = Credentials.create(web3Auth.getPrivateKey())
}
}val loginFuture: CompletableFuture<Web3AuthResponse> =
web3Auth.login(LoginParams(Provider.GOOGLE))
loginFuture.whenComplete { _, error ->
if (error == null) {
val credentials = Credentials.create(web3Auth.getPrivateKey())
}
}val loginFuture: CompletableFuture<Web3AuthResponse> =
web3Auth.login(
LoginParams(
Provider.JWT,
extraLoginOptions = ExtraLoginOptions(
domain = AUTH0_DOMAIN,
verifierIdField = "email",
isVerifierIdCaseSensitive = false
)
)
)Both login methods resolve to the same wallet address because they share the same
groupedAuthConnectionId.
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
web3Auth.setResultUrl(intent?.data)
}
override fun onResume() {
super.onResume()
if (Web3Auth.getCustomTabsClosed()) {
web3Auth.setResultUrl(null)
Web3Auth.setCustomTabsClosed(false)
}
}- Android SDK Documentation
- Custom Authentication Guide
- MetaMask Embedded Wallets Docs
- Dashboard
- Builder Hub Community
MIT — see LICENSE for details.