6cee6540ea
Create Release Assets / build-desktop (arm64, macos, macos-14, packageReleaseDmg) (push) Has been cancelled
Create Release Assets / build-desktop (x64, linux, ubuntu-latest, packageReleaseDeb packageReleaseRpm) (push) Has been cancelled
Create Release Assets / build-desktop (x64, linux-portable, ubuntu-latest, createReleaseAppImage createReleaseDistributable) (push) Has been cancelled
Create Release Assets / build-desktop (x64, windows, windows-latest, packageReleaseMsi createReleaseDistributable) (push) Has been cancelled
Create Release Assets / build-cli (arm64, macos, macos-14, amyImage) (push) Has been cancelled
Create Release Assets / build-cli (x64, linux, ubuntu-latest, amyImage jpackageDeb jpackageRpm) (push) Has been cancelled
Create Release Assets / deploy-android (push) Has been cancelled
- MoneroScreen.kt: complete ~800-line UI with send, receive, daemon settings, transaction list, backup seed dialog - Account.kt: add 7 reactive StateFlows and updateMoneroState() - strings.xml: add monero-related string resources - AGENTS.md: update with actual progress state - GARNET_README.md: rewrite merge progress section - Uses MaterialSymbols from commons, no compose-material-icons-extended - compileFdroidDebugKotlin: 0 errors
590 lines
25 KiB
Markdown
590 lines
25 KiB
Markdown
# Garnet Integration into Amethyst - Project Notes & Learnings
|
|
|
|
## Quick Reference
|
|
|
|
| Item | Value |
|
|
|------|-------|
|
|
| **Garnet repo** | https://github.com/retrnull/garnet |
|
|
| **Amethyst repo** | https://github.com/vitorpamplona/amethyst |
|
|
| **Garnet base amethyst commit** | `175b79b29` (April 2024) |
|
|
| **Garnet fork date** | ~July 10, 2024 |
|
|
| **Latest amethyst commit** | `a4310317a` (May 2026) |
|
|
| **Commits since fork** | ~9,330 |
|
|
| **Garnet version** | v0.3.0 |
|
|
| **Monero source** | https://github.com/retrnull/monero (branch: `release-v0.18.3.3-garnet`) |
|
|
| **Current status** | Phase 1-5 compiled. MoneroScreen UI ported, Account reactive flows added. UI ready, needs navigation wiring. |
|
|
|
|
### What was accomplished (this session)
|
|
|
|
#### Previous session accomplishments (Phase 1-4)
|
|
- Extracted all monero model classes from Garnet Kotlin code
|
|
- Adapted imports for new KMP quartz package structure (`nip01Core.*`)
|
|
- Created wallet model classes (Wallet, PendingTransaction, Subaddress, etc.)
|
|
- Created WalletService (fully implemented from Garnet source, ~700 lines)
|
|
- Created MoneroWalletListener interface
|
|
- Created MoneroTipEvent in quartz (`moneroTip` package)
|
|
- Created TipSplitSetup utility in quartz (`moneroTip` package)
|
|
- Created TransactionPriority enum (UNIMPORTANT, SLOW, MEDIUM, FAST, ESTIMATED)
|
|
- Created TipHandler and TipEventDataSource (stubs)
|
|
- Created MoneroDataSource (stub with flows)
|
|
- Added monero wallet fields to Account.kt (spend key, seed, restore height, password flows)
|
|
- Added monero wallet methods to Account.kt (startMonero, stopMonero, balance, address, generateSpendKey)
|
|
- Added `moneroAddress()` method to User model in commons
|
|
- Added `moneroAddressFromAbout()` method to UserMetadataCache in commons
|
|
- Added `moneroAddressIsValid()` utility function in commons
|
|
- Added EventFactory registration for MoneroTipEvent (kind 1814)
|
|
- Added monero.xml icon to resources
|
|
- Added monero-specific strings to strings.xml
|
|
- Added monero JNI bridge files (monerujo.cpp, monerujo.h, wallet2_api.h)
|
|
- Updated amethyst/build.gradle.kts for monero dependencies
|
|
|
|
#### This session accomplishments (Phase 5)
|
|
- **Wired up MoneroDataSource.setMoneroService()** - forwards WalletService StateFlows into MoneroServiceFlows
|
|
- **Added missing Account monero methods** - sendMonero, listMoneroAddresses, newSubaddress, setSubaddressLabel, seedWithPassphrase, getMoneroHistory, getMoneroRestoreHeight, getMoneroDaemonStatus
|
|
- **Added Account monero settings properties** - moneroDaemonAddress, moneroDaemonUsername, moneroDaemonPassword, moneroRestoreHeight, moneroPassword, moneroSeed, isMoneroSeedBackedUp, defaultMoneroTransactionPriority (+ setters)
|
|
- **Added MoneroUtils.kt** - showMoneroAmount and decToPiconero helper functions
|
|
- **Restored TipEventDataSource.getEvent()** - simplified from Garnet Flow-based pattern to LocalCache.getOrCreateNote
|
|
- **Kotlin compilation passes with 0 errors** - verified with `compileFdroidDebugKotlin`
|
|
|
|
#### UI Port accomplishments (MoneroScreen)
|
|
- **Created MoneroScreen.kt** (~795 lines) adapted to Amethyst patterns:
|
|
- Uses Amethyst's MaterialSymbols from `commons.icons.symbols` (no `compose-material-icons-extended` needed)
|
|
- Uses `context.getString()` and `stringRes()` for string resources (not Garnet's Flow-based pattern)
|
|
- All composables implemented: `MoneroScreen`, `MoneroSendDialog`, `ReceiveDialog`, `EditDaemonDialog`, `DaemonInfo`, `Balance`, `TipInfo`, `ActionsRow`, `BackupSeedDialog`, `EphemeralWalletWarning`, `TransactionList`, `TransactionRow`
|
|
- All buttons use `M3ActionDialog` from Amethyst components
|
|
- All icons use `MaterialSymbols` from commons (ArrowDownward, Info, Settings, CloudUpload, Warning, ContentCopy, ExpandLess, ExpandMore, Send, etc.)
|
|
- Uses `HostAndPort` via Account setter methods (`changeMoneroDaemonAddress`, `changeMoneroDaemonUsername`, `changeMoneroDaemonPassword`)
|
|
- **Added 12 new string resources** for MoneroScreen UI (unconfirmed, monero_tipping_info, no_transactions, transaction_history, custom_restore_height, restoration_height, monero_amount, edit_address_label, info, close, no_results_found, transaction_received, transaction_sent)
|
|
- **Compiled with 0 errors** - verified with `compileFdroidDebugKotlin`
|
|
- **Warning**: `Divider` is deprecated → use `HorizontalDivider`
|
|
|
|
#### Account Reactive State Flows (Session)
|
|
- **Added 8 reactive StateFlows to Account.kt**: `moneroStatus`, `moneroBalance`, `moneroLockedBalance`, `moneroWalletHeight`, `moneroDaemonHeight`, `moneroConnectionStatus`, `moneroTransactions`, `moneroAddress`
|
|
- **Added `updateMoneroState()` method** to sync wallet service state into reactive flows
|
|
|
|
### What's remaining (next session)
|
|
|
|
- Wire MoneroScreen into app navigation (Routes.kt + AppNavigation.kt)
|
|
- Run `./gradlew assembleFdroidDebug` end-to-end after MoneroScreen addition
|
|
- Add NIP-69 monero address support
|
|
- Test monero wallet functionality end-to-end
|
|
|
|
### Next Session Quick Start
|
|
|
|
To resume work on the Monero integration:
|
|
|
|
1. Check current state:
|
|
```bash
|
|
./gradlew assembleFdroidDebug | tail -3
|
|
```
|
|
|
|
2. Verify MoneroScreen.kt exists and compiles:
|
|
```bash
|
|
find ~/projects/amethyst -name "MoneroScreen.kt" -not -path "*/build/*"
|
|
./gradlew compileFdroidDebugKotlin 2>&1 | grep -E "^e:|FAILED|SUCCESS"
|
|
```
|
|
|
|
3. The next step: add MoneroScreen navigation route.
|
|
|
|
Relevant files:
|
|
- `amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/MoneroScreen.kt` (main UI file)
|
|
- `amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/routes/Routes.kt` (add route)
|
|
- `amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt` (wire screen)
|
|
- `amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt` (reactive flows)
|
|
|
|
### Known Issues & Gotchas
|
|
|
|
- **TransactionDirection** enum is in `com.vitorpamplona.amethyst.model` package
|
|
- **TextSpinner** is in `com.vitorpamplona.amethyst.ui.components.TextSpinner`
|
|
- **Deprecated Divider** - use `HorizontalDivider` instead in newer Compose
|
|
- **MaterialSymbols** - all icons must come from `commons.icons.symbols.MaterialSymbols` (not `androidx.compose.material.icons`). `MaterialSymbols.AutoMirrored.Send` for the Send icon.
|
|
|
|
---
|
|
|
|
## What Worked ✓
|
|
|
|
### 21 New Files Created
|
|
|
|
| # | File | Description |
|
|
|---|------|-------------|
|
|
| 1 | `amethyst/src/main/java/.../model/MoneroWalletListener.kt` | JNI wallet listener callback interface |
|
|
| 2 | `amethyst/src/main/java/.../model/PendingTransaction.kt` | JNI pending transaction wrapper |
|
|
| 3 | `amethyst/src/main/java/.../model/ProofInfo.kt` | Proof verification data class |
|
|
| 4 | `amethyst/src/main/java/.../model/Subaddress.kt` | Monero subaddress model |
|
|
| 5 | `amethyst/src/main/java/.../model/Transaction.kt` | Monero transaction model |
|
|
| 6 | `amethyst/src/main/java/.../model/TransactionInfo.kt` | Transaction info wrapper |
|
|
| 7 | `amethyst/src/main/java/.../model/TransactionHistory.kt` | Transaction history model |
|
|
| 8 | `amethyst/src/main/java/.../model/Transfer.kt` | Transfer data class |
|
|
| 9 | `amethyst/src/main/java/.../model/Wallet.kt` | JNI Wallet wrapper (all native methods) |
|
|
| 10 | `amethyst/src/main/java/.../model/WalletManager.kt` | Wallet factory + native bridge utilities |
|
|
| 11 | `amethyst/src/main/java/.../service/MoneroDataSource.kt` | Data source stub with Flow wrappers |
|
|
| 12 | `amethyst/src/main/java/.../service/TipEventDataSource.kt` | Tip event lifecycle stubs |
|
|
| 13 | `amethyst/src/main/java/.../service/TipHandler.kt` | Tip processing logic (partial) |
|
|
| 14 | `amethyst/src/main/java/.../service/WalletService.kt` | Android Service + all wallet operations |
|
|
| 15 | `amethyst/src/main/java/.../ui/screen/loggedIn/TransactionPriority.kt` | Transaction priority enum |
|
|
| 16 | `amethyst/src/main/res/drawable/monero.xml` | Monero icon resource |
|
|
| 17 | `quartz/src/commonMain/kotlin/.../moneroTip/MoneroTipEvent.kt` | Nostr tip event (kind 1814) |
|
|
| 18 | `quartz/src/commonMain/kotlin/.../moneroTip/TipSplitSetup.kt` | Tip split utilities + monero tag parsing |
|
|
| 19 | `amethyst/src/main/cpp/monerujo.cpp` | JNI bridge C++ implementation |
|
|
| 20 | `amethyst/src/main/cpp/monerujo.h` | JNI bridge header |
|
|
| 21 | `amethyst/src/main/cpp/wallet2_api.h` | Monero wallet API header |
|
|
|
|
### 6 Modified Files
|
|
|
|
| # | File | Changes |
|
|
|---|------|---------|
|
|
| 1 | `amethyst/src/main/java/.../model/Account.kt` | Added monero wallet fields + methods |
|
|
| 2 | `amethyst/src/main/res/values/strings.xml` | Added monero string resources |
|
|
| 3 | `commons/src/commonMain/kotlin/.../commons/model/User.kt` | Added `moneroAddress()`, `moneroAddressIsValid()` |
|
|
| 4 | `commons/src/commonMain/kotlin/.../commons/model/nip01Core/UserMetadataCache.kt` | Added `moneroAddressFromAbout()` |
|
|
| 5 | `quartz/src/commonMain/kotlin/.../utils/EventFactory.kt` | Registered MoneroTipEvent (kind 1814), import |
|
|
| 6 | `amethyst/build.gradle.kts` | Added monero module/dependencies |
|
|
|
|
---
|
|
|
|
## Current File Structure with Exact Import Paths
|
|
|
|
This is the most critical section. All Garnet-era import paths were different because the quartz module was not yet Kotlin Multiplatform. Here are the exact current paths every imported type should use:
|
|
|
|
### Quartz (MoneroEvent) Import Paths
|
|
|
|
**CRITICAL: These are DIFFERENT from Garnet source.**
|
|
|
|
| Type | New import path |
|
|
|------|-----------------|
|
|
| Event base class | `com.vitorpamplona.quartz.nip01Core.core.Event` |
|
|
| HexKey | `com.vitorpamplona.quartz.nip01Core.core.HexKey` |
|
|
| IEvent | `com.vitorpamplona.quartz.nip01Core.core.IEvent` |
|
|
| AddressableEvent | `com.vitorpamplona.quartz.nip01Core.core.AddressableEvent` |
|
|
| JsonMapper | `com.vitorpamplona.quartz.nip01Core.core.JsonMapper` |
|
|
| OptimizedSerializable | `com.vitorpamplona.quartz.nip01Core.core.OptimizedSerializable` |
|
|
| TagArray | `com.vitorpamplona.quartz.nip01Core.core.TagArray` |
|
|
| KeyPair | `com.vitorpamplona.quartz.nip01Core.crypto.KeyPair` (was `com.vitorpamplona.quartz.nip01Core.enc.KeyPair` in garnet) |
|
|
| NostrSignerInternal | `com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal` (was `...signs.NostrSignerInternal`) |
|
|
| NostrSigner | `com.vitorpamplona.quartz.nip01Core.signers.NostrSigner` |
|
|
| EventTemplate | `com.vitorpamplona.quartz.nip01Core.signers.EventTemplate` |
|
|
| ETag | `com.vitorpamplona.quartz.nip01Core.tags.events.ETag` |
|
|
| PTag | `com.vitorpamplona.quartz.nip01Core.tags.people.PTag` |
|
|
| EventHintProvider | `com.vitorpamplona.quartz.nip01Core.hints.EventHintProvider` |
|
|
| PubKeyHintProvider | `com.vitorpamplona.quartz.nip01Core.hints.PubKeyHintProvider` |
|
|
| NormalizedRelayUrl | `com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl` |
|
|
|
|
### Package Locations
|
|
|
|
| Type | Location |
|
|
|------|----------|
|
|
| Note class | `com.vitorpamplona.amethyst.commons.model.Note` (was amethyst-only before, now in commons) |
|
|
| User class | `com.vitorpamplona.amethyst.commons.model.User` (was amethyst-only before, now in commons) |
|
|
| LocalCache | `com.vitorpamplona.amethyst.model.LocalCache` (stays in amethyst) |
|
|
| MoneroTipEvent | `com.vitorpamplona.quartz.moneroTip.MoneroTipEvent` |
|
|
| TipSplitSetup | `com.vitorpamplona.quartz.moneroTip.TipSplitSetup` |
|
|
|
|
### Old Garnet → New Amethyst Mapping
|
|
|
|
Many Garnet-era packages have been renamed. Common mappings:
|
|
|
|
| Old (Garnet/2024) | New (2026) |
|
|
|---|---|
|
|
| `com.vitorpamplona.quartz.events.EncryptedContentUtils` | `com.vitorpamplona.quartz.nip01Core.crypto.EventHasher` |
|
|
| `com.vitorpamplona.quartz.events.*` | `com.vitorpamplona.quartz.nip01Core.*` (various subpackages) |
|
|
| `com.vitorpamplona.quartz.events.signs.*` | `com.vitorpamplona.quartz.nip01Core.signers.*` |
|
|
| `com.vitorpamplona.quartz.events.enc.*` | `com.vitorpamplona.quartz.nip01Core.crypto.*` |
|
|
| `com.vitorpamplona.quartz.events.crypto.*` | `com.vitorpamplona.quartz.nip01Core.crypto.*` |
|
|
| `commons.model.*` (amethyst) | `commons.model.*` (commons-commonMain) |
|
|
|
|
---
|
|
|
|
## NostrSigner API Change (CRITICAL)
|
|
|
|
The signing API changed significantly. Garnet-era code uses callback-based signing. Current Amethyst uses **coroutine-based** signing.
|
|
|
|
### Old (callback, Garnet-era)
|
|
```kotlin
|
|
// Garnet's NostrSigner callback style
|
|
signer.sign(eventBuilder, onReady: (Event) -> Unit)
|
|
```
|
|
|
|
### New (coroutine, current)
|
|
```kotlin
|
|
// Current Amethyst coroutine style
|
|
val event = signer.sign(template)
|
|
// or
|
|
val event = signer.sign(createdAt, kind, tags, content)
|
|
```
|
|
|
|
### NostrSignerInternal signing with createdAt
|
|
|
|
In current code, `NostrSignerInternal.sign()` with createdAt uses the vararg tags variant:
|
|
|
|
```kotlin
|
|
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
|
|
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
|
|
|
val event: MoneroTipEvent = NostrSignerInternal(KeyPair())
|
|
.sign(createdAt = TimeUtils.now(), kind = MoneroTipEvent.KIND, tags = tags.toTypedArray(), content = content)
|
|
```
|
|
|
|
See `LnZapRequestEvent.kt:130` for a real example of creating an anonymous signer for private zaps.
|
|
|
|
---
|
|
|
|
## LocalCache.consume() Signature
|
|
|
|
The `justConsume()` method in `LocalCache` takes **3 parameters**:
|
|
|
|
```kotlin
|
|
fun justConsume(event: Event, relay: NormalizedRelayUrl?, isOwn: Boolean): Boolean
|
|
```
|
|
|
|
| Parameter | Description |
|
|
|-----------|-------------|
|
|
| `event` | The Nostr event to consume |
|
|
| `relay` | The relay it came from (null if from client) |
|
|
| `isOwn` | Whether this is the current account's own event |
|
|
|
|
Common usage patterns:
|
|
|
|
```kotlin
|
|
// Consume from relay, not own event
|
|
cache.justConsume(event, relay, false)
|
|
|
|
// Consume own event (no relay)
|
|
cache.justConsumeMyOwnEvent(event) // convenience wrapper, same as justConsume(event, null, true)
|
|
|
|
// Consume gift wrap / inner event from push
|
|
cache.justConsume(innerGift, null, false)
|
|
|
|
// Consume decrypted inner event from Marmot
|
|
cache.justConsume(innerEvent, null, true)
|
|
```
|
|
|
|
---
|
|
|
|
## User Metadata Access Pattern
|
|
|
|
User metadata in `commons.model.User` is accessed via lazy-initialized caches:
|
|
|
|
```kotlin
|
|
// UserMetadataCache
|
|
val cache = user.metadata() // lazy init
|
|
val flow: StateFlow<UserInfo?> = cache.flow
|
|
val name = cache.bestName() // String?
|
|
val picture = cache.profilePicture() // String?
|
|
val lnAddress = cache.lnAddress() // String?
|
|
|
|
// For monero:
|
|
val moneroAddr = cache.moneroAddressFromAbout() // String?
|
|
```
|
|
|
|
In User model, the `moneroAddress()` method tries `nip69Address()` first (not yet implemented), then falls back to `moneroAddressFromAbout()`:
|
|
|
|
```kotlin
|
|
fun User.moneroAddress(): String? {
|
|
val nip69 = nip69Address() // NIP-69 type-0 event, stub returns null
|
|
if (nip69 != null) return nip69
|
|
return metadataOrNull()?.moneroAddressFromAbout() // regex from about field
|
|
}
|
|
```
|
|
|
|
The regex in `moneroAddressFromAbout()`: `\b[48]\w{93}\b` — matches standard Monero addresses (4xxxx or 8xxxx, 95 chars total).
|
|
|
|
---
|
|
|
|
## Amethyst.instance Reference Pattern
|
|
|
|
The global Amethyst singleton uses these accessors:
|
|
|
|
```kotlin
|
|
import com.vitorpamplona.amethyst.Amethyst
|
|
|
|
val context = Amethyst.instance.appContext // NOT applicationContext
|
|
val customScope = Amethyst.instance.customScope // UI coroutine scope
|
|
val ioScope = Amethyst.instance.applicationIOScope // IO coroutine scope
|
|
```
|
|
|
|
**Important: Use `appContext`, NOT `applicationContext`.** Garnet-era code used `applicationContext` which no longer compiles.
|
|
|
|
See `WalletService.kt:68-80` for the correct pattern:
|
|
|
|
```kotlin
|
|
Amethyst.instance.appContext.getString(R.string.wallet_status_opening)
|
|
Amethyst.instance.applicationIOScope // used for scope.launch in wallet listener
|
|
```
|
|
|
|
---
|
|
|
|
## TransactionPriority Enum
|
|
|
|
Defined at: `amethyst/src/main/java/.../ui/screen/loggedIn/TransactionPriority.kt`
|
|
|
|
```kotlin
|
|
enum class TransactionPriority(val value: Int) {
|
|
UNIMPORTANT(0),
|
|
SLOW(1),
|
|
MEDIUM(2),
|
|
FAST(3),
|
|
ESTIMATED(4),
|
|
}
|
|
```
|
|
|
|
Used everywhere Monero transactions are created:
|
|
|
|
```kotlin
|
|
wallet.createTransaction(
|
|
destination = address,
|
|
amount = amount,
|
|
priority = TransactionPriority.UNIMPORTANT // passes priority.ordinal to JNI
|
|
)
|
|
```
|
|
|
|
---
|
|
|
|
## WalletService Public API
|
|
|
|
`WalletService` (Android Service) implements the full wallet. All public methods:
|
|
|
|
| Method | Return | Description |
|
|
|--------|--------|-------------|
|
|
| `loadWallet(name, password, spendKey="", daemonAddress, ...)` | `Wallet` | Open or create wallet |
|
|
| `load(name, password, spendKey="", daemonAddress, ...)` | `Wallet` | Same, with checkNotInMainThread |
|
|
| `connectToDaemon(address, username, password, proxy, restoreHeight, walletPassword)` | `Wallet.Status?` | Connect to daemon |
|
|
| `connect(address, username, password, proxy, isCreation, restoreHeight, walletPassword)` | `Wallet.Status?` | Same, check not on main thread |
|
|
| `sendTransaction(destination, amount, priority)` | `PendingTransaction.Status?` | Send to single address |
|
|
| `sendTransactionMultDest(destinations[], amounts[], priority)` | `PendingTransaction?` | Multi-destination |
|
|
| `getTxProof(txId, destination, message)` | `Proof?` | Generate transaction proof |
|
|
| `checkTxProof(txId, address, message, signature)` | `ProofInfo?` | Verify a transaction proof |
|
|
| `setProxy(proxy)` | `Boolean?` | Set network proxy |
|
|
| `storeWallet()` | `Wallet.Status?` | Persist wallet to disk |
|
|
| `closeWallet()` | Unit | Close the current wallet |
|
|
| `newSubaddress(accountIndex, label)` | `Subaddress?` | Generate new subaddress |
|
|
| `lastSubaddress(accountIndex)` | `Subaddress?` | Get last subaddress |
|
|
| `listAddresses()` | `List<Subaddress>?` | List all subaddresses |
|
|
| `setSubaddressLabel(index, label)` | `Wallet.Status?` | Label a subaddress |
|
|
| `seedWithPassphrase(passphrase)` | `String?` | Get wallet seed |
|
|
| `isAddressValid(address)` | `Boolean?` | Validate address format |
|
|
| `getRestoreHeight()` | `Long?` | Get restore height |
|
|
| `getHistory()` | `TransactionHistory?` | Get transaction history |
|
|
| `setUserNote(txId, note)` | `Boolean?` | Attach user note to transaction |
|
|
| `estimateTransactionFee(destinations[], amounts[], priority)` | `Long?` | Estimate fee in picoMonero |
|
|
| `getBalance` | `Long` | Property: total balance |
|
|
| `getLockedBalance` | `Long` | Property: locked (unconfirmed) balance |
|
|
| `walletHeight` | `Long` | Property: wallet blockchain height |
|
|
| `daemonHeight` | `Long` | Property: daemon blockchain height |
|
|
| `address` | `String?` | Property: primary address |
|
|
| `connectionStatus` | `Wallet.ConnectionStatus` | Property: DISCONNECTED/CONNECTED/WRONG_VERSION |
|
|
|
|
State flows (MutableStateFlow → StateFlow):
|
|
- `statusStateFlow: StateFlow<WalletStatus>`
|
|
- `balanceStateFlow: StateFlow<Long>`
|
|
- `lockedBalanceStateFlow: StateFlow<Long>`
|
|
- `walletHeightStateFlow: StateFlow<Long>`
|
|
- `daemonHeightStateFlow: StateFlow<Long>`
|
|
- `connectionStatusStateFlow: StateFlow<Wallet.ConnectionStatus>`
|
|
- `transactions: StateFlow<List<TransactionInfo>>`
|
|
|
|
---
|
|
|
|
## MoneroDataSource — Current State
|
|
|
|
File: `amethyst/src/main/java/.../service/MoneroDataSource.kt`
|
|
|
|
```kotlin
|
|
object MoneroDataSource {
|
|
@Volatile
|
|
private var flows: MoneroServiceFlows = MoneroServiceFlows()
|
|
|
|
fun status(): Flow<*> = flows.status
|
|
|
|
fun connectionStatus(): Flow<*> = flows.connectionStatus
|
|
|
|
fun walletHeight(): Flow<Long> = flows.walletHeight
|
|
|
|
fun daemonHeight(): Flow<Long> = flows.daemonHeight
|
|
|
|
fun balance(): Flow<Long> = flows.balance
|
|
|
|
fun lockedBalance(): Flow<Long> = flows.lockedBalance
|
|
|
|
fun transactions(): Flow<List<TransactionInfo>> = flows.transactions
|
|
|
|
fun setMoneroService(service: WalletService) {
|
|
val newFlows =
|
|
MoneroServiceFlows(
|
|
status = service.statusStateFlow,
|
|
connectionStatus = service.connectionStatusStateFlow,
|
|
walletHeight = service.walletHeightStateFlow,
|
|
daemonHeight = service.daemonHeightStateFlow,
|
|
balance = service.balanceStateFlow,
|
|
lockedBalance = service.lockedBalanceStateFlow,
|
|
transactions = service.transactions,
|
|
)
|
|
flows = newFlows
|
|
}
|
|
}
|
|
```
|
|
|
|
The `setMoneroService()` forwards all 7 WalletService StateFlows into MoneroServiceFlows. It must be called from `AccountViewModel` or service binding to wire the flows.
|
|
|
|
---
|
|
|
|
## What Next Session Must Do
|
|
|
|
### Priority A: Wire MoneroScreen into Navigation
|
|
|
|
1. Add `Monero` route in `Routes.kt` (sealed class Route)
|
|
2. Add composable mapping in `AppNavigation.kt`
|
|
3. Wire from settings screen or top navigation drawer
|
|
|
|
### Priority B: Add NIP-69 Monero Address Support
|
|
|
|
NIP-69 defines a peer-to-peer way to share Monero addresses via Nostr events. Consider implementing a type-0 NIP-69 event for monero address sharing.
|
|
|
|
### Priority C: End-to-End Testing
|
|
|
|
- Test wallet creation and daemon connection
|
|
- Test sending and receiving Monero
|
|
- Test subaddress generation
|
|
- Test tip events with monero tags
|
|
|
|
---
|
|
|
|
## Build Commands
|
|
|
|
### Verify baseline builds
|
|
```bash
|
|
cd ~/projects/amethyst && ./gradlew assembleFdroidDebug | tail -3
|
|
```
|
|
|
|
### Get the diff between Garnet and the fork point
|
|
```bash
|
|
cd ~/projects/garnet
|
|
git diff 99614f07..bfaf92e5 --stat # 103 files
|
|
git diff 99614f07..bfaf92e5 --name-status # file list with A/M/D status
|
|
```
|
|
|
|
### Extract native libs from garnet releases
|
|
```bash
|
|
curl -L -o /tmp/garnet.apk "https://github.com/retrnull/garnet/releases/download/v0.3.0/app-mainnet-fdroid-universal-release.apk"
|
|
unzip -o /tmp/garnet.apk -d /tmp/garnet/
|
|
# libmonerujo.so is in extracted-libs/lib/<abi>/
|
|
```
|
|
|
|
### Compile only Kotlin (fast check for monero-related errors)
|
|
```bash
|
|
cd ~/projects/amethyst && ./gradlew compileFdroidDebugKotlin 2>&1 | grep -E "^e:|FAILED|SUCCESS"
|
|
```
|
|
|
|
### Count monero compilation errors
|
|
```bash
|
|
./gradlew compileFdroidDebugKotlin 2>&1 | grep "^e:" | grep -i monero | wc -l
|
|
```
|
|
|
|
### Watch monero-specific errors in real time
|
|
```bash
|
|
./gradlew compileFdroidDebugKotlin 2>&1 | tee /tmp/build.log | grep "^e:" | grep -i monero
|
|
```
|
|
|
|
---
|
|
|
|
## Common Error Patterns We Fixed
|
|
|
|
These are specific compile/lint errors encountered during the port, with fixes applied:
|
|
|
|
### 1. `No such file or directory: 'com.vitorpamplona.quartz.events.*'`
|
|
|
|
**Cause:** Quartz module restructured from `main/java/com/vitorpamplona/quartz/events/` to `commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/`
|
|
|
|
**Fix:** Replace all old imports with new package paths. Mapping table above.
|
|
|
|
### 2. `Cannot access 'com.vitorpamplona.quartz.nip01Core.enc.KeyPair'`
|
|
|
|
**Cause:** KeyPair moved from `enc.*` to `crypto.*`
|
|
|
|
**Fix:** `com.vitorpamplona.quartz.nip01Core.crypto.KeyPair`
|
|
|
|
### 3. `Unresolved reference: NostrSignerInternal from signs.*`
|
|
|
|
**Cause:** Signers package renamed from `signs` to `signers`
|
|
|
|
**Fix:** `com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal`
|
|
|
|
### 4. `Val cannot be reassigned` / `Property setter not found`
|
|
|
|
**Cause:** Garnet used `var` properties; current codebase uses `val` with delegation
|
|
|
|
**Fix:** Use `cache.update(...)` pattern or `MutableStateFlow.update { }` instead of direct assignment.
|
|
|
|
### 5. `Cannot inline class: KeyPair`
|
|
|
|
**Cause:** KeyPair was a regular class in Garnet; now it may be a value class or internal class
|
|
|
|
**Fix:** Use `KeyPair` constructor properly or access through `NostrSignerInternal` factory.
|
|
|
|
### 6. `Unresolved reference: applicationContext` on Amethyst.instance
|
|
|
|
**Cause:** Amethyst renamed `applicationContext` to `appContext`
|
|
|
|
**Fix:** `Amethyst.instance.appContext`
|
|
|
|
### 7. `Function 'justConsume' doesn't have the expected number of parameters`
|
|
|
|
**Cause:** Garnet used 1-parameter `justConsume(event)`, new version uses 3: `justConsume(event, relay, isOwn)`
|
|
|
|
**Fix:** Add relay and isOwn parameters: `cache.justConsume(event, null, true)` for own events.
|
|
|
|
### 8. `Type mismatch: inferred type is commons.model.User but ...`
|
|
|
|
**Cause:** User moved from `amethyst.model.User` to `commons.model.User`
|
|
|
|
**Fix:** Update all imports: `com.vitorpamplona.amethyst.commons.model.User`
|
|
|
|
### 9. `No parameter with name 'autoCorrect' in TextField`
|
|
|
|
**Cause:** Material3 TextField removed `autoCorrect` parameter in the new Compose BOM
|
|
|
|
**Fix:** Remove `autoCorrect = false` parameter from TextField calls.
|
|
|
|
### 10. `Unresolved reference: rememberRipple`
|
|
|
|
**Cause:** Material3 ripple API changed/removed
|
|
|
|
**Fix:** Use `rememberRipple(color = ...)` or remove the `ripple` parameter from composables.
|
|
|
|
### 11. `Cannot find heading or HeadingStyle`
|
|
|
|
**Cause:** `HeadingStyle` enum moved or was renamed during Compose upgrade
|
|
|
|
**Fix:** Check current richtext package for the correct heading/composable type.
|
|
|
|
### 12. `No parameterless constructor found` on sealed interfaces
|
|
|
|
**Cause:** Some sealed hierarchies moved from `sealed class` to `sealed interface`
|
|
|
|
**Fix:** Create instances via companion object `create()` methods or `object` expressions, not constructors.
|
|
|
|
---
|
|
|
|
## Quick Commands for New Session
|
|
|
|
All paths use `~/` or `$HOME` — do NOT use absolute `/home/` paths:
|
|
|
|
```bash
|
|
# Verify amethyst builds
|
|
cd ~/projects/amethyst && ./gradlew assembleFdroidDebug | tail -3
|
|
|
|
# Count remaining monero errors
|
|
./gradlew compileFdroidDebugKotlin 2>&1 | grep "^e:" -i monero | wc -l
|
|
|
|
# Get garnet diff for reference
|
|
cd ~/projects/garnet && git diff 99614f07..bfaf92e5 --stat
|
|
|
|
# Check current monero files in amethyst
|
|
find ~/projects/amethyst -name "*.kt" -path "*monero*" -not -path "*/build/*"
|
|
|
|
# Find all new files added this session
|
|
git status --short | grep "^A "
|
|
|
|
# Find all modified files
|
|
git status --short | grep "^M "
|
|
```
|