2020117-agent 0.3.4 → 0.3.5
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 +54 -33
- package/dist/session.d.ts +2 -2
- package/dist/session.js +20 -43
- package/dist/swarm.d.ts +0 -2
- 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 {
|
|
72
|
+
import { initClinkAgent, collectPayment } 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,10 @@ 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
|
+
if (!msg.ndebit) {
|
|
345
|
+
node.send(socket, { type: 'error', id: msg.id, message: 'session_start requires ndebit authorization' });
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
339
348
|
const satsPerMinute = state.skill?.pricing?.sats_per_minute
|
|
340
349
|
|| Number(process.env.SATS_PER_MINUTE)
|
|
341
350
|
|| msg.sats_per_minute
|
|
@@ -349,6 +358,7 @@ async function startSwarmListener(label) {
|
|
|
349
358
|
peerId,
|
|
350
359
|
sessionId,
|
|
351
360
|
satsPerMinute,
|
|
361
|
+
ndebit: msg.ndebit,
|
|
352
362
|
totalEarned: 0,
|
|
353
363
|
startedAt: Date.now(),
|
|
354
364
|
lastDebitAt: Date.now(),
|
|
@@ -356,67 +366,78 @@ async function startSwarmListener(label) {
|
|
|
356
366
|
timeoutTimer: null,
|
|
357
367
|
};
|
|
358
368
|
activeSessions.set(sessionId, session);
|
|
359
|
-
//
|
|
360
|
-
let
|
|
369
|
+
// Debit first 10 minutes immediately (prepaid model)
|
|
370
|
+
let firstDebit;
|
|
361
371
|
try {
|
|
362
|
-
|
|
372
|
+
firstDebit = await collectPayment({
|
|
373
|
+
ndebit: session.ndebit,
|
|
374
|
+
lightningAddress: LIGHTNING_ADDRESS,
|
|
375
|
+
amountSats: debitAmount,
|
|
376
|
+
});
|
|
363
377
|
}
|
|
364
378
|
catch (e) {
|
|
365
|
-
console.warn(`[${label}] Session ${sessionId}:
|
|
366
|
-
node.send(socket, { type: 'error', id: msg.id, message: `
|
|
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}` });
|
|
367
381
|
activeSessions.delete(sessionId);
|
|
368
382
|
return;
|
|
369
383
|
}
|
|
370
|
-
|
|
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)`);
|
|
371
393
|
node.send(socket, {
|
|
372
394
|
type: 'session_ack',
|
|
373
395
|
id: msg.id,
|
|
374
396
|
session_id: sessionId,
|
|
375
397
|
sats_per_minute: satsPerMinute,
|
|
376
398
|
});
|
|
377
|
-
//
|
|
378
|
-
const tickId = randomBytes(4).toString('hex');
|
|
399
|
+
// Notify customer of the debit
|
|
379
400
|
node.send(socket, {
|
|
380
|
-
type: '
|
|
381
|
-
id:
|
|
401
|
+
type: 'session_tick_ack',
|
|
402
|
+
id: sessionId,
|
|
382
403
|
session_id: sessionId,
|
|
383
|
-
bolt11: firstInvoice,
|
|
384
404
|
amount: debitAmount,
|
|
405
|
+
balance: msg.budget ? msg.budget - session.totalEarned : undefined,
|
|
385
406
|
});
|
|
386
|
-
|
|
387
|
-
// Billing every 10 minutes — send invoice, wait for customer to pay
|
|
407
|
+
// Debit every 10 minutes
|
|
388
408
|
session.debitTimer = setInterval(async () => {
|
|
389
|
-
let
|
|
409
|
+
let debit;
|
|
390
410
|
try {
|
|
391
|
-
|
|
411
|
+
debit = await collectPayment({
|
|
412
|
+
ndebit: session.ndebit,
|
|
413
|
+
lightningAddress: LIGHTNING_ADDRESS,
|
|
414
|
+
amountSats: debitAmount,
|
|
415
|
+
});
|
|
392
416
|
}
|
|
393
417
|
catch (e) {
|
|
394
|
-
console.log(`[${label}] Session ${sessionId}:
|
|
418
|
+
console.log(`[${label}] Session ${sessionId}: debit error (${e.message}) — ending session`);
|
|
395
419
|
endSession(node, session, label);
|
|
396
420
|
return;
|
|
397
421
|
}
|
|
398
|
-
|
|
422
|
+
if (!debit.ok) {
|
|
423
|
+
console.log(`[${label}] Session ${sessionId}: debit failed (${debit.error}) — ending session`);
|
|
424
|
+
endSession(node, session, label);
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
session.totalEarned += debitAmount;
|
|
428
|
+
session.lastDebitAt = Date.now();
|
|
429
|
+
console.log(`[${label}] Session ${sessionId}: debit OK (+${debitAmount}, total: ${session.totalEarned} sats)`);
|
|
430
|
+
// Notify customer
|
|
399
431
|
node.send(socket, {
|
|
400
|
-
type: '
|
|
401
|
-
id:
|
|
432
|
+
type: 'session_tick_ack',
|
|
433
|
+
id: sessionId,
|
|
402
434
|
session_id: sessionId,
|
|
403
|
-
bolt11: invoice,
|
|
404
435
|
amount: debitAmount,
|
|
436
|
+
balance: msg.budget ? msg.budget - session.totalEarned : undefined,
|
|
405
437
|
});
|
|
406
|
-
console.log(`[${label}] Session ${sessionId}: sent invoice for ${debitAmount} sats`);
|
|
407
438
|
}, BILLING_INTERVAL_MIN * 60_000);
|
|
408
439
|
return;
|
|
409
440
|
}
|
|
410
|
-
if (msg.type === 'session_tick_ack') {
|
|
411
|
-
const session = activeSessions.get(msg.session_id || '');
|
|
412
|
-
if (!session)
|
|
413
|
-
return;
|
|
414
|
-
const amount = msg.amount || 0;
|
|
415
|
-
session.totalEarned += amount;
|
|
416
|
-
session.lastDebitAt = Date.now();
|
|
417
|
-
console.log(`[${label}] Session ${session.sessionId}: payment received (+${amount}, total: ${session.totalEarned} sats)`);
|
|
418
|
-
return;
|
|
419
|
-
}
|
|
420
441
|
if (msg.type === 'session_end') {
|
|
421
442
|
const session = activeSessions.get(msg.session_id || '');
|
|
422
443
|
if (!session)
|
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 CLINK debit payments.
|
|
5
5
|
*
|
|
6
6
|
* Features:
|
|
7
|
-
* -
|
|
7
|
+
* - Provider pulls per-minute payments via CLINK debit (ndebit authorization)
|
|
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 CLINK debit payments.
|
|
5
5
|
*
|
|
6
6
|
* Features:
|
|
7
|
-
* -
|
|
7
|
+
* - Provider pulls per-minute payments via CLINK debit (ndebit authorization)
|
|
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 {
|
|
47
|
+
import { loadNdebit } 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 = {
|
|
@@ -108,7 +112,7 @@ function sendAndWait(msg, timeoutMs) {
|
|
|
108
112
|
}
|
|
109
113
|
// --- 6. Message handler ---
|
|
110
114
|
function setupMessageHandler() {
|
|
111
|
-
state.node.on('message',
|
|
115
|
+
state.node.on('message', (msg) => {
|
|
112
116
|
// Handle chunked HTTP responses — reassemble before resolving
|
|
113
117
|
if (msg.type === 'http_response' && msg.chunk_total && msg.chunk_total > 1) {
|
|
114
118
|
const id = msg.id;
|
|
@@ -158,37 +162,8 @@ function setupMessageHandler() {
|
|
|
158
162
|
cleanup();
|
|
159
163
|
break;
|
|
160
164
|
}
|
|
161
|
-
case 'session_tick': {
|
|
162
|
-
// Provider sent an invoice for the next billing period — pay it
|
|
163
|
-
if (msg.bolt11 && msg.amount !== undefined) {
|
|
164
|
-
const amount = msg.amount;
|
|
165
|
-
if (state.totalSpent + amount > BUDGET) {
|
|
166
|
-
log(`Budget exhausted (need ${amount}, remaining ${remainingSats()}) — ending session`);
|
|
167
|
-
endSession();
|
|
168
|
-
break;
|
|
169
|
-
}
|
|
170
|
-
log(`Paying invoice: ${amount} sats...`);
|
|
171
|
-
const payResult = await walletPayInvoice(msg.bolt11);
|
|
172
|
-
if (payResult.ok) {
|
|
173
|
-
state.totalSpent += amount;
|
|
174
|
-
log(`Paid ${amount} sats (total: ${state.totalSpent}, ~${estimatedMinutesLeft()} min left)`);
|
|
175
|
-
state.node.send(state.socket, {
|
|
176
|
-
type: 'session_tick_ack',
|
|
177
|
-
id: msg.id,
|
|
178
|
-
session_id: state.sessionId,
|
|
179
|
-
preimage: payResult.preimage,
|
|
180
|
-
amount,
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
else {
|
|
184
|
-
warn(`Payment failed: ${payResult.error} — ending session`);
|
|
185
|
-
endSession();
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
break;
|
|
189
|
-
}
|
|
190
165
|
case 'session_tick_ack': {
|
|
191
|
-
//
|
|
166
|
+
// Provider debited our wallet and is reporting the result
|
|
192
167
|
if (msg.amount !== undefined) {
|
|
193
168
|
state.totalSpent += msg.amount;
|
|
194
169
|
}
|
|
@@ -663,20 +638,22 @@ async function main() {
|
|
|
663
638
|
state.satsPerMinute = satsPerMinute;
|
|
664
639
|
log(`Pricing: ${satsPerMinute} sats/min`);
|
|
665
640
|
log(`Budget: ${BUDGET} sats (~${Math.floor(BUDGET / satsPerMinute)} min)`);
|
|
666
|
-
// 4.
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
warn(
|
|
670
|
-
|
|
641
|
+
// 4. Verify CLINK ndebit authorization
|
|
642
|
+
if (!NDEBIT) {
|
|
643
|
+
warn('No ndebit authorization. Provide --ndebit=ndebit1... or set CLINK_NDEBIT env var.');
|
|
644
|
+
warn('Get your ndebit from your CLINK-compatible wallet (e.g. ShockWallet → Connections).');
|
|
645
|
+
await node.destroy();
|
|
646
|
+
process.exit(1);
|
|
671
647
|
}
|
|
672
|
-
log(`
|
|
673
|
-
// 5. Send session_start, wait for session_ack
|
|
648
|
+
log(`Payment: CLINK debit (provider pulls per-minute)`);
|
|
649
|
+
// 5. Send session_start with ndebit, wait for session_ack
|
|
674
650
|
const startId = randomBytes(4).toString('hex');
|
|
675
651
|
const ackResp = await sendAndWait({
|
|
676
652
|
type: 'session_start',
|
|
677
653
|
id: startId,
|
|
678
654
|
budget: BUDGET,
|
|
679
655
|
sats_per_minute: satsPerMinute,
|
|
656
|
+
ndebit: NDEBIT,
|
|
680
657
|
}, 15_000);
|
|
681
658
|
if (ackResp.type !== 'session_ack' || !ackResp.session_id) {
|
|
682
659
|
warn(`Unexpected response: ${ackResp.type}`);
|
|
@@ -691,7 +668,7 @@ async function main() {
|
|
|
691
668
|
log(`Provider adjusted rate: ${ackResp.sats_per_minute} sats/min`);
|
|
692
669
|
}
|
|
693
670
|
log(`Session started: ${state.sessionId}`);
|
|
694
|
-
log(`
|
|
671
|
+
log(`Provider will debit ${state.satsPerMinute} sats/min via CLINK`);
|
|
695
672
|
// 6. Start HTTP proxy
|
|
696
673
|
try {
|
|
697
674
|
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.5",
|
|
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",
|