@elisym/sdk 0.10.4 → 0.12.0

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/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { getAddMemoInstruction } from '@solana-program/memo';
1
2
  import { getTransferSolInstruction } from '@solana-program/system';
2
3
  import { findAssociatedTokenPda, TOKEN_PROGRAM_ADDRESS, getCreateAssociatedTokenIdempotentInstruction, ASSOCIATED_TOKEN_PROGRAM_ADDRESS, getTransferCheckedInstruction } from '@solana-program/token';
3
4
  import { pipe, createTransactionMessage, setTransactionMessageFeePayerSigner, setTransactionMessageLifetimeUsingBlockhash, setTransactionMessageComputeUnitLimit, setTransactionMessageComputeUnitPrice, appendTransactionMessageInstructions, signTransactionMessageWithSigners, address, AccountRole, isAddress, getProgramDerivedAddress, assertAccountExists, getAddressDecoder, fetchEncodedAccount, decodeAccount, getStructDecoder, fixDecoderSize, getBytesDecoder, getU8Decoder, getOptionDecoder, getU16Decoder, getBooleanDecoder, getI64Decoder } from '@solana/kit';
@@ -39,6 +40,7 @@ var LAMPORTS_PER_SOL = 1e9;
39
40
  var PROTOCOL_FEE_BPS = 300;
40
41
  var PROTOCOL_TREASURY = "GY7vnWMkKpftU4nQ16C2ATkj1JwrQpHhknkaBUn67VTy";
41
42
  var PROTOCOL_PROGRAM_ID_DEVNET = "BrX1CRkSgvcjxBvc2bgc3QqgWjinusofDmeP7ZVxvwrE";
43
+ var ELISYM_PROTOCOL_TAG = "ELiZksgwDt41LaeuPDLkUfWgFXhGgVayTMP7L5nTSEL8";
42
44
  function getProtocolProgramId(cluster) {
43
45
  switch (cluster) {
44
46
  case "devnet":
@@ -94,13 +96,13 @@ function decodeConfig(encodedAccount) {
94
96
  getConfigDecoder()
95
97
  );
96
98
  }
97
- async function fetchConfig(rpc, address3, config) {
98
- const maybeAccount = await fetchMaybeConfig(rpc, address3, config);
99
+ async function fetchConfig(rpc, address4, config) {
100
+ const maybeAccount = await fetchMaybeConfig(rpc, address4, config);
99
101
  assertAccountExists(maybeAccount);
100
102
  return maybeAccount;
101
103
  }
102
- async function fetchMaybeConfig(rpc, address3, config) {
103
- const maybeAccount = await fetchEncodedAccount(rpc, address3, config);
104
+ async function fetchMaybeConfig(rpc, address4, config) {
105
+ const maybeAccount = await fetchEncodedAccount(rpc, address4, config);
104
106
  return decodeConfig(maybeAccount);
105
107
  }
106
108
  if (process.env.NODE_ENV !== "production") ;
@@ -572,7 +574,9 @@ var SolanaPaymentStrategy = class {
572
574
  if (!Number.isInteger(computeUnitLimit) || computeUnitLimit <= 0) {
573
575
  throw new Error(`Invalid computeUnitLimit: ${computeUnitLimit}. Must be a positive integer.`);
574
576
  }
575
- const paymentInstructions = await buildPaymentInstructions(paymentRequest, payerSigner);
577
+ const paymentInstructions = await buildPaymentInstructions(paymentRequest, payerSigner, {
578
+ jobEventId: options?.jobEventId
579
+ });
576
580
  const priorityFeeMicroLamports = options?.priorityFeeMicroLamports ?? await estimatePriorityFeeMicroLamports(rpc, {
577
581
  percentile: options?.priorityFeePercentile ?? DEFAULT_PRIORITY_FEE_PERCENTILE
578
582
  });
@@ -869,9 +873,10 @@ function bigIntDelta(post, pre) {
869
873
  function waitMs(ms) {
870
874
  return new Promise((resolve) => setTimeout(resolve, ms));
871
875
  }
872
- async function buildPaymentInstructions(paymentRequest, payerSigner) {
876
+ async function buildPaymentInstructions(paymentRequest, payerSigner, options) {
873
877
  const recipient = address(paymentRequest.recipient);
874
878
  const reference = address(paymentRequest.reference);
879
+ const protocolTag = address(ELISYM_PROTOCOL_TAG);
875
880
  const feeAmount = paymentRequest.fee_amount ?? 0;
876
881
  const providerAmount = paymentRequest.fee_address && feeAmount > 0 ? paymentRequest.amount - feeAmount : paymentRequest.amount;
877
882
  if (providerAmount <= 0) {
@@ -879,6 +884,7 @@ async function buildPaymentInstructions(paymentRequest, payerSigner) {
879
884
  `Fee amount (${feeAmount}) exceeds or equals total amount (${paymentRequest.amount}). Cannot create transaction with non-positive provider amount.`
880
885
  );
881
886
  }
887
+ const memoInstruction = options?.jobEventId ? getAddMemoInstruction({ memo: `elisym:v1:${options.jobEventId}` }) : null;
882
888
  const asset = resolveAssetFromPaymentRequest(paymentRequest);
883
889
  if (!asset.mint) {
884
890
  const providerTransferIx2 = getTransferSolInstruction({
@@ -886,14 +892,19 @@ async function buildPaymentInstructions(paymentRequest, payerSigner) {
886
892
  destination: recipient,
887
893
  amount: BigInt(providerAmount)
888
894
  });
889
- const providerTransferIxWithReference2 = {
895
+ const providerTransferIxWithMarkers2 = {
890
896
  ...providerTransferIx2,
891
897
  accounts: [
892
898
  ...providerTransferIx2.accounts,
893
- { address: reference, role: AccountRole.READONLY }
899
+ { address: reference, role: AccountRole.READONLY },
900
+ { address: protocolTag, role: AccountRole.READONLY }
894
901
  ]
895
902
  };
896
- const instructions2 = [providerTransferIxWithReference2];
903
+ const instructions2 = [];
904
+ if (memoInstruction) {
905
+ instructions2.push(memoInstruction);
906
+ }
907
+ instructions2.push(providerTransferIxWithMarkers2);
897
908
  if (paymentRequest.fee_address && feeAmount > 0) {
898
909
  instructions2.push(
899
910
  getTransferSolInstruction({
@@ -918,6 +929,9 @@ async function buildPaymentInstructions(paymentRequest, payerSigner) {
918
929
  mint
919
930
  });
920
931
  const instructions = [];
932
+ if (memoInstruction) {
933
+ instructions.push(memoInstruction);
934
+ }
921
935
  instructions.push(
922
936
  getCreateAssociatedTokenIdempotentInstruction(
923
937
  {
@@ -957,11 +971,15 @@ async function buildPaymentInstructions(paymentRequest, payerSigner) {
957
971
  amount: BigInt(providerAmount),
958
972
  decimals: asset.decimals
959
973
  });
960
- const providerTransferIxWithReference = {
974
+ const providerTransferIxWithMarkers = {
961
975
  ...providerTransferIx,
962
- accounts: [...providerTransferIx.accounts, { address: reference, role: AccountRole.READONLY }]
976
+ accounts: [
977
+ ...providerTransferIx.accounts,
978
+ { address: reference, role: AccountRole.READONLY },
979
+ { address: protocolTag, role: AccountRole.READONLY }
980
+ ]
963
981
  };
964
- instructions.push(providerTransferIxWithReference);
982
+ instructions.push(providerTransferIxWithMarkers);
965
983
  if (treasuryAta && paymentRequest.fee_address && feeAmount > 0) {
966
984
  instructions.push(
967
985
  getTransferCheckedInstruction({
@@ -989,6 +1007,7 @@ async function createPaymentRequestWithOnchainConfig(rpc, programId, recipient,
989
1007
  var RANKING_ACTIVITY_WINDOW_SECS = 30 * 24 * 60 * 60;
990
1008
  var RANKING_BUCKET_SIZE_SECS = 60;
991
1009
  var COLD_START_BUCKET = -Infinity;
1010
+ var NEVER_ABORTED_SIGNAL = new AbortController().signal;
992
1011
  function toDTag(name) {
993
1012
  const tag = name.toLowerCase().replace(/[^a-z0-9\s-]/g, (ch) => "_" + ch.charCodeAt(0).toString(16).padStart(2, "0")).replace(/\s+/g, "-").replace(/^-+|-+$/g, "");
994
1013
  if (!tag) {
@@ -1018,13 +1037,63 @@ function compareAgentsByRank(a, b) {
1018
1037
  }
1019
1038
  return kb.lastSeen - ka.lastSeen;
1020
1039
  }
1040
+ function parseCapabilityEvent(event, network) {
1041
+ if (!verifyEvent(event)) {
1042
+ return null;
1043
+ }
1044
+ if (!event.content) {
1045
+ return null;
1046
+ }
1047
+ let raw;
1048
+ try {
1049
+ raw = JSON.parse(event.content);
1050
+ } catch {
1051
+ return null;
1052
+ }
1053
+ if (!raw || typeof raw !== "object") {
1054
+ return null;
1055
+ }
1056
+ const candidate = raw;
1057
+ if (typeof candidate.name !== "string" || !candidate.name) {
1058
+ return null;
1059
+ }
1060
+ if (typeof candidate.description !== "string") {
1061
+ return null;
1062
+ }
1063
+ if (!Array.isArray(candidate.capabilities) || !candidate.capabilities.every((cap) => typeof cap === "string")) {
1064
+ return null;
1065
+ }
1066
+ if (candidate.deleted) {
1067
+ return null;
1068
+ }
1069
+ const card = candidate;
1070
+ if (card.payment && (typeof card.payment.chain !== "string" || typeof card.payment.network !== "string" || typeof card.payment.address !== "string")) {
1071
+ return null;
1072
+ }
1073
+ if (card.payment?.job_price !== null && card.payment?.job_price !== void 0 && (!Number.isInteger(card.payment.job_price) || card.payment.job_price < 0)) {
1074
+ return null;
1075
+ }
1076
+ const agentNetwork = card.payment?.network ?? "devnet";
1077
+ if (agentNetwork !== network) {
1078
+ return null;
1079
+ }
1080
+ const kTags = event.tags.filter((tag) => tag[0] === "k").map((tag) => parseInt(tag[1] ?? "", 10)).filter((kind) => !isNaN(kind));
1081
+ return {
1082
+ pubkey: event.pubkey,
1083
+ npub: nip19.npubEncode(event.pubkey),
1084
+ cards: [card],
1085
+ eventId: event.id,
1086
+ supportedKinds: kTags,
1087
+ lastSeen: event.created_at
1088
+ };
1089
+ }
1021
1090
  function buildAgentsFromEvents(events, network) {
1022
1091
  const latestByDTag = /* @__PURE__ */ new Map();
1023
1092
  for (const event of events) {
1024
1093
  if (!verifyEvent(event)) {
1025
1094
  continue;
1026
1095
  }
1027
- const dTag = event.tags.find((t) => t[0] === "d")?.[1] ?? "";
1096
+ const dTag = event.tags.find((tag) => tag[0] === "d")?.[1] ?? "";
1028
1097
  const key = `${event.pubkey}:${dTag}`;
1029
1098
  const prev = latestByDTag.get(key);
1030
1099
  if (!prev || event.created_at > prev.created_at) {
@@ -1033,82 +1102,51 @@ function buildAgentsFromEvents(events, network) {
1033
1102
  }
1034
1103
  const accumMap = /* @__PURE__ */ new Map();
1035
1104
  for (const event of latestByDTag.values()) {
1036
- try {
1037
- if (!event.content) {
1038
- continue;
1039
- }
1040
- const raw = JSON.parse(event.content);
1041
- if (!raw || typeof raw !== "object") {
1042
- continue;
1043
- }
1044
- if (typeof raw.name !== "string" || !raw.name) {
1045
- continue;
1046
- }
1047
- if (typeof raw.description !== "string") {
1048
- continue;
1049
- }
1050
- if (!Array.isArray(raw.capabilities) || !raw.capabilities.every((c) => typeof c === "string")) {
1051
- continue;
1052
- }
1053
- if (raw.deleted) {
1054
- continue;
1055
- }
1056
- const card = raw;
1057
- if (card.payment && (typeof card.payment.chain !== "string" || typeof card.payment.network !== "string" || typeof card.payment.address !== "string")) {
1058
- continue;
1059
- }
1060
- if (card.payment?.job_price !== null && card.payment?.job_price !== void 0 && (!Number.isInteger(card.payment.job_price) || card.payment.job_price < 0)) {
1061
- continue;
1062
- }
1063
- const agentNetwork = card.payment?.network ?? "devnet";
1064
- if (agentNetwork !== network) {
1065
- continue;
1066
- }
1067
- const kTags = event.tags.filter((t) => t[0] === "k").map((t) => parseInt(t[1] ?? "", 10)).filter((k) => !isNaN(k));
1068
- const entry = { card, kTags, createdAt: event.created_at };
1069
- const existing = accumMap.get(event.pubkey);
1070
- if (existing) {
1071
- const dupIndex = existing.entries.findIndex((e) => e.card.name === card.name);
1072
- if (dupIndex >= 0) {
1073
- if (entry.createdAt >= existing.entries[dupIndex].createdAt) {
1074
- existing.entries[dupIndex] = entry;
1105
+ const parsed = parseCapabilityEvent(event, network);
1106
+ if (!parsed) {
1107
+ continue;
1108
+ }
1109
+ const card = parsed.cards[0];
1110
+ const cardKinds = parsed.supportedKinds;
1111
+ const createdAt = parsed.lastSeen;
1112
+ const existing = accumMap.get(parsed.pubkey);
1113
+ if (existing) {
1114
+ const prevForName = existing.perCard.get(card.name);
1115
+ if (prevForName) {
1116
+ if (createdAt >= prevForName.createdAt) {
1117
+ const idx = existing.agent.cards.findIndex(
1118
+ (existingCard) => existingCard.name === card.name
1119
+ );
1120
+ if (idx >= 0) {
1121
+ existing.agent.cards[idx] = card;
1075
1122
  }
1076
- } else {
1077
- existing.entries.push(entry);
1078
- }
1079
- if (event.created_at > existing.lastSeen) {
1080
- existing.lastSeen = event.created_at;
1081
- existing.eventId = event.id;
1123
+ existing.perCard.set(card.name, { createdAt, kTags: cardKinds });
1082
1124
  }
1083
1125
  } else {
1084
- accumMap.set(event.pubkey, {
1085
- pubkey: event.pubkey,
1086
- npub: nip19.npubEncode(event.pubkey),
1087
- entries: [entry],
1088
- eventId: event.id,
1089
- lastSeen: event.created_at
1090
- });
1126
+ existing.agent.cards.push(card);
1127
+ existing.perCard.set(card.name, { createdAt, kTags: cardKinds });
1091
1128
  }
1092
- } catch {
1129
+ if (createdAt > existing.agent.lastSeen) {
1130
+ existing.agent.lastSeen = createdAt;
1131
+ existing.agent.eventId = parsed.eventId;
1132
+ }
1133
+ } else {
1134
+ accumMap.set(parsed.pubkey, {
1135
+ agent: parsed,
1136
+ perCard: /* @__PURE__ */ new Map([[card.name, { createdAt, kTags: cardKinds }]])
1137
+ });
1093
1138
  }
1094
1139
  }
1095
1140
  const agentMap = /* @__PURE__ */ new Map();
1096
1141
  for (const [pubkey, acc] of accumMap) {
1097
1142
  const kindsSet = /* @__PURE__ */ new Set();
1098
- for (const e of acc.entries) {
1099
- for (const k of e.kTags) {
1100
- kindsSet.add(k);
1101
- }
1102
- }
1103
- const supportedKinds = [...kindsSet];
1104
- agentMap.set(pubkey, {
1105
- pubkey: acc.pubkey,
1106
- npub: acc.npub,
1107
- cards: acc.entries.map((e) => e.card),
1108
- eventId: acc.eventId,
1109
- supportedKinds,
1110
- lastSeen: acc.lastSeen
1111
- });
1143
+ for (const { kTags } of acc.perCard.values()) {
1144
+ for (const kind of kTags) {
1145
+ kindsSet.add(kind);
1146
+ }
1147
+ }
1148
+ acc.agent.supportedKinds = [...kindsSet];
1149
+ agentMap.set(pubkey, acc.agent);
1112
1150
  }
1113
1151
  return agentMap;
1114
1152
  }
@@ -1116,21 +1154,6 @@ var DiscoveryService = class {
1116
1154
  constructor(pool) {
1117
1155
  this.pool = pool;
1118
1156
  }
1119
- /** Count elisym agents (kind:31990 with "elisym" tag). */
1120
- async fetchAllAgentCount() {
1121
- const events = await this.pool.querySync({
1122
- kinds: [KIND_APP_HANDLER],
1123
- "#t": ["elisym"]
1124
- });
1125
- const uniquePubkeys = /* @__PURE__ */ new Set();
1126
- for (const event of events) {
1127
- if (!verifyEvent(event)) {
1128
- continue;
1129
- }
1130
- uniquePubkeys.add(event.pubkey);
1131
- }
1132
- return uniquePubkeys.size;
1133
- }
1134
1157
  /**
1135
1158
  * Fetch a single page of elisym agents with relay-side pagination.
1136
1159
  * Uses `until` cursor for Nostr cursor-based pagination.
@@ -1238,6 +1261,19 @@ var DiscoveryService = class {
1238
1261
  const events = await this.pool.querySync(filter);
1239
1262
  const agentMap = buildAgentsFromEvents(events, network);
1240
1263
  const agents = Array.from(agentMap.values());
1264
+ return this.runEnrichment(agents, agentMap, NEVER_ABORTED_SIGNAL);
1265
+ }
1266
+ /**
1267
+ * Enrich an agent map with paid-job stats, feedback counters, and kind:0
1268
+ * metadata, then return them sorted by `compareAgentsByRank`. Mutates the
1269
+ * passed-in `Agent` objects in place.
1270
+ *
1271
+ * Shared between `fetchAgents` (one-shot) and `streamAgents` (post-EOSE
1272
+ * second pass). The `signal` short-circuits the post-query work; in-flight
1273
+ * pool queries are not cancellable today (they fall through to the standard
1274
+ * timeout) and the caller drops the resolved value.
1275
+ */
1276
+ async runEnrichment(agents, agentMap, signal) {
1241
1277
  const agentPubkeys = Array.from(agentMap.keys());
1242
1278
  if (agentPubkeys.length === 0) {
1243
1279
  return agents;
@@ -1245,9 +1281,9 @@ var DiscoveryService = class {
1245
1281
  const activitySince = Math.floor(Date.now() / 1e3) - RANKING_ACTIVITY_WINDOW_SECS;
1246
1282
  const resultKinds = /* @__PURE__ */ new Set();
1247
1283
  for (const agent of agentMap.values()) {
1248
- for (const k of agent.supportedKinds) {
1249
- if (k >= KIND_JOB_REQUEST_BASE && k < KIND_JOB_RESULT_BASE) {
1250
- resultKinds.add(KIND_JOB_RESULT_BASE + (k - KIND_JOB_REQUEST_BASE));
1284
+ for (const supportedKind of agent.supportedKinds) {
1285
+ if (supportedKind >= KIND_JOB_REQUEST_BASE && supportedKind < KIND_JOB_RESULT_BASE) {
1286
+ resultKinds.add(KIND_JOB_RESULT_BASE + (supportedKind - KIND_JOB_REQUEST_BASE));
1251
1287
  }
1252
1288
  }
1253
1289
  }
@@ -1267,6 +1303,9 @@ var DiscoveryService = class {
1267
1303
  ),
1268
1304
  this.enrichWithMetadata(agents)
1269
1305
  ]);
1306
+ if (signal.aborted) {
1307
+ return agents;
1308
+ }
1270
1309
  const deliveredJobsByProvider = /* @__PURE__ */ new Map();
1271
1310
  for (const ev of resultEvents) {
1272
1311
  if (!verifyEvent(ev)) {
@@ -1279,7 +1318,7 @@ var DiscoveryService = class {
1279
1318
  if (ev.created_at > agent.lastSeen) {
1280
1319
  agent.lastSeen = ev.created_at;
1281
1320
  }
1282
- const jobEventId = ev.tags.find((t) => t[0] === "e")?.[1];
1321
+ const jobEventId = ev.tags.find((tag) => tag[0] === "e")?.[1];
1283
1322
  if (jobEventId) {
1284
1323
  let delivered = deliveredJobsByProvider.get(ev.pubkey);
1285
1324
  if (!delivered) {
@@ -1293,7 +1332,7 @@ var DiscoveryService = class {
1293
1332
  if (!verifyEvent(ev)) {
1294
1333
  continue;
1295
1334
  }
1296
- const targetPubkey = ev.tags.find((t) => t[0] === "p")?.[1];
1335
+ const targetPubkey = ev.tags.find((tag) => tag[0] === "p")?.[1];
1297
1336
  if (!targetPubkey) {
1298
1337
  continue;
1299
1338
  }
@@ -1304,17 +1343,17 @@ var DiscoveryService = class {
1304
1343
  if (ev.created_at > agent.lastSeen) {
1305
1344
  agent.lastSeen = ev.created_at;
1306
1345
  }
1307
- const rating = ev.tags.find((t) => t[0] === "rating")?.[1];
1346
+ const rating = ev.tags.find((tag) => tag[0] === "rating")?.[1];
1308
1347
  if (rating === "1" || rating === "0") {
1309
1348
  agent.totalRatingCount = (agent.totalRatingCount ?? 0) + 1;
1310
1349
  if (rating === "1") {
1311
1350
  agent.positiveCount = (agent.positiveCount ?? 0) + 1;
1312
1351
  }
1313
1352
  }
1314
- const status = ev.tags.find((t) => t[0] === "status")?.[1];
1315
- const txTag = ev.tags.find((t) => t[0] === "tx");
1353
+ const status = ev.tags.find((tag) => tag[0] === "status")?.[1];
1354
+ const txTag = ev.tags.find((tag) => tag[0] === "tx");
1316
1355
  const txSignature = txTag?.[1];
1317
- const jobEventId = ev.tags.find((t) => t[0] === "e")?.[1];
1356
+ const jobEventId = ev.tags.find((tag) => tag[0] === "e")?.[1];
1318
1357
  const hasDeliveredResult = jobEventId !== void 0 && deliveredJobsByProvider.get(targetPubkey)?.has(jobEventId) === true;
1319
1358
  if (status === "payment-completed" && typeof txSignature === "string" && txSignature && hasDeliveredResult) {
1320
1359
  if (!agent.lastPaidJobAt || ev.created_at > agent.lastPaidJobAt) {
@@ -1326,6 +1365,135 @@ var DiscoveryService = class {
1326
1365
  agents.sort(compareAgentsByRank);
1327
1366
  return agents;
1328
1367
  }
1368
+ /**
1369
+ * Stream elisym agents progressively as relays deliver events.
1370
+ *
1371
+ * Two live subscriptions:
1372
+ * - kind:31990 (capability cards) - emits `onAgent(agent)` for every new or
1373
+ * updated `(pubkey, d-tag)`. The emitted Agent is the merged view across
1374
+ * all surviving cards for that author.
1375
+ * - kind:6100 (default-offset job results) tagged `t=elisym` since 30d ago -
1376
+ * emits `onPaidJob(pubkey, ts)` for each delivered result. Custom-kind
1377
+ * results (offset != 100) are not on this stream; they enter the final
1378
+ * ranking via the post-EOSE enrichment pass.
1379
+ *
1380
+ * After capabilities EOSE, an enrichment pass runs in parallel to the live
1381
+ * subscriptions and produces a ranked snapshot via `onComplete`. The snapshot
1382
+ * is a clone, so further live updates do not mutate it.
1383
+ *
1384
+ * `closer.close()` tears down both subscriptions and aborts an in-flight
1385
+ * enrichment. If `opts.signal` is provided, aborting it does the same.
1386
+ */
1387
+ streamAgents(network, opts) {
1388
+ const eventsByPubkey = /* @__PURE__ */ new Map();
1389
+ const agentByPubkey = /* @__PURE__ */ new Map();
1390
+ const eoseSeen = { caps: false, results: false };
1391
+ let enrichmentStarted = false;
1392
+ const enrichmentAbort = new AbortController();
1393
+ const onExternalAbort = () => enrichmentAbort.abort();
1394
+ if (opts.signal) {
1395
+ if (opts.signal.aborted) {
1396
+ enrichmentAbort.abort();
1397
+ } else {
1398
+ opts.signal.addEventListener("abort", onExternalAbort, { once: true });
1399
+ }
1400
+ }
1401
+ const checkEose = () => {
1402
+ if (eoseSeen.caps && eoseSeen.results) {
1403
+ opts.onEose?.();
1404
+ }
1405
+ };
1406
+ const startEnrichment = () => {
1407
+ if (enrichmentStarted) {
1408
+ return;
1409
+ }
1410
+ enrichmentStarted = true;
1411
+ const snapshotAgents = Array.from(agentByPubkey.values()).map((agent) => ({ ...agent }));
1412
+ const snapshotMap = new Map(snapshotAgents.map((agent) => [agent.pubkey, agent]));
1413
+ void this.runEnrichment(snapshotAgents, snapshotMap, enrichmentAbort.signal).then(
1414
+ (sorted) => {
1415
+ if (enrichmentAbort.signal.aborted) {
1416
+ return;
1417
+ }
1418
+ opts.onComplete?.(sorted);
1419
+ },
1420
+ () => {
1421
+ }
1422
+ );
1423
+ };
1424
+ const capSub = this.pool.subscribe(
1425
+ { kinds: [KIND_APP_HANDLER], "#t": ["elisym"] },
1426
+ (event) => {
1427
+ const dTag = event.tags.find((tag) => tag[0] === "d")?.[1] ?? "";
1428
+ let perDTag = eventsByPubkey.get(event.pubkey);
1429
+ const prev = perDTag?.get(dTag);
1430
+ if (prev && event.created_at <= prev.created_at) {
1431
+ return;
1432
+ }
1433
+ if (!verifyEvent(event)) {
1434
+ return;
1435
+ }
1436
+ if (!event.content) {
1437
+ return;
1438
+ }
1439
+ let payload;
1440
+ try {
1441
+ payload = JSON.parse(event.content);
1442
+ } catch {
1443
+ return;
1444
+ }
1445
+ const isTombstone = payload !== null && typeof payload === "object" && Boolean(payload.deleted);
1446
+ if (!isTombstone && !parseCapabilityEvent(event, network)) {
1447
+ return;
1448
+ }
1449
+ if (!perDTag) {
1450
+ perDTag = /* @__PURE__ */ new Map();
1451
+ eventsByPubkey.set(event.pubkey, perDTag);
1452
+ }
1453
+ perDTag.set(dTag, event);
1454
+ const merged = buildAgentsFromEvents(Array.from(perDTag.values()), network).get(
1455
+ event.pubkey
1456
+ );
1457
+ if (!merged) {
1458
+ agentByPubkey.delete(event.pubkey);
1459
+ return;
1460
+ }
1461
+ agentByPubkey.set(event.pubkey, merged);
1462
+ opts.onAgent(merged);
1463
+ },
1464
+ {
1465
+ oneose: () => {
1466
+ eoseSeen.caps = true;
1467
+ startEnrichment();
1468
+ checkEose();
1469
+ }
1470
+ }
1471
+ );
1472
+ const activitySince = Math.floor(Date.now() / 1e3) - RANKING_ACTIVITY_WINDOW_SECS;
1473
+ const resultsSub = this.pool.subscribe(
1474
+ { kinds: [KIND_JOB_RESULT], "#t": ["elisym"], since: activitySince },
1475
+ (event) => {
1476
+ if (!verifyEvent(event)) {
1477
+ return;
1478
+ }
1479
+ opts.onPaidJob?.(event.pubkey, event.created_at);
1480
+ },
1481
+ {
1482
+ oneose: () => {
1483
+ eoseSeen.results = true;
1484
+ checkEose();
1485
+ }
1486
+ }
1487
+ );
1488
+ return {
1489
+ close: (reason) => {
1490
+ capSub.close(reason);
1491
+ resultsSub.close(reason);
1492
+ enrichmentAbort.abort();
1493
+ opts.signal?.removeEventListener("abort", onExternalAbort);
1494
+ }
1495
+ };
1496
+ }
1329
1497
  /**
1330
1498
  * Publish a capability card (kind:31990) as a provider.
1331
1499
  * Solana address is validated for Base58 format only - full decode
@@ -2541,8 +2709,11 @@ var NostrPool = class {
2541
2709
  throw new Error(`Failed to publish to all ${this.relays.length} relays`);
2542
2710
  }
2543
2711
  }
2544
- subscribe(filter, onEvent) {
2545
- const rawSub = this.pool.subscribeMany(this.relays, filter, { onevent: onEvent });
2712
+ subscribe(filter, onEvent, opts) {
2713
+ const rawSub = this.pool.subscribeMany(this.relays, filter, {
2714
+ onevent: onEvent,
2715
+ oneose: opts?.oneose
2716
+ });
2546
2717
  const tracked = {
2547
2718
  close: (reason) => {
2548
2719
  this.activeSubscriptions.delete(tracked);
@@ -2893,6 +3064,83 @@ async function doVerifyOnce(rpc, txSignature, expectedRecipient) {
2893
3064
  }
2894
3065
  return { verified: false, txSignature: sigStr, reason: "recipient_mismatch" };
2895
3066
  }
3067
+ var DEFAULT_LIMIT = 1e3;
3068
+ var NATIVE_KEY = "native";
3069
+ async function aggregateNetworkStats(rpc, options) {
3070
+ const limit = options?.limit ?? DEFAULT_LIMIT;
3071
+ const concurrency = options?.concurrency ?? DEFAULTS.QUERY_MAX_CONCURRENCY;
3072
+ const tag = address(ELISYM_PROTOCOL_TAG);
3073
+ const signatures = await rpc.getSignaturesForAddress(tag, { limit, before: options?.before }).send();
3074
+ const validSigs = signatures.filter((entry) => entry.err === null);
3075
+ if (validSigs.length === 0) {
3076
+ return { jobCount: 0, volumeByAsset: {} };
3077
+ }
3078
+ const volumeByAsset = {};
3079
+ let jobCount = 0;
3080
+ for (let start = 0; start < validSigs.length; start += concurrency) {
3081
+ const batch = validSigs.slice(start, start + concurrency);
3082
+ const txResults = await Promise.all(
3083
+ batch.map(
3084
+ (entry) => rpc.getTransaction(entry.signature, {
3085
+ commitment: "confirmed",
3086
+ encoding: "json",
3087
+ maxSupportedTransactionVersion: 0
3088
+ }).send().catch(() => null)
3089
+ )
3090
+ );
3091
+ for (const tx of txResults) {
3092
+ if (!tx?.meta || tx.meta.err) {
3093
+ continue;
3094
+ }
3095
+ jobCount += 1;
3096
+ accumulateTransfers(tx, volumeByAsset);
3097
+ }
3098
+ }
3099
+ const latest = validSigs[0]?.signature;
3100
+ const oldest = validSigs.at(-1)?.signature;
3101
+ return {
3102
+ jobCount,
3103
+ volumeByAsset,
3104
+ latestSignature: latest,
3105
+ oldestSignature: oldest
3106
+ };
3107
+ }
3108
+ function accumulateTransfers(tx, volumeByAsset) {
3109
+ const raw = tx;
3110
+ const meta = raw.meta;
3111
+ if (!meta) {
3112
+ return;
3113
+ }
3114
+ const preTokens = meta.preTokenBalances ?? [];
3115
+ const postTokens = meta.postTokenBalances ?? [];
3116
+ const isSpl = postTokens.length > 0 || preTokens.length > 0;
3117
+ if (isSpl) {
3118
+ accumulateSplDeltas(preTokens, postTokens, volumeByAsset);
3119
+ return;
3120
+ }
3121
+ accumulateNativeDeltas(meta.preBalances, meta.postBalances, volumeByAsset);
3122
+ }
3123
+ function accumulateSplDeltas(pre, post, volumeByAsset) {
3124
+ for (const postEntry of post) {
3125
+ const preEntry = pre.find((entry) => entry.accountIndex === postEntry.accountIndex);
3126
+ const preAmount = preEntry ? BigInt(preEntry.uiTokenAmount.amount) : 0n;
3127
+ const postAmount = BigInt(postEntry.uiTokenAmount.amount);
3128
+ const delta = postAmount - preAmount;
3129
+ if (delta > 0n) {
3130
+ volumeByAsset[postEntry.mint] = (volumeByAsset[postEntry.mint] ?? 0n) + delta;
3131
+ }
3132
+ }
3133
+ }
3134
+ function accumulateNativeDeltas(pre, post, volumeByAsset) {
3135
+ for (let i = 1; i < post.length; i++) {
3136
+ const preValue = pre[i] ?? 0n;
3137
+ const postValue = post[i] ?? 0n;
3138
+ const delta = BigInt(postValue) - BigInt(preValue);
3139
+ if (delta > 0n) {
3140
+ volumeByAsset[NATIVE_KEY] = (volumeByAsset[NATIVE_KEY] ?? 0n) + delta;
3141
+ }
3142
+ }
3143
+ }
2896
3144
  var SessionSpendLimitEntrySchema = z.object({
2897
3145
  chain: z.enum(["solana"]),
2898
3146
  token: z.string().min(1).max(16).regex(/^[a-z0-9]+$/, "token must be lowercase alphanumeric"),
@@ -3093,6 +3341,6 @@ function makeCensor() {
3093
3341
  };
3094
3342
  }
3095
3343
 
3096
- export { BoundedSet, DEFAULTS, DEFAULT_KIND_OFFSET, DEFAULT_REDACT_PATHS, DiscoveryService, ElisymClient, ElisymIdentity, GlobalConfigSchema, INPUT_REDACT_PATHS, KIND_APP_HANDLER, KIND_JOB_FEEDBACK, KIND_JOB_REQUEST, KIND_JOB_REQUEST_BASE, KIND_JOB_RESULT, KIND_JOB_RESULT_BASE, KIND_PING, KIND_PONG, KNOWN_ASSETS, LAMPORTS_PER_SOL, LIMITS, MarketplaceService, MediaService, NATIVE_SOL, NostrPool, PROTOCOL_FEE_BPS, PROTOCOL_PROGRAM_ID_DEVNET, PROTOCOL_TREASURY, PaymentRequestSchema, PingService, RELAYS, SECRET_REDACT_PATHS, SessionSpendLimitEntrySchema, SolanaPaymentStrategy, USDC_SOLANA_DEVNET, assertExpiry, assertLamports, assetByKey, assetKey, buildPaymentInstructions, calculateProtocolFee, clearPriorityFeeCache, clearProtocolConfigCache, clearQuickVerifyCache, compareAgentsByRank, computeRankKey, createPaymentRequestWithOnchainConfig, createSlidingWindowLimiter, estimatePriorityFeeMicroLamports, estimateSolFeeLamports, formatAssetAmount, formatFeeBreakdown, formatSol, getProtocolConfig, getProtocolProgramId, jobRequestKind, jobResultKind, makeCensor, nip44Decrypt, nip44Encrypt, parseAssetAmount, parsePaymentRequest, pickPercentileFee, resolveAssetFromPaymentRequest, resolveKnownAsset, timeAgo, toDTag, truncateKey, validateAgentName, validateExpiry, verifyJobPaymentQuick };
3344
+ export { BoundedSet, DEFAULTS, DEFAULT_KIND_OFFSET, DEFAULT_REDACT_PATHS, DiscoveryService, ELISYM_PROTOCOL_TAG, ElisymClient, ElisymIdentity, GlobalConfigSchema, INPUT_REDACT_PATHS, KIND_APP_HANDLER, KIND_JOB_FEEDBACK, KIND_JOB_REQUEST, KIND_JOB_REQUEST_BASE, KIND_JOB_RESULT, KIND_JOB_RESULT_BASE, KIND_PING, KIND_PONG, KNOWN_ASSETS, LAMPORTS_PER_SOL, LIMITS, MarketplaceService, MediaService, NATIVE_SOL, NostrPool, PROTOCOL_FEE_BPS, PROTOCOL_PROGRAM_ID_DEVNET, PROTOCOL_TREASURY, PaymentRequestSchema, PingService, RELAYS, SECRET_REDACT_PATHS, SessionSpendLimitEntrySchema, SolanaPaymentStrategy, USDC_SOLANA_DEVNET, aggregateNetworkStats, assertExpiry, assertLamports, assetByKey, assetKey, buildPaymentInstructions, calculateProtocolFee, clearPriorityFeeCache, clearProtocolConfigCache, clearQuickVerifyCache, compareAgentsByRank, computeRankKey, createPaymentRequestWithOnchainConfig, createSlidingWindowLimiter, estimatePriorityFeeMicroLamports, estimateSolFeeLamports, formatAssetAmount, formatFeeBreakdown, formatSol, getProtocolConfig, getProtocolProgramId, jobRequestKind, jobResultKind, makeCensor, nip44Decrypt, nip44Encrypt, parseAssetAmount, parsePaymentRequest, pickPercentileFee, resolveAssetFromPaymentRequest, resolveKnownAsset, timeAgo, toDTag, truncateKey, validateAgentName, validateExpiry, verifyJobPaymentQuick };
3097
3345
  //# sourceMappingURL=index.js.map
3098
3346
  //# sourceMappingURL=index.js.map