2020117-agent 0.4.2 → 0.4.4
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 +43 -6
- package/dist/session.js +109 -22
- package/package.json +1 -1
package/dist/agent.js
CHANGED
|
@@ -333,6 +333,10 @@ async function publishHandlerInfo(label) {
|
|
|
333
333
|
about: state.skill?.description || `DVM agent (kind ${KIND})`,
|
|
334
334
|
pricing: { [String(KIND)]: SATS_PER_CHUNK * CHUNKS_PER_PAYMENT },
|
|
335
335
|
};
|
|
336
|
+
if (LIGHTNING_ADDRESS) {
|
|
337
|
+
content.payment = { lightning_address: LIGHTNING_ADDRESS };
|
|
338
|
+
content.lud16 = LIGHTNING_ADDRESS;
|
|
339
|
+
}
|
|
336
340
|
const event = signEvent({
|
|
337
341
|
kind: 31990,
|
|
338
342
|
tags: [
|
|
@@ -449,17 +453,43 @@ async function handleDvmRequest(label, event) {
|
|
|
449
453
|
console.log(`[${label}] DVM result: ${result.length} chars`);
|
|
450
454
|
// Send result (Kind 6xxx = request kind + 1000)
|
|
451
455
|
const resultKind = KIND + 1000;
|
|
456
|
+
const resultTags = [
|
|
457
|
+
['p', event.pubkey],
|
|
458
|
+
['e', event.id],
|
|
459
|
+
['request', JSON.stringify(event)],
|
|
460
|
+
['amount', String(SATS_PER_CHUNK * CHUNKS_PER_PAYMENT * 1000)], // msats
|
|
461
|
+
];
|
|
462
|
+
if (LIGHTNING_ADDRESS) {
|
|
463
|
+
resultTags.push(['lightning_address', LIGHTNING_ADDRESS]);
|
|
464
|
+
}
|
|
452
465
|
const resultEvent = signEvent({
|
|
453
466
|
kind: resultKind,
|
|
454
|
-
tags:
|
|
455
|
-
['p', event.pubkey],
|
|
456
|
-
['e', event.id],
|
|
457
|
-
['request', JSON.stringify(event)],
|
|
458
|
-
],
|
|
467
|
+
tags: resultTags,
|
|
459
468
|
content: result,
|
|
460
469
|
}, state.sovereignKeys.privkey);
|
|
461
470
|
await state.relayPool.publish(resultEvent);
|
|
462
471
|
console.log(`[${label}] Published DVM result (Kind ${resultKind}) via relay`);
|
|
472
|
+
// Publish reputation endorsement (Kind 30311) for customer
|
|
473
|
+
try {
|
|
474
|
+
const endorsementEvent = signEvent({
|
|
475
|
+
kind: 30311,
|
|
476
|
+
tags: [
|
|
477
|
+
['d', event.pubkey],
|
|
478
|
+
['p', event.pubkey],
|
|
479
|
+
['rating', '5'],
|
|
480
|
+
['k', String(KIND)],
|
|
481
|
+
],
|
|
482
|
+
content: JSON.stringify({
|
|
483
|
+
rating: 5,
|
|
484
|
+
context: { jobs_together: 1, kinds: [KIND], last_job_at: Math.floor(Date.now() / 1000) },
|
|
485
|
+
}),
|
|
486
|
+
}, state.sovereignKeys.privkey);
|
|
487
|
+
await state.relayPool.publish(endorsementEvent);
|
|
488
|
+
console.log(`[${label}] Published endorsement (Kind 30311) for ${event.pubkey.slice(0, 8)}`);
|
|
489
|
+
}
|
|
490
|
+
catch (e) {
|
|
491
|
+
console.warn(`[${label}] Failed to publish endorsement: ${e.message}`);
|
|
492
|
+
}
|
|
463
493
|
}
|
|
464
494
|
finally {
|
|
465
495
|
releaseSlot();
|
|
@@ -676,7 +706,14 @@ async function startSwarmListener(label) {
|
|
|
676
706
|
node.on('message', async (msg, socket, peerId) => {
|
|
677
707
|
const tag = peerId.slice(0, 8);
|
|
678
708
|
if (msg.type === 'skill_request') {
|
|
679
|
-
|
|
709
|
+
const satsPerMinute = state.skill?.pricing?.sats_per_minute
|
|
710
|
+
|| Number(process.env.SATS_PER_MINUTE)
|
|
711
|
+
|| 10;
|
|
712
|
+
// Always include runtime pricing, even without a skill file
|
|
713
|
+
const skillWithPricing = state.skill
|
|
714
|
+
? { ...state.skill, pricing: { ...(state.skill.pricing || {}), sats_per_minute: satsPerMinute } }
|
|
715
|
+
: { pricing: { sats_per_minute: satsPerMinute }, payment_methods: LIGHTNING_ADDRESS ? ['cashu', 'invoice'] : ['cashu'] };
|
|
716
|
+
node.send(socket, { type: 'skill_response', id: msg.id, skill: skillWithPricing });
|
|
680
717
|
return;
|
|
681
718
|
}
|
|
682
719
|
// --- Session protocol ---
|
package/dist/session.js
CHANGED
|
@@ -43,12 +43,17 @@ for (const arg of process.argv.slice(2)) {
|
|
|
43
43
|
case '--mint':
|
|
44
44
|
process.env.CASHU_MINT_URL = val;
|
|
45
45
|
break;
|
|
46
|
+
case '--nwc':
|
|
47
|
+
process.env.NWC_URI = val;
|
|
48
|
+
break;
|
|
46
49
|
}
|
|
47
50
|
}
|
|
48
51
|
import { SwarmNode, topicFromKind } from './swarm.js';
|
|
49
52
|
import { queryProviderSkill } from './p2p-customer.js';
|
|
50
53
|
import { walletPayInvoice, walletGetBalance, hasApiKey } from './api.js';
|
|
51
54
|
import { decodeCashuToken, sendCashuToken, createMintQuote, claimMintQuote } from './cashu.js';
|
|
55
|
+
import { parseNwcUri, nwcGetBalance, nwcPayInvoice } from './nwc.js';
|
|
56
|
+
import { loadSovereignKeys } from './nostr.js';
|
|
52
57
|
import { randomBytes } from 'crypto';
|
|
53
58
|
import { createServer } from 'http';
|
|
54
59
|
import { createInterface } from 'readline';
|
|
@@ -60,10 +65,24 @@ const BUDGET = Number(process.env.BUDGET_SATS) || 500;
|
|
|
60
65
|
const PORT = Number(process.env.SESSION_PORT) || 8080;
|
|
61
66
|
const CASHU_TOKEN = process.env.CASHU_TOKEN || '';
|
|
62
67
|
const MINT_URL = process.env.CASHU_MINT_URL || 'https://8333.space:3338';
|
|
68
|
+
// Load NWC URI: --nwc flag > .2020117_keys nwc_uri > none
|
|
69
|
+
let nwcParsed = null;
|
|
70
|
+
const nwcUri = process.env.NWC_URI
|
|
71
|
+
|| loadSovereignKeys(process.env.AGENT)?.nwc_uri
|
|
72
|
+
|| null;
|
|
63
73
|
// Mutable Cashu wallet state (loaded from CASHU_TOKEN at startup)
|
|
64
74
|
let cashuState = null;
|
|
65
75
|
const TICK_INTERVAL_MS = 60_000;
|
|
66
76
|
const HTTP_TIMEOUT_MS = 60_000;
|
|
77
|
+
function confirmPrompt(question) {
|
|
78
|
+
return new Promise(resolve => {
|
|
79
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
80
|
+
rl.question(question, answer => {
|
|
81
|
+
rl.close();
|
|
82
|
+
resolve(answer.trim().toLowerCase() === 'y' || answer.trim().toLowerCase() === 'yes');
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
}
|
|
67
86
|
const state = {
|
|
68
87
|
node: null,
|
|
69
88
|
socket: null,
|
|
@@ -175,23 +194,43 @@ function setupMessageHandler() {
|
|
|
175
194
|
break;
|
|
176
195
|
}
|
|
177
196
|
if (msg.bolt11) {
|
|
178
|
-
// Invoice mode: pay bolt11 via
|
|
197
|
+
// Invoice mode: pay bolt11 via NWC direct or platform API
|
|
179
198
|
log(`Paying invoice: ${amount} sats...`);
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
199
|
+
if (nwcParsed) {
|
|
200
|
+
try {
|
|
201
|
+
const { preimage } = await nwcPayInvoice(nwcParsed, msg.bolt11);
|
|
202
|
+
state.totalSpent += amount;
|
|
203
|
+
log(`Paid ${amount} sats via NWC (total: ${state.totalSpent}, ~${estimatedMinutesLeft()} min left)`);
|
|
204
|
+
state.node.send(state.socket, {
|
|
205
|
+
type: 'session_tick_ack',
|
|
206
|
+
id: msg.id,
|
|
207
|
+
session_id: state.sessionId,
|
|
208
|
+
preimage,
|
|
209
|
+
amount,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
catch (e) {
|
|
213
|
+
warn(`NWC invoice payment failed: ${e.message} — ending session`);
|
|
214
|
+
endSession();
|
|
215
|
+
}
|
|
191
216
|
}
|
|
192
217
|
else {
|
|
193
|
-
|
|
194
|
-
|
|
218
|
+
const payResult = await walletPayInvoice(msg.bolt11);
|
|
219
|
+
if (payResult.ok) {
|
|
220
|
+
state.totalSpent += amount;
|
|
221
|
+
log(`Paid ${amount} sats (total: ${state.totalSpent}, ~${estimatedMinutesLeft()} min left)`);
|
|
222
|
+
state.node.send(state.socket, {
|
|
223
|
+
type: 'session_tick_ack',
|
|
224
|
+
id: msg.id,
|
|
225
|
+
session_id: state.sessionId,
|
|
226
|
+
preimage: payResult.preimage,
|
|
227
|
+
amount,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
warn(`Invoice payment failed: ${payResult.error} — ending session`);
|
|
232
|
+
endSession();
|
|
233
|
+
}
|
|
195
234
|
}
|
|
196
235
|
}
|
|
197
236
|
else if (cashuState) {
|
|
@@ -701,8 +740,26 @@ async function main() {
|
|
|
701
740
|
const pricing = skill?.pricing;
|
|
702
741
|
const satsPerMinute = Number(pricing?.sats_per_minute) || 10;
|
|
703
742
|
state.satsPerMinute = satsPerMinute;
|
|
704
|
-
|
|
705
|
-
|
|
743
|
+
// Price confirmation
|
|
744
|
+
const minutesAvailable = Math.floor(BUDGET / satsPerMinute);
|
|
745
|
+
log('');
|
|
746
|
+
log('┌─────────────────────────────────');
|
|
747
|
+
log(`│ Rate: ${satsPerMinute} sats/min`);
|
|
748
|
+
log(`│ Budget: ${BUDGET} sats`);
|
|
749
|
+
log(`│ Duration: ~${minutesAvailable} min`);
|
|
750
|
+
if (skill?.payment_methods) {
|
|
751
|
+
log(`│ Payment: ${skill.payment_methods.join(', ')}`);
|
|
752
|
+
}
|
|
753
|
+
log('└─────────────────────────────────');
|
|
754
|
+
log('');
|
|
755
|
+
if (process.stdin.isTTY) {
|
|
756
|
+
const confirmed = await confirmPrompt('Start session? (y/n): ');
|
|
757
|
+
if (!confirmed) {
|
|
758
|
+
log('Session cancelled.');
|
|
759
|
+
await node.destroy();
|
|
760
|
+
process.exit(0);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
706
763
|
// 4. Determine payment method: Cashu (default) or invoice (fallback)
|
|
707
764
|
let paymentMethod;
|
|
708
765
|
if (CASHU_TOKEN) {
|
|
@@ -716,8 +773,40 @@ async function main() {
|
|
|
716
773
|
warn(`Cashu token (${tokenAmount} sats) is less than budget (${BUDGET} sats)`);
|
|
717
774
|
}
|
|
718
775
|
}
|
|
776
|
+
else if (nwcUri) {
|
|
777
|
+
// NWC direct: auto-mint Cashu tokens via local NWC wallet (no platform API)
|
|
778
|
+
nwcParsed = parseNwcUri(nwcUri);
|
|
779
|
+
const { balance_msats } = await nwcGetBalance(nwcParsed);
|
|
780
|
+
const balance = Math.floor(balance_msats / 1000);
|
|
781
|
+
if (balance <= 0) {
|
|
782
|
+
warn('NWC wallet balance is 0. Cannot auto-mint Cashu tokens.');
|
|
783
|
+
await node.destroy();
|
|
784
|
+
process.exit(1);
|
|
785
|
+
}
|
|
786
|
+
const mintAmount = Math.min(balance, BUDGET);
|
|
787
|
+
log(`NWC wallet balance: ${balance} sats — minting ${mintAmount} sats from ${MINT_URL}`);
|
|
788
|
+
try {
|
|
789
|
+
const { quote, invoice } = await createMintQuote(MINT_URL, mintAmount);
|
|
790
|
+
log(`Mint quote: ${quote} (invoice: ${invoice.slice(0, 30)}...)`);
|
|
791
|
+
log('Paying mint invoice via NWC...');
|
|
792
|
+
const { preimage } = await nwcPayInvoice(nwcParsed, invoice);
|
|
793
|
+
log(`Invoice paid (preimage: ${preimage?.slice(0, 16)}...)`);
|
|
794
|
+
log('Claiming minted tokens...');
|
|
795
|
+
const token = await claimMintQuote(MINT_URL, mintAmount, quote);
|
|
796
|
+
const { mint, proofs } = decodeCashuToken(token);
|
|
797
|
+
cashuState = { mintUrl: mint, proofs };
|
|
798
|
+
paymentMethod = 'cashu';
|
|
799
|
+
const totalMinted = proofs.reduce((s, p) => s + p.amount, 0);
|
|
800
|
+
log(`Minted ${totalMinted} sats Cashu token — using Cashu payment mode`);
|
|
801
|
+
}
|
|
802
|
+
catch (e) {
|
|
803
|
+
warn(`NWC auto-mint failed: ${e.message}`);
|
|
804
|
+
await node.destroy();
|
|
805
|
+
process.exit(1);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
719
808
|
else if (hasApiKey()) {
|
|
720
|
-
//
|
|
809
|
+
// Platform API fallback: auto-mint Cashu tokens via platform wallet proxy
|
|
721
810
|
const balance = await walletGetBalance();
|
|
722
811
|
if (balance <= 0) {
|
|
723
812
|
warn('Wallet balance is 0. Cannot auto-mint Cashu tokens.');
|
|
@@ -727,17 +816,14 @@ async function main() {
|
|
|
727
816
|
const mintAmount = Math.min(balance, BUDGET);
|
|
728
817
|
log(`Wallet balance: ${balance} sats — minting ${mintAmount} sats from ${MINT_URL}`);
|
|
729
818
|
try {
|
|
730
|
-
// 1. Request mint quote (Lightning invoice)
|
|
731
819
|
const { quote, invoice } = await createMintQuote(MINT_URL, mintAmount);
|
|
732
820
|
log(`Mint quote: ${quote} (invoice: ${invoice.slice(0, 30)}...)`);
|
|
733
|
-
|
|
734
|
-
log('Paying mint invoice via NWC wallet...');
|
|
821
|
+
log('Paying mint invoice via platform wallet...');
|
|
735
822
|
const payResult = await walletPayInvoice(invoice);
|
|
736
823
|
if (!payResult.ok) {
|
|
737
824
|
throw new Error(`Payment failed: ${payResult.error}`);
|
|
738
825
|
}
|
|
739
826
|
log(`Invoice paid (preimage: ${payResult.preimage?.slice(0, 16)}...)`);
|
|
740
|
-
// 3. Claim minted proofs
|
|
741
827
|
log('Claiming minted tokens...');
|
|
742
828
|
const token = await claimMintQuote(MINT_URL, mintAmount, quote);
|
|
743
829
|
const { mint, proofs } = decodeCashuToken(token);
|
|
@@ -755,7 +841,8 @@ async function main() {
|
|
|
755
841
|
else {
|
|
756
842
|
warn('No payment method available.');
|
|
757
843
|
warn(' Option 1 (default): --cashu-token=cashuA... (Cashu eCash token)');
|
|
758
|
-
warn(' Option 2: --
|
|
844
|
+
warn(' Option 2: --nwc="nostr+walletconnect://..." (NWC direct wallet)');
|
|
845
|
+
warn(' Option 3: --agent=NAME (auto-mints via platform API)');
|
|
759
846
|
await node.destroy();
|
|
760
847
|
process.exit(1);
|
|
761
848
|
}
|