@elisym/sdk 0.22.0 → 0.23.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.d.cts CHANGED
@@ -3,7 +3,7 @@ import { Filter, Event } from 'nostr-tools';
3
3
  import { A as Asset } from './assets-C-nzSYD4.cjs';
4
4
  export { C as Chain, K as KNOWN_ASSETS, N as NATIVE_SOL, U as USDC_SOLANA_DEVNET, a as assetByKey, b as assetKey, f as formatAssetAmount, p as parseAssetAmount, r as resolveAssetFromPaymentRequest, c as resolveKnownAsset } from './assets-C-nzSYD4.cjs';
5
5
  import { z } from 'zod';
6
- export { G as GlobalConfig, a as GlobalConfigSchema, S as SessionSpendLimitEntry, b as SessionSpendLimitEntrySchema } from './global-schema-CddHP2nk.cjs';
6
+ export { G as GlobalConfig, a as GlobalConfigSchema, S as SessionSpendLimitEntry, b as SessionSpendLimitEntrySchema } from './global-schema-BsVFDtkt.cjs';
7
7
  export { R as RateLimitDecision, S as SlidingWindowLimiter, a as SlidingWindowLimiterOptions, c as createSlidingWindowLimiter } from './rateLimiter-CoEmZkSX.cjs';
8
8
 
9
9
  declare class ElisymIdentity {
@@ -1053,7 +1053,16 @@ declare function parsePaymentRequest(input: string, options?: ParseOptions): Par
1053
1053
  */
1054
1054
  type QuickVerifyReason = 'not_found' | 'tx_failed' | 'recipient_mismatch' | 'rpc_error' | 'invalid_input';
1055
1055
  interface QuickVerifyResult {
1056
- verified: boolean;
1056
+ /**
1057
+ * True when the recipient address received funds in this transaction.
1058
+ *
1059
+ * NOTE: this is NOT proof of a valid elisym job payment. It does not check
1060
+ * the payment `reference` key or that the amount matches the job price - it
1061
+ * is a best-effort signal for the fast discovery-ranking path, where the
1062
+ * original payment request is unavailable. For an authoritative check
1063
+ * (amount + reference) use `SolanaPaymentStrategy.verifyPayment`.
1064
+ */
1065
+ receivedFunds: boolean;
1057
1066
  txSignature: string;
1058
1067
  reason?: QuickVerifyReason;
1059
1068
  }
@@ -1105,6 +1114,12 @@ declare function aggregateNetworkStats(rpc: Rpc<SolanaRpcApi>, options?: Aggrega
1105
1114
  * Returns `null` when the PDA has not been initialized yet (admin must call
1106
1115
  * `initialize_stats` once after program upgrade).
1107
1116
  */
1117
+ /**
1118
+ * Best-effort network counters read from the on-chain PDA. These are NOT bound
1119
+ * to verified transfers and can be inflated by a malicious caller, so present
1120
+ * them as approximate/unverified, never as authoritative proof of activity. For
1121
+ * a transfer-derived figure use `aggregateNetworkStats`.
1122
+ */
1108
1123
  interface OnchainNetworkStats {
1109
1124
  jobCount: number;
1110
1125
  volumeNative: bigint;
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@ import { Filter, Event } from 'nostr-tools';
3
3
  import { A as Asset } from './assets-C-nzSYD4.js';
4
4
  export { C as Chain, K as KNOWN_ASSETS, N as NATIVE_SOL, U as USDC_SOLANA_DEVNET, a as assetByKey, b as assetKey, f as formatAssetAmount, p as parseAssetAmount, r as resolveAssetFromPaymentRequest, c as resolveKnownAsset } from './assets-C-nzSYD4.js';
5
5
  import { z } from 'zod';
6
- export { G as GlobalConfig, a as GlobalConfigSchema, S as SessionSpendLimitEntry, b as SessionSpendLimitEntrySchema } from './global-schema-CddHP2nk.js';
6
+ export { G as GlobalConfig, a as GlobalConfigSchema, S as SessionSpendLimitEntry, b as SessionSpendLimitEntrySchema } from './global-schema-BsVFDtkt.js';
7
7
  export { R as RateLimitDecision, S as SlidingWindowLimiter, a as SlidingWindowLimiterOptions, c as createSlidingWindowLimiter } from './rateLimiter-CoEmZkSX.js';
8
8
 
9
9
  declare class ElisymIdentity {
@@ -1053,7 +1053,16 @@ declare function parsePaymentRequest(input: string, options?: ParseOptions): Par
1053
1053
  */
1054
1054
  type QuickVerifyReason = 'not_found' | 'tx_failed' | 'recipient_mismatch' | 'rpc_error' | 'invalid_input';
1055
1055
  interface QuickVerifyResult {
1056
- verified: boolean;
1056
+ /**
1057
+ * True when the recipient address received funds in this transaction.
1058
+ *
1059
+ * NOTE: this is NOT proof of a valid elisym job payment. It does not check
1060
+ * the payment `reference` key or that the amount matches the job price - it
1061
+ * is a best-effort signal for the fast discovery-ranking path, where the
1062
+ * original payment request is unavailable. For an authoritative check
1063
+ * (amount + reference) use `SolanaPaymentStrategy.verifyPayment`.
1064
+ */
1065
+ receivedFunds: boolean;
1057
1066
  txSignature: string;
1058
1067
  reason?: QuickVerifyReason;
1059
1068
  }
@@ -1105,6 +1114,12 @@ declare function aggregateNetworkStats(rpc: Rpc<SolanaRpcApi>, options?: Aggrega
1105
1114
  * Returns `null` when the PDA has not been initialized yet (admin must call
1106
1115
  * `initialize_stats` once after program upgrade).
1107
1116
  */
1117
+ /**
1118
+ * Best-effort network counters read from the on-chain PDA. These are NOT bound
1119
+ * to verified transfers and can be inflated by a malicious caller, so present
1120
+ * them as approximate/unverified, never as authoritative proof of activity. For
1121
+ * a transfer-derived figure use `aggregateNetworkStats`.
1122
+ */
1108
1123
  interface OnchainNetworkStats {
1109
1124
  jobCount: number;
1110
1125
  volumeNative: bigint;
package/dist/index.js CHANGED
@@ -1472,6 +1472,7 @@ var DiscoveryService = class {
1472
1472
  return agents;
1473
1473
  }
1474
1474
  const deliveredJobsByProvider = /* @__PURE__ */ new Map();
1475
+ const customerByJob = /* @__PURE__ */ new Map();
1475
1476
  for (const ev of resultEvents) {
1476
1477
  if (!verifyEvent(ev)) {
1477
1478
  continue;
@@ -1491,8 +1492,13 @@ var DiscoveryService = class {
1491
1492
  deliveredJobsByProvider.set(ev.pubkey, delivered);
1492
1493
  }
1493
1494
  delivered.add(jobEventId);
1495
+ const customerPubkey = ev.tags.find((tag) => tag[0] === "p")?.[1];
1496
+ if (customerPubkey) {
1497
+ customerByJob.set(jobEventId, customerPubkey);
1498
+ }
1494
1499
  }
1495
1500
  }
1501
+ const countedRatings = /* @__PURE__ */ new Set();
1496
1502
  for (const ev of feedbackEvents) {
1497
1503
  if (!verifyEvent(ev)) {
1498
1504
  continue;
@@ -1508,19 +1514,25 @@ var DiscoveryService = class {
1508
1514
  if (ev.created_at > agent.lastSeen) {
1509
1515
  agent.lastSeen = ev.created_at;
1510
1516
  }
1517
+ const jobEventId = ev.tags.find((tag) => tag[0] === "e")?.[1];
1518
+ const hasDeliveredResult = jobEventId !== void 0 && deliveredJobsByProvider.get(targetPubkey)?.has(jobEventId) === true;
1519
+ const jobCustomer = jobEventId !== void 0 ? customerByJob.get(jobEventId) : void 0;
1520
+ const authoredByCustomer = jobCustomer !== void 0 && ev.pubkey === jobCustomer;
1511
1521
  const rating = ev.tags.find((tag) => tag[0] === "rating")?.[1];
1512
- if (rating === "1" || rating === "0") {
1513
- agent.totalRatingCount = (agent.totalRatingCount ?? 0) + 1;
1514
- if (rating === "1") {
1515
- agent.positiveCount = (agent.positiveCount ?? 0) + 1;
1522
+ if ((rating === "1" || rating === "0") && hasDeliveredResult && authoredByCustomer) {
1523
+ const ratingKey = `${ev.pubkey}:${jobEventId}`;
1524
+ if (!countedRatings.has(ratingKey)) {
1525
+ countedRatings.add(ratingKey);
1526
+ agent.totalRatingCount = (agent.totalRatingCount ?? 0) + 1;
1527
+ if (rating === "1") {
1528
+ agent.positiveCount = (agent.positiveCount ?? 0) + 1;
1529
+ }
1516
1530
  }
1517
1531
  }
1518
1532
  const status = ev.tags.find((tag) => tag[0] === "status")?.[1];
1519
1533
  const txTag = ev.tags.find((tag) => tag[0] === "tx");
1520
1534
  const txSignature = txTag?.[1];
1521
- const jobEventId = ev.tags.find((tag) => tag[0] === "e")?.[1];
1522
- const hasDeliveredResult = jobEventId !== void 0 && deliveredJobsByProvider.get(targetPubkey)?.has(jobEventId) === true;
1523
- if (status === "payment-completed" && typeof txSignature === "string" && txSignature && hasDeliveredResult) {
1535
+ if (status === "payment-completed" && typeof txSignature === "string" && txSignature && hasDeliveredResult && authoredByCustomer) {
1524
1536
  if (!agent.lastPaidJobAt || ev.created_at > agent.lastPaidJobAt) {
1525
1537
  agent.lastPaidJobAt = ev.created_at;
1526
1538
  agent.lastPaidJobTx = txSignature;
@@ -3404,35 +3416,43 @@ function formatNetworkBaseline(estimate) {
3404
3416
  return `Estimated network gas: ${total} SOL (${parts.join(" + ")}).`;
3405
3417
  }
3406
3418
  var NEGATIVE_CACHE_TTL_MS = 6e4;
3419
+ var MAX_CACHE_ENTRIES = 5e3;
3407
3420
  var verifyCache = /* @__PURE__ */ new Map();
3408
3421
  function clearQuickVerifyCache() {
3409
3422
  verifyCache.clear();
3410
3423
  }
3411
3424
  async function verifyJobPaymentQuick(rpc, txSignature, expectedRecipient) {
3412
3425
  if (!txSignature) {
3413
- return { verified: false, txSignature: "", reason: "invalid_input" };
3426
+ return { receivedFunds: false, txSignature: "", reason: "invalid_input" };
3414
3427
  }
3415
3428
  if (!expectedRecipient || !isAddress(expectedRecipient)) {
3416
- return { verified: false, txSignature, reason: "invalid_input" };
3429
+ return { receivedFunds: false, txSignature, reason: "invalid_input" };
3417
3430
  }
3418
3431
  const cacheKey2 = `${txSignature}:${expectedRecipient}`;
3419
3432
  const cached = verifyCache.get(cacheKey2);
3420
3433
  if (cached) {
3421
- if (cached.result.verified) {
3434
+ if (cached.result.receivedFunds) {
3422
3435
  return cached.result;
3423
3436
  }
3424
3437
  if (Date.now() - cached.cachedAt < NEGATIVE_CACHE_TTL_MS) {
3425
3438
  return cached.result;
3426
3439
  }
3440
+ verifyCache.delete(cacheKey2);
3427
3441
  }
3428
3442
  const result = await doVerifyOnce(rpc, txSignature, expectedRecipient);
3443
+ if (verifyCache.size >= MAX_CACHE_ENTRIES) {
3444
+ const oldest = verifyCache.keys().next().value;
3445
+ if (oldest !== void 0) {
3446
+ verifyCache.delete(oldest);
3447
+ }
3448
+ }
3429
3449
  verifyCache.set(cacheKey2, { result, cachedAt: Date.now() });
3430
3450
  return result;
3431
3451
  }
3432
3452
  async function doVerifyOnce(rpc, txSignature, expectedRecipient) {
3433
3453
  const sigStr = txSignature;
3434
3454
  if (!rpc || typeof rpc.getTransaction !== "function") {
3435
- return { verified: false, txSignature: sigStr, reason: "rpc_error" };
3455
+ return { receivedFunds: false, txSignature: sigStr, reason: "rpc_error" };
3436
3456
  }
3437
3457
  let tx;
3438
3458
  try {
@@ -3442,13 +3462,13 @@ async function doVerifyOnce(rpc, txSignature, expectedRecipient) {
3442
3462
  maxSupportedTransactionVersion: 0
3443
3463
  }).send();
3444
3464
  } catch {
3445
- return { verified: false, txSignature: sigStr, reason: "rpc_error" };
3465
+ return { receivedFunds: false, txSignature: sigStr, reason: "rpc_error" };
3446
3466
  }
3447
3467
  if (!tx) {
3448
- return { verified: false, txSignature: sigStr, reason: "not_found" };
3468
+ return { receivedFunds: false, txSignature: sigStr, reason: "not_found" };
3449
3469
  }
3450
3470
  if (!tx.meta || tx.meta.err) {
3451
- return { verified: false, txSignature: sigStr, reason: "tx_failed" };
3471
+ return { receivedFunds: false, txSignature: sigStr, reason: "tx_failed" };
3452
3472
  }
3453
3473
  const accountKeys = tx.transaction.message.accountKeys;
3454
3474
  const recipientStr = expectedRecipient;
@@ -3462,7 +3482,7 @@ async function doVerifyOnce(rpc, txSignature, expectedRecipient) {
3462
3482
  if (pre !== void 0 && post !== void 0) {
3463
3483
  const delta = BigInt(post) - BigInt(pre);
3464
3484
  if (delta > 0n) {
3465
- return { verified: true, txSignature: sigStr };
3485
+ return { receivedFunds: true, txSignature: sigStr };
3466
3486
  }
3467
3487
  }
3468
3488
  }
@@ -3480,11 +3500,11 @@ async function doVerifyOnce(rpc, txSignature, expectedRecipient) {
3480
3500
  const preAmount = pre ? BigInt(pre.uiTokenAmount.amount) : 0n;
3481
3501
  const postAmount = BigInt(post.uiTokenAmount.amount);
3482
3502
  if (postAmount > preAmount) {
3483
- return { verified: true, txSignature: sigStr };
3503
+ return { receivedFunds: true, txSignature: sigStr };
3484
3504
  }
3485
3505
  }
3486
3506
  }
3487
- return { verified: false, txSignature: sigStr, reason: "recipient_mismatch" };
3507
+ return { receivedFunds: false, txSignature: sigStr, reason: "recipient_mismatch" };
3488
3508
  }
3489
3509
  var DEFAULT_LIMIT = 1e3;
3490
3510
  var NATIVE_KEY = "native";
@@ -3579,7 +3599,12 @@ var SessionSpendLimitEntrySchema = z.object({
3579
3599
  chain: z.enum(["solana"]),
3580
3600
  token: z.string().min(1).max(16).regex(/^[a-z0-9]+$/, "token must be lowercase alphanumeric"),
3581
3601
  mint: z.string().min(1).max(64).optional(),
3582
- amount: z.number().positive().finite()
3602
+ // Stored as a string to preserve the operator's exact decimal text (avoids
3603
+ // Number round-tripping to scientific notation). Legacy configs persisted a
3604
+ // number; accept both and normalize to a positive-decimal string.
3605
+ amount: z.union([z.string(), z.number()]).transform((value) => typeof value === "number" ? String(value) : value.trim()).refine((value) => /^\d+(?:\.\d+)?$/.test(value) && /[1-9]/.test(value), {
3606
+ message: 'amount must be a positive decimal (e.g. "0.5", "1")'
3607
+ })
3583
3608
  }).strict();
3584
3609
  var GlobalConfigSchema = z.object({
3585
3610
  session_spend_limits: z.array(SessionSpendLimitEntrySchema).max(16).optional()