2020117-agent 0.3.5 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent.js +121 -55
- package/dist/session.d.ts +2 -2
- package/dist/session.js +57 -13
- package/dist/swarm.d.ts +3 -0
- package/package.json +1 -1
package/dist/agent.js
CHANGED
|
@@ -69,7 +69,7 @@ import { randomBytes } from 'crypto';
|
|
|
69
69
|
import { SwarmNode, topicFromKind } from './swarm.js';
|
|
70
70
|
import { createProcessor } from './processor.js';
|
|
71
71
|
import { hasApiKey, loadAgentName, registerService, startHeartbeatLoop, getInbox, acceptJob, sendFeedback, submitResult, createJob, getJob, getProfile, reportSession, } from './api.js';
|
|
72
|
-
import { initClinkAgent, collectPayment } from './clink.js';
|
|
72
|
+
import { initClinkAgent, collectPayment, generateInvoice } from './clink.js';
|
|
73
73
|
import { readFileSync } from 'fs';
|
|
74
74
|
import WebSocket from 'ws';
|
|
75
75
|
// Polyfill global WebSocket for Node.js < 22 (needed by @shocknet/clink-sdk)
|
|
@@ -341,8 +341,10 @@ async function startSwarmListener(label) {
|
|
|
341
341
|
node.send(socket, { type: 'error', id: msg.id, message: 'Provider has no Lightning Address configured' });
|
|
342
342
|
return;
|
|
343
343
|
}
|
|
344
|
-
|
|
345
|
-
|
|
344
|
+
// Negotiate payment method: customer requests "invoice" or "ndebit" (default)
|
|
345
|
+
const paymentMethod = msg.payment_method || (msg.ndebit ? 'ndebit' : 'invoice');
|
|
346
|
+
if (paymentMethod === 'ndebit' && !msg.ndebit) {
|
|
347
|
+
node.send(socket, { type: 'error', id: msg.id, message: 'ndebit payment requires ndebit authorization' });
|
|
346
348
|
return;
|
|
347
349
|
}
|
|
348
350
|
const satsPerMinute = state.skill?.pricing?.sats_per_minute
|
|
@@ -352,13 +354,14 @@ async function startSwarmListener(label) {
|
|
|
352
354
|
const BILLING_INTERVAL_MIN = 10;
|
|
353
355
|
const debitAmount = satsPerMinute * BILLING_INTERVAL_MIN;
|
|
354
356
|
const sessionId = randomBytes(8).toString('hex');
|
|
355
|
-
console.log(`[${label}] Session ${sessionId} from ${tag}: ${satsPerMinute} sats/min, billing every ${BILLING_INTERVAL_MIN}min (${debitAmount} sats)`);
|
|
357
|
+
console.log(`[${label}] Session ${sessionId} from ${tag}: ${satsPerMinute} sats/min, payment=${paymentMethod}, billing every ${BILLING_INTERVAL_MIN}min (${debitAmount} sats)`);
|
|
356
358
|
const session = {
|
|
357
359
|
socket,
|
|
358
360
|
peerId,
|
|
359
361
|
sessionId,
|
|
360
362
|
satsPerMinute,
|
|
361
|
-
|
|
363
|
+
paymentMethod,
|
|
364
|
+
ndebit: msg.ndebit || '',
|
|
362
365
|
totalEarned: 0,
|
|
363
366
|
startedAt: Date.now(),
|
|
364
367
|
lastDebitAt: Date.now(),
|
|
@@ -366,68 +369,38 @@ async function startSwarmListener(label) {
|
|
|
366
369
|
timeoutTimer: null,
|
|
367
370
|
};
|
|
368
371
|
activeSessions.set(sessionId, session);
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
firstDebit = await collectPayment({
|
|
373
|
-
ndebit: session.ndebit,
|
|
374
|
-
lightningAddress: LIGHTNING_ADDRESS,
|
|
375
|
-
amountSats: debitAmount,
|
|
376
|
-
});
|
|
377
|
-
}
|
|
378
|
-
catch (e) {
|
|
379
|
-
console.warn(`[${label}] Session ${sessionId}: first debit error: ${e.message}`);
|
|
380
|
-
node.send(socket, { type: 'error', id: msg.id, message: `Payment error: ${e.message}` });
|
|
381
|
-
activeSessions.delete(sessionId);
|
|
382
|
-
return;
|
|
383
|
-
}
|
|
384
|
-
if (!firstDebit.ok) {
|
|
385
|
-
console.warn(`[${label}] Session ${sessionId}: first debit failed: ${firstDebit.error}`);
|
|
386
|
-
node.send(socket, { type: 'error', id: msg.id, message: `Payment failed: ${firstDebit.error}` });
|
|
387
|
-
activeSessions.delete(sessionId);
|
|
388
|
-
return;
|
|
389
|
-
}
|
|
390
|
-
session.totalEarned += debitAmount;
|
|
391
|
-
session.lastDebitAt = Date.now();
|
|
392
|
-
console.log(`[${label}] Session ${sessionId}: first ${BILLING_INTERVAL_MIN}min paid (${debitAmount} sats)`);
|
|
393
|
-
node.send(socket, {
|
|
394
|
-
type: 'session_ack',
|
|
395
|
-
id: msg.id,
|
|
396
|
-
session_id: sessionId,
|
|
397
|
-
sats_per_minute: satsPerMinute,
|
|
398
|
-
});
|
|
399
|
-
// Notify customer of the debit
|
|
400
|
-
node.send(socket, {
|
|
401
|
-
type: 'session_tick_ack',
|
|
402
|
-
id: sessionId,
|
|
403
|
-
session_id: sessionId,
|
|
404
|
-
amount: debitAmount,
|
|
405
|
-
balance: msg.budget ? msg.budget - session.totalEarned : undefined,
|
|
406
|
-
});
|
|
407
|
-
// Debit every 10 minutes
|
|
408
|
-
session.debitTimer = setInterval(async () => {
|
|
409
|
-
let debit;
|
|
372
|
+
if (paymentMethod === 'ndebit') {
|
|
373
|
+
// --- CLINK pull mode: provider debits customer immediately ---
|
|
374
|
+
let firstDebit;
|
|
410
375
|
try {
|
|
411
|
-
|
|
376
|
+
firstDebit = await collectPayment({
|
|
412
377
|
ndebit: session.ndebit,
|
|
413
378
|
lightningAddress: LIGHTNING_ADDRESS,
|
|
414
379
|
amountSats: debitAmount,
|
|
415
380
|
});
|
|
416
381
|
}
|
|
417
382
|
catch (e) {
|
|
418
|
-
console.
|
|
419
|
-
|
|
383
|
+
console.warn(`[${label}] Session ${sessionId}: first debit error: ${e.message}`);
|
|
384
|
+
node.send(socket, { type: 'error', id: msg.id, message: `Payment error: ${e.message}` });
|
|
385
|
+
activeSessions.delete(sessionId);
|
|
420
386
|
return;
|
|
421
387
|
}
|
|
422
|
-
if (!
|
|
423
|
-
console.
|
|
424
|
-
|
|
388
|
+
if (!firstDebit.ok) {
|
|
389
|
+
console.warn(`[${label}] Session ${sessionId}: first debit failed: ${firstDebit.error}`);
|
|
390
|
+
node.send(socket, { type: 'error', id: msg.id, message: `Payment failed: ${firstDebit.error}` });
|
|
391
|
+
activeSessions.delete(sessionId);
|
|
425
392
|
return;
|
|
426
393
|
}
|
|
427
394
|
session.totalEarned += debitAmount;
|
|
428
395
|
session.lastDebitAt = Date.now();
|
|
429
|
-
console.log(`[${label}] Session ${sessionId}:
|
|
430
|
-
|
|
396
|
+
console.log(`[${label}] Session ${sessionId}: first ${BILLING_INTERVAL_MIN}min paid (${debitAmount} sats)`);
|
|
397
|
+
node.send(socket, {
|
|
398
|
+
type: 'session_ack',
|
|
399
|
+
id: msg.id,
|
|
400
|
+
session_id: sessionId,
|
|
401
|
+
sats_per_minute: satsPerMinute,
|
|
402
|
+
payment_method: 'ndebit',
|
|
403
|
+
});
|
|
431
404
|
node.send(socket, {
|
|
432
405
|
type: 'session_tick_ack',
|
|
433
406
|
id: sessionId,
|
|
@@ -435,7 +408,100 @@ async function startSwarmListener(label) {
|
|
|
435
408
|
amount: debitAmount,
|
|
436
409
|
balance: msg.budget ? msg.budget - session.totalEarned : undefined,
|
|
437
410
|
});
|
|
438
|
-
|
|
411
|
+
// Recurring debit every 10 minutes
|
|
412
|
+
session.debitTimer = setInterval(async () => {
|
|
413
|
+
let debit;
|
|
414
|
+
try {
|
|
415
|
+
debit = await collectPayment({
|
|
416
|
+
ndebit: session.ndebit,
|
|
417
|
+
lightningAddress: LIGHTNING_ADDRESS,
|
|
418
|
+
amountSats: debitAmount,
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
catch (e) {
|
|
422
|
+
console.log(`[${label}] Session ${sessionId}: debit error (${e.message}) — ending session`);
|
|
423
|
+
endSession(node, session, label);
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
if (!debit.ok) {
|
|
427
|
+
console.log(`[${label}] Session ${sessionId}: debit failed (${debit.error}) — ending session`);
|
|
428
|
+
endSession(node, session, label);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
session.totalEarned += debitAmount;
|
|
432
|
+
session.lastDebitAt = Date.now();
|
|
433
|
+
console.log(`[${label}] Session ${sessionId}: debit OK (+${debitAmount}, total: ${session.totalEarned} sats)`);
|
|
434
|
+
node.send(socket, {
|
|
435
|
+
type: 'session_tick_ack',
|
|
436
|
+
id: sessionId,
|
|
437
|
+
session_id: sessionId,
|
|
438
|
+
amount: debitAmount,
|
|
439
|
+
balance: msg.budget ? msg.budget - session.totalEarned : undefined,
|
|
440
|
+
});
|
|
441
|
+
}, BILLING_INTERVAL_MIN * 60_000);
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
// --- Invoice push mode: provider sends bolt11, customer pays ---
|
|
445
|
+
node.send(socket, {
|
|
446
|
+
type: 'session_ack',
|
|
447
|
+
id: msg.id,
|
|
448
|
+
session_id: sessionId,
|
|
449
|
+
sats_per_minute: satsPerMinute,
|
|
450
|
+
payment_method: 'invoice',
|
|
451
|
+
});
|
|
452
|
+
// Generate and send first invoice
|
|
453
|
+
let firstInvoice;
|
|
454
|
+
try {
|
|
455
|
+
firstInvoice = await generateInvoice(LIGHTNING_ADDRESS, debitAmount);
|
|
456
|
+
}
|
|
457
|
+
catch (e) {
|
|
458
|
+
console.warn(`[${label}] Session ${sessionId}: invoice error: ${e.message}`);
|
|
459
|
+
node.send(socket, { type: 'error', id: msg.id, message: `Invoice error: ${e.message}` });
|
|
460
|
+
activeSessions.delete(sessionId);
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
const tickId = randomBytes(4).toString('hex');
|
|
464
|
+
node.send(socket, {
|
|
465
|
+
type: 'session_tick',
|
|
466
|
+
id: tickId,
|
|
467
|
+
session_id: sessionId,
|
|
468
|
+
bolt11: firstInvoice,
|
|
469
|
+
amount: debitAmount,
|
|
470
|
+
});
|
|
471
|
+
console.log(`[${label}] Session ${sessionId}: sent first invoice (${debitAmount} sats)`);
|
|
472
|
+
// Recurring invoices every 10 minutes
|
|
473
|
+
session.debitTimer = setInterval(async () => {
|
|
474
|
+
let invoice;
|
|
475
|
+
try {
|
|
476
|
+
invoice = await generateInvoice(LIGHTNING_ADDRESS, debitAmount);
|
|
477
|
+
}
|
|
478
|
+
catch (e) {
|
|
479
|
+
console.log(`[${label}] Session ${sessionId}: invoice error (${e.message}) — ending session`);
|
|
480
|
+
endSession(node, session, label);
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
const tid = randomBytes(4).toString('hex');
|
|
484
|
+
node.send(socket, {
|
|
485
|
+
type: 'session_tick',
|
|
486
|
+
id: tid,
|
|
487
|
+
session_id: sessionId,
|
|
488
|
+
bolt11: invoice,
|
|
489
|
+
amount: debitAmount,
|
|
490
|
+
});
|
|
491
|
+
console.log(`[${label}] Session ${sessionId}: sent invoice for ${debitAmount} sats`);
|
|
492
|
+
}, BILLING_INTERVAL_MIN * 60_000);
|
|
493
|
+
}
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
// Handle invoice payment confirmation (invoice mode only)
|
|
497
|
+
if (msg.type === 'session_tick_ack' && msg.preimage) {
|
|
498
|
+
const session = activeSessions.get(msg.session_id || '');
|
|
499
|
+
if (!session || session.paymentMethod !== 'invoice')
|
|
500
|
+
return;
|
|
501
|
+
const amount = msg.amount || 0;
|
|
502
|
+
session.totalEarned += amount;
|
|
503
|
+
session.lastDebitAt = Date.now();
|
|
504
|
+
console.log(`[${label}] Session ${session.sessionId}: payment received (+${amount}, total: ${session.totalEarned} sats)`);
|
|
439
505
|
return;
|
|
440
506
|
}
|
|
441
507
|
if (msg.type === 'session_end') {
|
package/dist/session.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* P2P Session Client — "rent" a provider's service over Hyperswarm with
|
|
4
|
-
* per-minute CLINK
|
|
4
|
+
* per-minute payments (CLINK ndebit or Lightning invoice).
|
|
5
5
|
*
|
|
6
6
|
* Features:
|
|
7
|
-
* -
|
|
7
|
+
* - Payment negotiation: ndebit (provider pulls) or invoice (customer pushes)
|
|
8
8
|
* - HTTP proxy server for browser-based access to provider APIs
|
|
9
9
|
* - Interactive CLI REPL (generate, status, skill, help, quit)
|
|
10
10
|
*
|
package/dist/session.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* P2P Session Client — "rent" a provider's service over Hyperswarm with
|
|
4
|
-
* per-minute CLINK
|
|
4
|
+
* per-minute payments (CLINK ndebit or Lightning invoice).
|
|
5
5
|
*
|
|
6
6
|
* Features:
|
|
7
|
-
* -
|
|
7
|
+
* - Payment negotiation: ndebit (provider pulls) or invoice (customer pushes)
|
|
8
8
|
* - HTTP proxy server for browser-based access to provider APIs
|
|
9
9
|
* - Interactive CLI REPL (generate, status, skill, help, quit)
|
|
10
10
|
*
|
|
@@ -44,7 +44,7 @@ for (const arg of process.argv.slice(2)) {
|
|
|
44
44
|
}
|
|
45
45
|
import { SwarmNode, topicFromKind } from './swarm.js';
|
|
46
46
|
import { queryProviderSkill } from './p2p-customer.js';
|
|
47
|
-
import { loadNdebit } from './api.js';
|
|
47
|
+
import { loadNdebit, walletPayInvoice, walletGetBalance, hasApiKey } from './api.js';
|
|
48
48
|
import { randomBytes } from 'crypto';
|
|
49
49
|
import { createServer } from 'http';
|
|
50
50
|
import { createInterface } from 'readline';
|
|
@@ -112,7 +112,7 @@ function sendAndWait(msg, timeoutMs) {
|
|
|
112
112
|
}
|
|
113
113
|
// --- 6. Message handler ---
|
|
114
114
|
function setupMessageHandler() {
|
|
115
|
-
state.node.on('message', (msg) => {
|
|
115
|
+
state.node.on('message', async (msg) => {
|
|
116
116
|
// Handle chunked HTTP responses — reassemble before resolving
|
|
117
117
|
if (msg.type === 'http_response' && msg.chunk_total && msg.chunk_total > 1) {
|
|
118
118
|
const id = msg.id;
|
|
@@ -162,8 +162,37 @@ function setupMessageHandler() {
|
|
|
162
162
|
cleanup();
|
|
163
163
|
break;
|
|
164
164
|
}
|
|
165
|
+
case 'session_tick': {
|
|
166
|
+
// Invoice mode: provider sent a bolt11 invoice — pay it
|
|
167
|
+
if (msg.bolt11 && msg.amount !== undefined) {
|
|
168
|
+
const amount = msg.amount;
|
|
169
|
+
if (state.totalSpent + amount > BUDGET) {
|
|
170
|
+
log(`Budget exhausted (need ${amount}, remaining ${remainingSats()}) — ending session`);
|
|
171
|
+
endSession();
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
log(`Paying invoice: ${amount} sats...`);
|
|
175
|
+
const payResult = await walletPayInvoice(msg.bolt11);
|
|
176
|
+
if (payResult.ok) {
|
|
177
|
+
state.totalSpent += amount;
|
|
178
|
+
log(`Paid ${amount} sats (total: ${state.totalSpent}, ~${estimatedMinutesLeft()} min left)`);
|
|
179
|
+
state.node.send(state.socket, {
|
|
180
|
+
type: 'session_tick_ack',
|
|
181
|
+
id: msg.id,
|
|
182
|
+
session_id: state.sessionId,
|
|
183
|
+
preimage: payResult.preimage,
|
|
184
|
+
amount,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
warn(`Payment failed: ${payResult.error} — ending session`);
|
|
189
|
+
endSession();
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
165
194
|
case 'session_tick_ack': {
|
|
166
|
-
//
|
|
195
|
+
// Ndebit mode: provider debited our wallet and is reporting the result
|
|
167
196
|
if (msg.amount !== undefined) {
|
|
168
197
|
state.totalSpent += msg.amount;
|
|
169
198
|
}
|
|
@@ -638,22 +667,37 @@ async function main() {
|
|
|
638
667
|
state.satsPerMinute = satsPerMinute;
|
|
639
668
|
log(`Pricing: ${satsPerMinute} sats/min`);
|
|
640
669
|
log(`Budget: ${BUDGET} sats (~${Math.floor(BUDGET / satsPerMinute)} min)`);
|
|
641
|
-
// 4.
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
670
|
+
// 4. Negotiate payment method: ndebit (CLINK pull) or invoice (wallet push)
|
|
671
|
+
let paymentMethod;
|
|
672
|
+
if (NDEBIT) {
|
|
673
|
+
paymentMethod = 'ndebit';
|
|
674
|
+
log(`Payment: CLINK ndebit (provider pulls per-minute)`);
|
|
675
|
+
}
|
|
676
|
+
else if (hasApiKey()) {
|
|
677
|
+
paymentMethod = 'invoice';
|
|
678
|
+
const balance = await walletGetBalance();
|
|
679
|
+
if (balance < BUDGET) {
|
|
680
|
+
warn(`Wallet balance (${balance} sats) is less than budget (${BUDGET} sats).`);
|
|
681
|
+
warn(`Fund your wallet: POST ${process.env.API_2020117_URL || 'https://2020117.xyz'}/api/wallet/invoice`);
|
|
682
|
+
}
|
|
683
|
+
log(`Payment: invoice (wallet pays provider, balance: ${balance} sats)`);
|
|
684
|
+
}
|
|
685
|
+
else {
|
|
686
|
+
warn('No payment method available.');
|
|
687
|
+
warn(' Option 1: --ndebit=ndebit1... (CLINK wallet authorization)');
|
|
688
|
+
warn(' Option 2: --agent=NAME (built-in wallet via API key)');
|
|
645
689
|
await node.destroy();
|
|
646
690
|
process.exit(1);
|
|
647
691
|
}
|
|
648
|
-
|
|
649
|
-
// 5. Send session_start with ndebit, wait for session_ack
|
|
692
|
+
// 5. Send session_start, wait for session_ack
|
|
650
693
|
const startId = randomBytes(4).toString('hex');
|
|
651
694
|
const ackResp = await sendAndWait({
|
|
652
695
|
type: 'session_start',
|
|
653
696
|
id: startId,
|
|
654
697
|
budget: BUDGET,
|
|
655
698
|
sats_per_minute: satsPerMinute,
|
|
656
|
-
|
|
699
|
+
payment_method: paymentMethod,
|
|
700
|
+
...(paymentMethod === 'ndebit' ? { ndebit: NDEBIT } : {}),
|
|
657
701
|
}, 15_000);
|
|
658
702
|
if (ackResp.type !== 'session_ack' || !ackResp.session_id) {
|
|
659
703
|
warn(`Unexpected response: ${ackResp.type}`);
|
|
@@ -668,7 +712,7 @@ async function main() {
|
|
|
668
712
|
log(`Provider adjusted rate: ${ackResp.sats_per_minute} sats/min`);
|
|
669
713
|
}
|
|
670
714
|
log(`Session started: ${state.sessionId}`);
|
|
671
|
-
log(`
|
|
715
|
+
log(`Billing: ${state.satsPerMinute} sats/min via ${paymentMethod}`);
|
|
672
716
|
// 6. Start HTTP proxy
|
|
673
717
|
try {
|
|
674
718
|
await startHttpProxy();
|
package/dist/swarm.d.ts
CHANGED