2020117-agent 0.3.4 → 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 +130 -43
- package/dist/session.d.ts +2 -2
- package/dist/session.js +34 -13
- package/dist/swarm.d.ts +1 -0
- package/package.json +2 -2
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 { generateInvoice } 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)
|
|
@@ -161,7 +161,12 @@ async function main() {
|
|
|
161
161
|
console.log(`[${label}] Lightning Address loaded from platform: ${LIGHTNING_ADDRESS}`);
|
|
162
162
|
}
|
|
163
163
|
}
|
|
164
|
-
// 3.
|
|
164
|
+
// 3. Initialize CLINK agent identity (for P2P session debit)
|
|
165
|
+
if (LIGHTNING_ADDRESS) {
|
|
166
|
+
const { pubkey } = initClinkAgent();
|
|
167
|
+
console.log(`[${label}] CLINK: ${LIGHTNING_ADDRESS} (agent pubkey: ${pubkey.slice(0, 16)}...)`);
|
|
168
|
+
}
|
|
169
|
+
// 4. Platform registration + heartbeat
|
|
165
170
|
await setupPlatform(label);
|
|
166
171
|
// 5. Async inbox poller
|
|
167
172
|
startInboxPoller(label);
|
|
@@ -336,6 +341,12 @@ async function startSwarmListener(label) {
|
|
|
336
341
|
node.send(socket, { type: 'error', id: msg.id, message: 'Provider has no Lightning Address configured' });
|
|
337
342
|
return;
|
|
338
343
|
}
|
|
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' });
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
339
350
|
const satsPerMinute = state.skill?.pricing?.sats_per_minute
|
|
340
351
|
|| Number(process.env.SATS_PER_MINUTE)
|
|
341
352
|
|| msg.sats_per_minute
|
|
@@ -343,12 +354,14 @@ async function startSwarmListener(label) {
|
|
|
343
354
|
const BILLING_INTERVAL_MIN = 10;
|
|
344
355
|
const debitAmount = satsPerMinute * BILLING_INTERVAL_MIN;
|
|
345
356
|
const sessionId = randomBytes(8).toString('hex');
|
|
346
|
-
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)`);
|
|
347
358
|
const session = {
|
|
348
359
|
socket,
|
|
349
360
|
peerId,
|
|
350
361
|
sessionId,
|
|
351
362
|
satsPerMinute,
|
|
363
|
+
paymentMethod,
|
|
364
|
+
ndebit: msg.ndebit || '',
|
|
352
365
|
totalEarned: 0,
|
|
353
366
|
startedAt: Date.now(),
|
|
354
367
|
lastDebitAt: Date.now(),
|
|
@@ -356,60 +369,134 @@ async function startSwarmListener(label) {
|
|
|
356
369
|
timeoutTimer: null,
|
|
357
370
|
};
|
|
358
371
|
activeSessions.set(sessionId, session);
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
372
|
+
if (paymentMethod === 'ndebit') {
|
|
373
|
+
// --- CLINK pull mode: provider debits customer immediately ---
|
|
374
|
+
let firstDebit;
|
|
375
|
+
try {
|
|
376
|
+
firstDebit = await collectPayment({
|
|
377
|
+
ndebit: session.ndebit,
|
|
378
|
+
lightningAddress: LIGHTNING_ADDRESS,
|
|
379
|
+
amountSats: debitAmount,
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
catch (e) {
|
|
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);
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
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);
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
session.totalEarned += debitAmount;
|
|
395
|
+
session.lastDebitAt = Date.now();
|
|
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
|
+
});
|
|
404
|
+
node.send(socket, {
|
|
405
|
+
type: 'session_tick_ack',
|
|
406
|
+
id: sessionId,
|
|
407
|
+
session_id: sessionId,
|
|
408
|
+
amount: debitAmount,
|
|
409
|
+
balance: msg.budget ? msg.budget - session.totalEarned : undefined,
|
|
410
|
+
});
|
|
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);
|
|
369
442
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
id: tickId,
|
|
382
|
-
session_id: sessionId,
|
|
383
|
-
bolt11: firstInvoice,
|
|
384
|
-
amount: debitAmount,
|
|
385
|
-
});
|
|
386
|
-
console.log(`[${label}] Session ${sessionId}: waiting for first payment (${debitAmount} sats)`);
|
|
387
|
-
// Billing every 10 minutes — send invoice, wait for customer to pay
|
|
388
|
-
session.debitTimer = setInterval(async () => {
|
|
389
|
-
let invoice;
|
|
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;
|
|
390
454
|
try {
|
|
391
|
-
|
|
455
|
+
firstInvoice = await generateInvoice(LIGHTNING_ADDRESS, debitAmount);
|
|
392
456
|
}
|
|
393
457
|
catch (e) {
|
|
394
|
-
console.
|
|
395
|
-
|
|
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);
|
|
396
461
|
return;
|
|
397
462
|
}
|
|
398
|
-
const
|
|
463
|
+
const tickId = randomBytes(4).toString('hex');
|
|
399
464
|
node.send(socket, {
|
|
400
465
|
type: 'session_tick',
|
|
401
|
-
id:
|
|
466
|
+
id: tickId,
|
|
402
467
|
session_id: sessionId,
|
|
403
|
-
bolt11:
|
|
468
|
+
bolt11: firstInvoice,
|
|
404
469
|
amount: debitAmount,
|
|
405
470
|
});
|
|
406
|
-
console.log(`[${label}] Session ${sessionId}: sent invoice
|
|
407
|
-
|
|
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
|
+
}
|
|
408
494
|
return;
|
|
409
495
|
}
|
|
410
|
-
|
|
496
|
+
// Handle invoice payment confirmation (invoice mode only)
|
|
497
|
+
if (msg.type === 'session_tick_ack' && msg.preimage) {
|
|
411
498
|
const session = activeSessions.get(msg.session_id || '');
|
|
412
|
-
if (!session)
|
|
499
|
+
if (!session || session.paymentMethod !== 'invoice')
|
|
413
500
|
return;
|
|
414
501
|
const amount = msg.amount || 0;
|
|
415
502
|
session.totalEarned += amount;
|
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
|
|
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
|
|
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
|
*
|
|
@@ -39,21 +39,25 @@ for (const arg of process.argv.slice(2)) {
|
|
|
39
39
|
break;
|
|
40
40
|
case '--ndebit':
|
|
41
41
|
process.env.CLINK_NDEBIT = val;
|
|
42
|
-
break;
|
|
42
|
+
break;
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
import { SwarmNode, topicFromKind } from './swarm.js';
|
|
46
46
|
import { queryProviderSkill } from './p2p-customer.js';
|
|
47
|
-
import { walletPayInvoice, walletGetBalance } 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';
|
|
51
51
|
import { mkdirSync, writeFileSync } from 'fs';
|
|
52
52
|
import { WebSocketServer, WebSocket as WsWebSocket } from 'ws';
|
|
53
|
+
// Polyfill global WebSocket for Node.js < 22 (needed by @shocknet/clink-sdk)
|
|
54
|
+
if (!globalThis.WebSocket)
|
|
55
|
+
globalThis.WebSocket = WsWebSocket;
|
|
53
56
|
// --- Config ---
|
|
54
57
|
const KIND = Number(process.env.DVM_KIND) || 5200;
|
|
55
58
|
const BUDGET = Number(process.env.BUDGET_SATS) || 500;
|
|
56
59
|
const PORT = Number(process.env.SESSION_PORT) || 8080;
|
|
60
|
+
const NDEBIT = process.env.CLINK_NDEBIT || loadNdebit() || '';
|
|
57
61
|
const TICK_INTERVAL_MS = 60_000;
|
|
58
62
|
const HTTP_TIMEOUT_MS = 60_000;
|
|
59
63
|
const state = {
|
|
@@ -159,7 +163,7 @@ function setupMessageHandler() {
|
|
|
159
163
|
break;
|
|
160
164
|
}
|
|
161
165
|
case 'session_tick': {
|
|
162
|
-
//
|
|
166
|
+
// Invoice mode: provider sent a bolt11 invoice — pay it
|
|
163
167
|
if (msg.bolt11 && msg.amount !== undefined) {
|
|
164
168
|
const amount = msg.amount;
|
|
165
169
|
if (state.totalSpent + amount > BUDGET) {
|
|
@@ -188,7 +192,7 @@ function setupMessageHandler() {
|
|
|
188
192
|
break;
|
|
189
193
|
}
|
|
190
194
|
case 'session_tick_ack': {
|
|
191
|
-
//
|
|
195
|
+
// Ndebit mode: provider debited our wallet and is reporting the result
|
|
192
196
|
if (msg.amount !== undefined) {
|
|
193
197
|
state.totalSpent += msg.amount;
|
|
194
198
|
}
|
|
@@ -663,13 +667,28 @@ async function main() {
|
|
|
663
667
|
state.satsPerMinute = satsPerMinute;
|
|
664
668
|
log(`Pricing: ${satsPerMinute} sats/min`);
|
|
665
669
|
log(`Budget: ${BUDGET} sats (~${Math.floor(BUDGET / satsPerMinute)} min)`);
|
|
666
|
-
// 4.
|
|
667
|
-
|
|
668
|
-
if (
|
|
669
|
-
|
|
670
|
-
|
|
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)');
|
|
689
|
+
await node.destroy();
|
|
690
|
+
process.exit(1);
|
|
671
691
|
}
|
|
672
|
-
log(`Wallet balance: ${balance} sats`);
|
|
673
692
|
// 5. Send session_start, wait for session_ack
|
|
674
693
|
const startId = randomBytes(4).toString('hex');
|
|
675
694
|
const ackResp = await sendAndWait({
|
|
@@ -677,6 +696,8 @@ async function main() {
|
|
|
677
696
|
id: startId,
|
|
678
697
|
budget: BUDGET,
|
|
679
698
|
sats_per_minute: satsPerMinute,
|
|
699
|
+
payment_method: paymentMethod,
|
|
700
|
+
...(paymentMethod === 'ndebit' ? { ndebit: NDEBIT } : {}),
|
|
680
701
|
}, 15_000);
|
|
681
702
|
if (ackResp.type !== 'session_ack' || !ackResp.session_id) {
|
|
682
703
|
warn(`Unexpected response: ${ackResp.type}`);
|
|
@@ -691,7 +712,7 @@ async function main() {
|
|
|
691
712
|
log(`Provider adjusted rate: ${ackResp.sats_per_minute} sats/min`);
|
|
692
713
|
}
|
|
693
714
|
log(`Session started: ${state.sessionId}`);
|
|
694
|
-
log(`Billing: ${state.satsPerMinute} sats/min
|
|
715
|
+
log(`Billing: ${state.satsPerMinute} sats/min via ${paymentMethod}`);
|
|
695
716
|
// 6. Start HTTP proxy
|
|
696
717
|
try {
|
|
697
718
|
await startHttpProxy();
|
package/dist/swarm.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "2020117-agent",
|
|
3
|
-
"version": "0.3.
|
|
4
|
-
"description": "2020117 agent runtime — API polling + Hyperswarm P2P +
|
|
3
|
+
"version": "0.3.6",
|
|
4
|
+
"description": "2020117 agent runtime — API polling + Hyperswarm P2P + CLINK Lightning payments",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"2020117-agent": "./dist/agent.js",
|