Android SDK for managing smart contract sub-accounts on ethOS devices. Uses local P-256 cryptography (Android KeyStore) combined with the ethOS system wallet service for recovery, supporting ERC-4337 Account Abstraction, ERC-6492 signatures, and EIP-712 typed data signing.
This library provides two SDKs:
SubWalletSDK— sub-account operations (local P-256 signing, ERC-4337 UserOperations)WalletSDK— OS-level system wallet (from the transitiveWalletSDKdependency)
- Local P-256 signing via Android KeyStore (no private keys leave the device)
- Smart wallet address computation using CREATE2 (CoinbaseSmartWallet factory)
- ERC-4337 UserOperations — build, sign, and submit through any bundler
- Message signing —
personal_sign(ERC-191) andeth_signTypedData(EIP-712) - ERC-6492 signature wrapping for undeployed wallets
- Multi-chain support — Ethereum, Base, Optimism, Polygon, Arbitrum, and more
- Batch transactions via
executeBatch - OS-level wallet access — transitive dependency on
WalletSDKfor system wallet operations
- Android minSdk 28 (Android 9+)
- ethOS device — the SDK requires the ethOS system wallet service. Throws
NoSysWalletExceptionon non-ethOS devices. - Bundler RPC endpoint (e.g. Pimlico, Alchemy)
- RPC endpoint (e.g. Alchemy, Infura)
Add the JitPack repository to your root settings.gradle.kts:
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven { url = uri("https://jitpack.io") }
}
}Add the dependency to your module build.gradle.kts:
dependencies {
implementation("com.github.EthereumPhone:DgenSubAccountSDK:0.2.0")
}Replace
0.2.0with the latest release tag or usemain-SNAPSHOTfor the latest commit.
This single dependency gives you both SubWalletSDK and the OS-level WalletSDK (exposed transitively via api).
If you encounter META-INF conflicts from web3j/netty transitive dependencies, add this to your app module's build.gradle.kts:
android {
packaging {
resources {
pickFirsts += listOf(
"META-INF/versions/9/OSGI-INF/MANIFEST.MF",
"META-INF/DISCLAIMER",
)
excludes += listOf(
"META-INF/INDEX.LIST",
"META-INF/DEPENDENCIES",
"META-INF/LICENSE.md",
"META-INF/NOTICE.md",
"META-INF/io.netty.versions.properties",
"META-INF/FastDoubleParser-*",
"META-INF/BigDecimal*",
)
}
}
}If using Java 17 records from web3j (AGP 8.x app modules), set:
android {
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
}import org.ethereumphone.subwalletsdk.SubWalletSDK
import org.ethereumphone.subwalletsdk.model.NoSysWalletException
import org.web3j.protocol.Web3j
import org.web3j.protocol.http.HttpService
try {
val sdk = SubWalletSDK(
context = applicationContext,
web3jInstance = Web3j.build(HttpService("https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY")),
bundlerRPCUrl = "https://api.pimlico.io/v2/1/rpc?apikey=YOUR_KEY"
)
} catch (e: NoSysWalletException) {
// Not running on an ethOS device
}The OS-level WalletSDK is available transitively — no extra dependency needed:
import org.ethereumphone.walletsdk.WalletSDK
val sysWallet = WalletSDK(context)
val sysAddress = sysWallet.getAddress()val address: String = sdk.getAddress()// ERC-191 personal_sign
val signature = sdk.signMessage(
message = "Hello, Ethereum!",
chainId = 1,
type = "personal_sign"
)
// EIP-712 typed data
val typedSig = sdk.signMessage(
message = typedDataJsonString,
chainId = 1,
type = "eth_signTypedData"
)val txHash = sdk.sendTransaction(
to = "0xRecipientAddress",
value = "1000000000000000000", // 1 ETH in wei
data = "0x",
callGas = null, // auto-estimate
chainId = 1
)val txHash = sdk.sendTransaction(
txParamsList = listOf(
SubWalletSDK.TxParams("0xAddr1", "1000000000000000000", "0x"),
SubWalletSDK.TxParams("0xAddr2", "2000000000000000000", "0x"),
),
callGas = null,
chainId = 1
)sdk.changeChain(
chainId = 8453,
rpcEndpoint = "https://base-mainnet.g.alchemy.com/v2/YOUR_KEY",
mBundlerRPCUrl = "https://api.pimlico.io/v2/8453/rpc?apikey=YOUR_KEY"
)SubWalletSDK(
context: Context,
web3jInstance: Web3j = Web3j.build(HttpService("https://rpc.ankr.com/eth")),
factoryAddress: String = "0x0BA5ED0c6AA8c49038F819E587E2633c4A9F428a",
entryPointAddress: String = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789",
bundlerRPCUrl: String, // required
keyAlias: String = "p256_walletsdk"
)| Parameter | Description |
|---|---|
context |
Android Context |
web3jInstance |
Web3j RPC client (pass an authenticated RPC) |
bundlerRPCUrl |
ERC-4337 bundler endpoint (required) |
factoryAddress |
CoinbaseSmartWallet factory address |
entryPointAddress |
ERC-4337 EntryPoint v0.6 address |
keyAlias |
Android KeyStore alias for the P-256 signing key |
| Method | Returns | Description |
|---|---|---|
suspend getAddress() |
String |
Smart wallet counterfactual address |
suspend sendTransaction(to, value, data, callGas, chainId?, rpcEndpoint?, gasProvider?) |
String |
Send a single transaction via UserOperation |
suspend sendTransaction(txParamsList, callGas, chainId?, ...) |
String |
Batch multiple transactions |
suspend sendTransaction(userOp, chainId?, ...) |
String |
Submit a pre-built UserOperation |
suspend signMessage(message, chainId, type?) |
String |
Sign message (personal_sign or eth_signTypedData) |
fun signTypedData(typedDataJson, chainId) |
String |
Sign EIP-712 typed data directly |
suspend changeChain(chainId, rpcEndpoint, bundlerRPCUrl) |
String |
Switch RPC and bundler to a different chain |
suspend getNonce(senderAddress, rpcEndpoint?) |
BigInteger |
Get EntryPoint nonce |
fun isDeployed(address) |
Boolean |
Check if wallet contract is deployed |
suspend getPair() |
Pair<BigInteger, BigInteger>? |
Get P-256 public key coordinates (X, Y) |
fun isEthOS() |
Boolean |
Always true (SDK requires ethOS) |
data class TxParams(val to: String, val value: String, val data: String)
data class UserOperation(
val sender: String,
val nonce: BigInteger,
val initCode: String,
val callData: String,
val callGasLimit: BigInteger,
val verificationGasLimit: BigInteger,
val preVerificationGas: BigInteger,
val maxFeePerGas: BigInteger,
val maxPriorityFeePerGas: BigInteger,
val paymasterAndData: String,
var signature: String
)
data class GasEstimation(
val preVerificationGas: BigInteger,
val verificationGasLimit: BigInteger,
val callGasLimit: BigInteger
)
data class GasPrice(
val maxFeePerGas: BigInteger,
val maxPriorityFeePerGas: BigInteger
)The SDK includes built-in RPC mappings via SubWalletSDK.getRPCforChainId():
| Chain | ID |
|---|---|
| Ethereum | 1 |
| Optimism | 10 |
| BNB Chain | 56 |
| Polygon | 137 |
| Arbitrum | 42161 |
| Base | 8453 |
| Base Sepolia | 84532 |
| Zora | 7777777 |
| Avalanche | 43114 |
Custom chains are supported by passing your own web3jInstance and bundlerRPCUrl.
The app/ module contains a Compose-based demo app that exercises both SDKs. To run it:
- Add your API keys to
local.properties:
ALCHEMY_API=your_alchemy_api_key
BUNDLER_API=your_pimlico_api_key- Build and install:
./gradlew :app:installDebugThe demo app provides:
- Chain selector dropdown
- SDK initialization with error handling
- Sub-wallet address and system wallet address display
- P-256 public key display
- Message signing (personal_sign)
- Transaction sending form
- Result log
┌─────────────────────────────────────────────────┐
│ Your App │
├────────────────────────┬────────────────────────┤
│ SubWalletSDK │ WalletSDK │
│ (sub-accounts) │ (OS-level wallet) │
│ ┌──────────┐ ┌─────┐│ │
│ │ Local P-256│ │Create│ (from transitive dep) │
│ │ KeyStore │ │2 Addr│ │
│ └────┬─────┘ └─────┘│ │
│ │ │ │
│ ┌────▼──────────────┐ │ │
│ │ Sign & Submit │ │ │
│ │ (ERC-4337) │ │ │
│ └────────┬──────────┘ │ │
├───────────┼────────────┼─────────────────────────┤
│ ethOS System Service │ Bundler RPC │
│ (recovery addr) │ (Pimlico, etc.) │
└────────────────────────┴─────────────────────────┘
- SubWalletSDK — local P-256 key (Android KeyStore) as primary wallet owner, signs all ERC-4337 operations
- WalletSDK — OS-level system wallet, accessed via the ethOS system service
- ethOS system service — provides the device recovery address (second owner for sub-accounts)
- CoinbaseSmartWallet factory — CREATE2 address computation and wallet deployment
- ERC-4337 bundler — gas estimation and UserOperation submission
See LICENSE for details.