feat(wallet): wire fmcd as core app + dual-ecash receive

Fedimint never appeared in Wallet > Settings > Fedimint because the
fmcd (fedimint-clientd) sidecar was never installed: ensure_default_
federation() needs the fmcd password to reach the daemon, found none,
and silently no-oped, leaving the registry empty.

- prod_orchestrator: add fedimint-clientd to the baseline auto-install
  set so it self-heals onto every node and auto-joins the default
  federation; generate the fmcd-password secret before secret_env
  resolves.
- fedimint_client: ensure_fmcd_password (random hex, 0600) shared with
  the container's secret_env; from_node reads the same secret (legacy
  fmcd/password kept as fallback); reissue_into_any redeems received
  notes into the first joined federation that accepts them.
- wallet.ecash-receive: dual-token — cashu* tokens redeem at the mint,
  anything else is reissued via fmcd; returns the kind + federation_id.
- UI: receive box advertises "Cashu or Fedimint" and reports which kind.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-06-19 09:52:26 -04:00
co-authored by Claude Opus 4.8
parent 298595069d
commit c4855526fe
4 changed files with 119 additions and 14 deletions
@@ -157,8 +157,8 @@
<div v-if="receiveMethod === 'ecash'">
<div class="mb-3">
<label class="text-white/60 text-sm block mb-1">Paste ecash token</label>
<textarea v-model="ecashReceiveToken" rows="3" placeholder="cashuSend_..." class="w-full input-glass"></textarea>
<label class="text-white/60 text-sm block mb-1">Paste ecash token (Cashu or Fedimint)</label>
<textarea v-model="ecashReceiveToken" rows="3" placeholder="cashuB… or Fedimint notes" class="w-full input-glass"></textarea>
</div>
<div v-if="ecashReceiveResult" class="mb-3 text-xs text-green-400">{{ ecashReceiveResult }}</div>
</div>
@@ -487,11 +487,12 @@ async function unifiedReceive() {
unifiedReceiveError.value = t('web5.pasteEcashToken')
return
}
const res = await rpcClient.call<{ received_sats: number }>({
const res = await rpcClient.call<{ received_sats: number; kind?: string }>({
method: 'wallet.ecash-receive',
params: { token: ecashReceiveToken.value.trim() },
})
ecashReceiveResult.value = `Received ${res.received_sats} sats!`
const label = res.kind === 'fedimint' ? 'Fedimint' : 'Cashu'
ecashReceiveResult.value = `Received ${res.received_sats} sats (${label})!`
ecashReceiveToken.value = ''
emit('balancesChanged')
}