bitspire/packages/nostr-client/dev/run-debit-agent.mjs
Patrick Mulligan 5448a620a9 chore(nostr-client): move dev scripts to dev/ folder
Move 9 development/testing .mjs scripts out of the package root into
dev/ to keep the published package clean. Update relative imports
and dev.sh reference.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 17:48:49 -05:00

221 lines
7.3 KiB
JavaScript

#!/usr/bin/env node
/**
* ATM Debit Approval Agent (TESTING ONLY)
*
* ⚠️ WARNING: This script auto-approves ALL debit requests without validation!
* ⚠️ DO NOT use in production - use the integrated debit approval service instead.
*
* Purpose:
* - Standalone debugging tool for testing the GetLiveDebitRequests subscription
* - Helps diagnose relay connectivity and message decryption issues
* - Useful when the integrated service isn't receiving events
*
* Usage:
* # Set environment variables (or create .env file in apps/machine/)
* export ATM_PRIVATE_KEY=<hex-private-key>
* export LIGHTNING_PUB_PUBKEY=<hex-pubkey>
* export RELAY_URL=ws://localhost:7777
*
* # Run the script
* node run-debit-agent.mjs
*
* Production alternative:
* The ATM app (apps/machine) includes an integrated debit approval service
* with session-based single-use protection. See:
* - apps/machine/src/services/lightning.ts (startDebitApprovalService)
* - docs/ndebit-cash-in-flow.md
*/
import { Relay } from 'nostr-tools/relay'
import { finalizeEvent, getPublicKey } from 'nostr-tools'
import * as nip44v1 from './nip44v1.mjs'
import fs from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
// Load .env file from apps/machine if it exists
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const envPath = path.join(__dirname, '../../apps/machine/.env')
if (fs.existsSync(envPath)) {
const envContent = fs.readFileSync(envPath, 'utf-8')
for (const line of envContent.split('\n')) {
const trimmed = line.trim()
if (trimmed && !trimmed.startsWith('#')) {
const [key, ...valueParts] = trimmed.split('=')
if (key && valueParts.length > 0) {
// Map VITE_ prefixed vars to non-prefixed
const envKey = key.replace(/^VITE_/, '')
process.env[envKey] = valueParts.join('=')
}
}
}
console.log('[Config] Loaded .env from:', envPath)
}
// Configuration from environment
const ATM_PRIVATE_KEY_HEX = process.env.ATM_PRIVATE_KEY
const LIGHTNING_PUB_PUBKEY = process.env.LIGHTNING_PUB_PUBKEY
const RELAY_URL = process.env.RELAY_URL || 'ws://localhost:7777'
// Validate required config
if (!ATM_PRIVATE_KEY_HEX) {
console.error('ERROR: ATM_PRIVATE_KEY environment variable is required')
console.error('Set it directly or create apps/machine/.env with VITE_ATM_PRIVATE_KEY')
process.exit(1)
}
if (!LIGHTNING_PUB_PUBKEY) {
console.error('ERROR: LIGHTNING_PUB_PUBKEY environment variable is required')
console.error('Set it directly or create apps/machine/.env with VITE_LIGHTNING_PUB_PUBKEY')
process.exit(1)
}
const ATM_PRIVATE_KEY = Uint8Array.from(Buffer.from(ATM_PRIVATE_KEY_HEX, 'hex'))
const ATM_PUBLIC_KEY = getPublicKey(ATM_PRIVATE_KEY)
console.log('')
console.log('='.repeat(60))
console.log(' ATM Debit Approval Agent (TESTING ONLY)')
console.log('='.repeat(60))
console.log('')
console.log('⚠️ WARNING: Auto-approves ALL requests without validation!')
console.log('⚠️ For production, use the integrated service in apps/machine')
console.log('')
console.log('ATM Pubkey:', ATM_PUBLIC_KEY)
console.log('Lightning.Pub Pubkey:', LIGHTNING_PUB_PUBKEY)
console.log('Relay URL:', RELAY_URL)
console.log('')
async function main() {
// Connect to relay
console.log('Connecting to relay...')
const relay = await Relay.connect(RELAY_URL)
console.log('Connected!')
console.log('')
// Create conversation key for NIP-44 v1 encryption
const conversationKey = nip44v1.getConversationKey(ATM_PRIVATE_KEY_HEX, LIGHTNING_PUB_PUBKEY)
// Subscribe to GetLiveDebitRequests
console.log('Subscribing to live debit requests...')
const subscribeRequest = {
rpcName: 'GetLiveDebitRequests',
authIdentifier: ATM_PUBLIC_KEY,
body: {},
}
const subEvent = finalizeEvent(
{
kind: 21000,
created_at: Math.floor(Date.now() / 1000),
tags: [['p', LIGHTNING_PUB_PUBKEY]],
content: nip44v1.encrypt(JSON.stringify(subscribeRequest), conversationKey),
},
ATM_PRIVATE_KEY
)
// Listen for debit requests
console.log('Listening for debit requests...')
console.log('(Test by scanning ndebit QR with ShockWallet)')
console.log('')
const debitSub = relay.subscribe(
[
{
kinds: [21000],
authors: [LIGHTNING_PUB_PUBKEY],
'#p': [ATM_PUBLIC_KEY],
since: Math.floor(Date.now() / 1000) - 5,
},
],
{
async onevent(evt) {
try {
const decrypted = nip44v1.decrypt(evt.content, conversationKey)
const message = JSON.parse(decrypted)
// Check if this is a live debit request
if (message.requestId === 'GetLiveDebitRequests' && message.debit) {
console.log('')
console.log('========================================')
console.log('DEBIT REQUEST RECEIVED!')
console.log(' Request ID:', message.request_id)
console.log(' From npub:', message.npub?.substring(0, 16) + '...')
console.log(' Debit type:', message.debit.type)
if (message.debit.invoice) {
console.log(' Invoice:', message.debit.invoice.substring(0, 50) + '...')
// Auto-approve by responding with INVOICE type
console.log('')
console.log('⚠️ AUTO-APPROVING debit request (NO VALIDATION)...')
const approveRequest = {
rpcName: 'RespondToDebit',
authIdentifier: ATM_PUBLIC_KEY,
body: {
npub: message.npub,
request_id: message.request_id,
response: {
type: 'invoice',
invoice: message.debit.invoice,
},
},
}
const approveEvent = finalizeEvent(
{
kind: 21000,
created_at: Math.floor(Date.now() / 1000),
tags: [['p', LIGHTNING_PUB_PUBKEY]],
content: nip44v1.encrypt(JSON.stringify(approveRequest), conversationKey),
},
ATM_PRIVATE_KEY
)
await relay.publish(approveEvent)
console.log('APPROVED! (event id:', approveEvent.id.substring(0, 16) + '...)')
}
console.log('========================================')
console.log('')
} else if (message.rpcName) {
console.log('RPC response:', message.rpcName, ':', message.status || 'received')
} else if (message.requestId) {
console.log('Live subscription active:', message.status)
}
} catch (err) {
// Ignore decryption failures for events not meant for us
if (!err.message?.includes('Unsupported')) {
// Uncomment for debugging:
// console.log('Decryption error:', err.message)
}
}
},
}
)
await relay.publish(subEvent)
console.log('Subscription sent, waiting for debit requests...')
console.log('')
// Keep running
process.on('SIGINT', () => {
console.log('\nShutting down...')
debitSub.close()
relay.close()
process.exit(0)
})
// Heartbeat
while (true) {
await new Promise((r) => setTimeout(r, 30000))
console.log('Still listening...')
}
}
main().catch((err) => {
console.error('Error:', err.message)
process.exit(1)
})