2020117-agent 0.6.6 → 0.6.8
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 +17 -6
- package/dist/session.js +95 -9
- package/dist/swarm.d.ts +1 -0
- package/package.json +1 -1
package/dist/agent.js
CHANGED
|
@@ -85,7 +85,7 @@ import { SwarmNode, topicFromKind } from './swarm.js';
|
|
|
85
85
|
import { createProcessor } from './processor.js';
|
|
86
86
|
import { generateInvoice } from './lnurl.js';
|
|
87
87
|
import { generateKeypair, loadSovereignKeys, saveSovereignKeys, loadAgentName, signEvent, signEventWithPow, nip44Encrypt, nip44Decrypt, pubkeyFromPrivkey, RelayPool, } from './nostr.js';
|
|
88
|
-
import { parseNwcUri, nwcGetBalance, nwcPayLightningAddress } from './nwc.js';
|
|
88
|
+
import { parseNwcUri, nwcGetBalance, nwcPayLightningAddress, nwcMakeInvoice } from './nwc.js';
|
|
89
89
|
import { readFileSync } from 'fs';
|
|
90
90
|
import WebSocket from 'ws';
|
|
91
91
|
// Polyfill global WebSocket for Node.js < 22 (needed by ws tunnel)
|
|
@@ -827,11 +827,21 @@ function markSeen(eventId) {
|
|
|
827
827
|
}
|
|
828
828
|
return true;
|
|
829
829
|
}
|
|
830
|
-
/** Send a billing tick to the customer — generate Lightning invoice
|
|
830
|
+
/** Send a billing tick to the customer — generate Lightning invoice.
|
|
831
|
+
* Prefers NWC make_invoice (if nwc_uri configured), falls back to LNURL-pay. */
|
|
831
832
|
async function sendBillingTick(node, session, amount, label) {
|
|
832
833
|
const tickId = randomBytes(4).toString('hex');
|
|
833
834
|
try {
|
|
834
|
-
|
|
835
|
+
let bolt11;
|
|
836
|
+
if (state.nwcParsed) {
|
|
837
|
+
// NWC make_invoice: wallet generates invoice directly — no lightning address needed
|
|
838
|
+
const result = await nwcMakeInvoice(state.nwcParsed, amount * 1000, `2020117 session ${session.sessionId}`);
|
|
839
|
+
bolt11 = result.bolt11;
|
|
840
|
+
}
|
|
841
|
+
else {
|
|
842
|
+
// Fallback: LNURL-pay via lightning address
|
|
843
|
+
bolt11 = await generateInvoice(LIGHTNING_ADDRESS, amount);
|
|
844
|
+
}
|
|
835
845
|
node.send(session.socket, {
|
|
836
846
|
type: 'session_tick',
|
|
837
847
|
id: tickId,
|
|
@@ -872,8 +882,8 @@ async function startSwarmListener(label) {
|
|
|
872
882
|
if (msg.type === 'session_start') {
|
|
873
883
|
const processorUrl = process.env.PROCESSOR || '';
|
|
874
884
|
const proxyMode = processorUrl.startsWith('http://') || processorUrl.startsWith('https://');
|
|
875
|
-
if (!proxyMode && !LIGHTNING_ADDRESS) {
|
|
876
|
-
node.send(socket, { type: 'error', id: msg.id, message: 'Provider
|
|
885
|
+
if (!proxyMode && !LIGHTNING_ADDRESS && !state.nwcParsed) {
|
|
886
|
+
node.send(socket, { type: 'error', id: msg.id, message: 'Provider payment not configured (need --lightning-address or --nwc)' });
|
|
877
887
|
return;
|
|
878
888
|
}
|
|
879
889
|
const paymentMethod = 'invoice';
|
|
@@ -909,9 +919,10 @@ async function startSwarmListener(label) {
|
|
|
909
919
|
sats_per_minute: satsPerMinute,
|
|
910
920
|
payment_method: paymentMethod,
|
|
911
921
|
pubkey: state.sovereignKeys?.pubkey,
|
|
922
|
+
proxy_mode: proxyMode,
|
|
912
923
|
});
|
|
913
924
|
if (proxyMode) {
|
|
914
|
-
if (billingAmount <= 0 || !LIGHTNING_ADDRESS) {
|
|
925
|
+
if (billingAmount <= 0 || (!LIGHTNING_ADDRESS && !state.nwcParsed)) {
|
|
915
926
|
// Free proxy — open TCP pipe immediately
|
|
916
927
|
startTcpProxy(session, node, processorUrl, label);
|
|
917
928
|
}
|
package/dist/session.js
CHANGED
|
@@ -50,6 +50,7 @@ import { randomBytes } from 'crypto';
|
|
|
50
50
|
import { createServer } from 'http';
|
|
51
51
|
import { createInterface } from 'readline';
|
|
52
52
|
import { mkdirSync, writeFileSync } from 'fs';
|
|
53
|
+
import * as net from 'net';
|
|
53
54
|
import { WebSocketServer, WebSocket as WsWebSocket } from 'ws';
|
|
54
55
|
// --- Config ---
|
|
55
56
|
const KIND = Number(process.env.DVM_KIND) || 5200;
|
|
@@ -87,6 +88,8 @@ const state = {
|
|
|
87
88
|
startedAt: 0,
|
|
88
89
|
httpServer: null,
|
|
89
90
|
shuttingDown: false,
|
|
91
|
+
isProxyMode: false,
|
|
92
|
+
proxyReady: null,
|
|
90
93
|
pendingRequests: new Map(),
|
|
91
94
|
chunkBuffers: new Map(),
|
|
92
95
|
activeWebSockets: new Map(),
|
|
@@ -195,7 +198,12 @@ function setupMessageHandler() {
|
|
|
195
198
|
try {
|
|
196
199
|
const { preimage } = await nwcPayInvoice(nwcParsed, msg.bolt11);
|
|
197
200
|
state.totalSpent += amount;
|
|
198
|
-
|
|
201
|
+
if (state.isProxyMode) {
|
|
202
|
+
log(`Paid ${amount} sats — activating TCP proxy...`);
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
log(`Paid ${amount} sats via NWC (total: ${state.totalSpent}, ~${estimatedMinutesLeft()} min left)`);
|
|
206
|
+
}
|
|
199
207
|
state.node.send(state.socket, {
|
|
200
208
|
type: 'session_tick_ack',
|
|
201
209
|
id: msg.id,
|
|
@@ -203,6 +211,11 @@ function setupMessageHandler() {
|
|
|
203
211
|
preimage,
|
|
204
212
|
amount,
|
|
205
213
|
});
|
|
214
|
+
// Proxy mode: payment done, signal TCP pipe is ready
|
|
215
|
+
if (state.isProxyMode && state.proxyReady) {
|
|
216
|
+
state.proxyReady();
|
|
217
|
+
state.proxyReady = null;
|
|
218
|
+
}
|
|
206
219
|
}
|
|
207
220
|
catch (e) {
|
|
208
221
|
warn(`NWC invoice payment failed: ${e.message} — ending session`);
|
|
@@ -351,6 +364,57 @@ function startHttpProxy() {
|
|
|
351
364
|
});
|
|
352
365
|
});
|
|
353
366
|
}
|
|
367
|
+
// --- 4a. TCP proxy (proxy mode) ---
|
|
368
|
+
// After payment, the Hyperswarm socket becomes a raw TCP pipe to provider's backend.
|
|
369
|
+
// We start a local TCP server and forward bytes between local clients and the provider socket.
|
|
370
|
+
function startTcpProxy() {
|
|
371
|
+
return new Promise((resolve, reject) => {
|
|
372
|
+
const rawSocket = state.socket;
|
|
373
|
+
// Remove SwarmNode's JSON framing — from here on it's raw bytes
|
|
374
|
+
rawSocket.removeAllListeners('data');
|
|
375
|
+
let currentClient = null;
|
|
376
|
+
// Forward provider data to whatever client is currently connected
|
|
377
|
+
rawSocket.on('data', (chunk) => {
|
|
378
|
+
if (currentClient && !currentClient.destroyed) {
|
|
379
|
+
currentClient.write(chunk);
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
rawSocket.on('end', () => {
|
|
383
|
+
if (currentClient && !currentClient.destroyed)
|
|
384
|
+
currentClient.end();
|
|
385
|
+
});
|
|
386
|
+
rawSocket.on('error', (err) => {
|
|
387
|
+
warn(`Provider socket error: ${err.message}`);
|
|
388
|
+
if (currentClient && !currentClient.destroyed)
|
|
389
|
+
currentClient.destroy();
|
|
390
|
+
cleanup();
|
|
391
|
+
});
|
|
392
|
+
const server = net.createServer((clientSocket) => {
|
|
393
|
+
if (currentClient && !currentClient.destroyed) {
|
|
394
|
+
// Only one connection at a time — reject new ones
|
|
395
|
+
clientSocket.destroy();
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
currentClient = clientSocket;
|
|
399
|
+
clientSocket.on('data', (chunk) => {
|
|
400
|
+
if (!rawSocket.destroyed)
|
|
401
|
+
rawSocket.write(chunk);
|
|
402
|
+
});
|
|
403
|
+
clientSocket.on('close', () => { currentClient = null; });
|
|
404
|
+
clientSocket.on('error', () => { currentClient = null; });
|
|
405
|
+
});
|
|
406
|
+
server.on('error', (err) => {
|
|
407
|
+
if (!state.httpServer)
|
|
408
|
+
reject(err);
|
|
409
|
+
else
|
|
410
|
+
warn(`TCP proxy error: ${err.message}`);
|
|
411
|
+
});
|
|
412
|
+
server.listen(PORT, () => {
|
|
413
|
+
state.httpServer = server;
|
|
414
|
+
resolve();
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
}
|
|
354
418
|
// --- 4b. WebSocket tunnel (via ws library) ---
|
|
355
419
|
function setupWebSocketProxy(server) {
|
|
356
420
|
const wss = new WebSocketServer({ noServer: true });
|
|
@@ -772,21 +836,43 @@ async function main() {
|
|
|
772
836
|
state.sessionId = ackResp.session_id;
|
|
773
837
|
state.startedAt = Date.now();
|
|
774
838
|
providerPubkey = ackResp.pubkey || null;
|
|
839
|
+
state.isProxyMode = !!ackResp.proxy_mode;
|
|
775
840
|
// If the provider dictated a different rate, use it
|
|
776
841
|
if (ackResp.sats_per_minute && ackResp.sats_per_minute !== satsPerMinute) {
|
|
777
842
|
state.satsPerMinute = ackResp.sats_per_minute;
|
|
778
843
|
log(`Provider adjusted rate: ${ackResp.sats_per_minute} sats/min`);
|
|
779
844
|
}
|
|
780
845
|
log(`Session started: ${state.sessionId}`);
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
try {
|
|
784
|
-
await startHttpProxy();
|
|
785
|
-
log(`Web proxy ready at http://localhost:${PORT}`);
|
|
846
|
+
if (state.isProxyMode) {
|
|
847
|
+
log(`Mode: TCP proxy (one-time fee, raw HTTP passthrough)`);
|
|
786
848
|
}
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
849
|
+
else {
|
|
850
|
+
log(`Billing: ${state.satsPerMinute} sats/min via ${paymentMethod}`);
|
|
851
|
+
}
|
|
852
|
+
// 6. Start proxy
|
|
853
|
+
if (state.isProxyMode) {
|
|
854
|
+
// Proxy mode: wait for payment, then switch socket to raw TCP pipe
|
|
855
|
+
const proxyReadyPromise = new Promise(resolve => { state.proxyReady = resolve; });
|
|
856
|
+
log('Waiting for invoice from provider...');
|
|
857
|
+
try {
|
|
858
|
+
await proxyReadyPromise;
|
|
859
|
+
await startTcpProxy();
|
|
860
|
+
log(`TCP proxy ready at http://localhost:${PORT}`);
|
|
861
|
+
}
|
|
862
|
+
catch (e) {
|
|
863
|
+
warn(`Failed to start TCP proxy on port ${PORT}: ${e.message}`);
|
|
864
|
+
warn('Continuing without proxy');
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
else {
|
|
868
|
+
try {
|
|
869
|
+
await startHttpProxy();
|
|
870
|
+
log(`Web proxy ready at http://localhost:${PORT}`);
|
|
871
|
+
}
|
|
872
|
+
catch (e) {
|
|
873
|
+
warn(`Failed to start HTTP proxy on port ${PORT}: ${e.message}`);
|
|
874
|
+
warn('Continuing without HTTP proxy');
|
|
875
|
+
}
|
|
790
876
|
}
|
|
791
877
|
// 7. Show ready message and start REPL
|
|
792
878
|
log("Type 'help' for commands");
|
package/dist/swarm.d.ts
CHANGED