# 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 = 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?` | 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` - `balanceStateFlow: StateFlow` - `lockedBalanceStateFlow: StateFlow` - `walletHeightStateFlow: StateFlow` - `daemonHeightStateFlow: StateFlow` - `connectionStatusStateFlow: StateFlow` - `transactions: StateFlow>` --- ## 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 = flows.walletHeight fun daemonHeight(): Flow = flows.daemonHeight fun balance(): Flow = flows.balance fun lockedBalance(): Flow = flows.lockedBalance fun transactions(): Flow> = 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// ``` ### 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 " ```