# dGEN SubAccount SDK

Android SDK for managing smart contract sub-accounts on [ethOS](https://ethosmobile.org/) 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.

### Features

* **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) and `eth_signTypedData` (EIP-712)
* **ERC-6492** signature wrapping for undeployed wallets
* **Multi-chain support** — Ethereum, Base, Optimism, Polygon, Arbitrum, and more
* **Batch transactions** via `executeBatch`

### Requirements

* **Android minSdk 28** (Android 9+)
* **ethOS device** — the SDK requires the ethOS system wallet service. Throws `NoSysWalletException` on non-ethOS devices.
* **Bundler RPC endpoint** (e.g. [Pimlico](https://pimlico.io/), [Alchemy](https://alchemy.com/))
* **RPC endpoint** (e.g. Alchemy, Infura)

### Installation

#### JitPack

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.1.0")
}
```

> Replace `0.1.0` with the [latest release tag](https://github.com/EthereumPhone/DgenSubAccountSDK/releases) or use `main-SNAPSHOT` for the latest commit.

#### Packaging conflicts

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"
    }
}
```

### Quick Start

#### Initialize the SDK

```
import org.ethereumphone.walletsdk.WalletSDK
import org.ethereumphone.walletsdk.model.NoSysWalletException
import org.web3j.protocol.Web3j
import org.web3j.protocol.http.HttpService

try {
    val sdk = WalletSDK(
        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
}
```

#### Get wallet address

```
val address: String = sdk.getAddress()
```

#### Sign a message

```
// 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"
)
```

#### Send a transaction

```
val txHash = sdk.sendTransaction(
    to = "0xRecipientAddress",
    value = "1000000000000000000", // 1 ETH in wei
    data = "0x",
    callGas = null, // auto-estimate
    chainId = 1
)
```

#### Batch transactions

```
val txHash = sdk.sendTransaction(
    txParamsList = listOf(
        WalletSDK.TxParams("0xAddr1", "1000000000000000000", "0x"),
        WalletSDK.TxParams("0xAddr2", "2000000000000000000", "0x"),
    ),
    callGas = null,
    chainId = 1
)
```

#### Switch chains

```
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"
)
```

### API Reference

#### Constructor

```
WalletSDK(
    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 |

#### Core Methods

| 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 Classes

```
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
)
```

#### Supported Chains

The SDK includes built-in RPC mappings via `WalletSDK.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`.

### Demo App

The `app/` module contains a Compose-based demo app that exercises the SDK. To run it:

1. Add your API keys to `local.properties`:

```
ALCHEMY_API=your_alchemy_api_key
BUNDLER_API=your_pimlico_api_key
```

2. Build and install:

```
./gradlew :app:installDebug
```

The demo app provides:

* Chain selector dropdown
* SDK initialization with error handling
* Wallet address and public key display
* Message signing (personal\_sign)
* Transaction sending form
* Result log

### Architecture

```
┌─────────────────────────────────────────────┐
│                  Your App                    │
├─────────────────────────────────────────────┤
│               WalletSDK                      │
│  ┌──────────┐  ┌──────────┐  ┌───────────┐ │
│  │ Local P-256│  │ Create2   │  │ UserOp    │ │
│  │ KeyStore  │  │ Address   │  │ Builder   │ │
│  └────┬─────┘  └──────────┘  └─────┬─────┘ │
│       │                             │        │
│  ┌────▼─────────────────────────────▼─────┐ │
│  │         Sign & Submit Pipeline          │ │
│  └────────────────┬───────────────────────┘ │
├───────────────────┼─────────────────────────┤
│  ethOS System     │         Bundler RPC      │
│  Service          │         (Pimlico, etc.)  │
│  (recovery addr)  │                          │
└───────────────────┴──────────────────────────┘
```

* **Local P-256 key** (Android KeyStore) — primary wallet owner, signs all operations
* **ethOS system service** — provides the device recovery address (second owner)
* **CoinbaseSmartWallet factory** — CREATE2 address computation and wallet deployment
* **ERC-4337 bundler** — gas estimation and UserOperation submission

### Github Repo:

{% embed url="<https://github.com/EthereumPhone/DgenSubAccountSDK/>" %}
