@elisym/sdk 0.9.0 → 0.10.1
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 +211 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +72 -3
- package/dist/index.d.ts +72 -3
- package/dist/index.js +209 -33
- 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,9 @@ async function createPaymentRequestWithOnchainConfig(rpc, programId, recipient,
|
|
|
1019
1019
|
options
|
|
1020
1020
|
);
|
|
1021
1021
|
}
|
|
1022
|
+
var RANKING_ACTIVITY_WINDOW_SECS = 30 * 24 * 60 * 60;
|
|
1023
|
+
var RANKING_BUCKET_SIZE_SECS = 60;
|
|
1024
|
+
var COLD_START_BUCKET = -Infinity;
|
|
1022
1025
|
function toDTag(name) {
|
|
1023
1026
|
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
1027
|
if (!tag) {
|
|
@@ -1026,6 +1029,28 @@ function toDTag(name) {
|
|
|
1026
1029
|
}
|
|
1027
1030
|
return tag;
|
|
1028
1031
|
}
|
|
1032
|
+
function computeRankKey(agent) {
|
|
1033
|
+
const lastPaidJobAt = agent.lastPaidJobAt ?? 0;
|
|
1034
|
+
const total = agent.totalRatingCount ?? 0;
|
|
1035
|
+
const positive = agent.positiveCount ?? 0;
|
|
1036
|
+
const rate = total > 0 ? positive / total : 0;
|
|
1037
|
+
const bucket = lastPaidJobAt > 0 ? Math.floor(lastPaidJobAt / RANKING_BUCKET_SIZE_SECS) * RANKING_BUCKET_SIZE_SECS : COLD_START_BUCKET;
|
|
1038
|
+
return { bucket, rate, lastPaidJobAt, lastSeen: agent.lastSeen };
|
|
1039
|
+
}
|
|
1040
|
+
function compareAgentsByRank(a, b) {
|
|
1041
|
+
const ka = computeRankKey(a);
|
|
1042
|
+
const kb = computeRankKey(b);
|
|
1043
|
+
if (kb.bucket !== ka.bucket) {
|
|
1044
|
+
return kb.bucket - ka.bucket;
|
|
1045
|
+
}
|
|
1046
|
+
if (kb.rate !== ka.rate) {
|
|
1047
|
+
return kb.rate - ka.rate;
|
|
1048
|
+
}
|
|
1049
|
+
if (kb.lastPaidJobAt !== ka.lastPaidJobAt) {
|
|
1050
|
+
return kb.lastPaidJobAt - ka.lastPaidJobAt;
|
|
1051
|
+
}
|
|
1052
|
+
return kb.lastSeen - ka.lastSeen;
|
|
1053
|
+
}
|
|
1029
1054
|
function buildAgentsFromEvents(events, network) {
|
|
1030
1055
|
const latestByDTag = /* @__PURE__ */ new Map();
|
|
1031
1056
|
for (const event of events) {
|
|
@@ -1210,7 +1235,26 @@ var DiscoveryService = class {
|
|
|
1210
1235
|
}
|
|
1211
1236
|
return agents;
|
|
1212
1237
|
}
|
|
1213
|
-
/**
|
|
1238
|
+
/**
|
|
1239
|
+
* Fetch elisym agents filtered by network, ranked by paid-job recency and
|
|
1240
|
+
* positive-feedback rate.
|
|
1241
|
+
*
|
|
1242
|
+
* Ranking algorithm:
|
|
1243
|
+
* 1. Bucket each agent into 1-minute slots by `lastPaidJobAt` (newest
|
|
1244
|
+
* `payment-completed` feedback timestamp). Cold-start agents go into a
|
|
1245
|
+
* sentinel bucket below all populated buckets.
|
|
1246
|
+
* 2. Within a bucket, sort by positive review rate descending.
|
|
1247
|
+
* 3. Tiebreak by raw `lastPaidJobAt`, then `lastSeen` (NIP-89 freshness).
|
|
1248
|
+
*
|
|
1249
|
+
* NOTE: We trust the `payment-completed` feedback timestamp directly; we do
|
|
1250
|
+
* not verify the embedded `tx` signature on-chain. Public Solana devnet RPC
|
|
1251
|
+
* rate-limits trivially exceed what discovery needs (N agents * up-to-5
|
|
1252
|
+
* candidates), and the resulting 429s blocked discovery entirely. This
|
|
1253
|
+
* means a malicious customer can publish a fake `payment-completed` to lift
|
|
1254
|
+
* an agent's ranking. Acceptable trade-off for devnet / MVP; tighten via
|
|
1255
|
+
* recipient-tied checks when the network moves to mainnet with a paid RPC
|
|
1256
|
+
* provider.
|
|
1257
|
+
*/
|
|
1214
1258
|
async fetchAgents(network = "devnet", limit) {
|
|
1215
1259
|
const filter = {
|
|
1216
1260
|
kinds: [KIND_APP_HANDLER],
|
|
@@ -1221,40 +1265,78 @@ var DiscoveryService = class {
|
|
|
1221
1265
|
}
|
|
1222
1266
|
const events = await this.pool.querySync(filter);
|
|
1223
1267
|
const agentMap = buildAgentsFromEvents(events, network);
|
|
1224
|
-
const agents = Array.from(agentMap.values())
|
|
1268
|
+
const agents = Array.from(agentMap.values());
|
|
1225
1269
|
const agentPubkeys = Array.from(agentMap.keys());
|
|
1226
|
-
if (agentPubkeys.length
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1270
|
+
if (agentPubkeys.length === 0) {
|
|
1271
|
+
return agents;
|
|
1272
|
+
}
|
|
1273
|
+
const activitySince = Math.floor(Date.now() / 1e3) - RANKING_ACTIVITY_WINDOW_SECS;
|
|
1274
|
+
const resultKinds = /* @__PURE__ */ new Set();
|
|
1275
|
+
for (const agent of agentMap.values()) {
|
|
1276
|
+
for (const k of agent.supportedKinds) {
|
|
1277
|
+
if (k >= KIND_JOB_REQUEST_BASE && k < KIND_JOB_RESULT_BASE) {
|
|
1278
|
+
resultKinds.add(KIND_JOB_RESULT_BASE + (k - KIND_JOB_REQUEST_BASE));
|
|
1234
1279
|
}
|
|
1235
1280
|
}
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1281
|
+
}
|
|
1282
|
+
resultKinds.add(jobResultKind(DEFAULT_KIND_OFFSET));
|
|
1283
|
+
const [resultEvents, feedbackEvents] = await Promise.all([
|
|
1284
|
+
this.pool.queryBatched(
|
|
1285
|
+
{
|
|
1286
|
+
kinds: [...resultKinds],
|
|
1287
|
+
since: activitySince
|
|
1288
|
+
},
|
|
1289
|
+
agentPubkeys
|
|
1290
|
+
),
|
|
1291
|
+
this.pool.queryBatchedByTag(
|
|
1292
|
+
{ kinds: [KIND_JOB_FEEDBACK], since: activitySince },
|
|
1293
|
+
"p",
|
|
1294
|
+
agentPubkeys
|
|
1295
|
+
),
|
|
1296
|
+
this.enrichWithMetadata(agents)
|
|
1297
|
+
]);
|
|
1298
|
+
for (const ev of resultEvents) {
|
|
1299
|
+
if (!nostrTools.verifyEvent(ev)) {
|
|
1300
|
+
continue;
|
|
1301
|
+
}
|
|
1302
|
+
const agent = agentMap.get(ev.pubkey);
|
|
1303
|
+
if (agent && ev.created_at > agent.lastSeen) {
|
|
1304
|
+
agent.lastSeen = ev.created_at;
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
for (const ev of feedbackEvents) {
|
|
1308
|
+
if (!nostrTools.verifyEvent(ev)) {
|
|
1309
|
+
continue;
|
|
1310
|
+
}
|
|
1311
|
+
const targetPubkey = ev.tags.find((t) => t[0] === "p")?.[1];
|
|
1312
|
+
if (!targetPubkey) {
|
|
1313
|
+
continue;
|
|
1314
|
+
}
|
|
1315
|
+
const agent = agentMap.get(targetPubkey);
|
|
1316
|
+
if (!agent) {
|
|
1317
|
+
continue;
|
|
1318
|
+
}
|
|
1319
|
+
if (ev.created_at > agent.lastSeen) {
|
|
1320
|
+
agent.lastSeen = ev.created_at;
|
|
1321
|
+
}
|
|
1322
|
+
const rating = ev.tags.find((t) => t[0] === "rating")?.[1];
|
|
1323
|
+
if (rating === "1" || rating === "0") {
|
|
1324
|
+
agent.totalRatingCount = (agent.totalRatingCount ?? 0) + 1;
|
|
1325
|
+
if (rating === "1") {
|
|
1326
|
+
agent.positiveCount = (agent.positiveCount ?? 0) + 1;
|
|
1250
1327
|
}
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1328
|
+
}
|
|
1329
|
+
const status = ev.tags.find((t) => t[0] === "status")?.[1];
|
|
1330
|
+
const txTag = ev.tags.find((t) => t[0] === "tx");
|
|
1331
|
+
const txSignature = txTag?.[1];
|
|
1332
|
+
if (status === "payment-completed" && typeof txSignature === "string" && txSignature) {
|
|
1333
|
+
if (!agent.lastPaidJobAt || ev.created_at > agent.lastPaidJobAt) {
|
|
1334
|
+
agent.lastPaidJobAt = ev.created_at;
|
|
1335
|
+
agent.lastPaidJobTx = txSignature;
|
|
1254
1336
|
}
|
|
1255
1337
|
}
|
|
1256
|
-
agents.sort((a, b) => b.lastSeen - a.lastSeen);
|
|
1257
1338
|
}
|
|
1339
|
+
agents.sort(compareAgentsByRank);
|
|
1258
1340
|
return agents;
|
|
1259
1341
|
}
|
|
1260
1342
|
/**
|
|
@@ -1953,6 +2035,7 @@ var MarketplaceService = class {
|
|
|
1953
2035
|
let status = "processing";
|
|
1954
2036
|
let amount;
|
|
1955
2037
|
let txHash;
|
|
2038
|
+
let asset;
|
|
1956
2039
|
if (result) {
|
|
1957
2040
|
status = "success";
|
|
1958
2041
|
const amtTag = result.tags.find((t) => t[0] === "amount");
|
|
@@ -1961,9 +2044,18 @@ var MarketplaceService = class {
|
|
|
1961
2044
|
const allFeedbacksForReq = feedbacksByRequestId.get(req.id) ?? [];
|
|
1962
2045
|
for (const fb of allFeedbacksForReq) {
|
|
1963
2046
|
const txTag = fb.tags.find((t) => t[0] === "tx");
|
|
1964
|
-
if (txTag?.[1]) {
|
|
2047
|
+
if (txTag?.[1] && !txHash) {
|
|
1965
2048
|
txHash = txTag[1];
|
|
1966
|
-
|
|
2049
|
+
}
|
|
2050
|
+
if (!asset) {
|
|
2051
|
+
const amtTag = fb.tags.find((t) => t[0] === "amount");
|
|
2052
|
+
const requestJson = amtTag?.[2];
|
|
2053
|
+
if (requestJson) {
|
|
2054
|
+
const parsed = parsePaymentRequest(requestJson);
|
|
2055
|
+
if (parsed.ok && parsed.data.asset) {
|
|
2056
|
+
asset = parsed.data.asset;
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
1967
2059
|
}
|
|
1968
2060
|
}
|
|
1969
2061
|
if (feedback) {
|
|
@@ -1992,6 +2084,7 @@ var MarketplaceService = class {
|
|
|
1992
2084
|
resultEventId: result?.id,
|
|
1993
2085
|
amount,
|
|
1994
2086
|
txHash,
|
|
2087
|
+
asset,
|
|
1995
2088
|
createdAt: req.created_at
|
|
1996
2089
|
});
|
|
1997
2090
|
}
|
|
@@ -2730,6 +2823,89 @@ function lamportsToSol(lamports) {
|
|
|
2730
2823
|
const frac = lamports % LAMPORTS_PER_SOL2;
|
|
2731
2824
|
return `${whole}.${frac.toString().padStart(9, "0")}`;
|
|
2732
2825
|
}
|
|
2826
|
+
var NEGATIVE_CACHE_TTL_MS = 6e4;
|
|
2827
|
+
var verifyCache = /* @__PURE__ */ new Map();
|
|
2828
|
+
function clearQuickVerifyCache() {
|
|
2829
|
+
verifyCache.clear();
|
|
2830
|
+
}
|
|
2831
|
+
async function verifyJobPaymentQuick(rpc, txSignature, expectedRecipient) {
|
|
2832
|
+
if (!txSignature) {
|
|
2833
|
+
return { verified: false, txSignature: "", reason: "invalid_input" };
|
|
2834
|
+
}
|
|
2835
|
+
if (!expectedRecipient || !kit.isAddress(expectedRecipient)) {
|
|
2836
|
+
return { verified: false, txSignature, reason: "invalid_input" };
|
|
2837
|
+
}
|
|
2838
|
+
const cacheKey2 = `${txSignature}:${expectedRecipient}`;
|
|
2839
|
+
const cached = verifyCache.get(cacheKey2);
|
|
2840
|
+
if (cached) {
|
|
2841
|
+
if (cached.result.verified) {
|
|
2842
|
+
return cached.result;
|
|
2843
|
+
}
|
|
2844
|
+
if (Date.now() - cached.cachedAt < NEGATIVE_CACHE_TTL_MS) {
|
|
2845
|
+
return cached.result;
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
const result = await doVerifyOnce(rpc, txSignature, expectedRecipient);
|
|
2849
|
+
verifyCache.set(cacheKey2, { result, cachedAt: Date.now() });
|
|
2850
|
+
return result;
|
|
2851
|
+
}
|
|
2852
|
+
async function doVerifyOnce(rpc, txSignature, expectedRecipient) {
|
|
2853
|
+
const sigStr = txSignature;
|
|
2854
|
+
if (!rpc || typeof rpc.getTransaction !== "function") {
|
|
2855
|
+
return { verified: false, txSignature: sigStr, reason: "rpc_error" };
|
|
2856
|
+
}
|
|
2857
|
+
let tx;
|
|
2858
|
+
try {
|
|
2859
|
+
tx = await rpc.getTransaction(txSignature, {
|
|
2860
|
+
commitment: "confirmed",
|
|
2861
|
+
encoding: "json",
|
|
2862
|
+
maxSupportedTransactionVersion: 0
|
|
2863
|
+
}).send();
|
|
2864
|
+
} catch {
|
|
2865
|
+
return { verified: false, txSignature: sigStr, reason: "rpc_error" };
|
|
2866
|
+
}
|
|
2867
|
+
if (!tx) {
|
|
2868
|
+
return { verified: false, txSignature: sigStr, reason: "not_found" };
|
|
2869
|
+
}
|
|
2870
|
+
if (!tx.meta || tx.meta.err) {
|
|
2871
|
+
return { verified: false, txSignature: sigStr, reason: "tx_failed" };
|
|
2872
|
+
}
|
|
2873
|
+
const accountKeys = tx.transaction.message.accountKeys;
|
|
2874
|
+
const recipientStr = expectedRecipient;
|
|
2875
|
+
const recipientIdx = accountKeys.indexOf(recipientStr);
|
|
2876
|
+
if (recipientIdx !== -1) {
|
|
2877
|
+
const preBalances = tx.meta.preBalances;
|
|
2878
|
+
const postBalances = tx.meta.postBalances;
|
|
2879
|
+
if (preBalances && postBalances) {
|
|
2880
|
+
const pre = preBalances[recipientIdx];
|
|
2881
|
+
const post = postBalances[recipientIdx];
|
|
2882
|
+
if (pre !== void 0 && post !== void 0) {
|
|
2883
|
+
const delta = BigInt(post) - BigInt(pre);
|
|
2884
|
+
if (delta > 0n) {
|
|
2885
|
+
return { verified: true, txSignature: sigStr };
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
}
|
|
2889
|
+
}
|
|
2890
|
+
const postTokenBalances = tx.meta.postTokenBalances;
|
|
2891
|
+
const preTokenBalances = tx.meta.preTokenBalances;
|
|
2892
|
+
if (postTokenBalances) {
|
|
2893
|
+
for (const post of postTokenBalances) {
|
|
2894
|
+
if (post.owner !== recipientStr) {
|
|
2895
|
+
continue;
|
|
2896
|
+
}
|
|
2897
|
+
const pre = preTokenBalances?.find(
|
|
2898
|
+
(entry) => entry.owner === recipientStr && entry.mint === post.mint
|
|
2899
|
+
);
|
|
2900
|
+
const preAmount = pre ? BigInt(pre.uiTokenAmount.amount) : 0n;
|
|
2901
|
+
const postAmount = BigInt(post.uiTokenAmount.amount);
|
|
2902
|
+
if (postAmount > preAmount) {
|
|
2903
|
+
return { verified: true, txSignature: sigStr };
|
|
2904
|
+
}
|
|
2905
|
+
}
|
|
2906
|
+
}
|
|
2907
|
+
return { verified: false, txSignature: sigStr, reason: "recipient_mismatch" };
|
|
2908
|
+
}
|
|
2733
2909
|
var SessionSpendLimitEntrySchema = zod.z.object({
|
|
2734
2910
|
chain: zod.z.enum(["solana"]),
|
|
2735
2911
|
token: zod.z.string().min(1).max(16).regex(/^[a-z0-9]+$/, "token must be lowercase alphanumeric"),
|
|
@@ -2972,6 +3148,9 @@ exports.buildPaymentInstructions = buildPaymentInstructions;
|
|
|
2972
3148
|
exports.calculateProtocolFee = calculateProtocolFee;
|
|
2973
3149
|
exports.clearPriorityFeeCache = clearPriorityFeeCache;
|
|
2974
3150
|
exports.clearProtocolConfigCache = clearProtocolConfigCache;
|
|
3151
|
+
exports.clearQuickVerifyCache = clearQuickVerifyCache;
|
|
3152
|
+
exports.compareAgentsByRank = compareAgentsByRank;
|
|
3153
|
+
exports.computeRankKey = computeRankKey;
|
|
2975
3154
|
exports.createPaymentRequestWithOnchainConfig = createPaymentRequestWithOnchainConfig;
|
|
2976
3155
|
exports.createSlidingWindowLimiter = createSlidingWindowLimiter;
|
|
2977
3156
|
exports.estimatePriorityFeeMicroLamports = estimatePriorityFeeMicroLamports;
|
|
@@ -2996,5 +3175,6 @@ exports.toDTag = toDTag;
|
|
|
2996
3175
|
exports.truncateKey = truncateKey;
|
|
2997
3176
|
exports.validateAgentName = validateAgentName;
|
|
2998
3177
|
exports.validateExpiry = validateExpiry;
|
|
3178
|
+
exports.verifyJobPaymentQuick = verifyJobPaymentQuick;
|
|
2999
3179
|
//# sourceMappingURL=index.cjs.map
|
|
3000
3180
|
//# sourceMappingURL=index.cjs.map
|