Skip to content

Commit f308161

Browse files
committed
fix: add fee_ufvk to scanner PIVK cache for settlement invoice detection
Settlement invoices pay to fee_address, but the scanner only had merchant viewing keys in its decrypt loop. Payments to the fee wallet were invisible, so settlement invoices could never be auto-confirmed. Now refresh_key_cache accepts an optional fee_ufvk and appends a synthetic __platform_fee__ entry to the key cache, allowing the scanner to decrypt and match SETTLE-* memo payments like any other invoice.
1 parent 780e8ad commit f308161

1 file changed

Lines changed: 18 additions & 6 deletions

File tree

src/scanner/mod.rs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -208,9 +208,13 @@ pub async fn run(config: Config, pool: SqlitePool, http: reqwest::Client) {
208208
/// Build or refresh the PIVK cache when the merchant set changes.
209209
/// Compares merchant IDs (not just count) so additions, deletions,
210210
/// or replacements all trigger a rebuild.
211+
/// When `fee_ufvk` is set, a synthetic "__platform_fee__" entry is
212+
/// appended so settlement invoice payments to `fee_address` are
213+
/// decrypted and matched like any other invoice.
211214
fn refresh_key_cache<'a>(
212215
cache: &'a mut Option<KeyCache>,
213216
merchants: &[crate::merchants::Merchant],
217+
fee_ufvk: Option<&str>,
214218
) -> &'a [(String, decrypt::CachedKeys)] {
215219
let current_ids: Vec<String> = merchants.iter().map(|m| m.id.clone()).collect();
216220

@@ -220,13 +224,19 @@ fn refresh_key_cache<'a>(
220224
};
221225

222226
if needs_refresh {
223-
let mut keys = Vec::with_capacity(merchants.len());
227+
let mut keys = Vec::with_capacity(merchants.len() + 1);
224228
for m in merchants {
225229
match decrypt::prepare_keys(&m.ufvk) {
226230
Ok(k) => keys.push((m.id.clone(), k)),
227231
Err(e) => tracing::warn!(merchant_id = %m.id, error = %e, "Failed to prepare PIVK"),
228232
}
229233
}
234+
if let Some(ufvk) = fee_ufvk {
235+
match decrypt::prepare_keys(ufvk) {
236+
Ok(k) => keys.push(("__platform_fee__".to_string(), k)),
237+
Err(e) => tracing::warn!(error = %e, "Failed to prepare fee wallet PIVK"),
238+
}
239+
}
230240
tracing::info!(merchants = keys.len(), "PIVK cache refreshed");
231241
*cache = Some(KeyCache {
232242
merchant_ids: current_ids,
@@ -287,11 +297,12 @@ async fn scan_mempool(
287297
}
288298

289299
let merchants = crate::merchants::get_all_merchants(pool, &config.encryption_key).await?;
290-
if merchants.is_empty() {
300+
let fee_ufvk = config.fee_ufvk.as_deref();
301+
if merchants.is_empty() && fee_ufvk.is_none() {
291302
return Ok(());
292303
}
293304

294-
let cached_keys = refresh_key_cache(key_cache, &merchants);
305+
let cached_keys = refresh_key_cache(key_cache, &merchants, fee_ufvk);
295306

296307
let mempool_txids = mempool::fetch_mempool_txids(http, &config.cipherscan_api_url).await?;
297308

@@ -418,11 +429,12 @@ async fn process_ws_mempool_tx(
418429
}
419430

420431
let merchants = crate::merchants::get_all_merchants(pool, &config.encryption_key).await?;
421-
if merchants.is_empty() {
432+
let fee_ufvk = config.fee_ufvk.as_deref();
433+
if merchants.is_empty() && fee_ufvk.is_none() {
422434
return Ok(());
423435
}
424436

425-
let cached_keys = refresh_key_cache(key_cache, &merchants);
437+
let cached_keys = refresh_key_cache(key_cache, &merchants, fee_ufvk);
426438
let invoice_index = matching::InvoiceIndex::build(&pending);
427439

428440
let mut invoice_totals: HashMap<String, (invoices::Invoice, i64)> = HashMap::new();
@@ -579,7 +591,7 @@ async fn scan_blocks(
579591
}
580592

581593
let merchants = crate::merchants::get_all_merchants(pool, &config.encryption_key).await?;
582-
let cached_keys = refresh_key_cache(key_cache, &merchants);
594+
let cached_keys = refresh_key_cache(key_cache, &merchants, config.fee_ufvk.as_deref());
583595
let block_txids =
584596
blocks::fetch_block_txids(http, &config.cipherscan_api_url, start_height, batch_end)
585597
.await?;

0 commit comments

Comments
 (0)