@elisym/sdk 0.9.0 → 0.10.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/agent-store.cjs.map +1 -1
- package/dist/agent-store.d.cts +5 -0
- package/dist/agent-store.d.ts +5 -0
- package/dist/agent-store.js.map +1 -1
- package/dist/index.cjs +248 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +69 -5
- package/dist/index.d.ts +69 -5
- package/dist/index.js +246 -34
- package/dist/index.js.map +1 -1
- package/dist/skills.cjs.map +1 -1
- package/dist/skills.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -75,7 +75,7 @@ function getProtocolProgramId(cluster) {
|
|
|
75
75
|
}
|
|
76
76
|
var DEFAULTS = {
|
|
77
77
|
SUBSCRIPTION_TIMEOUT_MS: 12e4,
|
|
78
|
-
PING_TIMEOUT_MS:
|
|
78
|
+
PING_TIMEOUT_MS: 3e3,
|
|
79
79
|
PING_RETRIES: 2,
|
|
80
80
|
PING_CACHE_TTL_MS: 3e4,
|
|
81
81
|
PAYMENT_EXPIRY_SECS: 600,
|
|
@@ -1019,6 +1019,95 @@ async function createPaymentRequestWithOnchainConfig(rpc, programId, recipient,
|
|
|
1019
1019
|
options
|
|
1020
1020
|
);
|
|
1021
1021
|
}
|
|
1022
|
+
var NEGATIVE_CACHE_TTL_MS = 6e4;
|
|
1023
|
+
var verifyCache = /* @__PURE__ */ new Map();
|
|
1024
|
+
function clearQuickVerifyCache() {
|
|
1025
|
+
verifyCache.clear();
|
|
1026
|
+
}
|
|
1027
|
+
async function verifyJobPaymentQuick(rpc, txSignature, expectedRecipient) {
|
|
1028
|
+
if (!txSignature) {
|
|
1029
|
+
return { verified: false, txSignature: "", reason: "invalid_input" };
|
|
1030
|
+
}
|
|
1031
|
+
if (!expectedRecipient || !kit.isAddress(expectedRecipient)) {
|
|
1032
|
+
return { verified: false, txSignature, reason: "invalid_input" };
|
|
1033
|
+
}
|
|
1034
|
+
const cacheKey2 = `${txSignature}:${expectedRecipient}`;
|
|
1035
|
+
const cached = verifyCache.get(cacheKey2);
|
|
1036
|
+
if (cached) {
|
|
1037
|
+
if (cached.result.verified) {
|
|
1038
|
+
return cached.result;
|
|
1039
|
+
}
|
|
1040
|
+
if (Date.now() - cached.cachedAt < NEGATIVE_CACHE_TTL_MS) {
|
|
1041
|
+
return cached.result;
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
const result = await doVerifyOnce(rpc, txSignature, expectedRecipient);
|
|
1045
|
+
verifyCache.set(cacheKey2, { result, cachedAt: Date.now() });
|
|
1046
|
+
return result;
|
|
1047
|
+
}
|
|
1048
|
+
async function doVerifyOnce(rpc, txSignature, expectedRecipient) {
|
|
1049
|
+
const sigStr = txSignature;
|
|
1050
|
+
if (!rpc || typeof rpc.getTransaction !== "function") {
|
|
1051
|
+
return { verified: false, txSignature: sigStr, reason: "rpc_error" };
|
|
1052
|
+
}
|
|
1053
|
+
let tx;
|
|
1054
|
+
try {
|
|
1055
|
+
tx = await rpc.getTransaction(txSignature, {
|
|
1056
|
+
commitment: "confirmed",
|
|
1057
|
+
encoding: "json",
|
|
1058
|
+
maxSupportedTransactionVersion: 0
|
|
1059
|
+
}).send();
|
|
1060
|
+
} catch {
|
|
1061
|
+
return { verified: false, txSignature: sigStr, reason: "rpc_error" };
|
|
1062
|
+
}
|
|
1063
|
+
if (!tx) {
|
|
1064
|
+
return { verified: false, txSignature: sigStr, reason: "not_found" };
|
|
1065
|
+
}
|
|
1066
|
+
if (!tx.meta || tx.meta.err) {
|
|
1067
|
+
return { verified: false, txSignature: sigStr, reason: "tx_failed" };
|
|
1068
|
+
}
|
|
1069
|
+
const accountKeys = tx.transaction.message.accountKeys;
|
|
1070
|
+
const recipientStr = expectedRecipient;
|
|
1071
|
+
const recipientIdx = accountKeys.indexOf(recipientStr);
|
|
1072
|
+
if (recipientIdx !== -1) {
|
|
1073
|
+
const preBalances = tx.meta.preBalances;
|
|
1074
|
+
const postBalances = tx.meta.postBalances;
|
|
1075
|
+
if (preBalances && postBalances) {
|
|
1076
|
+
const pre = preBalances[recipientIdx];
|
|
1077
|
+
const post = postBalances[recipientIdx];
|
|
1078
|
+
if (pre !== void 0 && post !== void 0) {
|
|
1079
|
+
const delta = BigInt(post) - BigInt(pre);
|
|
1080
|
+
if (delta > 0n) {
|
|
1081
|
+
return { verified: true, txSignature: sigStr };
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
const postTokenBalances = tx.meta.postTokenBalances;
|
|
1087
|
+
const preTokenBalances = tx.meta.preTokenBalances;
|
|
1088
|
+
if (postTokenBalances) {
|
|
1089
|
+
for (const post of postTokenBalances) {
|
|
1090
|
+
if (post.owner !== recipientStr) {
|
|
1091
|
+
continue;
|
|
1092
|
+
}
|
|
1093
|
+
const pre = preTokenBalances?.find(
|
|
1094
|
+
(entry) => entry.owner === recipientStr && entry.mint === post.mint
|
|
1095
|
+
);
|
|
1096
|
+
const preAmount = pre ? BigInt(pre.uiTokenAmount.amount) : 0n;
|
|
1097
|
+
const postAmount = BigInt(post.uiTokenAmount.amount);
|
|
1098
|
+
if (postAmount > preAmount) {
|
|
1099
|
+
return { verified: true, txSignature: sigStr };
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
return { verified: false, txSignature: sigStr, reason: "recipient_mismatch" };
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
// src/services/discovery.ts
|
|
1107
|
+
var RANKING_ACTIVITY_WINDOW_SECS = 30 * 24 * 60 * 60;
|
|
1108
|
+
var RANKING_BUCKET_SIZE_SECS = 60;
|
|
1109
|
+
var COLD_START_BUCKET = -Infinity;
|
|
1110
|
+
var MAX_PAID_CANDIDATES_PER_AGENT = 5;
|
|
1022
1111
|
function toDTag(name) {
|
|
1023
1112
|
const tag = name.toLowerCase().replace(/[^a-z0-9\s-]/g, (ch) => "_" + ch.charCodeAt(0).toString(16).padStart(2, "0")).replace(/\s+/g, "-").replace(/^-+|-+$/g, "");
|
|
1024
1113
|
if (!tag) {
|
|
@@ -1026,6 +1115,28 @@ function toDTag(name) {
|
|
|
1026
1115
|
}
|
|
1027
1116
|
return tag;
|
|
1028
1117
|
}
|
|
1118
|
+
function computeRankKey(agent) {
|
|
1119
|
+
const lastPaidJobAt = agent.lastPaidJobAt ?? 0;
|
|
1120
|
+
const total = agent.totalRatingCount ?? 0;
|
|
1121
|
+
const positive = agent.positiveCount ?? 0;
|
|
1122
|
+
const rate = total > 0 ? positive / total : 0;
|
|
1123
|
+
const bucket = lastPaidJobAt > 0 ? Math.floor(lastPaidJobAt / RANKING_BUCKET_SIZE_SECS) * RANKING_BUCKET_SIZE_SECS : COLD_START_BUCKET;
|
|
1124
|
+
return { bucket, rate, lastPaidJobAt, lastSeen: agent.lastSeen };
|
|
1125
|
+
}
|
|
1126
|
+
function compareAgentsByRank(a, b) {
|
|
1127
|
+
const ka = computeRankKey(a);
|
|
1128
|
+
const kb = computeRankKey(b);
|
|
1129
|
+
if (kb.bucket !== ka.bucket) {
|
|
1130
|
+
return kb.bucket - ka.bucket;
|
|
1131
|
+
}
|
|
1132
|
+
if (kb.rate !== ka.rate) {
|
|
1133
|
+
return kb.rate - ka.rate;
|
|
1134
|
+
}
|
|
1135
|
+
if (kb.lastPaidJobAt !== ka.lastPaidJobAt) {
|
|
1136
|
+
return kb.lastPaidJobAt - ka.lastPaidJobAt;
|
|
1137
|
+
}
|
|
1138
|
+
return kb.lastSeen - ka.lastSeen;
|
|
1139
|
+
}
|
|
1029
1140
|
function buildAgentsFromEvents(events, network) {
|
|
1030
1141
|
const latestByDTag = /* @__PURE__ */ new Map();
|
|
1031
1142
|
for (const event of events) {
|
|
@@ -1120,9 +1231,37 @@ function buildAgentsFromEvents(events, network) {
|
|
|
1120
1231
|
}
|
|
1121
1232
|
return agentMap;
|
|
1122
1233
|
}
|
|
1234
|
+
function pickSolanaAddress(agent) {
|
|
1235
|
+
for (const card of agent.cards) {
|
|
1236
|
+
const addr = card.payment?.address;
|
|
1237
|
+
if (card.payment?.chain === "solana" && typeof addr === "string" && addr.length > 0) {
|
|
1238
|
+
return addr;
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
return null;
|
|
1242
|
+
}
|
|
1243
|
+
async function verifyNewestPaidCandidate(rpc, recipient, candidatesNewestFirst) {
|
|
1244
|
+
const settled = await Promise.allSettled(
|
|
1245
|
+
candidatesNewestFirst.map(
|
|
1246
|
+
(candidate) => verifyJobPaymentQuick(rpc, candidate.txSignature, recipient)
|
|
1247
|
+
)
|
|
1248
|
+
);
|
|
1249
|
+
for (let i = 0; i < settled.length; i++) {
|
|
1250
|
+
const entry = settled[i];
|
|
1251
|
+
if (entry?.status === "fulfilled" && entry.value.verified) {
|
|
1252
|
+
return candidatesNewestFirst[i] ?? null;
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
return null;
|
|
1256
|
+
}
|
|
1123
1257
|
var DiscoveryService = class {
|
|
1124
|
-
constructor(pool) {
|
|
1258
|
+
constructor(pool, defaultRpc) {
|
|
1125
1259
|
this.pool = pool;
|
|
1260
|
+
this.defaultRpc = defaultRpc;
|
|
1261
|
+
}
|
|
1262
|
+
/** Configure the Solana RPC used for on-chain payment verification in `fetchAgents`. */
|
|
1263
|
+
setRpc(rpc) {
|
|
1264
|
+
this.defaultRpc = rpc;
|
|
1126
1265
|
}
|
|
1127
1266
|
/** Count elisym agents (kind:31990 with "elisym" tag). */
|
|
1128
1267
|
async fetchAllAgentCount() {
|
|
@@ -1210,8 +1349,21 @@ var DiscoveryService = class {
|
|
|
1210
1349
|
}
|
|
1211
1350
|
return agents;
|
|
1212
1351
|
}
|
|
1213
|
-
/**
|
|
1214
|
-
|
|
1352
|
+
/**
|
|
1353
|
+
* Fetch elisym agents filtered by network, ranked by verified paid-job recency.
|
|
1354
|
+
*
|
|
1355
|
+
* Ranking algorithm:
|
|
1356
|
+
* 1. Bucket each agent into 1-minute slots by `lastPaidJobAt` (last on-chain
|
|
1357
|
+
* verified payment). Cold-start agents (no verified paid job) go into a
|
|
1358
|
+
* sentinel bucket below all populated buckets.
|
|
1359
|
+
* 2. Within a bucket, sort by positive review rate descending.
|
|
1360
|
+
* 3. Tiebreak by raw `lastPaidJobAt`, then `lastSeen` (NIP-89 freshness).
|
|
1361
|
+
*
|
|
1362
|
+
* On-chain verification uses {@link verifyJobPaymentQuick} - one-shot, cached.
|
|
1363
|
+
* If `rpc` is not configured, all agents fall through to cold-start and order
|
|
1364
|
+
* is determined by `lastSeen` only.
|
|
1365
|
+
*/
|
|
1366
|
+
async fetchAgents(network = "devnet", limit, rpcOverride) {
|
|
1215
1367
|
const filter = {
|
|
1216
1368
|
kinds: [KIND_APP_HANDLER],
|
|
1217
1369
|
"#t": ["elisym"]
|
|
@@ -1221,40 +1373,100 @@ var DiscoveryService = class {
|
|
|
1221
1373
|
}
|
|
1222
1374
|
const events = await this.pool.querySync(filter);
|
|
1223
1375
|
const agentMap = buildAgentsFromEvents(events, network);
|
|
1224
|
-
const agents = Array.from(agentMap.values())
|
|
1376
|
+
const agents = Array.from(agentMap.values());
|
|
1225
1377
|
const agentPubkeys = Array.from(agentMap.keys());
|
|
1226
|
-
if (agentPubkeys.length
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1378
|
+
if (agentPubkeys.length === 0) {
|
|
1379
|
+
return agents;
|
|
1380
|
+
}
|
|
1381
|
+
const activitySince = Math.floor(Date.now() / 1e3) - RANKING_ACTIVITY_WINDOW_SECS;
|
|
1382
|
+
const resultKinds = /* @__PURE__ */ new Set();
|
|
1383
|
+
for (const agent of agentMap.values()) {
|
|
1384
|
+
for (const k of agent.supportedKinds) {
|
|
1385
|
+
if (k >= KIND_JOB_REQUEST_BASE && k < KIND_JOB_RESULT_BASE) {
|
|
1386
|
+
resultKinds.add(KIND_JOB_RESULT_BASE + (k - KIND_JOB_REQUEST_BASE));
|
|
1234
1387
|
}
|
|
1235
1388
|
}
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1389
|
+
}
|
|
1390
|
+
resultKinds.add(jobResultKind(DEFAULT_KIND_OFFSET));
|
|
1391
|
+
const [resultEvents, feedbackEvents] = await Promise.all([
|
|
1392
|
+
this.pool.queryBatched(
|
|
1393
|
+
{
|
|
1394
|
+
kinds: [...resultKinds],
|
|
1395
|
+
since: activitySince
|
|
1396
|
+
},
|
|
1397
|
+
agentPubkeys
|
|
1398
|
+
),
|
|
1399
|
+
this.pool.queryBatchedByTag(
|
|
1400
|
+
{ kinds: [KIND_JOB_FEEDBACK], since: activitySince },
|
|
1401
|
+
"p",
|
|
1402
|
+
agentPubkeys
|
|
1403
|
+
),
|
|
1404
|
+
this.enrichWithMetadata(agents)
|
|
1405
|
+
]);
|
|
1406
|
+
for (const ev of resultEvents) {
|
|
1407
|
+
if (!nostrTools.verifyEvent(ev)) {
|
|
1408
|
+
continue;
|
|
1409
|
+
}
|
|
1410
|
+
const agent = agentMap.get(ev.pubkey);
|
|
1411
|
+
if (agent && ev.created_at > agent.lastSeen) {
|
|
1412
|
+
agent.lastSeen = ev.created_at;
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
const paidCandidates = /* @__PURE__ */ new Map();
|
|
1416
|
+
for (const ev of feedbackEvents) {
|
|
1417
|
+
if (!nostrTools.verifyEvent(ev)) {
|
|
1418
|
+
continue;
|
|
1419
|
+
}
|
|
1420
|
+
const targetPubkey = ev.tags.find((t) => t[0] === "p")?.[1];
|
|
1421
|
+
if (!targetPubkey) {
|
|
1422
|
+
continue;
|
|
1423
|
+
}
|
|
1424
|
+
const agent = agentMap.get(targetPubkey);
|
|
1425
|
+
if (!agent) {
|
|
1426
|
+
continue;
|
|
1427
|
+
}
|
|
1428
|
+
if (ev.created_at > agent.lastSeen) {
|
|
1429
|
+
agent.lastSeen = ev.created_at;
|
|
1430
|
+
}
|
|
1431
|
+
const rating = ev.tags.find((t) => t[0] === "rating")?.[1];
|
|
1432
|
+
if (rating === "1" || rating === "0") {
|
|
1433
|
+
agent.totalRatingCount = (agent.totalRatingCount ?? 0) + 1;
|
|
1434
|
+
if (rating === "1") {
|
|
1435
|
+
agent.positiveCount = (agent.positiveCount ?? 0) + 1;
|
|
1250
1436
|
}
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1437
|
+
}
|
|
1438
|
+
const status = ev.tags.find((t) => t[0] === "status")?.[1];
|
|
1439
|
+
const txTag = ev.tags.find((t) => t[0] === "tx");
|
|
1440
|
+
const txSignature = txTag?.[1];
|
|
1441
|
+
if (status === "payment-completed" && typeof txSignature === "string" && txSignature) {
|
|
1442
|
+
const list = paidCandidates.get(targetPubkey);
|
|
1443
|
+
const candidate = { txSignature, createdAt: ev.created_at };
|
|
1444
|
+
if (list) {
|
|
1445
|
+
list.push(candidate);
|
|
1446
|
+
} else {
|
|
1447
|
+
paidCandidates.set(targetPubkey, [candidate]);
|
|
1254
1448
|
}
|
|
1255
1449
|
}
|
|
1256
|
-
agents.sort((a, b) => b.lastSeen - a.lastSeen);
|
|
1257
1450
|
}
|
|
1451
|
+
const rpc = rpcOverride ?? this.defaultRpc;
|
|
1452
|
+
if (rpc && paidCandidates.size > 0) {
|
|
1453
|
+
const perAgent = Array.from(paidCandidates.entries()).map(([agentPubkey, list]) => {
|
|
1454
|
+
const agent = agentMap.get(agentPubkey);
|
|
1455
|
+
const recipient = agent ? pickSolanaAddress(agent) : null;
|
|
1456
|
+
if (!agent || !recipient) {
|
|
1457
|
+
return Promise.resolve();
|
|
1458
|
+
}
|
|
1459
|
+
const ordered = [...list].sort((a, b) => b.createdAt - a.createdAt).slice(0, MAX_PAID_CANDIDATES_PER_AGENT);
|
|
1460
|
+
return verifyNewestPaidCandidate(rpc, recipient, ordered).then((winner) => {
|
|
1461
|
+
if (winner) {
|
|
1462
|
+
agent.lastPaidJobAt = winner.createdAt;
|
|
1463
|
+
agent.lastPaidJobTx = winner.txSignature;
|
|
1464
|
+
}
|
|
1465
|
+
});
|
|
1466
|
+
});
|
|
1467
|
+
await Promise.all(perAgent);
|
|
1468
|
+
}
|
|
1469
|
+
agents.sort(compareAgentsByRank);
|
|
1258
1470
|
return agents;
|
|
1259
1471
|
}
|
|
1260
1472
|
/**
|
|
@@ -2602,7 +2814,7 @@ var ElisymClient = class {
|
|
|
2602
2814
|
payment;
|
|
2603
2815
|
constructor(config = {}) {
|
|
2604
2816
|
this.pool = new NostrPool(config.relays ?? RELAYS);
|
|
2605
|
-
this.discovery = new DiscoveryService(this.pool);
|
|
2817
|
+
this.discovery = new DiscoveryService(this.pool, config.rpc);
|
|
2606
2818
|
this.marketplace = new MarketplaceService(this.pool);
|
|
2607
2819
|
this.ping = new PingService(this.pool);
|
|
2608
2820
|
this.media = new MediaService(config.uploadUrl);
|
|
@@ -2972,6 +3184,9 @@ exports.buildPaymentInstructions = buildPaymentInstructions;
|
|
|
2972
3184
|
exports.calculateProtocolFee = calculateProtocolFee;
|
|
2973
3185
|
exports.clearPriorityFeeCache = clearPriorityFeeCache;
|
|
2974
3186
|
exports.clearProtocolConfigCache = clearProtocolConfigCache;
|
|
3187
|
+
exports.clearQuickVerifyCache = clearQuickVerifyCache;
|
|
3188
|
+
exports.compareAgentsByRank = compareAgentsByRank;
|
|
3189
|
+
exports.computeRankKey = computeRankKey;
|
|
2975
3190
|
exports.createPaymentRequestWithOnchainConfig = createPaymentRequestWithOnchainConfig;
|
|
2976
3191
|
exports.createSlidingWindowLimiter = createSlidingWindowLimiter;
|
|
2977
3192
|
exports.estimatePriorityFeeMicroLamports = estimatePriorityFeeMicroLamports;
|
|
@@ -2996,5 +3211,6 @@ exports.toDTag = toDTag;
|
|
|
2996
3211
|
exports.truncateKey = truncateKey;
|
|
2997
3212
|
exports.validateAgentName = validateAgentName;
|
|
2998
3213
|
exports.validateExpiry = validateExpiry;
|
|
3214
|
+
exports.verifyJobPaymentQuick = verifyJobPaymentQuick;
|
|
2999
3215
|
//# sourceMappingURL=index.cjs.map
|
|
3000
3216
|
//# sourceMappingURL=index.cjs.map
|