feat(ecash): adopt the reference NUT-02 resolver + real/test network switch
Demo images / Build & push demo images (push) Failing after 2m26s
Demo images / Build & push demo images (push) Failing after 2m26s
Executes steps 1-3 of docs/cashu-cdk-migration-plan.md, plus the test-coin
switch needed to exercise these routes without spending real sats.
Protocol layer: depend on `cashu` 0.17.5 (MIT, the crate CDK is built on,
default-features off, `wallet` only). Keyset ids now go through upstream's
`Id::from_short_keyset_id` / `ShortKeysetId` instead of the prefix match
hand-rolled in 2277fc46 — same repair, but implemented by the reference
code that defines the rule, so the next spec turn is a version bump rather
than another incident. `MintClient` feeds it the mint's `/v1/keysets` in
upstream's own `KeySetInfo` shape, parsing entries individually so one
keyset in an unmodelled unit can't block resolving the id we need.
Adding the crate required relaxing `bip39 = "=2.1.0"` to `"2.1"` (resolves
2.2.2): the exact pin held `unicode-normalization` at 0.1.22 and no
resolution existed otherwise. The pin carried no recorded rationale; seed
tests cover the bump.
Network switch: `wallet.ecash-network` / `wallet.ecash-set-network`, with a
Test mode toggle in Wallet Settings → Cashu. Cashu has no testnet, so this
points the wallet at the public `testnut` mint — but crucially each network
gets its OWN wallet and accepted-mints file, because test and real proofs
in one purse would be spendable interchangeably and the balance would be a
lie. Mainnet keeps the original filenames, so existing funds files are
untouched and switching is reversible: tests assert a real balance survives
a round trip through test mode.
Headless coverage: scripts/test-ecash-routes.sh drives every ecash RPC over
the real HTTP path (network get/set, balance, history, mint quote + claim,
send, receive, double-redeem refusal, garbage input, melt quote), restores
the node's original network on exit, and exits non-zero with the failure
count.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
03cf74696d
commit
be2cfb8293
@@ -24,6 +24,35 @@
|
||||
|
||||
<!-- ===================== Cashu Mints ===================== -->
|
||||
<div v-show="activeTab === 'cashu'">
|
||||
<!-- Real vs test ecash. Each network keeps its own wallet, so switching
|
||||
parks one balance and reveals the other — it never merges them. -->
|
||||
<div class="mb-4 rounded-lg border border-white/10 bg-white/[0.04] p-3">
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<p class="text-white/80 text-sm font-medium">{{ ecashIsTest ? 'Test ecash' : 'Real ecash' }}</p>
|
||||
<p class="text-white/40 text-xs mt-0.5">
|
||||
{{ ecashIsTest
|
||||
? 'Practice coins with no value, from a public test mint.'
|
||||
: 'Real, spendable sats.' }}
|
||||
</p>
|
||||
</div>
|
||||
<label class="flex items-center gap-2 cursor-pointer shrink-0">
|
||||
<span class="text-white/60 text-xs">Test mode</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="ecashIsTest"
|
||||
:disabled="switchingNetwork"
|
||||
class="h-4 w-4 accent-orange-500"
|
||||
@change="setEcashNetwork(($event.target as HTMLInputElement).checked)"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<p v-if="ecashIsTest" class="mt-2 text-xs text-orange-200">
|
||||
Your real ecash balance is safe and untouched — it reappears when you turn test mode off.
|
||||
</p>
|
||||
<p v-if="networkError" class="mt-2 text-xs text-red-300">{{ networkError }}</p>
|
||||
</div>
|
||||
|
||||
<p class="text-white/60 text-sm mb-4">
|
||||
Cashu ecash tokens can only be received from mints in this list. Add a mint's URL to accept tokens issued by it.
|
||||
</p>
|
||||
@@ -359,12 +388,48 @@ watch(
|
||||
(open) => {
|
||||
if (open) {
|
||||
loadMints()
|
||||
loadEcashNetwork()
|
||||
if (fedimintBackendReady) loadFederations()
|
||||
loadArk()
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
// ---- Ecash network (real vs test coins) ----
|
||||
const ecashIsTest = ref(false)
|
||||
const switchingNetwork = ref(false)
|
||||
const networkError = ref('')
|
||||
|
||||
async function loadEcashNetwork() {
|
||||
try {
|
||||
const res = await rpcClient.call<{ is_test: boolean }>({ method: 'wallet.ecash-network' })
|
||||
ecashIsTest.value = !!res.is_test
|
||||
} catch {
|
||||
// Older node without the setting: treat as real ecash, which is the
|
||||
// safe reading — never imply a real balance is "test".
|
||||
ecashIsTest.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function setEcashNetwork(test: boolean) {
|
||||
switchingNetwork.value = true
|
||||
networkError.value = ''
|
||||
try {
|
||||
await rpcClient.call({
|
||||
method: 'wallet.ecash-set-network',
|
||||
params: { network: test ? 'testnet' : 'mainnet' },
|
||||
})
|
||||
ecashIsTest.value = test
|
||||
// The mint list is per-network, so it must be re-read after a switch.
|
||||
await loadMints()
|
||||
} catch (err: unknown) {
|
||||
networkError.value = err instanceof Error ? err.message : 'Failed to switch network'
|
||||
await loadEcashNetwork()
|
||||
} finally {
|
||||
switchingNetwork.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function loadMints() {
|
||||
loadingMints.value = true
|
||||
mintError.value = ''
|
||||
|
||||
Reference in New Issue
Block a user