Skip to content

Milestone v1 WIP#1

Merged
ALENOC merged 182 commits intomasterfrom
milestone-v1-wip
Apr 25, 2026
Merged

Milestone v1 WIP#1
ALENOC merged 182 commits intomasterfrom
milestone-v1-wip

Conversation

@ALENOC
Copy link
Copy Markdown
Owner

@ALENOC ALENOC commented Apr 25, 2026

Summary

  • Rimossa directory .planning/ dal tracking git, aggiunta a .gitignore
  • Aggiunta directory .claude/ a .gitignore
  • Fix newline finale in config.json

Test plan

  • Verify .planning/ no longer appears in repo
  • Verify .claude/ no longer appears in repo
  • Verify .gitignore entries are effective

ALENOC added 30 commits April 12, 2026 01:15
Performance:
- WalletManager: add getAddressBatch() for single Keystore decrypt per N addresses
- RavencoinPublicNode: add getTotalBalance(), getTotalAssetBalances(), getAddressStatusBatch(), getAssetMetaBatch() for pipelined batch TLS calls
- discoverCurrentIndex(): batch size 20 with getAddressBatch + getAddressStatusBatch (was 5 individual calls each)
- sweepOldAddressesInternal(): use getAddressBatch + getAddressStatusBatch instead of per-address calls
- loadOwnedAssets(): pre-fetch all IPFS hashes in one batch RPC call, raise Semaphore from 3 to 8
- WalletPollingWorker: use getAddressBatch + getTotalBalance + getTotalAssetBalances
- NfcCounterCache: lazy init to avoid blocking main thread at startup
- CONNECT_TIMEOUT_MS: 12000ms -> 5000ms

Bug fixes:
- Asset issuance: owner token (ROOT!, ROOT/SUB!) was sent to toAddress instead of changeAddress; if toAddress was an external recipient the brand permanently lost sub-asset issuance rights
- Asset issuance: redirect toAddress to nextAddress when issuing to own wallet, asset lands quantum-safe immediately without extra sweep tx
- WalletPollingWorker: trigger sweepOldAddresses() on any detected incoming transfer, consolidates HAS_OUTGOING addresses in background (app closed)
- loadWalletInfo(): run discoverCurrentIndex() when balance is null (fixes Brand app showing 0 balance with same mnemonic as Consumer)
- Remove redundant loadOwnedAssets() call from initWallet()
Three related bugs fixed:

1. discoverCurrentIndex() was resetting currentIndex to 0 when the first
   batch failed (network/Keystore unavailable at process restart). Chain:
   getLocalBalance() fails -> balance==null -> discoverCurrentIndex()
   triggered -> getAddressBatch() returns emptyMap() -> loop breaks with
   lastUsed=-1 -> setCurrentAddressIndex(0) -> next balance check returns
   0 RVN. Fix: if first batch is empty, return stored index unchanged.
   Also never decrease the stored index (transient failure != rollback).

2. loadWalletInfo() reset walletInfo to balanceRvn=0.0 at the start of
   every load, flashing 0 on screen during reload. Fix: preserve existing
   balance/address while loading; only replace with new data on success,
   keeping stale data visible if the network call fails.

3. initWallet() was called unconditionally from onCreate(), relaunching
   all loading coroutines even when the ViewModel already had data (e.g.
   after a screen rotation where the Activity is recreated but the
   ViewModel survives). Fix: skip loadWalletInfo() if walletInfo != null.

Also: keep asset preview images on refresh (IPFS content is immutable,
no need to clear and reload images already in memory or disk cache).
…r/issue

sendRvnLocal, transferAssetLocal, issueAssetLocal now use at most 2 TLS
connections for all UTXO data (down from N+2 sequential connections) and a
single Keystore decrypt for both signing keys (down from 2 decrypts).

- Add getUtxosAndAllAssetUtxosBatch() to RavencoinPublicNode: fetches the
  full UTXO list in 1 TLS connection, then batch-fetches all raw transactions
  needed for asset classification and script extraction in a second TLS
  connection, regardless of how many assets are held.
- Add getKeyPair() to WalletManager: single getSeed() call returning both
  private and public key bytes, saving ~250ms per transaction.
- Convert sendRvnLocal, transferAssetLocal, issueAssetLocal to suspend fun
  and parallelize UTXO fetching with fee-rate lookup using async.

With 19 assets: ~43 TLS connections reduced to ~3, wall time ~13s to ~2s.
During restore/first load, assetsLoading becomes true only after
loadOwnedAssets() is called, leaving a brief window where the UI showed
'nessun asset' with no spinner even though walletInfo.isLoading was true.

The spinner next to 'My Assets' now also triggers on walletInfo.isLoading,
and the 'nessun asset' empty state is suppressed while the wallet is loading.
…ition lag

WalletScreen is an expensive composable (asset cards, scroll state, dialogs).
The previous when(currentTab) pattern destroyed it on every tab switch and
rebuilt from scratch on return, costing more than one frame.

WalletScreen is now kept in the Box composition tree after the first visit:
- alpha(0f) hides it when inactive without removing it from the tree
- pointer-blocking overlay prevents the invisible scrollable content from
  intercepting touches meant for the active tab
- Other tabs (Scan, Brand, Settings) continue to use the when branch since
  their LaunchedEffects must only fire when actually visited
RavencoinTxBuilder - buildAndSignRvnSendWithAssetSweep:
- RVN change (P2PKH) was placed after OP_RVN_ASSET outputs, violating
  Ravencoin consensus ordering: all P2PKH outputs must precede asset outputs.
  Consequence: node accepted the tx but ignored asset transfers, RVN moved
  but assets stayed on the old address.

RavencoinTxBuilder - buildAndSignMultiAddressSend (new function):
- Same P2PKH-after-OP_RVN_ASSET ordering bug, now fixed.
- Dust hardcoded to 600 sat for every asset output regardless of input dust.
  Ravencoin requires: if input asset UTXO has 0 sat, output must also be 0 sat
  (assets issued with dustOut=0 would trigger bad-txns-asset-transfer-amount
  and the tx would be rejected, assets not moved).
  Fixed: dust derived from input UTXO satoshis per asset.

WalletManager - sweepOldAddressesInternal:
- Funding transaction used hardcoded fee of 2000 sat, far below minimum relay
  fee (200+ sat/byte * ~226 bytes = 45000+ sat). Funding tx was rejected and
  asset-only addresses could not be swept.
  Fixed: fee calculated from getMinRelayFeeRateSatPerByte().

WalletManager - healAndSweepTarget:
- Funding transaction signed with the TARGET address key pair (index being swept)
  instead of the CURRENT address key pair (owner of the UTXOs being spent).
  Invalid signatures caused the funding tx to be rejected every time.
  Fixed: separate getKeyPair(currentIndex) used for the funding tx, zeroed in
  finally block. Fee also now calculated dynamically from satPerByte.
Two UI bugs caused the wallet to show 0 balance and no assets after a
network hiccup, even when data had already been loaded:

1. loadWalletBalance: when both ElectrumX and the backend API failed,
   balance was silently reset to 0.0, overwriting a previously correct
   value. Now falls back to the last known walletInfo.balanceRvn.

2. WalletScreen: when assetsLoadError=true, the error card was shown
   INSTEAD of assets, hiding 19 cached assets after a failed second
   loadOwnedAssets call. Error card now only shows when ownedAssets is
   also empty.
Five concrete bugs fixed:

1. transferAssetLocal scanned only currentIndex for assets. If assets were
   on an old HAS_OUTGOING address (not yet swept), the transfer failed or
   only RVN moved. Now scans all 0..currentIndex addresses and includes
   every found asset UTXO in the transaction, each signed with its own
   address key via the new buildAndSignMultiAddressAssetTransfer.

2. refreshBalance launched healAndSweepTarget for every old address IN
   PARALLEL, creating dozens of simultaneous ElectrumX TCP connections
   that triggered server-side connection resets. Replaced with a single
   sequential sweepOldAddresses call.

3. sweepOldAddressesInternal funded asset-only addresses FROM the current
   clean address, exposing its private key and defeating post-quantum
   protection. That early funding block is removed; the correct sacrificial
   (HAS_OUTGOING) funding path (fundOldAddressForSweep) remains.

4. getLocalBalance returned null for genuinely empty wallets (takeIf > 0),
   triggering discoverCurrentIndex on every startup even when the index
   was already correct. Now returns 0.0 for empty and null only on network
   failure.

5. loadWalletInfo triggered discoverCurrentIndex whenever balance was null,
   which also fired on transient network errors. Now discovery only runs
   when currentIndex == 0 (wallet just restored from mnemonic).
…address checks

- Replace reconcileCurrentAddressIndex() with simpler forward-only index verification
- Remove ensureCurrentAddressClean() calls from wallet loading flow
- Streamline address index management to post-quantum safe forward progression only
- Enhanced loadOwnedAssets() to detect funds (RVN + assets) scattered across old addresses
- Added consolidateAllFundsToFreshAddress() in WalletManager to move all funds to a virgin address (currentIndex + 1)
- Added consolidation banner in WalletScreen UI when funds detected on old addresses
- Implemented consolidateFunds() in MainActivity to trigger the consolidation process
- Portfolio scan now checks both asset balances and RVN balances in parallel
- Consolidation transfers all assets and RVN to a fresh, quantum-safe address and advances the index

This solves the issue where the portfolio scan wasn't finding funds on old addresses that needed to be consolidated.
- consolidateAllFundsToFreshAddress() now uses existing sweepOldAddresses() logic
- Old addresses with HAS_OUTGOING status are properly funded via sacrificial address
- Two-phase approach: (1) sweep old addresses to currentIndex, (2) sweep currentIndex to fresh
- Handles case where only currentIndex has funds (direct sweep to fresh)
- Properly filters asset UTXOs from RVN UTXOs for fee calculation
- Respects post-quantum safety by only consolidating HAS_OUTGOING addresses
- Refactored consolidateAllFundsToFreshAddress to be atomic and multi-address
- Optimized Keystore usage with getAddressBatch and getKeyPairBatch
- Increased network throughput by adjusting OkHttp dispatcher settings
- Enabled full-range address scanning without arbitrary limitations
…exposed

On mnemonic import, currentIndex now points to the address where funds
actually reside. If that address has never signed an outgoing transaction
(public key not yet revealed), currentIndex stays there and sweep is a
no-op. Only if the address has HAS_OUTGOING status is currentIndex
advanced by one, triggering a sweep to a fresh address.

Removes the old unconditional +1 and the follow-up HAS_OUTGOING skip
loop that were causing unnecessary fund consolidation on import.
During restoreWallet, hasWallet is now forced to false immediately so the
WalletSetupCard stays visible (with spinner) for the entire duration of
discoverCurrentIndex. Address and balance are shown only after the correct
blockchain index is resolved.

Added restoreError state to surface invalid-mnemonic and network errors
inside WalletSetupCard instead of the invisible BalanceCard. Added
walletScanningBlockchain string (9 languages) shown below the spinner
during blockchain scanning.
…0-address scan

discoverCurrentIndex: Phase 2 now uses a single getAddressesWithFunds batch call
(get_balance?asset=true pipelined) instead of N*2 sequential TLS calls per address
with history. With 15 addresses with history this drops from 30 sequential calls to
one batch (2 TLS connections). Estimated 3-5x speedup on mnemonic import.

sendRvnLocal: old-fund discovery now scans only 0..currentIndex-1 (not hardcoded
100) and uses getAddressesWithFunds to pre-filter before fetching full UTXOs.
Eliminates 100 sequential TLS calls on every send when currentIndex is low.

sweepOldAddressesInternal: converted to suspend fun, replacing runBlocking{delay()}
with proper coroutine delay(). Thread is now suspended instead of blocked.

loadOwnedAssets: consolidation check for old addresses replaced from two full
getTotalAssetBalances+getTotalBalance calls to one lightweight getAddressesWithFunds
batch call.

New: RavencoinPublicNode.getAddressesWithFunds(addresses) - single batched
get_balance?asset=true returning Set<address> of addresses with any funds.
…up display

On first load after startup, loadOwnedAssets now reads the previous session's
asset list from SharedPreferences and shows it immediately (no network wait).
The network fetch still runs in parallel and replaces the cache once fresh data
arrives. The cache is keyed by wallet address, so it auto-invalidates on wallet
change. Images and IPFS metadata are preserved across sessions via the existing
merge logic that already carried over enriched fields.
Enrichment jobs now use async instead of fire-and-forget launch. After all
enrichments complete, the cache is saved again with imageUrl and description
populated, so images appear immediately on the next startup without a network
round-trip to IPFS.
Satoshis carried by other-asset UTXOs were added as inputs but not
counted in totalIn, causing the miner to receive extra fee instead
of the surplus going to the RVN change output at currentIndex+1.
Some Ravencoin ElectrumX servers return asset balances via
get_balance?asset=true but omit asset UTXOs from listunspent.
Three fixes:

1. getAddressesWithFunds: parse top-level confirmed/unconfirmed as
   primitives (RVN balance), then nested objects as asset balances.
   Previously only checked nested JsonObjects, missing addresses
   that had only RVN dust from asset UTXOs.

2. getUtxosAndAllAssetUtxosBatch: secondary asset check after
   listunspent. If no asset UTXOs found, falls back to
   getAssetBalances + getAssetUtxosFull to retrieve them explicitly.
   This ensures sendRvnLocal enters the atomic branch and includes
   all assets in the same transaction.

3. buildAndSignAssetIssueWithAssetSweep: include satoshis from
   swept other-asset UTXOs in totalIn so they go to RVN change
   instead of miners.
When both consumer and brand apps share the same HD wallet, one app
advancing currentIndex (via a send tx) left the other showing the
stale address until mnemonic re-import.

Adds syncCurrentIndex() in WalletManager: lightweight 3-batch-call
check that detects if the stored index is stale (current address has
HAS_OUTGOING status) and scans forward up to 10 addresses to find the
correct position. Much faster than full discoverCurrentIndex().

refreshBalance() now calls syncCurrentIndex() first and updates the
displayed address immediately if the index changed, before reloading
balance, assets, and transaction history.
On onPause sets a flag; on onResume calls refreshBalance() if the
wallet exists. This ensures address, balance, and asset list are
current when switching between app flavors or returning from
background, without waiting for the 60-second polling loop.
assetsLoading was set to true unconditionally at the start of
loadOwnedAssets, causing the spinner to appear on every background
refresh even when assets were already visible. Now the spinner only
shows when there is nothing to display yet (empty list and no cache).
Subsequent refreshes update the list silently in the background.
7 structured documents covering stack, integrations, architecture,
structure, conventions, testing, and technical concerns.
ALENOC and others added 27 commits April 25, 2026 20:54
…, tappable txid

Add MultiStepProgressIndicator, StepRow, PreIssuanceWarning, ConfirmationProgressRow
composables. Gate SubmitButton on IssueStep.Idle. Add currentStep/issuedTxid/warningType
parameters to IssueAssetScreen signature.
Add pollingLoop for N/6 confirmations after issuance. Enhance
processIssueAndWrite with step state transitions, classifyIssuanceError,
retryWithBackoff (SocketTimeout excluded per D-08), and post-flow polling.
Update onTagTapped to set IssueStep.Failed on error.
…d UI strings

- Convert FR/DE/ES to cloneStrings(stringsEn) for safe English fallback
- Add 88 missing translations to FR/DE/ES (Phase 30-40 strings)
- Add 92 missing translations to ZH/JA/KO/RU
- Fix RU authSubtitle (was Chinese, now Russian)
- Replace hardcoded strings in WalletScreen, RegisterChipScreen,
  BrandDashboardScreen, SplashScreen with LocalStrings references
- Add 17 new AppStrings properties for hardcoded UI strings
- All 9 languages now have 456/456 properties
- Fix Italian hardcoded strings (Asset ciclati, Chiudi)
…le across 9 languages

- Add walletCycledMultiAsset format string with translations in EN/IT/FR/DE/ES/ZH/JA/KO/RU
- Add walletCycledAssetsTitle translations for 7 missing languages (FR/DE/ES/ZH/JA/KO/RU)
- Fix "insufficient RVN" warning flashing on startup by guarding on walletInfo.isLoading
…over

Seed balance/block-height/tx-history from SQLite cache on cold start so
the wallet screen renders instantly instead of showing "Loading…".
Change balanceRvn from Double to Double? so the UI distinguishes
"not yet loaded" from an actual zero balance. Reduce ElectrumX connect
timeout from 5s to 2.5s and persist last-good-host across restarts to
skip failover rotation on resume. Also optimistically deduct sent
amount after send for instant balance update before network confirms.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Server instance captured in `const server` for graceful shutdown.
unhandledRejection closes HTTP server then SQLite, exits with code 1.
uncaughtException logs stack trace and exits immediately (unsafe state).
…in getAssetHierarchy

Concurrency limited to 5 per chunk. Failed sub-branches add entry to errors
array with assetName and error. Response includes partial: true when any
branch fails.
…hy route

listSubAssets and getAssetHierarchy now accept optional limit (default 200)
and offset (default 0) params. Hierarchy route adds response envelope with
total, limit, offset, hasMore. Backward compatible with existing clients.
startLogCleanup() deletes rows older than 30 days, runs every 24h and once
at startup. nfc_counters explicitly excluded (anti-replay security).
Migration 6 renamed to log_retention_cleanup_one_shot with reference to
the new periodic cleanup in logger.ts.
…ckup

Node.js backup uses better-sqlite3 .backup() for consistent WAL snapshots
before encryption. Docker backup container updated to use sqlite3 CLI
.backup command, 6h interval (was 24h), keep 3 backups (was 7).
`npm run db:explore` opens a REPL with pre-built domain commands
(.assets, .brands, .revoked, .stats, .help). Database opened in
read-only mode. No write operations exposed.
All 6 plans executed. Verification passed (22/22 must_haves).
Backend now has graceful crash handling, parallelized hierarchy,
pagination, periodic log cleanup, WAL-safe backups, and DB explorer.
Move 5 backend requirements to Validated section.
Update last-updated date.
30 plans across 5 phases (10-50). Archives roadmap, creates MILESTONES.md
entry, full PROJECT.md evolution, retrospective, and reorganized ROADMAP.
@github-actions
Copy link
Copy Markdown

🤖 Hi @ALENOC, I've received your request, and I'm working on it now! You can track my progress in the logs for more details.

@ALENOC ALENOC merged commit 3bba0e2 into master Apr 25, 2026
4 of 5 checks passed
@github-actions
Copy link
Copy Markdown

🤖 I'm sorry @ALENOC, but I was unable to process your request. Please see the logs for more details.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant