Add comprehensive Cardano specs review for native wallet implementation
Covers: - CIP-1852 key derivation with code examples - Transaction building (cardano-client-lib + CSL) - Android library comparison (cardano-client-lib recommended) - CIP-0013 payment URI parsing - CIP-0030/CIP-0045 dApp connectivity - Koios vs Blockfrost API analysis - MVP architecture recommendations
This commit is contained in:
parent
7215ebb509
commit
10e73d484b
1 changed files with 879 additions and 0 deletions
879
CARDANO-SPECS-REVIEW.md
Normal file
879
CARDANO-SPECS-REVIEW.md
Normal file
|
|
@ -0,0 +1,879 @@
|
|||
# Cardano Technical Specifications Review for Element X ADA Wallet
|
||||
|
||||
**Date:** 2026-03-26
|
||||
**Purpose:** Deep technical review for building a native Cardano wallet inside an Android app (Element X fork)
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
1. [Key Derivation (CIP-1852)](#1-key-derivation-cip-1852)
|
||||
2. [Transaction Building & Signing](#2-transaction-building--signing)
|
||||
3. [Android Library Options](#3-android-library-options)
|
||||
4. [Minimum Viable TX Flow](#4-minimum-viable-tx-flow)
|
||||
5. [CIP-0013: Payment URI Format](#5-cip-0013-payment-uri-format)
|
||||
6. [CIP-0030: dApp-Wallet Bridge](#6-cip-0030-dapp-wallet-bridge)
|
||||
7. [CIP-0045: WebRTC dApp-Wallet Communication](#7-cip-0045-webrtc-dapp-wallet-communication)
|
||||
8. [WalletConnect v2 on Cardano](#8-walletconnect-v2-on-cardano)
|
||||
9. [Backend APIs: Koios vs Blockfrost](#9-backend-apis-koios-vs-blockfrost)
|
||||
10. [Recommendations](#10-recommendations)
|
||||
|
||||
---
|
||||
|
||||
## 1. Key Derivation (CIP-1852)
|
||||
|
||||
### Standard Derivation Path
|
||||
|
||||
CIP-1852 defines the HD wallet derivation for Cardano Shelley-era wallets, based on BIP-44/BIP-32-Ed25519.
|
||||
|
||||
**Standard path:**
|
||||
```
|
||||
m / purpose' / coin_type' / account' / role / index
|
||||
```
|
||||
|
||||
**Concrete values:**
|
||||
- `purpose`: **1852'** (hardened) - Shelley wallets (or 44' for Byron)
|
||||
- `coin_type`: **1815'** (hardened) - Cardano's registered coin type
|
||||
- `account`: **0'** (hardened) - First account
|
||||
- `role`:
|
||||
- **0** = External chain (receiving addresses)
|
||||
- **1** = Internal chain (change addresses)
|
||||
- **2** = Staking key
|
||||
- `index`: Sequential address index
|
||||
|
||||
**Example derivation paths:**
|
||||
```
|
||||
m/1852'/1815'/0'/0/0 → First external/receiving address
|
||||
m/1852'/1815'/0'/1/0 → First internal/change address
|
||||
m/1852'/1815'/0'/2/0 → Staking key
|
||||
```
|
||||
|
||||
### Code Example (cardano-serialization-lib / JavaScript)
|
||||
|
||||
```javascript
|
||||
function harden(num) {
|
||||
return 0x80000000 + num;
|
||||
}
|
||||
|
||||
// From mnemonic to root key
|
||||
import { mnemonicToEntropy } from 'bip39';
|
||||
|
||||
const entropy = mnemonicToEntropy(
|
||||
"test walk nut penalty hip pave soap entry language right filter choice"
|
||||
);
|
||||
|
||||
const rootKey = CardanoWasm.Bip32PrivateKey.from_bip39_entropy(
|
||||
Buffer.from(entropy, 'hex'),
|
||||
Buffer.from(''), // empty password
|
||||
);
|
||||
|
||||
// Derive account key
|
||||
const accountKey = rootKey
|
||||
.derive(harden(1852)) // purpose
|
||||
.derive(harden(1815)) // coin type
|
||||
.derive(harden(0)); // account #0
|
||||
|
||||
// Derive payment key (external, index 0)
|
||||
const utxoPubKey = accountKey
|
||||
.derive(0) // external chain
|
||||
.derive(0) // index 0
|
||||
.to_public();
|
||||
|
||||
// Derive staking key
|
||||
const stakeKey = accountKey
|
||||
.derive(2) // staking role
|
||||
.derive(0)
|
||||
.to_public();
|
||||
|
||||
// Create base address (payment + staking)
|
||||
const baseAddr = CardanoWasm.BaseAddress.new(
|
||||
CardanoWasm.NetworkInfo.mainnet().network_id(),
|
||||
CardanoWasm.StakeCredential.from_keyhash(utxoPubKey.to_raw_key().hash()),
|
||||
CardanoWasm.StakeCredential.from_keyhash(stakeKey.to_raw_key().hash()),
|
||||
);
|
||||
|
||||
console.log(baseAddr.to_address().to_bech32());
|
||||
// Output: addr1q...
|
||||
```
|
||||
|
||||
### Code Example (cardano-client-lib / Java/Kotlin)
|
||||
|
||||
```kotlin
|
||||
import com.bloxbean.cardano.client.account.Account
|
||||
import com.bloxbean.cardano.client.common.model.Networks
|
||||
|
||||
// Create new wallet from mnemonic
|
||||
val mnemonic = "test walk nut penalty hip pave soap entry language right filter choice"
|
||||
val account = Account(Networks.mainnet(), mnemonic)
|
||||
|
||||
// Get addresses
|
||||
val baseAddress = account.baseAddress() // addr1q...
|
||||
val enterpriseAddress = account.enterpriseAddress()
|
||||
val stakeAddress = account.stakeAddress() // stake1u...
|
||||
|
||||
// For derivation at specific index
|
||||
val account5 = Account(Networks.mainnet(), mnemonic, 5) // account index 5
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Transaction Building & Signing
|
||||
|
||||
### Simple ADA Transfer with cardano-client-lib (Java/Kotlin)
|
||||
|
||||
This is the **recommended approach for Android** as it's pure Java/Kotlin.
|
||||
|
||||
```kotlin
|
||||
import com.bloxbean.cardano.client.account.Account
|
||||
import com.bloxbean.cardano.client.backend.blockfrost.service.BFBackendService
|
||||
import com.bloxbean.cardano.client.common.model.Networks
|
||||
import com.bloxbean.cardano.client.quicktx.QuickTxBuilder
|
||||
import com.bloxbean.cardano.client.quicktx.Tx
|
||||
import com.bloxbean.cardano.client.function.helper.SignerProviders
|
||||
|
||||
// Setup
|
||||
val backendService = BFBackendService(
|
||||
"https://cardano-mainnet.blockfrost.io/api/v0",
|
||||
"<PROJECT_ID>"
|
||||
)
|
||||
|
||||
val sender = Account(Networks.mainnet(), "<mnemonic>")
|
||||
val senderAddress = sender.baseAddress()
|
||||
val receiverAddress = "addr1q..."
|
||||
|
||||
// Build and submit transaction
|
||||
val tx = Tx()
|
||||
.payToAddress(receiverAddress, Amount.ada(5.0))
|
||||
.from(senderAddress)
|
||||
|
||||
val quickTxBuilder = QuickTxBuilder(backendService)
|
||||
val result = quickTxBuilder
|
||||
.compose(tx)
|
||||
.withSigner(SignerProviders.signerFrom(sender))
|
||||
.completeAndWait { txHash -> println("Submitted: $txHash") }
|
||||
|
||||
if (result.isSuccessful) {
|
||||
println("Transaction hash: ${result.value}")
|
||||
}
|
||||
```
|
||||
|
||||
### Low-level Transaction Building (cardano-client-lib)
|
||||
|
||||
```kotlin
|
||||
import com.bloxbean.cardano.client.function.TxBuilder
|
||||
import com.bloxbean.cardano.client.function.TxBuilderContext
|
||||
import com.bloxbean.cardano.client.function.Output
|
||||
import com.bloxbean.cardano.client.function.helper.*
|
||||
import com.bloxbean.cardano.client.backend.api.DefaultUtxoSupplier
|
||||
import com.bloxbean.cardano.client.backend.api.DefaultProtocolParamsSupplier
|
||||
|
||||
// Define outputs
|
||||
val output = Output.builder()
|
||||
.address(receiverAddress)
|
||||
.assetName("lovelace")
|
||||
.qty(5_000_000) // 5 ADA in lovelace
|
||||
.build()
|
||||
|
||||
// Build transaction
|
||||
val txBuilder = output.outputBuilder()
|
||||
.buildInputs(InputBuilders.createFromSender(senderAddress, senderAddress))
|
||||
.andThen(BalanceTxBuilders.balanceTx(senderAddress, 1))
|
||||
|
||||
val utxoSupplier = DefaultUtxoSupplier(backendService.utxoService)
|
||||
val protocolParamsSupplier = DefaultProtocolParamsSupplier(backendService.epochService)
|
||||
|
||||
// Build and sign
|
||||
val signedTx = TxBuilderContext.init(utxoSupplier, protocolParamsSupplier)
|
||||
.buildAndSign(txBuilder, SignerProviders.signerFrom(sender))
|
||||
|
||||
// Submit
|
||||
val result = backendService.transactionService.submitTransaction(signedTx.serialize())
|
||||
```
|
||||
|
||||
### Transaction Building with cardano-serialization-lib (JavaScript/WASM)
|
||||
|
||||
```javascript
|
||||
// Protocol parameters (these change - fetch from backend)
|
||||
const linearFee = CardanoWasm.LinearFee.new(
|
||||
CardanoWasm.BigNum.from_str('44'),
|
||||
CardanoWasm.BigNum.from_str('155381')
|
||||
);
|
||||
|
||||
const txBuilderCfg = CardanoWasm.TransactionBuilderConfigBuilder.new()
|
||||
.fee_algo(linearFee)
|
||||
.pool_deposit(CardanoWasm.BigNum.from_str('500000000'))
|
||||
.key_deposit(CardanoWasm.BigNum.from_str('2000000'))
|
||||
.max_value_size(4000)
|
||||
.max_tx_size(8000)
|
||||
.coins_per_utxo_word(CardanoWasm.BigNum.from_str('34482'))
|
||||
.build();
|
||||
|
||||
const txBuilder = CardanoWasm.TransactionBuilder.new(txBuilderCfg);
|
||||
|
||||
// Add input (must provide UTxO details from backend)
|
||||
txBuilder.add_key_input(
|
||||
prvKey.to_public().hash(),
|
||||
CardanoWasm.TransactionInput.new(
|
||||
CardanoWasm.TransactionHash.from_bytes(
|
||||
Buffer.from("8561258e210352fba2ac0488afed67b3427a27ccf1d41ec030c98a8199bc22ec", "hex")
|
||||
),
|
||||
0 // index
|
||||
),
|
||||
CardanoWasm.Value.new(CardanoWasm.BigNum.from_str('10000000'))
|
||||
);
|
||||
|
||||
// Add output
|
||||
const outputAddr = CardanoWasm.Address.from_bech32("addr_test1qpu...");
|
||||
txBuilder.add_output(
|
||||
CardanoWasm.TransactionOutput.new(
|
||||
outputAddr,
|
||||
CardanoWasm.Value.new(CardanoWasm.BigNum.from_str('5000000'))
|
||||
)
|
||||
);
|
||||
|
||||
// Set TTL (time-to-live)
|
||||
txBuilder.set_ttl(410021);
|
||||
|
||||
// Add change output (calculates fee automatically)
|
||||
const changeAddr = CardanoWasm.Address.from_bech32("addr_test1...");
|
||||
txBuilder.add_change_if_needed(changeAddr);
|
||||
|
||||
// Build transaction body
|
||||
const txBody = txBuilder.build();
|
||||
const txHash = CardanoWasm.hash_transaction(txBody);
|
||||
|
||||
// Sign
|
||||
const witnesses = CardanoWasm.TransactionWitnessSet.new();
|
||||
const vkeyWitnesses = CardanoWasm.Vkeywitnesses.new();
|
||||
const vkeyWitness = CardanoWasm.make_vkey_witness(txHash, prvKey);
|
||||
vkeyWitnesses.add(vkeyWitness);
|
||||
witnesses.set_vkeys(vkeyWitnesses);
|
||||
|
||||
// Assemble final transaction
|
||||
const transaction = CardanoWasm.Transaction.new(txBody, witnesses, undefined);
|
||||
const signedTxCbor = transaction.to_bytes();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Android Library Options
|
||||
|
||||
### Option A: cardano-client-lib (RECOMMENDED)
|
||||
|
||||
**Repository:** https://github.com/bloxbean/cardano-client-lib
|
||||
|
||||
**Why it's the best choice for Android:**
|
||||
- **Pure Java** - No JNI/native dependencies
|
||||
- **Works on Android out of the box** - Min SDK 21+
|
||||
- **Active development** - Regular releases, good documentation
|
||||
- **Modular design** - Include only what you need
|
||||
- **Backend-agnostic** - Supports Blockfrost, Koios, Ogmios/Kupo
|
||||
- **CIP implementations** - CIP-8 (message signing), CIP-20, CIP-25, CIP-30, CIP-67, CIP-68
|
||||
|
||||
**Gradle dependencies:**
|
||||
```groovy
|
||||
dependencies {
|
||||
// Core library
|
||||
implementation 'com.bloxbean.cardano:cardano-client-lib:0.7.1'
|
||||
|
||||
// Pick ONE backend:
|
||||
implementation 'com.bloxbean.cardano:cardano-client-backend-blockfrost:0.7.1'
|
||||
// OR
|
||||
implementation 'com.bloxbean.cardano:cardano-client-backend-koios:0.7.1'
|
||||
}
|
||||
```
|
||||
|
||||
**Key modules:**
|
||||
| Module | Purpose |
|
||||
|--------|---------|
|
||||
| `cardano-client-crypto` | BIP32, BIP39, CIP1852 key derivation |
|
||||
| `cardano-client-address` | Address generation (Base, Enterprise, Stake) |
|
||||
| `cardano-client-transaction-spec` | Transaction serialization (CBOR) |
|
||||
| `cardano-client-quicktx` | High-level transaction builder |
|
||||
| `cardano-client-cip30` | dApp-Wallet bridge implementation |
|
||||
|
||||
### Option B: cardano-serialization-lib (CSL)
|
||||
|
||||
**Repository:** https://github.com/Emurgo/cardano-serialization-lib
|
||||
|
||||
**Android challenges:**
|
||||
- Written in **Rust**, compiled to WebAssembly
|
||||
- For Android, requires **JNI bindings** via Rust FFI
|
||||
- React Native wrapper exists (`react-native-haskell-shelley`) but adds complexity
|
||||
- **No official AAR** published
|
||||
- Would need to compile Rust to Android NDK targets (arm64-v8a, armeabi-v7a, x86, x86_64)
|
||||
|
||||
**When to consider:**
|
||||
- If you need exact byte-level compatibility with other CSL implementations
|
||||
- If building a complex dApp that needs Plutus script support
|
||||
- If your team already has Rust/JNI expertise
|
||||
|
||||
**Build requirements for Android:**
|
||||
```bash
|
||||
# Would need to set up:
|
||||
rustup target add aarch64-linux-android
|
||||
rustup target add armv7-linux-androideabi
|
||||
rustup target add i686-linux-android
|
||||
rustup target add x86_64-linux-android
|
||||
|
||||
# Then build with cargo-ndk or similar
|
||||
```
|
||||
|
||||
### Option C: Blockfrost Kotlin SDK
|
||||
|
||||
**Repository:** https://github.com/blockfrost/blockfrost-kotlin
|
||||
|
||||
**Purpose:** API client only - does NOT handle key management or transaction building
|
||||
|
||||
**Use for:** Querying blockchain data, submitting pre-built transactions
|
||||
|
||||
```kotlin
|
||||
import io.blockfrost.sdk_kotlin.api.CardanoAddressesApi
|
||||
import io.blockfrost.sdk_kotlin.infrastructure.ApiClient
|
||||
|
||||
ApiClient.apiKey["project_id"] = "<PROJECT_ID>"
|
||||
|
||||
val addressApi = CardanoAddressesApi("https://cardano-mainnet.blockfrost.io/api/v0")
|
||||
val utxos = addressApi.getAddressUtxos("addr1q...")
|
||||
```
|
||||
|
||||
### Recommendation Matrix
|
||||
|
||||
| Use Case | Recommended Library |
|
||||
|----------|---------------------|
|
||||
| Simple wallet (send/receive ADA) | cardano-client-lib |
|
||||
| NFT minting | cardano-client-lib |
|
||||
| Staking delegation | cardano-client-lib |
|
||||
| Complex smart contracts | cardano-serialization-lib (with significant integration effort) |
|
||||
| Just querying data | Blockfrost Kotlin SDK |
|
||||
|
||||
---
|
||||
|
||||
## 4. Minimum Viable TX Flow
|
||||
|
||||
### High-Level Flow
|
||||
|
||||
```
|
||||
1. USER INITIATES PAYMENT
|
||||
└─> Parse recipient address, amount
|
||||
|
||||
2. FETCH UTXOs
|
||||
└─> Query backend for sender's unspent outputs
|
||||
└─> Select UTxOs covering amount + fees
|
||||
|
||||
3. BUILD TRANSACTION
|
||||
├─> Inputs: Selected UTxOs
|
||||
├─> Outputs: Payment output + change output
|
||||
├─> Calculate fees based on tx size
|
||||
└─> Set TTL (current slot + ~7200 slots = ~2 hours)
|
||||
|
||||
4. SIGN TRANSACTION
|
||||
└─> Use payment private key to create witness
|
||||
|
||||
5. SUBMIT TRANSACTION
|
||||
└─> POST serialized CBOR to backend
|
||||
|
||||
6. CONFIRM
|
||||
└─> Poll for confirmation or use websocket
|
||||
```
|
||||
|
||||
### Kotlin Implementation
|
||||
|
||||
```kotlin
|
||||
class CardanoWallet(
|
||||
private val backendService: BackendService,
|
||||
private val account: Account
|
||||
) {
|
||||
suspend fun sendAda(
|
||||
recipientAddress: String,
|
||||
amountAda: Double
|
||||
): Result<String> = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val tx = Tx()
|
||||
.payToAddress(recipientAddress, Amount.ada(amountAda))
|
||||
.from(account.baseAddress())
|
||||
|
||||
val result = QuickTxBuilder(backendService)
|
||||
.compose(tx)
|
||||
.withSigner(SignerProviders.signerFrom(account))
|
||||
.complete()
|
||||
|
||||
if (result.isSuccessful) {
|
||||
// Submit
|
||||
val submitResult = backendService.transactionService
|
||||
.submitTransaction(result.value.serialize())
|
||||
|
||||
if (submitResult.isSuccessful) {
|
||||
Result.success(submitResult.value)
|
||||
} else {
|
||||
Result.failure(Exception(submitResult.response))
|
||||
}
|
||||
} else {
|
||||
Result.failure(Exception("Failed to build transaction"))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Result.failure(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Fee Calculation
|
||||
|
||||
Cardano fees are calculated as:
|
||||
```
|
||||
fee = a * tx_size_bytes + b
|
||||
```
|
||||
|
||||
Current mainnet parameters (as of March 2026):
|
||||
- `a` = 44 lovelace/byte
|
||||
- `b` = 155,381 lovelace (base fee)
|
||||
|
||||
Typical simple transaction: ~250-350 bytes → ~165,000-175,000 lovelace (~0.17 ADA)
|
||||
|
||||
---
|
||||
|
||||
## 5. CIP-0013: Payment URI Format
|
||||
|
||||
### URI Format
|
||||
|
||||
```
|
||||
web+cardano:<address>[?amount=<amount>][&message=<message>]
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
| Parameter | Required | Description |
|
||||
|-----------|----------|-------------|
|
||||
| `address` | Yes | Bech32 address (addr1...) or $handle |
|
||||
| `amount` | No | Amount in lovelace (1 ADA = 1,000,000 lovelace) |
|
||||
| `message` | No | URL-encoded message (for tx metadata) |
|
||||
|
||||
### Examples
|
||||
|
||||
```
|
||||
# Simple payment request
|
||||
web+cardano:addr1qxck...
|
||||
|
||||
# With amount (5 ADA = 5,000,000 lovelace)
|
||||
web+cardano:addr1qxck...?amount=5000000
|
||||
|
||||
# With amount and message
|
||||
web+cardano:addr1qxck...?amount=5000000&message=Coffee%20payment
|
||||
|
||||
# Using ADA handle
|
||||
web+cardano:$kayos
|
||||
```
|
||||
|
||||
### Kotlin Parser
|
||||
|
||||
```kotlin
|
||||
data class CardanoPaymentRequest(
|
||||
val address: String,
|
||||
val amount: Long? = null, // in lovelace
|
||||
val message: String? = null
|
||||
)
|
||||
|
||||
fun parseCardanoUri(uri: String): CardanoPaymentRequest? {
|
||||
val prefix = "web+cardano:"
|
||||
if (!uri.startsWith(prefix)) return null
|
||||
|
||||
val withoutPrefix = uri.removePrefix(prefix)
|
||||
val parts = withoutPrefix.split("?", limit = 2)
|
||||
|
||||
val address = parts[0]
|
||||
val params = if (parts.size > 1) {
|
||||
parts[1].split("&").associate { param ->
|
||||
val kv = param.split("=", limit = 2)
|
||||
kv[0] to (if (kv.size > 1) URLDecoder.decode(kv[1], "UTF-8") else "")
|
||||
}
|
||||
} else emptyMap()
|
||||
|
||||
return CardanoPaymentRequest(
|
||||
address = address,
|
||||
amount = params["amount"]?.toLongOrNull(),
|
||||
message = params["message"]
|
||||
)
|
||||
}
|
||||
|
||||
// Usage
|
||||
val request = parseCardanoUri("web+cardano:addr1q...?amount=5000000&message=Test")
|
||||
// CardanoPaymentRequest(address="addr1q...", amount=5000000, message="Test")
|
||||
```
|
||||
|
||||
### Android Intent Filter
|
||||
|
||||
```xml
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="web+cardano" />
|
||||
</intent-filter>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. CIP-0030: dApp-Wallet Bridge
|
||||
|
||||
### Overview
|
||||
|
||||
CIP-0030 defines a JavaScript API for web dApps to communicate with browser extension wallets. For a mobile wallet, this is relevant when:
|
||||
- Implementing WalletConnect-style connectivity
|
||||
- Building an in-app dApp browser
|
||||
- Providing SDK for dApps to integrate
|
||||
|
||||
### API Methods
|
||||
|
||||
**Initialization:**
|
||||
```javascript
|
||||
// dApp requests connection
|
||||
const api = await window.cardano.nami.enable();
|
||||
```
|
||||
|
||||
**Key Methods:**
|
||||
| Method | Returns | Description |
|
||||
|--------|---------|-------------|
|
||||
| `getNetworkId()` | `Promise<number>` | 0 = testnet, 1 = mainnet |
|
||||
| `getUtxos()` | `Promise<TransactionUnspentOutput[]>` | Wallet's UTxOs in CBOR |
|
||||
| `getBalance()` | `Promise<Value>` | Total balance (CBOR) |
|
||||
| `getUsedAddresses()` | `Promise<Address[]>` | Used addresses |
|
||||
| `getUnusedAddresses()` | `Promise<Address[]>` | Fresh addresses |
|
||||
| `getChangeAddress()` | `Promise<Address>` | Address for change |
|
||||
| `getRewardAddresses()` | `Promise<Address[]>` | Staking addresses |
|
||||
| `signTx(tx, partialSign)` | `Promise<TransactionWitnessSet>` | Sign transaction |
|
||||
| `signData(addr, payload)` | `Promise<DataSignature>` | CIP-8 message signing |
|
||||
| `submitTx(tx)` | `Promise<TransactionHash>` | Submit to network |
|
||||
|
||||
### cardano-client-lib CIP-30 Support
|
||||
|
||||
```kotlin
|
||||
import com.bloxbean.cardano.client.cip.cip30.CIP30DataSigner
|
||||
|
||||
// Sign arbitrary data (CIP-8)
|
||||
val signature = CIP30DataSigner.sign(
|
||||
account.hdKeyPair().privateKey,
|
||||
"Hello Cardano!".toByteArray()
|
||||
)
|
||||
|
||||
// Returns: { signature: "...", key: "..." }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. CIP-0045: WebRTC dApp-Wallet Communication
|
||||
|
||||
### Overview
|
||||
|
||||
CIP-0045 enables **P2P communication** between dApps and mobile wallets using WebRTC/WebTorrent, eliminating the need for browser extensions.
|
||||
|
||||
### How It Works
|
||||
|
||||
```
|
||||
┌─────────────┐ WebRTC/WebTorrent ┌──────────────┐
|
||||
│ dApp │ <─────────────────────────────────>│ Mobile Wallet│
|
||||
│ (Browser) │ P2P via BitTorrent trackers │ (App) │
|
||||
└─────────────┘ └──────────────┘
|
||||
1. dApp displays QR code
|
||||
2. Wallet scans, establishes P2P
|
||||
3. CIP-30 API over the channel
|
||||
```
|
||||
|
||||
### Reference Implementation
|
||||
|
||||
**Library:** `@fabianbormann/cardano-peer-connect`
|
||||
**Repository:** https://github.com/fabianbormann/cardano-peer-connect
|
||||
|
||||
### Wallet Implementation (TypeScript/React Native)
|
||||
|
||||
```typescript
|
||||
import { CardanoPeerConnect } from '@fabianbormann/cardano-peer-connect';
|
||||
|
||||
class MyWalletPeerConnect extends CardanoPeerConnect {
|
||||
constructor() {
|
||||
super({
|
||||
name: 'Element X ADA',
|
||||
version: '1.0.0',
|
||||
icon: '<base64-icon>'
|
||||
});
|
||||
}
|
||||
|
||||
// Implement CIP-30 methods
|
||||
async getRewardAddresses(): Promise<string[]> {
|
||||
return [this.wallet.stakeAddress];
|
||||
}
|
||||
|
||||
async getUsedAddresses(): Promise<string[]> {
|
||||
return [this.wallet.baseAddress];
|
||||
}
|
||||
|
||||
async signTx(tx: string, partialSign: boolean): Promise<string> {
|
||||
// Show UI for user approval
|
||||
const approved = await this.showSigningDialog(tx);
|
||||
if (!approved) throw new Error('User rejected');
|
||||
|
||||
return this.wallet.sign(tx);
|
||||
}
|
||||
}
|
||||
|
||||
// Connect to dApp
|
||||
const peerConnect = new MyWalletPeerConnect();
|
||||
|
||||
peerConnect.setOnConnect((message) => {
|
||||
console.log('Connected to dApp:', message.dApp.name);
|
||||
});
|
||||
|
||||
// Scan QR code to get dApp identifier
|
||||
const dAppId = 'bYUh6Bn6A...388LR1JCrED';
|
||||
|
||||
peerConnect.connect(dAppId, [
|
||||
'wss://tracker.openwebtorrent.com:443/announce',
|
||||
'wss://tracker.btorrent.xyz'
|
||||
]);
|
||||
```
|
||||
|
||||
### Android Wallets Supporting CIP-0045
|
||||
|
||||
| Wallet | CIP-0045 Support | Notes |
|
||||
|--------|-----------------|-------|
|
||||
| Eternl | ✅ | Full support |
|
||||
| Typhon | ✅ | Full support |
|
||||
| Vespr | ⚠️ | WalletConnect preferred |
|
||||
| Nami | ❌ | Browser extension only |
|
||||
| Lace | ❌ | Browser extension only |
|
||||
|
||||
### Recommendation
|
||||
|
||||
CIP-0045 is powerful but complex. For MVP:
|
||||
1. **Start without CIP-0045** - focus on send/receive
|
||||
2. **Phase 2:** Add CIP-0013 URI handling
|
||||
3. **Phase 3:** Consider CIP-0045 for dApp integration
|
||||
|
||||
---
|
||||
|
||||
## 8. WalletConnect v2 on Cardano
|
||||
|
||||
### Current State
|
||||
|
||||
WalletConnect v2 on Cardano is **fragmented**:
|
||||
|
||||
- **No official Cardano namespace** in WC2 registry
|
||||
- Different wallets use different approaches:
|
||||
- **VESPR**: Uses custom implementation
|
||||
- **Flint**: Has WC2 support
|
||||
- **Most wallets**: Prefer CIP-0045
|
||||
|
||||
### VESPR Wallet
|
||||
|
||||
VESPR (https://vespr.xyz/) is a popular mobile wallet:
|
||||
- **Android:** Yes (Play Store)
|
||||
- **iOS:** Yes (App Store)
|
||||
- Supports both CIP-0045 and a custom WC-style protocol
|
||||
- Closed source
|
||||
|
||||
### For Element X ADA
|
||||
|
||||
**Recommendation:** Implement CIP-0045 rather than WalletConnect v2:
|
||||
- Better Cardano ecosystem adoption
|
||||
- Open standard with reference implementation
|
||||
- Works with more dApps
|
||||
|
||||
---
|
||||
|
||||
## 9. Backend APIs: Koios vs Blockfrost
|
||||
|
||||
### Feature Comparison
|
||||
|
||||
| Feature | Blockfrost | Koios |
|
||||
|---------|------------|-------|
|
||||
| **Pricing** | Freemium (50k req/day free) | Free (community) |
|
||||
| **Rate Limits** | 10 req/sec, 500 burst | 100 req/10s |
|
||||
| **Decentralization** | Centralized | Decentralized nodes |
|
||||
| **Kotlin SDK** | ✅ Official | ❌ (REST only) |
|
||||
| **Java SDK** | ✅ Official | Via cardano-client-lib |
|
||||
| **Self-hosting** | ❌ | ✅ |
|
||||
| **IPFS** | ✅ | ❌ |
|
||||
| **Reliability** | Very high | Good (varies by node) |
|
||||
|
||||
### API Comparison
|
||||
|
||||
**Get UTxOs:**
|
||||
|
||||
```bash
|
||||
# Blockfrost
|
||||
curl -H "project_id: <ID>" \
|
||||
"https://cardano-mainnet.blockfrost.io/api/v0/addresses/addr1.../utxos"
|
||||
|
||||
# Koios
|
||||
curl "https://api.koios.rest/api/v1/address_utxos?_address=addr1..."
|
||||
```
|
||||
|
||||
**Submit Transaction:**
|
||||
|
||||
```bash
|
||||
# Blockfrost
|
||||
curl -X POST -H "project_id: <ID>" \
|
||||
-H "Content-Type: application/cbor" \
|
||||
--data-binary @tx.signed \
|
||||
"https://cardano-mainnet.blockfrost.io/api/v0/tx/submit"
|
||||
|
||||
# Koios
|
||||
curl -X POST \
|
||||
-H "Content-Type: application/cbor" \
|
||||
--data-binary @tx.signed \
|
||||
"https://api.koios.rest/api/v1/submittx"
|
||||
```
|
||||
|
||||
### Kotlin/Android Usage
|
||||
|
||||
**Blockfrost with cardano-client-lib:**
|
||||
```kotlin
|
||||
val backendService = BFBackendService(
|
||||
Constants.BLOCKFROST_MAINNET_URL,
|
||||
"<PROJECT_ID>"
|
||||
)
|
||||
|
||||
val utxos = backendService.utxoService.getUtxos(address, 100, 1)
|
||||
```
|
||||
|
||||
**Koios with cardano-client-lib:**
|
||||
```kotlin
|
||||
val backendService = KoiosBackendService(
|
||||
Constants.KOIOS_MAINNET_URL
|
||||
)
|
||||
|
||||
val utxos = backendService.utxoService.getUtxos(address, 100, 1)
|
||||
```
|
||||
|
||||
### Recommendation for Element X ADA
|
||||
|
||||
**Use Blockfrost** for MVP:
|
||||
- Official Kotlin SDK
|
||||
- Higher reliability guarantees
|
||||
- Free tier sufficient for development/testing
|
||||
- Easy to switch to Koios later (same cardano-client-lib interface)
|
||||
|
||||
**Consider Koios** for production if:
|
||||
- Need to minimize costs at scale
|
||||
- Want decentralization
|
||||
- Plan to self-host infrastructure
|
||||
|
||||
---
|
||||
|
||||
## 10. Recommendations
|
||||
|
||||
### Architecture Summary
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ Element X ADA │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ UI Layer (Compose) │
|
||||
│ ├─ WalletScreen (balance, history) │
|
||||
│ ├─ SendScreen (CIP-0013 URI parsing) │
|
||||
│ └─ ReceiveScreen (QR generation) │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ Wallet Core │
|
||||
│ ├─ KeyManager (encrypted mnemonic storage) │
|
||||
│ ├─ TransactionBuilder (QuickTx API) │
|
||||
│ └─ AddressGenerator (CIP-1852) │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ cardano-client-lib (0.7.1) │
|
||||
│ ├─ cardano-client-lib │
|
||||
│ ├─ cardano-client-backend-blockfrost │
|
||||
│ └─ cardano-client-cip30 (for future dApp support) │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ Backend │
|
||||
│ └─ Blockfrost API (mainnet/testnet) │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### MVP Feature Set
|
||||
|
||||
1. **Wallet Management**
|
||||
- Generate new wallet (24-word mnemonic)
|
||||
- Import existing wallet
|
||||
- Secure storage (Android Keystore)
|
||||
|
||||
2. **Basic Operations**
|
||||
- View ADA balance
|
||||
- View transaction history
|
||||
- Send ADA (manual entry)
|
||||
- Receive ADA (QR code + address)
|
||||
|
||||
3. **CIP Support (Phase 1)**
|
||||
- CIP-1852: Key derivation ✅
|
||||
- CIP-0013: Payment URIs ✅
|
||||
|
||||
4. **Future Enhancements (Phase 2+)**
|
||||
- Native tokens display
|
||||
- Staking delegation
|
||||
- CIP-0045: dApp connectivity
|
||||
- NFT gallery
|
||||
|
||||
### Dependencies (build.gradle)
|
||||
|
||||
```groovy
|
||||
dependencies {
|
||||
// Cardano core
|
||||
implementation 'com.bloxbean.cardano:cardano-client-lib:0.7.1'
|
||||
implementation 'com.bloxbean.cardano:cardano-client-backend-blockfrost:0.7.1'
|
||||
|
||||
// For CIP-30 support (Phase 2)
|
||||
implementation 'com.bloxbean.cardano:cardano-client-cip30:0.7.1'
|
||||
|
||||
// Security
|
||||
implementation 'androidx.security:security-crypto:1.1.0-alpha06'
|
||||
|
||||
// QR codes
|
||||
implementation 'com.google.zxing:core:3.5.2'
|
||||
implementation 'com.journeyapps:zxing-android-embedded:4.3.0'
|
||||
}
|
||||
```
|
||||
|
||||
### Security Considerations
|
||||
|
||||
1. **Mnemonic Storage:**
|
||||
- Use Android Keystore + EncryptedSharedPreferences
|
||||
- Never store plaintext
|
||||
- Consider hardware-backed keys on supported devices
|
||||
|
||||
2. **Transaction Signing:**
|
||||
- Derive keys in memory only
|
||||
- Clear sensitive data after use
|
||||
- Require biometric/PIN for signing
|
||||
|
||||
3. **Network:**
|
||||
- Use certificate pinning for API calls
|
||||
- Validate all addresses before sending
|
||||
|
||||
---
|
||||
|
||||
## Appendix: Quick Reference
|
||||
|
||||
### Address Prefixes
|
||||
|
||||
| Type | Mainnet Prefix | Testnet Prefix |
|
||||
|------|---------------|----------------|
|
||||
| Base | addr1 | addr_test1 |
|
||||
| Enterprise | addr1 | addr_test1 |
|
||||
| Stake | stake1 | stake_test1 |
|
||||
| Script | addr1 | addr_test1 |
|
||||
|
||||
### Lovelace Conversions
|
||||
|
||||
```
|
||||
1 ADA = 1,000,000 lovelace
|
||||
1 lovelace = 0.000001 ADA
|
||||
|
||||
// Kotlin helper
|
||||
fun adaToLovelace(ada: Double): Long = (ada * 1_000_000).toLong()
|
||||
fun lovelaceToAda(lovelace: Long): Double = lovelace / 1_000_000.0
|
||||
```
|
||||
|
||||
### Testnet Faucet
|
||||
|
||||
https://docs.cardano.org/cardano-testnets/tools/faucet/
|
||||
|
||||
---
|
||||
|
||||
*Document prepared for Element X ADA project. Last updated: 2026-03-26*
|
||||
Loading…
Add table
Add a link
Reference in a new issue