Fix Cashu file redemption and Bitcoin-dependent wallet readiness

This commit is contained in:
archipelago
2026-09-29 14:42:44 -04:00
parent 540639d2c1
commit 0f85f588fb
41 changed files with 1641 additions and 214 deletions
+14 -9
View File
@@ -672,6 +672,11 @@ async function handleInstall(app: MarketplaceApp) {
return
}
if (installingApps.has(app.id) || isInstalled(app.id)) return
if (['bitcoin-core', 'bitcoin-knots'].includes(app.id)) {
installModalApp.value = app
showInstallModal.value = true
return
}
// Multi-version apps (Bitcoin Knots / Core): let the runner pick a version up
// front via a full-screen modal (latest pre-selected) instead of silently
// installing the default. Best-effort — if the lookup fails we install directly.
@@ -686,19 +691,19 @@ async function handleInstall(app: MarketplaceApp) {
startInstall(app)
}
function startInstall(app: MarketplaceApp, versionOverride?: string) {
function startInstall(app: MarketplaceApp, versionOverride?: string, prune?: boolean) {
if (app.source === 'local') {
installApp(app, versionOverride)
installApp(app, versionOverride, prune)
} else {
installCommunityApp(app, versionOverride)
installCommunityApp(app, versionOverride, prune)
}
}
function onInstallModalConfirm(version: string) {
function onInstallModalConfirm(version: string, prune?: boolean) {
const app = installModalApp.value
showInstallModal.value = false
installModalApp.value = null
if (app) startInstall(app, version)
if (app) startInstall(app, version, prune)
}
function viewAppDetails(app: MarketplaceApp) {
@@ -774,25 +779,25 @@ function failInstall(app: MarketplaceApp, err: unknown) {
trackTimeout(() => { serverStore.clearInstallProgress(app.id) }, 5000)
}
async function installApp(app: MarketplaceApp, versionOverride?: string) {
async function installApp(app: MarketplaceApp, versionOverride?: string, prune?: boolean) {
if (installingApps.has(app.id) || isInstalled(app.id)) return
queueInstall(app)
installToast(app)
try {
const installUrl = app.url || app.manifestUrl || app.s9pkUrl
await rpcClient.call({ method: 'package.install', params: { id: app.id, url: installUrl, version: versionOverride || app.version }, timeout: 600000 })
await rpcClient.call({ method: 'package.install', params: { id: app.id, url: installUrl, version: versionOverride || app.version, ...(prune === undefined ? {} : { prune }) }, timeout: 600000 })
} catch (err) {
if (import.meta.env.DEV) console.error('Installation failed:', err)
failInstall(app, err)
}
}
async function installCommunityApp(app: MarketplaceApp, versionOverride?: string) {
async function installCommunityApp(app: MarketplaceApp, versionOverride?: string, prune?: boolean) {
if (installingApps.has(app.id) || isInstalled(app.id) || !app.dockerImage) return
queueInstall(app)
installToast(app)
try {
const installParams: Record<string, unknown> = { id: app.id, dockerImage: app.dockerImage, version: versionOverride || app.version }
const installParams: Record<string, unknown> = { id: app.id, dockerImage: app.dockerImage, version: versionOverride || app.version, ...(prune === undefined ? {} : { prune }) }
if ((app as Record<string, unknown>).containerConfig) {
installParams.containerConfig = (app as Record<string, unknown>).containerConfig
}
+15 -3
View File
@@ -71,10 +71,11 @@
{{ $ver(v.version) }}{{ v.default ? ' — latest' : '' }}{{ v.deprecated ? ' (deprecated)' : '' }}
</option>
</select>
<BitcoinPruningChoice v-if="!isInstalled && isBitcoinInstall && prunePrefsLoaded" v-model="pruneOnInstall" />
<button
v-if="!isInstalled"
@click="installApp"
:disabled="demoNoInstall || installing || (!installBlockedReason && !app.manifestUrl && !app.dockerImage)"
:disabled="demoNoInstall || installing || (isBitcoinInstall && !prunePrefsLoaded) || (!installBlockedReason && !app.manifestUrl && !app.dockerImage)"
:title="demoNoInstall ? 'Not available in the demo' : (installBlockedReason || undefined)"
class="glass-button glass-button-sm px-6 py-2.5 rounded-lg text-sm font-semibold flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed"
>
@@ -137,6 +138,7 @@
{{ $ver(v.version) }}{{ v.default ? ' — latest' : '' }}{{ v.deprecated ? ' (deprecated)' : '' }}
</option>
</select>
<BitcoinPruningChoice v-if="!isInstalled && isBitcoinInstall && prunePrefsLoaded" v-model="pruneOnInstall" />
<!-- Bottom: Action Buttons -->
<div class="grid grid-cols-2 gap-2">
@@ -153,7 +155,7 @@
<button
v-else
@click="installApp"
:disabled="demoNoInstall || installing || (!installBlockedReason && !app.manifestUrl && !app.dockerImage)"
:disabled="demoNoInstall || installing || (isBitcoinInstall && !prunePrefsLoaded) || (!installBlockedReason && !app.manifestUrl && !app.dockerImage)"
:title="demoNoInstall ? 'Not available in the demo' : (installBlockedReason || undefined)"
class="glass-button glass-button-sm px-4 py-2.5 rounded-lg text-sm font-semibold flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed col-span-2"
>
@@ -374,6 +376,7 @@
</template>
<script setup lang="ts">
import BitcoinPruningChoice from '@/components/BitcoinPruningChoice.vue'
import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
import { IS_DEMO, isDemoApp } from '@/composables/useDemoIntro'
import { useRouter, useRoute } from 'vue-router'
@@ -408,6 +411,10 @@ const bitcoinPruned = ref(false)
// Hidden when an app offers only one version — install stays one-click.
const installVersions = ref<{ version: string; default: boolean; deprecated: boolean; eol: string | null }[]>([])
const selectedInstallVersion = ref('')
const pruneOnInstall = ref(false)
const pruneSettingKnown = ref(false)
const prunePrefsLoaded = ref(false)
const isBitcoinInstall = computed(() => ['bitcoin-core', 'bitcoin-knots'].includes(app.value?.id || ''))
const backButtonLabel = computed(() => route.query.from === 'home' ? t('marketplaceDetails.backToHome') : t('marketplaceDetails.backToStore'))
const electrumxArchiveWarning = 'You need a full archival bitcoin node before downloading ElectrumX'
@@ -587,10 +594,13 @@ onMounted(() => {
// cached entry is missing or past its TTL, so a repeat open inside the TTL
// paints from cache with no new RPC.
async function loadInstallVersions() {
if (versionsResource.data.value === null || versionsResource.isStale.value) {
if (isBitcoinInstall.value || versionsResource.data.value === null || versionsResource.isStale.value) {
await versionsResource.refresh()
}
const info = versionsResource.data.value
pruneSettingKnown.value = !versionsResource.error.value && typeof info?.bitcoinPrune === 'boolean'
pruneOnInstall.value = pruneSettingKnown.value && info?.bitcoinPrune === true
prunePrefsLoaded.value = true
if (!info || !info.supportsVersions || info.versions.length < 2) {
installVersions.value = []
return
@@ -701,6 +711,7 @@ async function installApp() {
id: app.value.id,
dockerImage: app.value.dockerImage,
version: chosenVersion,
...(isBitcoinInstall.value && (pruneSettingKnown.value || pruneOnInstall.value) ? { prune: pruneOnInstall.value } : {}),
}
if (app.value.containerConfig) installParams.containerConfig = app.value.containerConfig
await rpcClient.call({
@@ -717,6 +728,7 @@ async function installApp() {
id: app.value.id,
url: installUrl,
version: chosenVersion,
...(isBitcoinInstall.value && (pruneSettingKnown.value || pruneOnInstall.value) ? { prune: pruneOnInstall.value } : {}),
},
timeout: 600000,
})
@@ -362,6 +362,22 @@ init()
</button>
</div>
<div class="overflow-y-auto flex-1 min-h-0 space-y-6 pr-1">
<!-- v1.8.20-alpha -->
<div>
<div class="flex items-center gap-2 mb-3">
<span class="text-xs font-mono px-2 py-0.5 rounded bg-orange-500/20 text-orange-300">v1.8.20-alpha</span>
<span class="text-xs text-white/40">September 29, 2026</span>
</div>
<div class="space-y-3 text-sm text-white/80 pl-3 border-l border-white/10">
<p>Fixed Cashu file payments rejected despite a shared mint, and preserved the payment amount when mint fees reduce change.</p>
<p>Payment failures now report whether a refund actually succeeded; missing files and unsupported payment methods are rejected before charging.</p>
<p>Bitcoin Core and Knots installation offers optional pruning on larger disks, using the same settings as automatic pruning.</p>
<p>Fixed false missing-port checks that unnecessarily restarted Bitcoin and LND; recovery now respects managed shutdown timeouts.</p>
<p>LND explains when it is waiting for Bitcoin installation, startup, or sync, without treating normal synchronization as a restart-worthy failure.</p>
<p>Bitcoin startup messages explain block-index loading without exposing raw RPC errors, and Lightning keeps known balances clearly marked during outages.</p>
<p>Changed the public transaction-explorer default to mempool.space while preserving local explorers and custom choices.</p>
</div>
</div>
<!-- v1.8.19-alpha -->
<div>
<div class="flex items-center gap-2 mb-3">