@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/README.md +32 -0
- package/dist/agent-store.cjs.map +1 -1
- package/dist/agent-store.js.map +1 -1
- package/dist/index.cjs +356 -106
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +109 -8
- package/dist/index.d.ts +109 -8
- package/dist/index.js +355 -107
- package/dist/index.js.map +1 -1
- package/dist/skills.cjs.map +1 -1
- package/dist/skills.js.map +1 -1
- package/package.json +3 -1
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var memo = require('@solana-program/memo');
|
|
3
4
|
var system = require('@solana-program/system');
|
|
4
5
|
var token = require('@solana-program/token');
|
|
5
6
|
var kit = require('@solana/kit');
|
|
@@ -64,6 +65,7 @@ var LAMPORTS_PER_SOL = 1e9;
|
|
|
64
65
|
var PROTOCOL_FEE_BPS = 300;
|
|
65
66
|
var PROTOCOL_TREASURY = "GY7vnWMkKpftU4nQ16C2ATkj1JwrQpHhknkaBUn67VTy";
|
|
66
67
|
var PROTOCOL_PROGRAM_ID_DEVNET = "BrX1CRkSgvcjxBvc2bgc3QqgWjinusofDmeP7ZVxvwrE";
|
|
68
|
+
var ELISYM_PROTOCOL_TAG = "ELiZksgwDt41LaeuPDLkUfWgFXhGgVayTMP7L5nTSEL8";
|
|
67
69
|
function getProtocolProgramId(cluster) {
|
|
68
70
|
switch (cluster) {
|
|
69
71
|
case "devnet":
|
|
@@ -119,13 +121,13 @@ function decodeConfig(encodedAccount) {
|
|
|
119
121
|
getConfigDecoder()
|
|
120
122
|
);
|
|
121
123
|
}
|
|
122
|
-
async function fetchConfig(rpc,
|
|
123
|
-
const maybeAccount = await fetchMaybeConfig(rpc,
|
|
124
|
+
async function fetchConfig(rpc, address4, config) {
|
|
125
|
+
const maybeAccount = await fetchMaybeConfig(rpc, address4, config);
|
|
124
126
|
kit.assertAccountExists(maybeAccount);
|
|
125
127
|
return maybeAccount;
|
|
126
128
|
}
|
|
127
|
-
async function fetchMaybeConfig(rpc,
|
|
128
|
-
const maybeAccount = await kit.fetchEncodedAccount(rpc,
|
|
129
|
+
async function fetchMaybeConfig(rpc, address4, config) {
|
|
130
|
+
const maybeAccount = await kit.fetchEncodedAccount(rpc, address4, config);
|
|
129
131
|
return decodeConfig(maybeAccount);
|
|
130
132
|
}
|
|
131
133
|
if (process.env.NODE_ENV !== "production") ;
|
|
@@ -597,7 +599,9 @@ var SolanaPaymentStrategy = class {
|
|
|
597
599
|
if (!Number.isInteger(computeUnitLimit) || computeUnitLimit <= 0) {
|
|
598
600
|
throw new Error(`Invalid computeUnitLimit: ${computeUnitLimit}. Must be a positive integer.`);
|
|
599
601
|
}
|
|
600
|
-
const paymentInstructions = await buildPaymentInstructions(paymentRequest, payerSigner
|
|
602
|
+
const paymentInstructions = await buildPaymentInstructions(paymentRequest, payerSigner, {
|
|
603
|
+
jobEventId: options?.jobEventId
|
|
604
|
+
});
|
|
601
605
|
const priorityFeeMicroLamports = options?.priorityFeeMicroLamports ?? await estimatePriorityFeeMicroLamports(rpc, {
|
|
602
606
|
percentile: options?.priorityFeePercentile ?? DEFAULT_PRIORITY_FEE_PERCENTILE
|
|
603
607
|
});
|
|
@@ -894,9 +898,10 @@ function bigIntDelta(post, pre) {
|
|
|
894
898
|
function waitMs(ms) {
|
|
895
899
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
896
900
|
}
|
|
897
|
-
async function buildPaymentInstructions(paymentRequest, payerSigner) {
|
|
901
|
+
async function buildPaymentInstructions(paymentRequest, payerSigner, options) {
|
|
898
902
|
const recipient = kit.address(paymentRequest.recipient);
|
|
899
903
|
const reference = kit.address(paymentRequest.reference);
|
|
904
|
+
const protocolTag = kit.address(ELISYM_PROTOCOL_TAG);
|
|
900
905
|
const feeAmount = paymentRequest.fee_amount ?? 0;
|
|
901
906
|
const providerAmount = paymentRequest.fee_address && feeAmount > 0 ? paymentRequest.amount - feeAmount : paymentRequest.amount;
|
|
902
907
|
if (providerAmount <= 0) {
|
|
@@ -904,6 +909,7 @@ async function buildPaymentInstructions(paymentRequest, payerSigner) {
|
|
|
904
909
|
`Fee amount (${feeAmount}) exceeds or equals total amount (${paymentRequest.amount}). Cannot create transaction with non-positive provider amount.`
|
|
905
910
|
);
|
|
906
911
|
}
|
|
912
|
+
const memoInstruction = options?.jobEventId ? memo.getAddMemoInstruction({ memo: `elisym:v1:${options.jobEventId}` }) : null;
|
|
907
913
|
const asset = resolveAssetFromPaymentRequest(paymentRequest);
|
|
908
914
|
if (!asset.mint) {
|
|
909
915
|
const providerTransferIx2 = system.getTransferSolInstruction({
|
|
@@ -911,14 +917,19 @@ async function buildPaymentInstructions(paymentRequest, payerSigner) {
|
|
|
911
917
|
destination: recipient,
|
|
912
918
|
amount: BigInt(providerAmount)
|
|
913
919
|
});
|
|
914
|
-
const
|
|
920
|
+
const providerTransferIxWithMarkers2 = {
|
|
915
921
|
...providerTransferIx2,
|
|
916
922
|
accounts: [
|
|
917
923
|
...providerTransferIx2.accounts,
|
|
918
|
-
{ address: reference, role: kit.AccountRole.READONLY }
|
|
924
|
+
{ address: reference, role: kit.AccountRole.READONLY },
|
|
925
|
+
{ address: protocolTag, role: kit.AccountRole.READONLY }
|
|
919
926
|
]
|
|
920
927
|
};
|
|
921
|
-
const instructions2 = [
|
|
928
|
+
const instructions2 = [];
|
|
929
|
+
if (memoInstruction) {
|
|
930
|
+
instructions2.push(memoInstruction);
|
|
931
|
+
}
|
|
932
|
+
instructions2.push(providerTransferIxWithMarkers2);
|
|
922
933
|
if (paymentRequest.fee_address && feeAmount > 0) {
|
|
923
934
|
instructions2.push(
|
|
924
935
|
system.getTransferSolInstruction({
|
|
@@ -943,6 +954,9 @@ async function buildPaymentInstructions(paymentRequest, payerSigner) {
|
|
|
943
954
|
mint
|
|
944
955
|
});
|
|
945
956
|
const instructions = [];
|
|
957
|
+
if (memoInstruction) {
|
|
958
|
+
instructions.push(memoInstruction);
|
|
959
|
+
}
|
|
946
960
|
instructions.push(
|
|
947
961
|
token.getCreateAssociatedTokenIdempotentInstruction(
|
|
948
962
|
{
|
|
@@ -982,11 +996,15 @@ async function buildPaymentInstructions(paymentRequest, payerSigner) {
|
|
|
982
996
|
amount: BigInt(providerAmount),
|
|
983
997
|
decimals: asset.decimals
|
|
984
998
|
});
|
|
985
|
-
const
|
|
999
|
+
const providerTransferIxWithMarkers = {
|
|
986
1000
|
...providerTransferIx,
|
|
987
|
-
accounts: [
|
|
1001
|
+
accounts: [
|
|
1002
|
+
...providerTransferIx.accounts,
|
|
1003
|
+
{ address: reference, role: kit.AccountRole.READONLY },
|
|
1004
|
+
{ address: protocolTag, role: kit.AccountRole.READONLY }
|
|
1005
|
+
]
|
|
988
1006
|
};
|
|
989
|
-
instructions.push(
|
|
1007
|
+
instructions.push(providerTransferIxWithMarkers);
|
|
990
1008
|
if (treasuryAta && paymentRequest.fee_address && feeAmount > 0) {
|
|
991
1009
|
instructions.push(
|
|
992
1010
|
token.getTransferCheckedInstruction({
|
|
@@ -1014,6 +1032,7 @@ async function createPaymentRequestWithOnchainConfig(rpc, programId, recipient,
|
|
|
1014
1032
|
var RANKING_ACTIVITY_WINDOW_SECS = 30 * 24 * 60 * 60;
|
|
1015
1033
|
var RANKING_BUCKET_SIZE_SECS = 60;
|
|
1016
1034
|
var COLD_START_BUCKET = -Infinity;
|
|
1035
|
+
var NEVER_ABORTED_SIGNAL = new AbortController().signal;
|
|
1017
1036
|
function toDTag(name) {
|
|
1018
1037
|
const tag = name.toLowerCase().replace(/[^a-z0-9\s-]/g, (ch) => "_" + ch.charCodeAt(0).toString(16).padStart(2, "0")).replace(/\s+/g, "-").replace(/^-+|-+$/g, "");
|
|
1019
1038
|
if (!tag) {
|
|
@@ -1043,13 +1062,63 @@ function compareAgentsByRank(a, b) {
|
|
|
1043
1062
|
}
|
|
1044
1063
|
return kb.lastSeen - ka.lastSeen;
|
|
1045
1064
|
}
|
|
1065
|
+
function parseCapabilityEvent(event, network) {
|
|
1066
|
+
if (!nostrTools.verifyEvent(event)) {
|
|
1067
|
+
return null;
|
|
1068
|
+
}
|
|
1069
|
+
if (!event.content) {
|
|
1070
|
+
return null;
|
|
1071
|
+
}
|
|
1072
|
+
let raw;
|
|
1073
|
+
try {
|
|
1074
|
+
raw = JSON.parse(event.content);
|
|
1075
|
+
} catch {
|
|
1076
|
+
return null;
|
|
1077
|
+
}
|
|
1078
|
+
if (!raw || typeof raw !== "object") {
|
|
1079
|
+
return null;
|
|
1080
|
+
}
|
|
1081
|
+
const candidate = raw;
|
|
1082
|
+
if (typeof candidate.name !== "string" || !candidate.name) {
|
|
1083
|
+
return null;
|
|
1084
|
+
}
|
|
1085
|
+
if (typeof candidate.description !== "string") {
|
|
1086
|
+
return null;
|
|
1087
|
+
}
|
|
1088
|
+
if (!Array.isArray(candidate.capabilities) || !candidate.capabilities.every((cap) => typeof cap === "string")) {
|
|
1089
|
+
return null;
|
|
1090
|
+
}
|
|
1091
|
+
if (candidate.deleted) {
|
|
1092
|
+
return null;
|
|
1093
|
+
}
|
|
1094
|
+
const card = candidate;
|
|
1095
|
+
if (card.payment && (typeof card.payment.chain !== "string" || typeof card.payment.network !== "string" || typeof card.payment.address !== "string")) {
|
|
1096
|
+
return null;
|
|
1097
|
+
}
|
|
1098
|
+
if (card.payment?.job_price !== null && card.payment?.job_price !== void 0 && (!Number.isInteger(card.payment.job_price) || card.payment.job_price < 0)) {
|
|
1099
|
+
return null;
|
|
1100
|
+
}
|
|
1101
|
+
const agentNetwork = card.payment?.network ?? "devnet";
|
|
1102
|
+
if (agentNetwork !== network) {
|
|
1103
|
+
return null;
|
|
1104
|
+
}
|
|
1105
|
+
const kTags = event.tags.filter((tag) => tag[0] === "k").map((tag) => parseInt(tag[1] ?? "", 10)).filter((kind) => !isNaN(kind));
|
|
1106
|
+
return {
|
|
1107
|
+
pubkey: event.pubkey,
|
|
1108
|
+
npub: nostrTools.nip19.npubEncode(event.pubkey),
|
|
1109
|
+
cards: [card],
|
|
1110
|
+
eventId: event.id,
|
|
1111
|
+
supportedKinds: kTags,
|
|
1112
|
+
lastSeen: event.created_at
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1046
1115
|
function buildAgentsFromEvents(events, network) {
|
|
1047
1116
|
const latestByDTag = /* @__PURE__ */ new Map();
|
|
1048
1117
|
for (const event of events) {
|
|
1049
1118
|
if (!nostrTools.verifyEvent(event)) {
|
|
1050
1119
|
continue;
|
|
1051
1120
|
}
|
|
1052
|
-
const dTag = event.tags.find((
|
|
1121
|
+
const dTag = event.tags.find((tag) => tag[0] === "d")?.[1] ?? "";
|
|
1053
1122
|
const key = `${event.pubkey}:${dTag}`;
|
|
1054
1123
|
const prev = latestByDTag.get(key);
|
|
1055
1124
|
if (!prev || event.created_at > prev.created_at) {
|
|
@@ -1058,82 +1127,51 @@ function buildAgentsFromEvents(events, network) {
|
|
|
1058
1127
|
}
|
|
1059
1128
|
const accumMap = /* @__PURE__ */ new Map();
|
|
1060
1129
|
for (const event of latestByDTag.values()) {
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
if (raw.deleted) {
|
|
1079
|
-
continue;
|
|
1080
|
-
}
|
|
1081
|
-
const card = raw;
|
|
1082
|
-
if (card.payment && (typeof card.payment.chain !== "string" || typeof card.payment.network !== "string" || typeof card.payment.address !== "string")) {
|
|
1083
|
-
continue;
|
|
1084
|
-
}
|
|
1085
|
-
if (card.payment?.job_price !== null && card.payment?.job_price !== void 0 && (!Number.isInteger(card.payment.job_price) || card.payment.job_price < 0)) {
|
|
1086
|
-
continue;
|
|
1087
|
-
}
|
|
1088
|
-
const agentNetwork = card.payment?.network ?? "devnet";
|
|
1089
|
-
if (agentNetwork !== network) {
|
|
1090
|
-
continue;
|
|
1091
|
-
}
|
|
1092
|
-
const kTags = event.tags.filter((t) => t[0] === "k").map((t) => parseInt(t[1] ?? "", 10)).filter((k) => !isNaN(k));
|
|
1093
|
-
const entry = { card, kTags, createdAt: event.created_at };
|
|
1094
|
-
const existing = accumMap.get(event.pubkey);
|
|
1095
|
-
if (existing) {
|
|
1096
|
-
const dupIndex = existing.entries.findIndex((e) => e.card.name === card.name);
|
|
1097
|
-
if (dupIndex >= 0) {
|
|
1098
|
-
if (entry.createdAt >= existing.entries[dupIndex].createdAt) {
|
|
1099
|
-
existing.entries[dupIndex] = entry;
|
|
1130
|
+
const parsed = parseCapabilityEvent(event, network);
|
|
1131
|
+
if (!parsed) {
|
|
1132
|
+
continue;
|
|
1133
|
+
}
|
|
1134
|
+
const card = parsed.cards[0];
|
|
1135
|
+
const cardKinds = parsed.supportedKinds;
|
|
1136
|
+
const createdAt = parsed.lastSeen;
|
|
1137
|
+
const existing = accumMap.get(parsed.pubkey);
|
|
1138
|
+
if (existing) {
|
|
1139
|
+
const prevForName = existing.perCard.get(card.name);
|
|
1140
|
+
if (prevForName) {
|
|
1141
|
+
if (createdAt >= prevForName.createdAt) {
|
|
1142
|
+
const idx = existing.agent.cards.findIndex(
|
|
1143
|
+
(existingCard) => existingCard.name === card.name
|
|
1144
|
+
);
|
|
1145
|
+
if (idx >= 0) {
|
|
1146
|
+
existing.agent.cards[idx] = card;
|
|
1100
1147
|
}
|
|
1101
|
-
|
|
1102
|
-
existing.entries.push(entry);
|
|
1103
|
-
}
|
|
1104
|
-
if (event.created_at > existing.lastSeen) {
|
|
1105
|
-
existing.lastSeen = event.created_at;
|
|
1106
|
-
existing.eventId = event.id;
|
|
1148
|
+
existing.perCard.set(card.name, { createdAt, kTags: cardKinds });
|
|
1107
1149
|
}
|
|
1108
1150
|
} else {
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
npub: nostrTools.nip19.npubEncode(event.pubkey),
|
|
1112
|
-
entries: [entry],
|
|
1113
|
-
eventId: event.id,
|
|
1114
|
-
lastSeen: event.created_at
|
|
1115
|
-
});
|
|
1151
|
+
existing.agent.cards.push(card);
|
|
1152
|
+
existing.perCard.set(card.name, { createdAt, kTags: cardKinds });
|
|
1116
1153
|
}
|
|
1117
|
-
|
|
1154
|
+
if (createdAt > existing.agent.lastSeen) {
|
|
1155
|
+
existing.agent.lastSeen = createdAt;
|
|
1156
|
+
existing.agent.eventId = parsed.eventId;
|
|
1157
|
+
}
|
|
1158
|
+
} else {
|
|
1159
|
+
accumMap.set(parsed.pubkey, {
|
|
1160
|
+
agent: parsed,
|
|
1161
|
+
perCard: /* @__PURE__ */ new Map([[card.name, { createdAt, kTags: cardKinds }]])
|
|
1162
|
+
});
|
|
1118
1163
|
}
|
|
1119
1164
|
}
|
|
1120
1165
|
const agentMap = /* @__PURE__ */ new Map();
|
|
1121
1166
|
for (const [pubkey, acc] of accumMap) {
|
|
1122
1167
|
const kindsSet = /* @__PURE__ */ new Set();
|
|
1123
|
-
for (const
|
|
1124
|
-
for (const
|
|
1125
|
-
kindsSet.add(
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
|
-
|
|
1129
|
-
agentMap.set(pubkey,
|
|
1130
|
-
pubkey: acc.pubkey,
|
|
1131
|
-
npub: acc.npub,
|
|
1132
|
-
cards: acc.entries.map((e) => e.card),
|
|
1133
|
-
eventId: acc.eventId,
|
|
1134
|
-
supportedKinds,
|
|
1135
|
-
lastSeen: acc.lastSeen
|
|
1136
|
-
});
|
|
1168
|
+
for (const { kTags } of acc.perCard.values()) {
|
|
1169
|
+
for (const kind of kTags) {
|
|
1170
|
+
kindsSet.add(kind);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
acc.agent.supportedKinds = [...kindsSet];
|
|
1174
|
+
agentMap.set(pubkey, acc.agent);
|
|
1137
1175
|
}
|
|
1138
1176
|
return agentMap;
|
|
1139
1177
|
}
|
|
@@ -1141,21 +1179,6 @@ var DiscoveryService = class {
|
|
|
1141
1179
|
constructor(pool) {
|
|
1142
1180
|
this.pool = pool;
|
|
1143
1181
|
}
|
|
1144
|
-
/** Count elisym agents (kind:31990 with "elisym" tag). */
|
|
1145
|
-
async fetchAllAgentCount() {
|
|
1146
|
-
const events = await this.pool.querySync({
|
|
1147
|
-
kinds: [KIND_APP_HANDLER],
|
|
1148
|
-
"#t": ["elisym"]
|
|
1149
|
-
});
|
|
1150
|
-
const uniquePubkeys = /* @__PURE__ */ new Set();
|
|
1151
|
-
for (const event of events) {
|
|
1152
|
-
if (!nostrTools.verifyEvent(event)) {
|
|
1153
|
-
continue;
|
|
1154
|
-
}
|
|
1155
|
-
uniquePubkeys.add(event.pubkey);
|
|
1156
|
-
}
|
|
1157
|
-
return uniquePubkeys.size;
|
|
1158
|
-
}
|
|
1159
1182
|
/**
|
|
1160
1183
|
* Fetch a single page of elisym agents with relay-side pagination.
|
|
1161
1184
|
* Uses `until` cursor for Nostr cursor-based pagination.
|
|
@@ -1263,6 +1286,19 @@ var DiscoveryService = class {
|
|
|
1263
1286
|
const events = await this.pool.querySync(filter);
|
|
1264
1287
|
const agentMap = buildAgentsFromEvents(events, network);
|
|
1265
1288
|
const agents = Array.from(agentMap.values());
|
|
1289
|
+
return this.runEnrichment(agents, agentMap, NEVER_ABORTED_SIGNAL);
|
|
1290
|
+
}
|
|
1291
|
+
/**
|
|
1292
|
+
* Enrich an agent map with paid-job stats, feedback counters, and kind:0
|
|
1293
|
+
* metadata, then return them sorted by `compareAgentsByRank`. Mutates the
|
|
1294
|
+
* passed-in `Agent` objects in place.
|
|
1295
|
+
*
|
|
1296
|
+
* Shared between `fetchAgents` (one-shot) and `streamAgents` (post-EOSE
|
|
1297
|
+
* second pass). The `signal` short-circuits the post-query work; in-flight
|
|
1298
|
+
* pool queries are not cancellable today (they fall through to the standard
|
|
1299
|
+
* timeout) and the caller drops the resolved value.
|
|
1300
|
+
*/
|
|
1301
|
+
async runEnrichment(agents, agentMap, signal) {
|
|
1266
1302
|
const agentPubkeys = Array.from(agentMap.keys());
|
|
1267
1303
|
if (agentPubkeys.length === 0) {
|
|
1268
1304
|
return agents;
|
|
@@ -1270,9 +1306,9 @@ var DiscoveryService = class {
|
|
|
1270
1306
|
const activitySince = Math.floor(Date.now() / 1e3) - RANKING_ACTIVITY_WINDOW_SECS;
|
|
1271
1307
|
const resultKinds = /* @__PURE__ */ new Set();
|
|
1272
1308
|
for (const agent of agentMap.values()) {
|
|
1273
|
-
for (const
|
|
1274
|
-
if (
|
|
1275
|
-
resultKinds.add(KIND_JOB_RESULT_BASE + (
|
|
1309
|
+
for (const supportedKind of agent.supportedKinds) {
|
|
1310
|
+
if (supportedKind >= KIND_JOB_REQUEST_BASE && supportedKind < KIND_JOB_RESULT_BASE) {
|
|
1311
|
+
resultKinds.add(KIND_JOB_RESULT_BASE + (supportedKind - KIND_JOB_REQUEST_BASE));
|
|
1276
1312
|
}
|
|
1277
1313
|
}
|
|
1278
1314
|
}
|
|
@@ -1292,6 +1328,9 @@ var DiscoveryService = class {
|
|
|
1292
1328
|
),
|
|
1293
1329
|
this.enrichWithMetadata(agents)
|
|
1294
1330
|
]);
|
|
1331
|
+
if (signal.aborted) {
|
|
1332
|
+
return agents;
|
|
1333
|
+
}
|
|
1295
1334
|
const deliveredJobsByProvider = /* @__PURE__ */ new Map();
|
|
1296
1335
|
for (const ev of resultEvents) {
|
|
1297
1336
|
if (!nostrTools.verifyEvent(ev)) {
|
|
@@ -1304,7 +1343,7 @@ var DiscoveryService = class {
|
|
|
1304
1343
|
if (ev.created_at > agent.lastSeen) {
|
|
1305
1344
|
agent.lastSeen = ev.created_at;
|
|
1306
1345
|
}
|
|
1307
|
-
const jobEventId = ev.tags.find((
|
|
1346
|
+
const jobEventId = ev.tags.find((tag) => tag[0] === "e")?.[1];
|
|
1308
1347
|
if (jobEventId) {
|
|
1309
1348
|
let delivered = deliveredJobsByProvider.get(ev.pubkey);
|
|
1310
1349
|
if (!delivered) {
|
|
@@ -1318,7 +1357,7 @@ var DiscoveryService = class {
|
|
|
1318
1357
|
if (!nostrTools.verifyEvent(ev)) {
|
|
1319
1358
|
continue;
|
|
1320
1359
|
}
|
|
1321
|
-
const targetPubkey = ev.tags.find((
|
|
1360
|
+
const targetPubkey = ev.tags.find((tag) => tag[0] === "p")?.[1];
|
|
1322
1361
|
if (!targetPubkey) {
|
|
1323
1362
|
continue;
|
|
1324
1363
|
}
|
|
@@ -1329,17 +1368,17 @@ var DiscoveryService = class {
|
|
|
1329
1368
|
if (ev.created_at > agent.lastSeen) {
|
|
1330
1369
|
agent.lastSeen = ev.created_at;
|
|
1331
1370
|
}
|
|
1332
|
-
const rating = ev.tags.find((
|
|
1371
|
+
const rating = ev.tags.find((tag) => tag[0] === "rating")?.[1];
|
|
1333
1372
|
if (rating === "1" || rating === "0") {
|
|
1334
1373
|
agent.totalRatingCount = (agent.totalRatingCount ?? 0) + 1;
|
|
1335
1374
|
if (rating === "1") {
|
|
1336
1375
|
agent.positiveCount = (agent.positiveCount ?? 0) + 1;
|
|
1337
1376
|
}
|
|
1338
1377
|
}
|
|
1339
|
-
const status = ev.tags.find((
|
|
1340
|
-
const txTag = ev.tags.find((
|
|
1378
|
+
const status = ev.tags.find((tag) => tag[0] === "status")?.[1];
|
|
1379
|
+
const txTag = ev.tags.find((tag) => tag[0] === "tx");
|
|
1341
1380
|
const txSignature = txTag?.[1];
|
|
1342
|
-
const jobEventId = ev.tags.find((
|
|
1381
|
+
const jobEventId = ev.tags.find((tag) => tag[0] === "e")?.[1];
|
|
1343
1382
|
const hasDeliveredResult = jobEventId !== void 0 && deliveredJobsByProvider.get(targetPubkey)?.has(jobEventId) === true;
|
|
1344
1383
|
if (status === "payment-completed" && typeof txSignature === "string" && txSignature && hasDeliveredResult) {
|
|
1345
1384
|
if (!agent.lastPaidJobAt || ev.created_at > agent.lastPaidJobAt) {
|
|
@@ -1351,6 +1390,135 @@ var DiscoveryService = class {
|
|
|
1351
1390
|
agents.sort(compareAgentsByRank);
|
|
1352
1391
|
return agents;
|
|
1353
1392
|
}
|
|
1393
|
+
/**
|
|
1394
|
+
* Stream elisym agents progressively as relays deliver events.
|
|
1395
|
+
*
|
|
1396
|
+
* Two live subscriptions:
|
|
1397
|
+
* - kind:31990 (capability cards) - emits `onAgent(agent)` for every new or
|
|
1398
|
+
* updated `(pubkey, d-tag)`. The emitted Agent is the merged view across
|
|
1399
|
+
* all surviving cards for that author.
|
|
1400
|
+
* - kind:6100 (default-offset job results) tagged `t=elisym` since 30d ago -
|
|
1401
|
+
* emits `onPaidJob(pubkey, ts)` for each delivered result. Custom-kind
|
|
1402
|
+
* results (offset != 100) are not on this stream; they enter the final
|
|
1403
|
+
* ranking via the post-EOSE enrichment pass.
|
|
1404
|
+
*
|
|
1405
|
+
* After capabilities EOSE, an enrichment pass runs in parallel to the live
|
|
1406
|
+
* subscriptions and produces a ranked snapshot via `onComplete`. The snapshot
|
|
1407
|
+
* is a clone, so further live updates do not mutate it.
|
|
1408
|
+
*
|
|
1409
|
+
* `closer.close()` tears down both subscriptions and aborts an in-flight
|
|
1410
|
+
* enrichment. If `opts.signal` is provided, aborting it does the same.
|
|
1411
|
+
*/
|
|
1412
|
+
streamAgents(network, opts) {
|
|
1413
|
+
const eventsByPubkey = /* @__PURE__ */ new Map();
|
|
1414
|
+
const agentByPubkey = /* @__PURE__ */ new Map();
|
|
1415
|
+
const eoseSeen = { caps: false, results: false };
|
|
1416
|
+
let enrichmentStarted = false;
|
|
1417
|
+
const enrichmentAbort = new AbortController();
|
|
1418
|
+
const onExternalAbort = () => enrichmentAbort.abort();
|
|
1419
|
+
if (opts.signal) {
|
|
1420
|
+
if (opts.signal.aborted) {
|
|
1421
|
+
enrichmentAbort.abort();
|
|
1422
|
+
} else {
|
|
1423
|
+
opts.signal.addEventListener("abort", onExternalAbort, { once: true });
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
const checkEose = () => {
|
|
1427
|
+
if (eoseSeen.caps && eoseSeen.results) {
|
|
1428
|
+
opts.onEose?.();
|
|
1429
|
+
}
|
|
1430
|
+
};
|
|
1431
|
+
const startEnrichment = () => {
|
|
1432
|
+
if (enrichmentStarted) {
|
|
1433
|
+
return;
|
|
1434
|
+
}
|
|
1435
|
+
enrichmentStarted = true;
|
|
1436
|
+
const snapshotAgents = Array.from(agentByPubkey.values()).map((agent) => ({ ...agent }));
|
|
1437
|
+
const snapshotMap = new Map(snapshotAgents.map((agent) => [agent.pubkey, agent]));
|
|
1438
|
+
void this.runEnrichment(snapshotAgents, snapshotMap, enrichmentAbort.signal).then(
|
|
1439
|
+
(sorted) => {
|
|
1440
|
+
if (enrichmentAbort.signal.aborted) {
|
|
1441
|
+
return;
|
|
1442
|
+
}
|
|
1443
|
+
opts.onComplete?.(sorted);
|
|
1444
|
+
},
|
|
1445
|
+
() => {
|
|
1446
|
+
}
|
|
1447
|
+
);
|
|
1448
|
+
};
|
|
1449
|
+
const capSub = this.pool.subscribe(
|
|
1450
|
+
{ kinds: [KIND_APP_HANDLER], "#t": ["elisym"] },
|
|
1451
|
+
(event) => {
|
|
1452
|
+
const dTag = event.tags.find((tag) => tag[0] === "d")?.[1] ?? "";
|
|
1453
|
+
let perDTag = eventsByPubkey.get(event.pubkey);
|
|
1454
|
+
const prev = perDTag?.get(dTag);
|
|
1455
|
+
if (prev && event.created_at <= prev.created_at) {
|
|
1456
|
+
return;
|
|
1457
|
+
}
|
|
1458
|
+
if (!nostrTools.verifyEvent(event)) {
|
|
1459
|
+
return;
|
|
1460
|
+
}
|
|
1461
|
+
if (!event.content) {
|
|
1462
|
+
return;
|
|
1463
|
+
}
|
|
1464
|
+
let payload;
|
|
1465
|
+
try {
|
|
1466
|
+
payload = JSON.parse(event.content);
|
|
1467
|
+
} catch {
|
|
1468
|
+
return;
|
|
1469
|
+
}
|
|
1470
|
+
const isTombstone = payload !== null && typeof payload === "object" && Boolean(payload.deleted);
|
|
1471
|
+
if (!isTombstone && !parseCapabilityEvent(event, network)) {
|
|
1472
|
+
return;
|
|
1473
|
+
}
|
|
1474
|
+
if (!perDTag) {
|
|
1475
|
+
perDTag = /* @__PURE__ */ new Map();
|
|
1476
|
+
eventsByPubkey.set(event.pubkey, perDTag);
|
|
1477
|
+
}
|
|
1478
|
+
perDTag.set(dTag, event);
|
|
1479
|
+
const merged = buildAgentsFromEvents(Array.from(perDTag.values()), network).get(
|
|
1480
|
+
event.pubkey
|
|
1481
|
+
);
|
|
1482
|
+
if (!merged) {
|
|
1483
|
+
agentByPubkey.delete(event.pubkey);
|
|
1484
|
+
return;
|
|
1485
|
+
}
|
|
1486
|
+
agentByPubkey.set(event.pubkey, merged);
|
|
1487
|
+
opts.onAgent(merged);
|
|
1488
|
+
},
|
|
1489
|
+
{
|
|
1490
|
+
oneose: () => {
|
|
1491
|
+
eoseSeen.caps = true;
|
|
1492
|
+
startEnrichment();
|
|
1493
|
+
checkEose();
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
);
|
|
1497
|
+
const activitySince = Math.floor(Date.now() / 1e3) - RANKING_ACTIVITY_WINDOW_SECS;
|
|
1498
|
+
const resultsSub = this.pool.subscribe(
|
|
1499
|
+
{ kinds: [KIND_JOB_RESULT], "#t": ["elisym"], since: activitySince },
|
|
1500
|
+
(event) => {
|
|
1501
|
+
if (!nostrTools.verifyEvent(event)) {
|
|
1502
|
+
return;
|
|
1503
|
+
}
|
|
1504
|
+
opts.onPaidJob?.(event.pubkey, event.created_at);
|
|
1505
|
+
},
|
|
1506
|
+
{
|
|
1507
|
+
oneose: () => {
|
|
1508
|
+
eoseSeen.results = true;
|
|
1509
|
+
checkEose();
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
);
|
|
1513
|
+
return {
|
|
1514
|
+
close: (reason) => {
|
|
1515
|
+
capSub.close(reason);
|
|
1516
|
+
resultsSub.close(reason);
|
|
1517
|
+
enrichmentAbort.abort();
|
|
1518
|
+
opts.signal?.removeEventListener("abort", onExternalAbort);
|
|
1519
|
+
}
|
|
1520
|
+
};
|
|
1521
|
+
}
|
|
1354
1522
|
/**
|
|
1355
1523
|
* Publish a capability card (kind:31990) as a provider.
|
|
1356
1524
|
* Solana address is validated for Base58 format only - full decode
|
|
@@ -2566,8 +2734,11 @@ var NostrPool = class {
|
|
|
2566
2734
|
throw new Error(`Failed to publish to all ${this.relays.length} relays`);
|
|
2567
2735
|
}
|
|
2568
2736
|
}
|
|
2569
|
-
subscribe(filter, onEvent) {
|
|
2570
|
-
const rawSub = this.pool.subscribeMany(this.relays, filter, {
|
|
2737
|
+
subscribe(filter, onEvent, opts) {
|
|
2738
|
+
const rawSub = this.pool.subscribeMany(this.relays, filter, {
|
|
2739
|
+
onevent: onEvent,
|
|
2740
|
+
oneose: opts?.oneose
|
|
2741
|
+
});
|
|
2571
2742
|
const tracked = {
|
|
2572
2743
|
close: (reason) => {
|
|
2573
2744
|
this.activeSubscriptions.delete(tracked);
|
|
@@ -2918,6 +3089,83 @@ async function doVerifyOnce(rpc, txSignature, expectedRecipient) {
|
|
|
2918
3089
|
}
|
|
2919
3090
|
return { verified: false, txSignature: sigStr, reason: "recipient_mismatch" };
|
|
2920
3091
|
}
|
|
3092
|
+
var DEFAULT_LIMIT = 1e3;
|
|
3093
|
+
var NATIVE_KEY = "native";
|
|
3094
|
+
async function aggregateNetworkStats(rpc, options) {
|
|
3095
|
+
const limit = options?.limit ?? DEFAULT_LIMIT;
|
|
3096
|
+
const concurrency = options?.concurrency ?? DEFAULTS.QUERY_MAX_CONCURRENCY;
|
|
3097
|
+
const tag = kit.address(ELISYM_PROTOCOL_TAG);
|
|
3098
|
+
const signatures = await rpc.getSignaturesForAddress(tag, { limit, before: options?.before }).send();
|
|
3099
|
+
const validSigs = signatures.filter((entry) => entry.err === null);
|
|
3100
|
+
if (validSigs.length === 0) {
|
|
3101
|
+
return { jobCount: 0, volumeByAsset: {} };
|
|
3102
|
+
}
|
|
3103
|
+
const volumeByAsset = {};
|
|
3104
|
+
let jobCount = 0;
|
|
3105
|
+
for (let start = 0; start < validSigs.length; start += concurrency) {
|
|
3106
|
+
const batch = validSigs.slice(start, start + concurrency);
|
|
3107
|
+
const txResults = await Promise.all(
|
|
3108
|
+
batch.map(
|
|
3109
|
+
(entry) => rpc.getTransaction(entry.signature, {
|
|
3110
|
+
commitment: "confirmed",
|
|
3111
|
+
encoding: "json",
|
|
3112
|
+
maxSupportedTransactionVersion: 0
|
|
3113
|
+
}).send().catch(() => null)
|
|
3114
|
+
)
|
|
3115
|
+
);
|
|
3116
|
+
for (const tx of txResults) {
|
|
3117
|
+
if (!tx?.meta || tx.meta.err) {
|
|
3118
|
+
continue;
|
|
3119
|
+
}
|
|
3120
|
+
jobCount += 1;
|
|
3121
|
+
accumulateTransfers(tx, volumeByAsset);
|
|
3122
|
+
}
|
|
3123
|
+
}
|
|
3124
|
+
const latest = validSigs[0]?.signature;
|
|
3125
|
+
const oldest = validSigs.at(-1)?.signature;
|
|
3126
|
+
return {
|
|
3127
|
+
jobCount,
|
|
3128
|
+
volumeByAsset,
|
|
3129
|
+
latestSignature: latest,
|
|
3130
|
+
oldestSignature: oldest
|
|
3131
|
+
};
|
|
3132
|
+
}
|
|
3133
|
+
function accumulateTransfers(tx, volumeByAsset) {
|
|
3134
|
+
const raw = tx;
|
|
3135
|
+
const meta = raw.meta;
|
|
3136
|
+
if (!meta) {
|
|
3137
|
+
return;
|
|
3138
|
+
}
|
|
3139
|
+
const preTokens = meta.preTokenBalances ?? [];
|
|
3140
|
+
const postTokens = meta.postTokenBalances ?? [];
|
|
3141
|
+
const isSpl = postTokens.length > 0 || preTokens.length > 0;
|
|
3142
|
+
if (isSpl) {
|
|
3143
|
+
accumulateSplDeltas(preTokens, postTokens, volumeByAsset);
|
|
3144
|
+
return;
|
|
3145
|
+
}
|
|
3146
|
+
accumulateNativeDeltas(meta.preBalances, meta.postBalances, volumeByAsset);
|
|
3147
|
+
}
|
|
3148
|
+
function accumulateSplDeltas(pre, post, volumeByAsset) {
|
|
3149
|
+
for (const postEntry of post) {
|
|
3150
|
+
const preEntry = pre.find((entry) => entry.accountIndex === postEntry.accountIndex);
|
|
3151
|
+
const preAmount = preEntry ? BigInt(preEntry.uiTokenAmount.amount) : 0n;
|
|
3152
|
+
const postAmount = BigInt(postEntry.uiTokenAmount.amount);
|
|
3153
|
+
const delta = postAmount - preAmount;
|
|
3154
|
+
if (delta > 0n) {
|
|
3155
|
+
volumeByAsset[postEntry.mint] = (volumeByAsset[postEntry.mint] ?? 0n) + delta;
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
3158
|
+
}
|
|
3159
|
+
function accumulateNativeDeltas(pre, post, volumeByAsset) {
|
|
3160
|
+
for (let i = 1; i < post.length; i++) {
|
|
3161
|
+
const preValue = pre[i] ?? 0n;
|
|
3162
|
+
const postValue = post[i] ?? 0n;
|
|
3163
|
+
const delta = BigInt(postValue) - BigInt(preValue);
|
|
3164
|
+
if (delta > 0n) {
|
|
3165
|
+
volumeByAsset[NATIVE_KEY] = (volumeByAsset[NATIVE_KEY] ?? 0n) + delta;
|
|
3166
|
+
}
|
|
3167
|
+
}
|
|
3168
|
+
}
|
|
2921
3169
|
var SessionSpendLimitEntrySchema = zod.z.object({
|
|
2922
3170
|
chain: zod.z.enum(["solana"]),
|
|
2923
3171
|
token: zod.z.string().min(1).max(16).regex(/^[a-z0-9]+$/, "token must be lowercase alphanumeric"),
|
|
@@ -3123,6 +3371,7 @@ exports.DEFAULTS = DEFAULTS;
|
|
|
3123
3371
|
exports.DEFAULT_KIND_OFFSET = DEFAULT_KIND_OFFSET;
|
|
3124
3372
|
exports.DEFAULT_REDACT_PATHS = DEFAULT_REDACT_PATHS;
|
|
3125
3373
|
exports.DiscoveryService = DiscoveryService;
|
|
3374
|
+
exports.ELISYM_PROTOCOL_TAG = ELISYM_PROTOCOL_TAG;
|
|
3126
3375
|
exports.ElisymClient = ElisymClient;
|
|
3127
3376
|
exports.ElisymIdentity = ElisymIdentity;
|
|
3128
3377
|
exports.GlobalConfigSchema = GlobalConfigSchema;
|
|
@@ -3152,6 +3401,7 @@ exports.SECRET_REDACT_PATHS = SECRET_REDACT_PATHS;
|
|
|
3152
3401
|
exports.SessionSpendLimitEntrySchema = SessionSpendLimitEntrySchema;
|
|
3153
3402
|
exports.SolanaPaymentStrategy = SolanaPaymentStrategy;
|
|
3154
3403
|
exports.USDC_SOLANA_DEVNET = USDC_SOLANA_DEVNET;
|
|
3404
|
+
exports.aggregateNetworkStats = aggregateNetworkStats;
|
|
3155
3405
|
exports.assertExpiry = assertExpiry;
|
|
3156
3406
|
exports.assertLamports = assertLamports;
|
|
3157
3407
|
exports.assetByKey = assetByKey;
|