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>
This commit is contained in:
parent
01bac04cdf
commit
5448a620a9
10 changed files with 3 additions and 3 deletions
270
packages/nostr-client/dev/atm-debit-agent.mjs
Normal file
270
packages/nostr-client/dev/atm-debit-agent.mjs
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
/**
|
||||
* ATM Debit Authorization Agent
|
||||
*
|
||||
* This script demonstrates how an ATM can act as the authorization authority
|
||||
* for CLINK debit requests, similar to an "admin macaroon" for Lightning.
|
||||
*
|
||||
* Flow:
|
||||
* 1. Generate keypair for ATM (or load from secure storage)
|
||||
* 2. Link keypair to Lightning.Pub user account
|
||||
* 3. Subscribe to live debit requests
|
||||
* 4. Auto-approve requests (within configured limits)
|
||||
*
|
||||
* Prerequisites:
|
||||
* - Get a linking token from Lightning.Pub HTTP API
|
||||
* - Run: curl -X POST "http://localhost:1776/api/app/user/npub/token/reset" \
|
||||
* -H "Authorization: Bearer $APP_TOKEN" \
|
||||
* -H "Content-Type: application/json" \
|
||||
* -d '{"user_identifier": "YOUR_USER_IDENTIFIER"}'
|
||||
*/
|
||||
|
||||
import { Relay } from 'nostr-tools/relay'
|
||||
import { finalizeEvent, getPublicKey, generateSecretKey } from 'nostr-tools'
|
||||
import * as nip44v1 from './nip44v1.mjs'
|
||||
|
||||
// Configuration
|
||||
const LINKING_TOKEN = process.argv[2]
|
||||
const LIGHTNING_PUB_PUBKEY = '6c59284e3da31b776cb1c06324c25f4a0b0308177af9f8aec5ebef07b44c3fdf'
|
||||
const RELAY_URL = process.env.RELAY_URL || 'ws://localhost:7777'
|
||||
|
||||
// Generate ATM keypair (in production, this would be stored securely)
|
||||
const ATM_PRIVATE_KEY = generateSecretKey()
|
||||
const ATM_PUBLIC_KEY = getPublicKey(ATM_PRIVATE_KEY)
|
||||
const ATM_PRIVATE_KEY_HEX = Buffer.from(ATM_PRIVATE_KEY).toString('hex')
|
||||
|
||||
if (!LINKING_TOKEN) {
|
||||
console.log('ATM Debit Authorization Agent')
|
||||
console.log('==============================')
|
||||
console.log('')
|
||||
console.log('Usage: node atm-debit-agent.mjs <linking-token>')
|
||||
console.log('')
|
||||
console.log('Get a linking token:')
|
||||
console.log(' curl -X POST "http://localhost:1776/api/app/user/npub/token/reset" \\')
|
||||
console.log(' -H "Authorization: Bearer $APP_TOKEN" \\')
|
||||
console.log(' -H "Content-Type: application/json" \\')
|
||||
console.log(' -d \'{"user_identifier": "YOUR_USER_IDENTIFIER"}\'')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
console.log('=== ATM Debit Authorization Agent ===')
|
||||
console.log('')
|
||||
console.log('ATM Pubkey:', ATM_PUBLIC_KEY)
|
||||
console.log('Lightning.Pub Pubkey:', LIGHTNING_PUB_PUBKEY)
|
||||
console.log('Linking Token:', LINKING_TOKEN.substring(0, 16) + '...')
|
||||
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 (used by Kind 21000 RPC)
|
||||
const conversationKey = nip44v1.getConversationKey(ATM_PRIVATE_KEY_HEX, LIGHTNING_PUB_PUBKEY)
|
||||
|
||||
// Step 1: Link NPub through token
|
||||
console.log('Step 1: Linking ATM keypair to user account...')
|
||||
|
||||
const linkRequest = {
|
||||
rpcName: 'LinkNPubThroughToken',
|
||||
authIdentifier: ATM_PUBLIC_KEY,
|
||||
body: {
|
||||
token: LINKING_TOKEN,
|
||||
},
|
||||
}
|
||||
|
||||
const linkEvent = finalizeEvent(
|
||||
{
|
||||
kind: 21000,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
tags: [['p', LIGHTNING_PUB_PUBKEY]],
|
||||
content: nip44v1.encrypt(JSON.stringify(linkRequest), conversationKey),
|
||||
},
|
||||
ATM_PRIVATE_KEY
|
||||
)
|
||||
|
||||
// Subscribe for response
|
||||
let linkingComplete = false
|
||||
const linkSub = relay.subscribe(
|
||||
[
|
||||
{
|
||||
kinds: [21000],
|
||||
authors: [LIGHTNING_PUB_PUBKEY],
|
||||
'#p': [ATM_PUBLIC_KEY],
|
||||
since: Math.floor(Date.now() / 1000) - 5,
|
||||
},
|
||||
],
|
||||
{
|
||||
onevent(evt) {
|
||||
try {
|
||||
const decrypted = nip44v1.decrypt(evt.content, conversationKey)
|
||||
const response = JSON.parse(decrypted)
|
||||
console.log('Link response:', JSON.stringify(response))
|
||||
if (response.status === 'OK') {
|
||||
linkingComplete = true
|
||||
console.log('Keypair linked successfully!')
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('Failed to decrypt link response:', err.message)
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
await relay.publish(linkEvent)
|
||||
console.log(
|
||||
'Link request sent (event id:',
|
||||
linkEvent.id.substring(0, 16) + '...), waiting for confirmation...'
|
||||
)
|
||||
|
||||
// Wait for linking to complete
|
||||
for (let i = 0; i < 10 && !linkingComplete; i++) {
|
||||
await new Promise((r) => setTimeout(r, 1000))
|
||||
if (i % 3 === 2) console.log('Still waiting for link confirmation...')
|
||||
}
|
||||
linkSub.close()
|
||||
|
||||
if (!linkingComplete) {
|
||||
console.log('Warning: Did not receive linking confirmation, continuing anyway...')
|
||||
}
|
||||
console.log('')
|
||||
|
||||
// Step 2: Subscribe to live debit requests
|
||||
console.log('Step 2: 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
|
||||
)
|
||||
|
||||
// Subscribe for debit requests and responses
|
||||
console.log('Listening for debit requests...')
|
||||
console.log('(Scan the ndebit QR code with ShockWallet to test)')
|
||||
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 debit request (has request_id and debit fields)
|
||||
if (message.requestId === 'GetLiveDebitRequests' && message.debit) {
|
||||
console.log('')
|
||||
console.log('========================================')
|
||||
console.log('Received debit request!')
|
||||
console.log(' Request ID:', message.request_id)
|
||||
console.log(' From npub:', message.npub)
|
||||
console.log(' Debit type:', message.debit.type)
|
||||
|
||||
if (message.debit.type === 'invoice' && 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...')
|
||||
|
||||
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('Approval sent! (event id:', approveEvent.id.substring(0, 16) + '...)')
|
||||
} else if (message.debit.type === 'budget') {
|
||||
console.log(' Budget request - ignoring for now')
|
||||
} else if (message.debit.type === 'fullAccess') {
|
||||
console.log(' Full access request - ignoring for now')
|
||||
}
|
||||
|
||||
console.log('========================================')
|
||||
console.log('')
|
||||
} else if (message.rpcName) {
|
||||
// This is a response to our RPC request
|
||||
console.log('RPC response:', message.rpcName, ':', message.status || 'received')
|
||||
} else {
|
||||
// Log other messages for debugging
|
||||
console.log('Message received:', JSON.stringify(message).substring(0, 100))
|
||||
}
|
||||
} catch (err) {
|
||||
// Ignore decryption failures (may be messages for other clients)
|
||||
if (!err.message.includes('Unsupported')) {
|
||||
console.log('Error processing message:', err.message)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
await relay.publish(subEvent)
|
||||
console.log('Subscription request sent (event id:', subEvent.id.substring(0, 16) + '...)')
|
||||
console.log('')
|
||||
|
||||
// Keep running
|
||||
console.log('ATM Debit Agent running. Press Ctrl+C to exit.')
|
||||
console.log('')
|
||||
|
||||
// Handle graceful shutdown
|
||||
process.on('SIGINT', () => {
|
||||
console.log('\nShutting down...')
|
||||
debitSub.close()
|
||||
relay.close()
|
||||
process.exit(0)
|
||||
})
|
||||
|
||||
// Keep alive with heartbeat
|
||||
let heartbeatCount = 0
|
||||
while (true) {
|
||||
await new Promise((r) => setTimeout(r, 10000))
|
||||
heartbeatCount++
|
||||
if (heartbeatCount % 6 === 0) {
|
||||
// Every minute
|
||||
console.log('Still listening... (' + heartbeatCount * 10 + 's)')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error('Error:', err.message)
|
||||
console.error(err.stack)
|
||||
process.exit(1)
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue