feat: seed-driven pairing over the LNbits nostr-transport (#70) #73
4 changed files with 3 additions and 128 deletions
refactor(machine): remove dead Lightning.Pub nprofile UI (post-3d cutover)
The LP backend was deleted on dev, so VITE_LIGHTNING_PUB_PUBKEY / config.lightningPubPubkey are never set — the "add this ATM's node to your wallet" nprofile QR (IdleView dev button + overlay, SupportView ShockWallet card + deep-link) rendered empty, and the LP fields in RuntimeConfig (lightningPubPubkey/lightningPubApiUrl/extensionApiUrl) were never populated. Remove them. The concept has no clean LNbits analog (the ATM is a cash↔LN gateway, not a node customers peer with) — tracked as a fresh feature request on lnbits. ShockWallet stays listed as a downloadable wallet (plain URL). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
commit
7abc2e3305
|
|
@ -17,10 +17,6 @@ export interface RuntimeConfig {
|
||||||
relayUrl: string
|
relayUrl: string
|
||||||
/** LNbits nostr-transport server pubkey (hex, 64 chars). */
|
/** LNbits nostr-transport server pubkey (hex, 64 chars). */
|
||||||
lnbitsServerPubkey: string
|
lnbitsServerPubkey: string
|
||||||
/** Legacy LP fields — retained until 3d removes the LP backend. Optional. */
|
|
||||||
lightningPubPubkey?: string
|
|
||||||
lightningPubApiUrl?: string
|
|
||||||
extensionApiUrl?: string
|
|
||||||
appId: string
|
appId: string
|
||||||
machineModel: string
|
machineModel: string
|
||||||
fiatCode: string
|
fiatCode: string
|
||||||
|
|
|
||||||
4
apps/machine/src/types/electron.d.ts
vendored
4
apps/machine/src/types/electron.d.ts
vendored
|
|
@ -6,10 +6,6 @@ export interface RuntimeConfig {
|
||||||
relayUrl: string
|
relayUrl: string
|
||||||
/** LNbits nostr-transport server pubkey (hex, 64 chars). */
|
/** LNbits nostr-transport server pubkey (hex, 64 chars). */
|
||||||
lnbitsServerPubkey: string
|
lnbitsServerPubkey: string
|
||||||
/** Legacy LP fields — retained until 3d removes the LP backend. Optional. */
|
|
||||||
lightningPubPubkey?: string
|
|
||||||
lightningPubApiUrl?: string
|
|
||||||
extensionApiUrl?: string
|
|
||||||
appId: string
|
appId: string
|
||||||
machineModel: string
|
machineModel: string
|
||||||
fiatCode: string
|
fiatCode: string
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, watch } from 'vue'
|
import { ref, watch } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { nip19 } from 'nostr-tools'
|
|
||||||
import { useAtmStore } from '@/stores/atm'
|
import { useAtmStore } from '@/stores/atm'
|
||||||
import { useBranding } from '@/composables/useBranding'
|
import { useBranding } from '@/composables/useBranding'
|
||||||
import { initialContext } from '@bitSpire/state-machine'
|
import { initialContext } from '@bitSpire/state-machine'
|
||||||
|
|
@ -15,18 +14,8 @@ const atmStore = useAtmStore()
|
||||||
const { logoUrl, title: brandTitle } = useBranding()
|
const { logoUrl, title: brandTitle } = useBranding()
|
||||||
const lndconnectUrl = import.meta.env.VITE_LNDCONNECT_URL || ''
|
const lndconnectUrl = import.meta.env.VITE_LNDCONNECT_URL || ''
|
||||||
const showZeusQR = ref(false)
|
const showZeusQR = ref(false)
|
||||||
const showLpQR = ref(false)
|
|
||||||
const copied = ref(false)
|
const copied = ref(false)
|
||||||
|
|
||||||
// Build nprofile for Lightning.Pub (pubkey + relay hint)
|
|
||||||
const lpNprofile = computed(() => {
|
|
||||||
const pubkey = import.meta.env.VITE_LIGHTNING_PUB_PUBKEY
|
|
||||||
if (!pubkey) return ''
|
|
||||||
const relayUrl = import.meta.env.VITE_RELAY_URL
|
|
||||||
const relays = relayUrl ? [relayUrl.replace('ws://', 'wss://')] : []
|
|
||||||
return nip19.nprofileEncode({ pubkey, relays })
|
|
||||||
})
|
|
||||||
|
|
||||||
async function copyToClipboard(value: string) {
|
async function copyToClipboard(value: string) {
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(value)
|
await navigator.clipboard.writeText(value)
|
||||||
|
|
@ -157,15 +146,6 @@ function handleCashOut() {
|
||||||
>
|
>
|
||||||
Zeus QR (lnd-alice)
|
Zeus QR (lnd-alice)
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
|
||||||
v-if="lpNprofile"
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
class="text-xs text-muted-foreground"
|
|
||||||
@click="showLpQR = true"
|
|
||||||
>
|
|
||||||
Lightning.Pub nprofile
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Zeus QR fullscreen overlay -->
|
<!-- Zeus QR fullscreen overlay -->
|
||||||
|
|
@ -193,27 +173,6 @@ function handleCashOut() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Lightning.Pub nprofile QR fullscreen overlay -->
|
|
||||||
<div
|
|
||||||
v-if="showLpQR"
|
|
||||||
class="fixed inset-0 z-[100] flex flex-col items-center justify-center gap-4 bg-black/90 p-4"
|
|
||||||
@click.self="showLpQR = false"
|
|
||||||
>
|
|
||||||
<p class="text-sm text-white/70">Lightning.Pub nprofile</p>
|
|
||||||
<div class="rounded-2xl">
|
|
||||||
<QRCode :value="lpNprofile" :size="400" />
|
|
||||||
</div>
|
|
||||||
<code class="max-w-[90vw] truncate text-xs text-white/50">{{ lpNprofile }}</code>
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<Button variant="outline" size="sm" class="text-white" @click="copyToClipboard(lpNprofile)">
|
|
||||||
{{ copied ? 'Copied!' : 'Copy' }}
|
|
||||||
</Button>
|
|
||||||
<Button variant="outline" size="sm" class="text-white" @click="showLpQR = false">
|
|
||||||
Close
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Help button (top-left) -->
|
<!-- Help button (top-left) -->
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { nip19 } from 'nostr-tools'
|
|
||||||
import { marked } from 'marked'
|
import { marked } from 'marked'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Card, CardContent } from '@/components/ui/card'
|
import { Card, CardContent } from '@/components/ui/card'
|
||||||
|
|
@ -23,35 +22,6 @@ import { QrCode, ExternalLink } from 'lucide-vue-next'
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const isElectron = typeof window !== 'undefined' && window.electronAPI !== undefined
|
const isElectron = typeof window !== 'undefined' && window.electronAPI !== undefined
|
||||||
|
|
||||||
// Lightning.Pub config loaded at runtime from Electron main process
|
|
||||||
const lpPubkey = ref('')
|
|
||||||
const relayUrl = ref('')
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
if (isElectron && window.electronAPI) {
|
|
||||||
const config = await window.electronAPI.getConfig()
|
|
||||||
lpPubkey.value = config.lightningPubPubkey || ''
|
|
||||||
relayUrl.value = config.relayUrl || ''
|
|
||||||
} else {
|
|
||||||
// Dev fallback: use Vite env vars
|
|
||||||
lpPubkey.value = import.meta.env.VITE_LIGHTNING_PUB_PUBKEY || ''
|
|
||||||
relayUrl.value = import.meta.env.VITE_RELAY_URL || ''
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Build nprofile for Lightning.Pub (pubkey + relay hint)
|
|
||||||
const lpNprofile = computed(() => {
|
|
||||||
if (!lpPubkey.value) return ''
|
|
||||||
const relays = relayUrl.value ? [relayUrl.value.replace('ws://', 'wss://')] : []
|
|
||||||
return nip19.nprofileEncode({ pubkey: lpPubkey.value, relays })
|
|
||||||
})
|
|
||||||
|
|
||||||
// Deep link URL: opens ShockWallet with this ATM's Lightning.Pub pre-filled
|
|
||||||
const shockwalletDeepLink = computed(() => {
|
|
||||||
if (!lpNprofile.value) return ''
|
|
||||||
return `https://wallet.aiolabs.dev/sources/add?nprofile=${encodeURIComponent(lpNprofile.value)}`
|
|
||||||
})
|
|
||||||
|
|
||||||
interface SupportPage {
|
interface SupportPage {
|
||||||
id: string
|
id: string
|
||||||
title: string
|
title: string
|
||||||
|
|
@ -74,17 +44,11 @@ const defaultPages: SupportPage[] = [
|
||||||
| Blink | No | Partial | Yes | Yes | https://www.blink.sv |
|
| Blink | No | Partial | Yes | Yes | https://www.blink.sv |
|
||||||
| Zeus | Yes | Yes | Yes | Yes | https://zeusln.com |
|
| Zeus | Yes | Yes | Yes | Yes | https://zeusln.com |
|
||||||
| Breez | Yes | Yes | Yes | Yes | https://breez.technology |
|
| Breez | Yes | Yes | Yes | Yes | https://breez.technology |
|
||||||
| ShockWallet | No | Yes | Yes | Yes | [shockwallet-deep-link] |
|
| ShockWallet | No | Yes | Yes | Yes | https://shockwallet.app |
|
||||||
|
|
||||||
Tap a QR icon to scan and download a wallet.
|
Tap a QR icon to scan and download a wallet.
|
||||||
|
|
||||||
**Non-custodial** means you hold your own keys and have full control of your Bitcoin. **KYC-free** means no identity verification is required. Partial (~) means limits apply without verification.
|
**Non-custodial** means you hold your own keys and have full control of your Bitcoin. **KYC-free** means no identity verification is required. Partial (~) means limits apply without verification.`,
|
||||||
|
|
||||||
## Using ShockWallet with this ATM
|
|
||||||
|
|
||||||
Scan the QR code below to add this ATM's Lightning node to your ShockWallet. This lets you send and receive sats directly through the ATM's payment system.
|
|
||||||
|
|
||||||
[lp-nprofile]`,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'faq',
|
id: 'faq',
|
||||||
|
|
@ -153,7 +117,6 @@ type Segment =
|
||||||
| { type: 'qr'; content: string }
|
| { type: 'qr'; content: string }
|
||||||
| { type: 'table'; table: ParsedTable }
|
| { type: 'table'; table: ParsedTable }
|
||||||
| { type: 'qr-placeholder' }
|
| { type: 'qr-placeholder' }
|
||||||
| { type: 'lp-nprofile' }
|
|
||||||
|
|
||||||
/** Parse markdown table into structured data */
|
/** Parse markdown table into structured data */
|
||||||
function parseMarkdownTable(tableLines: string[]): ParsedTable | null {
|
function parseMarkdownTable(tableLines: string[]): ParsedTable | null {
|
||||||
|
|
@ -176,17 +139,8 @@ function parseMarkdownTable(tableLines: string[]): ParsedTable | null {
|
||||||
return { headers, rows }
|
return { headers, rows }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Resolve dynamic placeholders in markdown content */
|
|
||||||
function resolvePlaceholders(md: string): string {
|
|
||||||
return md.replace(
|
|
||||||
'[shockwallet-deep-link]',
|
|
||||||
shockwalletDeepLink.value || 'https://wallet.aiolabs.dev'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Parse content into segments: html, qr, or table */
|
/** Parse content into segments: html, qr, or table */
|
||||||
function parseContent(md: string): Segment[] {
|
function parseContent(md: string): Segment[] {
|
||||||
md = resolvePlaceholders(md)
|
|
||||||
const segments: Segment[] = []
|
const segments: Segment[] = []
|
||||||
const lines = md.split('\n')
|
const lines = md.split('\n')
|
||||||
let htmlBlock = ''
|
let htmlBlock = ''
|
||||||
|
|
@ -235,9 +189,6 @@ function parseContent(md: string): Segment[] {
|
||||||
} else if (trimmed === '[operator-qr-placeholder]') {
|
} else if (trimmed === '[operator-qr-placeholder]') {
|
||||||
flushHtml()
|
flushHtml()
|
||||||
segments.push({ type: 'qr-placeholder' })
|
segments.push({ type: 'qr-placeholder' })
|
||||||
} else if (trimmed === '[lp-nprofile]') {
|
|
||||||
flushHtml()
|
|
||||||
segments.push({ type: 'lp-nprofile' })
|
|
||||||
} else {
|
} else {
|
||||||
htmlBlock += line + '\n'
|
htmlBlock += line + '\n'
|
||||||
}
|
}
|
||||||
|
|
@ -375,33 +326,6 @@ onUnmounted(() => {
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<!-- Lightning.Pub nprofile QR (scannable by ShockWallet) -->
|
|
||||||
<Card v-else-if="seg.type === 'lp-nprofile'" class="my-6 mx-auto max-w-xs">
|
|
||||||
<CardContent class="flex flex-col items-center gap-3 p-6">
|
|
||||||
<template v-if="lpNprofile">
|
|
||||||
<div class="rounded-xl bg-white p-3">
|
|
||||||
<QrcodeVue
|
|
||||||
:value="lpNprofile"
|
|
||||||
:size="180"
|
|
||||||
level="L"
|
|
||||||
render-as="svg"
|
|
||||||
background="#ffffff"
|
|
||||||
foreground="#000000"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<span class="text-xs text-muted-foreground text-center px-2">
|
|
||||||
Scan with ShockWallet to connect
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<QrCode class="h-16 w-16 text-muted-foreground/30" />
|
|
||||||
<span class="text-sm text-muted-foreground/50 text-center">
|
|
||||||
Lightning.Pub not configured
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<!-- Table with inline QR codes -->
|
<!-- Table with inline QR codes -->
|
||||||
<div v-else-if="seg.type === 'table'" class="mb-8">
|
<div v-else-if="seg.type === 'table'" class="mb-8">
|
||||||
<Table class="text-sm sm:text-base lg:text-xl w-full">
|
<Table class="text-sm sm:text-base lg:text-xl w-full">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue