2020117-agent 0.4.5 → 0.4.7

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 CHANGED
@@ -285,15 +285,17 @@ async function setupSovereign(label) {
285
285
  state.relayPool = new RelayPool(relayUrls);
286
286
  await state.relayPool.connect();
287
287
  console.log(`[${label}] Connected to ${state.relayPool.connectedCount} relay(s)`);
288
- // 4. Publish ai.info (Kind 31340) — NIP-XX capability advertisement
288
+ // 4. Publish profile (Kind 0) — name, about, lud16
289
+ await publishProfile(label);
290
+ // 5. Publish ai.info (Kind 31340) — NIP-XX capability advertisement
289
291
  await publishAiInfo(label);
290
- // 5. Publish handler info (Kind 31990) — NIP-89 DVM capability
292
+ // 6. Publish handler info (Kind 31990) — NIP-89 DVM capability
291
293
  await publishHandlerInfo(label);
292
- // 6. Subscribe to NIP-XX prompts (Kind 25802)
294
+ // 7. Subscribe to NIP-XX prompts (Kind 25802)
293
295
  subscribeNipXX(label);
294
- // 7. Subscribe to DVM requests (Kind 5xxx) directly from relay
296
+ // 8. Subscribe to DVM requests (Kind 5xxx) directly from relay
295
297
  subscribeDvmRequests(label);
296
- // 8. Start sovereign heartbeat (Kind 30333 to relay)
298
+ // 9. Start sovereign heartbeat (Kind 30333 to relay)
297
299
  startSovereignHeartbeat(label);
298
300
  }
299
301
  async function publishAiInfo(label) {
@@ -324,6 +326,26 @@ async function publishAiInfo(label) {
324
326
  const ok = await state.relayPool.publish(event);
325
327
  console.log(`[${label}] Published ai.info (Kind 31340): ${ok ? 'ok' : 'failed'}`);
326
328
  }
329
+ async function publishProfile(label) {
330
+ if (!state.sovereignKeys || !state.relayPool)
331
+ return;
332
+ const agentName = state.agentName || 'sovereign-agent';
333
+ const content = {
334
+ name: agentName,
335
+ about: state.skill?.description || `DVM agent (kind ${KIND})`,
336
+ picture: `https://robohash.org/${encodeURIComponent(agentName)}`,
337
+ };
338
+ if (LIGHTNING_ADDRESS) {
339
+ content.lud16 = LIGHTNING_ADDRESS;
340
+ }
341
+ const event = signEvent({
342
+ kind: 0,
343
+ tags: [],
344
+ content: JSON.stringify(content),
345
+ }, state.sovereignKeys.privkey);
346
+ const ok = await state.relayPool.publish(event);
347
+ console.log(`[${label}] Published profile (Kind 0): ${ok ? 'ok' : 'failed'}`);
348
+ }
327
349
  async function publishHandlerInfo(label) {
328
350
  if (!state.sovereignKeys || !state.relayPool)
329
351
  return;
@@ -743,6 +765,7 @@ async function startSwarmListener(label) {
743
765
  lastPaidAt: Date.now(),
744
766
  billingTimer: null,
745
767
  timeoutTimer: null,
768
+ customerPubkey: msg.pubkey || undefined,
746
769
  };
747
770
  activeSessions.set(sessionId, session);
748
771
  node.send(socket, {
@@ -751,6 +774,7 @@ async function startSwarmListener(label) {
751
774
  session_id: sessionId,
752
775
  sats_per_minute: satsPerMinute,
753
776
  payment_method: paymentMethod,
777
+ pubkey: state.sovereignKeys?.pubkey,
754
778
  });
755
779
  // Send first billing tick
756
780
  await sendBillingTick(node, session, billingAmount, label);
@@ -1035,6 +1059,32 @@ function endSession(node, session, label) {
1035
1059
  // Socket may already be closed (peer disconnect)
1036
1060
  }
1037
1061
  console.log(`[${label}] Session ${session.sessionId} ended: ${session.totalEarned} sats, ${durationS}s`);
1062
+ // Publish Kind 30311 endorsement for customer (best-effort)
1063
+ if (state.sovereignKeys && state.relayPool && session.customerPubkey) {
1064
+ try {
1065
+ const endorsement = signEvent({
1066
+ kind: 30311,
1067
+ tags: [
1068
+ ['d', session.customerPubkey],
1069
+ ['p', session.customerPubkey],
1070
+ ['rating', '5'],
1071
+ ['k', String(KIND)],
1072
+ ],
1073
+ content: JSON.stringify({
1074
+ rating: 5,
1075
+ context: {
1076
+ session_duration_s: durationS,
1077
+ total_sats: session.totalEarned,
1078
+ kinds: [KIND],
1079
+ last_job_at: Math.floor(Date.now() / 1000),
1080
+ },
1081
+ }),
1082
+ }, state.sovereignKeys.privkey);
1083
+ state.relayPool.publish(endorsement).catch(() => { });
1084
+ console.log(`[${label}] Published endorsement for customer ${session.customerPubkey.slice(0, 8)}`);
1085
+ }
1086
+ catch { }
1087
+ }
1038
1088
  // Update P2P lifetime counters
1039
1089
  state.p2pSessionsCompleted++;
1040
1090
  state.p2pTotalEarnedSats += session.totalEarned;
package/dist/session.js CHANGED
@@ -53,7 +53,7 @@ import { queryProviderSkill } from './p2p-customer.js';
53
53
  import { walletPayInvoice, walletGetBalance, hasApiKey } from './api.js';
54
54
  import { decodeCashuToken, sendCashuToken, createMintQuote, claimMintQuote, meltProofs, estimateMeltFee } from './cashu.js';
55
55
  import { parseNwcUri, nwcGetBalance, nwcPayInvoice, nwcMakeInvoice } from './nwc.js';
56
- import { loadSovereignKeys } from './nostr.js';
56
+ import { loadSovereignKeys, signEvent, RelayPool } from './nostr.js';
57
57
  import { randomBytes } from 'crypto';
58
58
  import { createServer } from 'http';
59
59
  import { createInterface } from 'readline';
@@ -65,11 +65,15 @@ const BUDGET = Number(process.env.BUDGET_SATS) || 500;
65
65
  const PORT = Number(process.env.SESSION_PORT) || 8080;
66
66
  const CASHU_TOKEN = process.env.CASHU_TOKEN || '';
67
67
  const MINT_URL = process.env.CASHU_MINT_URL || 'https://8333.space:3338';
68
+ // Load sovereign keys (for pubkey exchange + endorsement publishing)
69
+ const sovereignKeys = loadSovereignKeys(process.env.AGENT);
68
70
  // Load NWC URI: --nwc flag > .2020117_keys nwc_uri > none
69
71
  let nwcParsed = null;
70
72
  const nwcUri = process.env.NWC_URI
71
- || loadSovereignKeys(process.env.AGENT)?.nwc_uri
73
+ || sovereignKeys?.nwc_uri
72
74
  || null;
75
+ // Track provider's Nostr pubkey (received in session_ack)
76
+ let providerPubkey = null;
73
77
  // Mutable Cashu wallet state (loaded from CASHU_TOKEN at startup)
74
78
  let cashuState = null;
75
79
  const TICK_INTERVAL_MS = 60_000;
@@ -694,6 +698,36 @@ async function endSession() {
694
698
  }
695
699
  log(`Session ended. Total: ${state.totalSpent} sats for ${duration}s.`);
696
700
  }
701
+ // Publish Kind 30311 endorsement for provider (best-effort)
702
+ if (sovereignKeys?.privkey && providerPubkey) {
703
+ try {
704
+ const endorsement = signEvent({
705
+ kind: 30311,
706
+ tags: [
707
+ ['d', providerPubkey],
708
+ ['p', providerPubkey],
709
+ ['rating', '5'],
710
+ ['k', String(KIND)],
711
+ ],
712
+ content: JSON.stringify({
713
+ rating: 5,
714
+ context: {
715
+ session_duration_s: elapsedSeconds(),
716
+ total_sats: state.totalSpent,
717
+ kinds: [KIND],
718
+ last_job_at: Math.floor(Date.now() / 1000),
719
+ },
720
+ }),
721
+ }, sovereignKeys.privkey);
722
+ const relayUrls = sovereignKeys.relays?.length ? sovereignKeys.relays : ['wss://relay.2020117.xyz'];
723
+ const relay = new RelayPool(relayUrls);
724
+ await relay.connect();
725
+ await relay.publish(endorsement);
726
+ await relay.close();
727
+ log(`Published endorsement for provider ${providerPubkey.slice(0, 8)}`);
728
+ }
729
+ catch { }
730
+ }
697
731
  // Refund remaining Cashu proofs back to NWC wallet
698
732
  if (cashuState && cashuState.proofs.length > 0 && nwcParsed) {
699
733
  const remaining = cashuState.proofs.reduce((s, p) => s + p.amount, 0);
@@ -864,6 +898,7 @@ async function main() {
864
898
  budget: BUDGET,
865
899
  sats_per_minute: satsPerMinute,
866
900
  payment_method: paymentMethod,
901
+ pubkey: sovereignKeys?.pubkey,
867
902
  }, 15_000);
868
903
  if (ackResp.type !== 'session_ack' || !ackResp.session_id) {
869
904
  warn(`Unexpected response: ${ackResp.type}`);
@@ -872,6 +907,7 @@ async function main() {
872
907
  }
873
908
  state.sessionId = ackResp.session_id;
874
909
  state.startedAt = Date.now();
910
+ providerPubkey = ackResp.pubkey || null;
875
911
  // If the provider dictated a different rate, use it
876
912
  if (ackResp.sats_per_minute && ackResp.sats_per_minute !== satsPerMinute) {
877
913
  state.satsPerMinute = ackResp.sats_per_minute;
package/dist/swarm.d.ts CHANGED
@@ -46,6 +46,7 @@ export interface SwarmMessage {
46
46
  balance?: number;
47
47
  duration_s?: number;
48
48
  payment_method?: 'cashu' | 'invoice';
49
+ pubkey?: string;
49
50
  bolt11?: string;
50
51
  preimage?: string;
51
52
  cashu_token?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "2020117-agent",
3
- "version": "0.4.5",
3
+ "version": "0.4.7",
4
4
  "description": "2020117 agent runtime — API polling + Hyperswarm P2P + Sovereign Nostr mode + Cashu/Lightning payments",
5
5
  "type": "module",
6
6
  "bin": {