feat(demo): real testnet tx links + interactive buy-files flow

- Tx/explorer links open mempool.space/testnet/tx/<id>; the backend hydrates the
  wallet's transactions with REAL recent testnet txids at startup (best-effort,
  falls back to mock hashes offline). Mempool app + demo-external apps open in a
  new tab; deep-link paths are carried through.
- Add the content.* paid-download handlers the buy flow needs (owned-list,
  preview-peer, download-peer-{paid,invoice,onchain}, request-invoice,
  invoice-status, request-onchain, onchain-status) — every path resolves to a
  success state with testnet receive addresses / bolt11 invoices so visitors can
  walk the full buy → unlock journey.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-06-22 10:53:05 -04:00
co-authored by Claude Opus 4.8
parent 2cffa79d9d
commit 4d0c2d6717
3 changed files with 83 additions and 5 deletions
+6 -1
View File
@@ -109,6 +109,7 @@ import { useAppIdentity } from './appSession/useAppIdentity'
import { useNostrBridge } from './appSession/useNostrBridge'
import { openExternalUrl } from '@/utils/openExternal'
import { useElectrsSync } from '@/composables/useElectrsSync'
import { IS_DEMO, isDemoExternal } from '@/composables/useDemoIntro'
const props = defineProps<{
appIdProp?: string
@@ -157,7 +158,11 @@ const packageEntry = computed(() => store.data?.['package-data']?.[appId.value]
const blockedReason = computed(() => launchBlockedReason(appId.value, packageEntry.value))
const blockedTitle = computed(() => appId.value === 'fedimint' || appId.value === 'fedimintd' ? 'Waiting for Bitcoin sync' : 'App not ready')
const isMobile = typeof window !== 'undefined' && window.innerWidth < 768
const mustOpenNewTab = computed(() => NEW_TAB_APPS.has(appId.value))
// In the demo, apps backed by a real external site that blocks iframing
// (mempool.space) open in a new tab rather than the in-app session frame.
const mustOpenNewTab = computed(() =>
NEW_TAB_APPS.has(appId.value) || (IS_DEMO && isDemoExternal(appId.value))
)
// ElectrumX shows a sync screen before its real UI (the Electrum server only
// serves clients once its index is built). Poll /electrs-status while this is
@@ -78,9 +78,12 @@ export const IFRAME_BLOCKED_APPS = new Set<string>([])
/** Resolve app URL using direct port mapping (source of truth) */
export function resolveAppUrl(id: string, routeQueryPath?: string, runtimeUrl?: string): string {
// Demo: route to the app's mock UI or real external site (mempool.space,
// indee.tx1138.com). Non-demoable apps fall through to a generic notice page.
// indee.tx1138.com). Carry through a deep-link path (e.g. /tx/<hash> for
// mempool). Non-demoable apps fall through to a generic notice page.
if (IS_DEMO) {
return demoAppUrl(id) || `/app/${id}/`
const base = demoAppUrl(id)
if (base) return routeQueryPath ? base + routeQueryPath : base
return `/app/${id}/`
}
// External HTTPS apps