Compare commits
9 commits
82fbf12950
...
984ae9d71e
| Author | SHA1 | Date | |
|---|---|---|---|
| 984ae9d71e | |||
| d1105bbb38 | |||
| 24515df35a | |||
| a4e3b2ccc8 | |||
| ac96921074 | |||
|
|
d4314adc52 | ||
|
|
2c6c0f1a89 | ||
|
|
4f0e284a96 | ||
|
|
ba4fcbce2d |
27 changed files with 25 additions and 193 deletions
|
|
@ -73,18 +73,6 @@ VITE_SPIRE_SEED=
|
||||||
# Show "Under Service" screen and block all transactions
|
# Show "Under Service" screen and block all transactions
|
||||||
# VITE_MAINTENANCE_MODE=true
|
# VITE_MAINTENANCE_MODE=true
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# Public Web Demo
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
# Set ONLY for the browser demo build (atm.demo.aiolabs.dev). Leave blank on
|
|
||||||
# every real machine. When set it:
|
|
||||||
# - keeps the mouse cursor visible (kiosk builds hide it)
|
|
||||||
# - mints one extra, never-used LNbits wallet named with this exact string,
|
|
||||||
# so the throwaway accounts the demo creates (one per page load, each with
|
|
||||||
# its own ephemeral identity) can be swept by name instead of guessed at.
|
|
||||||
# VITE_DEMO_TAG=bitspire-web-demo
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Mock Fallback (Production Safety)
|
# Mock Fallback (Production Safety)
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,6 @@
|
||||||
* QR path keeps working — cash-out never depends on this.
|
* QR path keeps working — cash-out never depends on this.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { execFile } from 'node:child_process'
|
|
||||||
|
|
||||||
export type NfcState = 'ready' | 'reading' | 'error' | 'card-removed' | 'unavailable'
|
export type NfcState = 'ready' | 'reading' | 'error' | 'card-removed' | 'unavailable'
|
||||||
export interface NfcStatus {
|
export interface NfcStatus {
|
||||||
state: NfcState
|
state: NfcState
|
||||||
|
|
@ -85,11 +83,7 @@ export async function readNdefLnurlw(
|
||||||
const send = (bytes: number[]) => transmit(Buffer.from(bytes), 256)
|
const send = (bytes: number[]) => transmit(Buffer.from(bytes), 256)
|
||||||
|
|
||||||
// Select the NDEF Tag Application (AID D2760000850101).
|
// Select the NDEF Tag Application (AID D2760000850101).
|
||||||
if (
|
if (!swOk(await send([0x00, 0xa4, 0x04, 0x00, 0x07, 0xd2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x01, 0x00]))) {
|
||||||
!swOk(
|
|
||||||
await send([0x00, 0xa4, 0x04, 0x00, 0x07, 0xd2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x01, 0x00])
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,29 +107,6 @@ export async function readNdefLnurlw(
|
||||||
|
|
||||||
let stopFn: (() => void) | null = null
|
let stopFn: (() => void) | null = null
|
||||||
|
|
||||||
// ── Wedge auto-recovery ───────────────────────────────────────────────────
|
|
||||||
// Cheap CCID readers (the Feitian R502-CL especially) occasionally wedge: they
|
|
||||||
// keep detecting a card but every APDU returns "card absent or mute", and ONLY
|
|
||||||
// a USB power-cycle clears it — pcscd/app restarts do NOT. When we see a run of
|
|
||||||
// consecutive read failures we trigger nfc-reader-reset.service (a root oneshot
|
|
||||||
// that re-binds the reader's USB device = a software replug); nfc-pcsc then
|
|
||||||
// re-detects the reader on hotplug with no app restart. The trigger is gated by
|
|
||||||
// a cooldown so a still-wedged reader can't reset-loop. A quality reader (e.g.
|
|
||||||
// ACR1252U) wedges far less; this is belt-and-suspenders for any reader.
|
|
||||||
const WEDGE_FAILURE_THRESHOLD = 3
|
|
||||||
const RESET_COOLDOWN_MS = 30_000
|
|
||||||
// Persist across reader re-enumerations (a reset spawns a fresh reader closure).
|
|
||||||
let lastReaderResetAt = 0
|
|
||||||
|
|
||||||
/** Trigger the privileged USB power-cycle of the reader. Best-effort. */
|
|
||||||
function resetWedgedReader(): void {
|
|
||||||
// NixOS: the app runs unprivileged as `bitspire`; a polkit rule authorises it
|
|
||||||
// to start this one unit. systemctl lives at a stable path on the device.
|
|
||||||
execFile('/run/current-system/sw/bin/systemctl', ['start', 'nfc-reader-reset.service'], () => {
|
|
||||||
/* best-effort — if it fails the reader stays wedged until a manual reset */
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start listening for Bolt Card taps. Idempotent. Returns a stop function.
|
* Start listening for Bolt Card taps. Idempotent. Returns a stop function.
|
||||||
* Never throws — failures surface via onStatus.
|
* Never throws — failures surface via onStatus.
|
||||||
|
|
@ -188,10 +159,6 @@ export async function startNfcReader(
|
||||||
// a present↔empty storm when hammered, so ignore re-detections for a beat
|
// a present↔empty storm when hammered, so ignore re-detections for a beat
|
||||||
// after a failure. Successful reads don't cool down.
|
// after a failure. Successful reads don't cool down.
|
||||||
let cooldownUntil = 0
|
let cooldownUntil = 0
|
||||||
// Consecutive failed reads → wedge detection (see resetWedgedReader above).
|
|
||||||
// A completed read (Bolt Card or not) proves the reader is healthy and
|
|
||||||
// clears the count; only a run of thrown transmits trips the reset.
|
|
||||||
let consecutiveFailures = 0
|
|
||||||
r.on('card', async () => {
|
r.on('card', async () => {
|
||||||
if (Date.now() < cooldownUntil) return
|
if (Date.now() < cooldownUntil) return
|
||||||
onStatus({ state: 'reading', reader: name })
|
onStatus({ state: 'reading', reader: name })
|
||||||
|
|
@ -200,30 +167,13 @@ export async function startNfcReader(
|
||||||
// user simply re-taps.
|
// user simply re-taps.
|
||||||
try {
|
try {
|
||||||
const lnurlw = await readNdefLnurlw((apdu, maxLen) => r.transmit(apdu, maxLen))
|
const lnurlw = await readNdefLnurlw((apdu, maxLen) => r.transmit(apdu, maxLen))
|
||||||
consecutiveFailures = 0
|
|
||||||
if (lnurlw) {
|
if (lnurlw) {
|
||||||
onCard(lnurlw)
|
onCard(lnurlw)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
onStatus({ state: 'error', reader: name, message: 'not a Bolt Card' })
|
onStatus({ state: 'error', reader: name, message: 'not a Bolt Card' })
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
consecutiveFailures++
|
onStatus({ state: 'error', reader: name, message: 'card read failed — hold steady & retap' })
|
||||||
if (
|
|
||||||
consecutiveFailures >= WEDGE_FAILURE_THRESHOLD &&
|
|
||||||
Date.now() - lastReaderResetAt > RESET_COOLDOWN_MS
|
|
||||||
) {
|
|
||||||
// Reader looks wedged — auto power-cycle it (only fix that works).
|
|
||||||
lastReaderResetAt = Date.now()
|
|
||||||
consecutiveFailures = 0
|
|
||||||
onStatus({ state: 'error', reader: name, message: 'reader stuck — auto-resetting…' })
|
|
||||||
resetWedgedReader()
|
|
||||||
} else {
|
|
||||||
onStatus({
|
|
||||||
state: 'error',
|
|
||||||
reader: name,
|
|
||||||
message: 'card read failed — hold steady & retap',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
void e
|
void e
|
||||||
}
|
}
|
||||||
cooldownUntil = Date.now() + 1500
|
cooldownUntil = Date.now() + 1500
|
||||||
|
|
@ -232,9 +182,7 @@ export async function startNfcReader(
|
||||||
r.on('error', (err: unknown) =>
|
r.on('error', (err: unknown) =>
|
||||||
onStatus({ state: 'error', reader: name, message: errMsg(err) })
|
onStatus({ state: 'error', reader: name, message: errMsg(err) })
|
||||||
)
|
)
|
||||||
r.on('end', () =>
|
r.on('end', () => onStatus({ state: 'unavailable', reader: name, message: 'reader disconnected' }))
|
||||||
onStatus({ state: 'unavailable', reader: name, message: 'reader disconnected' })
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
nfc.on('error', (err: unknown) => onStatus({ state: 'error', message: errMsg(err) }))
|
nfc.on('error', (err: unknown) => onStatus({ state: 'error', message: errMsg(err) }))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
<html lang="en" class="dark">
|
<html lang="en" class="dark">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/png" href="/logo.png" />
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
|
||||||
<!--
|
<!--
|
||||||
Content Security Policy:
|
Content Security Policy:
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
http-equiv="Content-Security-Policy"
|
http-equiv="Content-Security-Policy"
|
||||||
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self' ws: wss: http: https:; img-src 'self' data: blob:; font-src 'self'; frame-src 'none'; object-src 'none'"
|
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self' ws: wss: http: https:; img-src 'self' data: blob:; font-src 'self'; frame-src 'none'; object-src 'none'"
|
||||||
/>
|
/>
|
||||||
<title>bitSpire ATM</title>
|
<title>Lamassu ATM</title>
|
||||||
<style>
|
<style>
|
||||||
/* Prevent text selection and context menu on kiosk */
|
/* Prevent text selection and context menu on kiosk */
|
||||||
* {
|
* {
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@
|
||||||
"dev:vite": "vite",
|
"dev:vite": "vite",
|
||||||
"electron:dev": "tsc -p electron/tsconfig.json && tsc -p electron/tsconfig.preload.json && electron dist-electron/main.js",
|
"electron:dev": "tsc -p electron/tsconfig.json && tsc -p electron/tsconfig.preload.json && electron dist-electron/main.js",
|
||||||
"build": "vue-tsc --noEmit && vite build && tsc -p electron/tsconfig.json && tsc -p electron/tsconfig.preload.json && npx esbuild electron/fund-atm.ts --bundle --platform=node --format=cjs --external:better-sqlite3 --outfile=dist-electron/fund-atm.bundle.cjs",
|
"build": "vue-tsc --noEmit && vite build && tsc -p electron/tsconfig.json && tsc -p electron/tsconfig.preload.json && npx esbuild electron/fund-atm.ts --bundle --platform=node --format=cjs --external:better-sqlite3 --outfile=dist-electron/fund-atm.bundle.cjs",
|
||||||
"build:web": "vite build",
|
|
||||||
"build:electron": "pnpm build && electron-builder",
|
"build:electron": "pnpm build && electron-builder",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"typecheck": "vue-tsc --noEmit",
|
"typecheck": "vue-tsc --noEmit",
|
||||||
|
|
|
||||||
|
|
@ -24,13 +24,6 @@ const router = createRouter({
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
// Kiosk chrome (hidden cursor) is the default — every real machine is a
|
|
||||||
// touchscreen. The public web demo (VITE_DEMO_TAG) runs in a normal browser,
|
|
||||||
// where an invisible pointer just reads as broken.
|
|
||||||
if (!import.meta.env.VITE_DEMO_TAG) {
|
|
||||||
document.documentElement.classList.add('kiosk')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create Pinia store
|
// Create Pinia store
|
||||||
const pinia = createPinia()
|
const pinia = createPinia()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -505,31 +505,6 @@ export async function initializeLightningServices(options?: {
|
||||||
}
|
}
|
||||||
console.log('[Lightning] LNbits wallet:', lnbitsWalletId)
|
console.log('[Lightning] LNbits wallet:', lnbitsWalletId)
|
||||||
|
|
||||||
// ── Public web demo: stamp the throwaway account so it can be swept ──────
|
|
||||||
// The browser demo (atm.demo.aiolabs.dev) runs with an EPHEMERAL identity —
|
|
||||||
// a fresh keypair per page load — so LNbits mints a new account + a fresh
|
|
||||||
// auto-credited wallet for every visitor. That isolation is the point (a
|
|
||||||
// single baked-in key would be credited exactly once and then drain), but it
|
|
||||||
// leaves throwaway accounts behind, and nothing in an auto-created row says
|
|
||||||
// "demo": pubkey-set/prvkey-NULL also describes a real ATM.
|
|
||||||
//
|
|
||||||
// A nostr pubkey can't carry a marker (you'd have to grind a vanity prefix,
|
|
||||||
// far too slow to do on page load), and the account/wallet the server
|
|
||||||
// auto-creates isn't nameable by the client. So we mint one extra,
|
|
||||||
// never-used wallet whose NAME is the tag: sweeping is then an exact string
|
|
||||||
// match on wallet name rather than a heuristic about what looks disposable.
|
|
||||||
//
|
|
||||||
// Unset on every real machine, so this is inert outside the demo build. The
|
|
||||||
// call is fire-and-forget: losing the marker degrades cleanup, not the demo.
|
|
||||||
const demoTag = (import.meta.env.VITE_DEMO_TAG as string | undefined)?.trim()
|
|
||||||
if (demoTag) {
|
|
||||||
void lnbits
|
|
||||||
.createWallet(demoTag)
|
|
||||||
// Never log the reply — create_wallet returns adminkey/inkey.
|
|
||||||
.then(() => console.log('[Lightning] Demo marker wallet created:', demoTag))
|
|
||||||
.catch((e) => console.warn('[Lightning] Demo marker wallet failed:', e))
|
|
||||||
}
|
|
||||||
|
|
||||||
// #70 P1: pull operator pubkey + fee config from LNbits over the authenticated
|
// #70 P1: pull operator pubkey + fee config from LNbits over the authenticated
|
||||||
// transport (spirekeeper#41 `get_machine_config`). A seed-only machine has no
|
// transport (spirekeeper#41 `get_machine_config`). A seed-only machine has no
|
||||||
// VITE_OPERATOR_PUBKEYS, so without this it can't trust its fee config and sits
|
// VITE_OPERATOR_PUBKEYS, so without this it can't trust its fee config and sits
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,10 @@
|
||||||
@import 'tailwindcss';
|
@import 'tailwindcss';
|
||||||
@import 'tw-animate-css';
|
@import 'tw-animate-css';
|
||||||
|
|
||||||
/* Hide cursor completely on touchscreen kiosk.
|
/* Hide cursor completely on touchscreen kiosk */
|
||||||
Scoped to .kiosk (set on <html> by main.ts) so the public web demo, which
|
*,
|
||||||
runs in an ordinary browser with a mouse, keeps a visible pointer. */
|
*::before,
|
||||||
.kiosk,
|
*::after {
|
||||||
.kiosk *,
|
|
||||||
.kiosk *::before,
|
|
||||||
.kiosk *::after {
|
|
||||||
cursor: none !important;
|
cursor: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ esac
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||||
|
|
||||||
echo "=== Building bitSpire ATM Live USB ISO (model: $MODEL) ==="
|
echo "=== Building Lamassu ATM Live USB ISO (model: $MODEL) ==="
|
||||||
echo ""
|
echo ""
|
||||||
echo "This is a pure Nix build — no local pnpm required."
|
echo "This is a pure Nix build — no local pnpm required."
|
||||||
echo ""
|
echo ""
|
||||||
|
|
|
||||||
|
|
@ -94,8 +94,7 @@
|
||||||
|
|
||||||
# pcscd gates client access via polkit; without a rule the sandboxed
|
# pcscd gates client access via polkit; without a rule the sandboxed
|
||||||
# `bitspire` service user is "Rejected unauthorized PC/SC client". Authorize
|
# `bitspire` service user is "Rejected unauthorized PC/SC client". Authorize
|
||||||
# it to talk to the daemon and the card. The second rule lets the app trigger
|
# it to talk to the daemon and the card.
|
||||||
# the NFC reader wedge-recovery service (see nfc-reader-reset below).
|
|
||||||
security.polkit.extraConfig = ''
|
security.polkit.extraConfig = ''
|
||||||
polkit.addRule(function(action, subject) {
|
polkit.addRule(function(action, subject) {
|
||||||
if ((action.id == "org.debian.pcsc-lite.access_pcsc" ||
|
if ((action.id == "org.debian.pcsc-lite.access_pcsc" ||
|
||||||
|
|
@ -104,47 +103,8 @@
|
||||||
return polkit.Result.YES;
|
return polkit.Result.YES;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
polkit.addRule(function(action, subject) {
|
|
||||||
if (action.id == "org.freedesktop.systemd1.manage-units" &&
|
|
||||||
action.lookup("unit") == "nfc-reader-reset.service" &&
|
|
||||||
subject.user == "bitspire") {
|
|
||||||
return polkit.Result.YES;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
'';
|
'';
|
||||||
|
|
||||||
# NFC reader wedge-recovery. The Feitian R502-CL CCID reader (and, less often,
|
|
||||||
# any CCID reader) can wedge: it keeps detecting a card but every APDU returns
|
|
||||||
# "card absent or mute", and ONLY a USB power-cycle clears it — restarting
|
|
||||||
# pcscd or the app does not. This oneshot re-binds the reader's USB device (a
|
|
||||||
# software replug); pcscd + nfc-pcsc then re-detect it on hotplug with no app
|
|
||||||
# restart (verified on-device). The app (unprivileged `bitspire`) starts it via
|
|
||||||
# the polkit rule above when it sees repeated read failures. Reader-agnostic:
|
|
||||||
# it matches the USB CCID interface class (0x0B), so it also covers a future
|
|
||||||
# ACR1252U swap without a config change.
|
|
||||||
systemd.services.nfc-reader-reset = {
|
|
||||||
description = "Power-cycle a wedged CCID NFC reader (USB re-bind)";
|
|
||||||
serviceConfig = {
|
|
||||||
Type = "oneshot";
|
|
||||||
ExecStart = pkgs.writeShellScript "reset-nfc-reader" ''
|
|
||||||
set -u
|
|
||||||
found=0
|
|
||||||
for iface in /sys/bus/usb/devices/*:*/bInterfaceClass; do
|
|
||||||
[ -f "$iface" ] || continue
|
|
||||||
[ "$(${pkgs.coreutils}/bin/cat "$iface" 2>/dev/null)" = "0b" ] || continue
|
|
||||||
ifname=$(${pkgs.coreutils}/bin/basename "$(${pkgs.coreutils}/bin/dirname "$iface")")
|
|
||||||
dev=''${ifname%%:*}
|
|
||||||
echo "reset-nfc-reader: power-cycling CCID reader USB device $dev" >&2
|
|
||||||
echo -n "$dev" > /sys/bus/usb/drivers/usb/unbind 2>/dev/null || true
|
|
||||||
${pkgs.coreutils}/bin/sleep 2
|
|
||||||
echo -n "$dev" > /sys/bus/usb/drivers/usb/bind 2>/dev/null || true
|
|
||||||
found=1
|
|
||||||
done
|
|
||||||
[ "$found" = 1 ] || { echo "reset-nfc-reader: no CCID reader found" >&2; exit 1; }
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# Disable suspend/hibernate for kiosk
|
# Disable suspend/hibernate for kiosk
|
||||||
systemd.targets = {
|
systemd.targets = {
|
||||||
sleep.enable = false;
|
sleep.enable = false;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# bitSpire ATM Live USB Configuration
|
# Lamassu ATM Live USB Configuration
|
||||||
# Bootable ISO for testing on physical hardware without installing to disk.
|
# Bootable ISO for testing on physical hardware without installing to disk.
|
||||||
#
|
#
|
||||||
# Parameterized by machineModel (passed via specialArgs from flake.nix):
|
# Parameterized by machineModel (passed via specialArgs from flake.nix):
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# bitSpire ATM Hardware udev Rules
|
# Lamassu ATM Hardware udev Rules
|
||||||
# Place in /etc/udev/rules.d/ or use services.udev.extraRules in NixOS
|
# Place in /etc/udev/rules.d/ or use services.udev.extraRules in NixOS
|
||||||
|
|
||||||
# ============================================
|
# ============================================
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
description = "bitSpire - Nostr-Native Lightning ATM";
|
description = "Lamassu Next - Nostr-Native Lightning ATM";
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
# Stable NixOS for the ATM OS base
|
# Stable NixOS for the ATM OS base
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Pure Nix derivation for the bitSpire ATM Electron app.
|
# Pure Nix derivation for the Lamassu ATM Electron app.
|
||||||
#
|
#
|
||||||
# Uses fetchPnpmDeps + pnpmConfigHook to build entirely inside the Nix sandbox,
|
# Uses fetchPnpmDeps + pnpmConfigHook to build entirely inside the Nix sandbox,
|
||||||
# eliminating the need for --impure or a local pnpm install.
|
# eliminating the need for --impure or a local pnpm install.
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "turbo dev",
|
"dev": "turbo dev",
|
||||||
"build": "turbo build",
|
"build": "turbo build",
|
||||||
"build:web": "turbo build:web",
|
|
||||||
"test": "turbo test",
|
"test": "turbo test",
|
||||||
"lint": "turbo lint",
|
"lint": "turbo lint",
|
||||||
"format": "prettier --write .",
|
"format": "prettier --write .",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@bitSpire/hal",
|
"name": "@bitSpire/hal",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"description": "Hardware Abstraction Layer for bitSpire ATM devices",
|
"description": "Hardware Abstraction Layer for Lamassu ATM devices",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
|
@ -44,7 +44,7 @@
|
||||||
"src"
|
"src"
|
||||||
],
|
],
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"bitspire",
|
"lamassu",
|
||||||
"atm",
|
"atm",
|
||||||
"hardware",
|
"hardware",
|
||||||
"bill-validator",
|
"bill-validator",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
/**
|
/**
|
||||||
* @bitSpire/hal - Hardware Abstraction Layer
|
* @bitSpire/hal - Hardware Abstraction Layer
|
||||||
*
|
*
|
||||||
* Provides drivers for bitSpire ATM hardware devices:
|
* Provides drivers for Lamassu ATM hardware devices:
|
||||||
* - Bill validators (JCM iVIZION via ID003 protocol)
|
* - Bill validators (JCM iVIZION via ID003 protocol)
|
||||||
* - Bill dispensers (Fujitsu F53/F56)
|
* - Bill dispensers (Fujitsu F53/F56)
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,6 @@ import type {
|
||||||
CreateInvoiceBody,
|
CreateInvoiceBody,
|
||||||
PayInvoiceBody,
|
PayInvoiceBody,
|
||||||
WalletInfo,
|
WalletInfo,
|
||||||
CreatedWallet,
|
|
||||||
MachineConfigResponse,
|
MachineConfigResponse,
|
||||||
SubscribePaymentsBody,
|
SubscribePaymentsBody,
|
||||||
SubscribeAck,
|
SubscribeAck,
|
||||||
|
|
@ -212,17 +211,6 @@ export class LnbitsClient {
|
||||||
return data ?? []
|
return data ?? []
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create an additional wallet on the calling account (`create_wallet`).
|
|
||||||
*
|
|
||||||
* Account-scoped (AUTH_ACCOUNT): the envelope carries no `wallet_id`, which
|
|
||||||
* is what makes the server resolve auth to the Account rather than a Wallet.
|
|
||||||
* NOT wrapped in `idempotent()` — a retry would mint a duplicate wallet.
|
|
||||||
*/
|
|
||||||
async createWallet(name: string): Promise<CreatedWallet> {
|
|
||||||
return this.sendRpc<CreatedWallet>('create_wallet', { body: { name } })
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Pull server-delivered machine config (operator pubkey + fee config) over
|
/** Pull server-delivered machine config (operator pubkey + fee config) over
|
||||||
* the authenticated transport — spirekeeper's `get_machine_config` RPC
|
* the authenticated transport — spirekeeper's `get_machine_config` RPC
|
||||||
* (bitspire#70 P1). Lets a seed-only ATM configure itself with no per-machine
|
* (bitspire#70 P1). Lets a seed-only ATM configure itself with no per-machine
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,6 @@ export type {
|
||||||
CreateInvoiceBody,
|
CreateInvoiceBody,
|
||||||
PayInvoiceBody,
|
PayInvoiceBody,
|
||||||
WalletInfo,
|
WalletInfo,
|
||||||
CreatedWallet,
|
|
||||||
SubscribePaymentsBody,
|
SubscribePaymentsBody,
|
||||||
SubscribeAck,
|
SubscribeAck,
|
||||||
SubscribePush,
|
SubscribePush,
|
||||||
|
|
|
||||||
|
|
@ -112,15 +112,6 @@ export interface WalletInfo {
|
||||||
balance: number
|
balance: number
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Reply shape of the `create_wallet` RPC — unlike WalletInfo it carries the
|
|
||||||
* fresh wallet's keys, so never log it verbatim. */
|
|
||||||
export interface CreatedWallet {
|
|
||||||
id: string
|
|
||||||
name: string
|
|
||||||
adminkey: string
|
|
||||||
inkey: string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Subscriptions
|
// Subscriptions
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@bitSpire/nostr-client",
|
"name": "@bitSpire/nostr-client",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"description": "Nostr client library for bitSpire ATM",
|
"description": "Nostr client library for Lamassu ATM",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/**
|
/**
|
||||||
* Nostr client for bitSpire ATM
|
* Nostr client for Lamassu ATM
|
||||||
*
|
*
|
||||||
* Manages connections to Nostr relays with support for:
|
* Manages connections to Nostr relays with support for:
|
||||||
* - NIP-42 authentication
|
* - NIP-42 authentication
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/**
|
/**
|
||||||
* Event creation utilities for bitSpire ATM
|
* Event creation utilities for Lamassu ATM
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { type Event, type EventTemplate, type VerifiedEvent, getEventHash } from 'nostr-tools'
|
import { type Event, type EventTemplate, type VerifiedEvent, getEventHash } from 'nostr-tools'
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
/**
|
/**
|
||||||
* @bitSpire/nostr-client
|
* @bitSpire/nostr-client
|
||||||
*
|
*
|
||||||
* Nostr client library for bitSpire ATM communication.
|
* Nostr client library for Lamassu ATM communication.
|
||||||
*
|
*
|
||||||
* Features:
|
* Features:
|
||||||
* - NIP-42 authentication for private relays
|
* - NIP-42 authentication for private relays
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/**
|
/**
|
||||||
* Nostr client type definitions for bitSpire ATM
|
* Nostr client type definitions for Lamassu ATM
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Event } from 'nostr-tools'
|
import type { Event } from 'nostr-tools'
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@bitSpire/ui-shared",
|
"name": "@bitSpire/ui-shared",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"description": "Shared Vue 3 components for bitSpire ATM and dashboard",
|
"description": "Shared Vue 3 components for Lamassu ATM and dashboard",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
/**
|
/**
|
||||||
* @bitSpire/ui-shared
|
* @bitSpire/ui-shared
|
||||||
*
|
*
|
||||||
* Shared Vue 3 components for bitSpire ATM and dashboard.
|
* Shared Vue 3 components for Lamassu ATM and dashboard.
|
||||||
* This package will contain common UI components like:
|
* This package will contain common UI components like:
|
||||||
* - QR code display
|
* - QR code display
|
||||||
* - Number pad
|
* - Number pad
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,6 @@
|
||||||
"dependsOn": ["^build"],
|
"dependsOn": ["^build"],
|
||||||
"outputs": ["dist/**", ".next/**", "!.next/cache/**"]
|
"outputs": ["dist/**", ".next/**", "!.next/cache/**"]
|
||||||
},
|
},
|
||||||
"build:web": {
|
|
||||||
"dependsOn": ["^build"],
|
|
||||||
"outputs": ["dist/**"],
|
|
||||||
"env": ["VITE_*"]
|
|
||||||
},
|
|
||||||
"dev": {
|
"dev": {
|
||||||
"cache": false,
|
"cache": false,
|
||||||
"persistent": true
|
"persistent": true
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue