@elisym/sdk 0.10.3 → 0.11.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.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, address3, config) {
123
- const maybeAccount = await fetchMaybeConfig(rpc, address3, config);
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, address3, config) {
128
- const maybeAccount = await kit.fetchEncodedAccount(rpc, address3, config);
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 providerTransferIxWithReference2 = {
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 = [providerTransferIxWithReference2];
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 providerTransferIxWithReference = {
999
+ const providerTransferIxWithMarkers = {
986
1000
  ...providerTransferIx,
987
- accounts: [...providerTransferIx.accounts, { address: reference, role: kit.AccountRole.READONLY }]
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(providerTransferIxWithReference);
1007
+ instructions.push(providerTransferIxWithMarkers);
990
1008
  if (treasuryAta && paymentRequest.fee_address && feeAmount > 0) {
991
1009
  instructions.push(
992
1010
  token.getTransferCheckedInstruction({
@@ -1141,21 +1159,6 @@ var DiscoveryService = class {
1141
1159
  constructor(pool) {
1142
1160
  this.pool = pool;
1143
1161
  }
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
1162
  /**
1160
1163
  * Fetch a single page of elisym agents with relay-side pagination.
1161
1164
  * Uses `until` cursor for Nostr cursor-based pagination.
@@ -1236,18 +1239,20 @@ var DiscoveryService = class {
1236
1239
  *
1237
1240
  * Ranking algorithm:
1238
1241
  * 1. Bucket each agent into 1-minute slots by `lastPaidJobAt` (newest
1239
- * `payment-completed` feedback timestamp). Cold-start agents go into a
1240
- * sentinel bucket below all populated buckets.
1242
+ * `payment-completed` feedback timestamp, gated by a matching kind:6xxx
1243
+ * result from the provider on the same job event). Cold-start agents go
1244
+ * into a sentinel bucket below all populated buckets.
1241
1245
  * 2. Within a bucket, sort by positive review rate descending.
1242
1246
  * 3. Tiebreak by raw `lastPaidJobAt`, then `lastSeen` (NIP-89 freshness).
1243
1247
  *
1244
- * NOTE: We trust the `payment-completed` feedback timestamp directly; we do
1245
- * not verify the embedded `tx` signature on-chain. Public Solana devnet RPC
1246
- * rate-limits trivially exceed what discovery needs (N agents * up-to-5
1247
- * candidates), and the resulting 429s blocked discovery entirely. This
1248
- * means a malicious customer can publish a fake `payment-completed` to lift
1249
- * an agent's ranking. Acceptable trade-off for devnet / MVP; tighten via
1250
- * recipient-tied checks when the network moves to mainnet with a paid RPC
1248
+ * NOTE: We do not verify the `tx` signature on-chain - public Solana devnet
1249
+ * RPC rate-limits trivially exceed what discovery needs (N agents * up-to-5
1250
+ * candidates), and the resulting 429s blocked discovery entirely. As a
1251
+ * lighter sybil mitigation we cross-check `payment-completed` feedback
1252
+ * against a kind:6xxx result event authored by the provider on the same
1253
+ * job: a customer can publish a fake `payment-completed`, but they cannot
1254
+ * forge a result event signed by the provider. Tighten with recipient-tied
1255
+ * on-chain checks when the network moves to mainnet with a paid RPC
1251
1256
  * provider.
1252
1257
  */
1253
1258
  async fetchAgents(network = "devnet", limit) {
@@ -1290,14 +1295,27 @@ var DiscoveryService = class {
1290
1295
  ),
1291
1296
  this.enrichWithMetadata(agents)
1292
1297
  ]);
1298
+ const deliveredJobsByProvider = /* @__PURE__ */ new Map();
1293
1299
  for (const ev of resultEvents) {
1294
1300
  if (!nostrTools.verifyEvent(ev)) {
1295
1301
  continue;
1296
1302
  }
1297
1303
  const agent = agentMap.get(ev.pubkey);
1298
- if (agent && ev.created_at > agent.lastSeen) {
1304
+ if (!agent) {
1305
+ continue;
1306
+ }
1307
+ if (ev.created_at > agent.lastSeen) {
1299
1308
  agent.lastSeen = ev.created_at;
1300
1309
  }
1310
+ const jobEventId = ev.tags.find((t) => t[0] === "e")?.[1];
1311
+ if (jobEventId) {
1312
+ let delivered = deliveredJobsByProvider.get(ev.pubkey);
1313
+ if (!delivered) {
1314
+ delivered = /* @__PURE__ */ new Set();
1315
+ deliveredJobsByProvider.set(ev.pubkey, delivered);
1316
+ }
1317
+ delivered.add(jobEventId);
1318
+ }
1301
1319
  }
1302
1320
  for (const ev of feedbackEvents) {
1303
1321
  if (!nostrTools.verifyEvent(ev)) {
@@ -1324,7 +1342,9 @@ var DiscoveryService = class {
1324
1342
  const status = ev.tags.find((t) => t[0] === "status")?.[1];
1325
1343
  const txTag = ev.tags.find((t) => t[0] === "tx");
1326
1344
  const txSignature = txTag?.[1];
1327
- if (status === "payment-completed" && typeof txSignature === "string" && txSignature) {
1345
+ const jobEventId = ev.tags.find((t) => t[0] === "e")?.[1];
1346
+ const hasDeliveredResult = jobEventId !== void 0 && deliveredJobsByProvider.get(targetPubkey)?.has(jobEventId) === true;
1347
+ if (status === "payment-completed" && typeof txSignature === "string" && txSignature && hasDeliveredResult) {
1328
1348
  if (!agent.lastPaidJobAt || ev.created_at > agent.lastPaidJobAt) {
1329
1349
  agent.lastPaidJobAt = ev.created_at;
1330
1350
  agent.lastPaidJobTx = txSignature;
@@ -2901,6 +2921,83 @@ async function doVerifyOnce(rpc, txSignature, expectedRecipient) {
2901
2921
  }
2902
2922
  return { verified: false, txSignature: sigStr, reason: "recipient_mismatch" };
2903
2923
  }
2924
+ var DEFAULT_LIMIT = 1e3;
2925
+ var NATIVE_KEY = "native";
2926
+ async function aggregateNetworkStats(rpc, options) {
2927
+ const limit = options?.limit ?? DEFAULT_LIMIT;
2928
+ const concurrency = options?.concurrency ?? DEFAULTS.QUERY_MAX_CONCURRENCY;
2929
+ const tag = kit.address(ELISYM_PROTOCOL_TAG);
2930
+ const signatures = await rpc.getSignaturesForAddress(tag, { limit, before: options?.before }).send();
2931
+ const validSigs = signatures.filter((entry) => entry.err === null);
2932
+ if (validSigs.length === 0) {
2933
+ return { jobCount: 0, volumeByAsset: {} };
2934
+ }
2935
+ const volumeByAsset = {};
2936
+ let jobCount = 0;
2937
+ for (let start = 0; start < validSigs.length; start += concurrency) {
2938
+ const batch = validSigs.slice(start, start + concurrency);
2939
+ const txResults = await Promise.all(
2940
+ batch.map(
2941
+ (entry) => rpc.getTransaction(entry.signature, {
2942
+ commitment: "confirmed",
2943
+ encoding: "json",
2944
+ maxSupportedTransactionVersion: 0
2945
+ }).send().catch(() => null)
2946
+ )
2947
+ );
2948
+ for (const tx of txResults) {
2949
+ if (!tx?.meta || tx.meta.err) {
2950
+ continue;
2951
+ }
2952
+ jobCount += 1;
2953
+ accumulateTransfers(tx, volumeByAsset);
2954
+ }
2955
+ }
2956
+ const latest = validSigs[0]?.signature;
2957
+ const oldest = validSigs.at(-1)?.signature;
2958
+ return {
2959
+ jobCount,
2960
+ volumeByAsset,
2961
+ latestSignature: latest,
2962
+ oldestSignature: oldest
2963
+ };
2964
+ }
2965
+ function accumulateTransfers(tx, volumeByAsset) {
2966
+ const raw = tx;
2967
+ const meta = raw.meta;
2968
+ if (!meta) {
2969
+ return;
2970
+ }
2971
+ const preTokens = meta.preTokenBalances ?? [];
2972
+ const postTokens = meta.postTokenBalances ?? [];
2973
+ const isSpl = postTokens.length > 0 || preTokens.length > 0;
2974
+ if (isSpl) {
2975
+ accumulateSplDeltas(preTokens, postTokens, volumeByAsset);
2976
+ return;
2977
+ }
2978
+ accumulateNativeDeltas(meta.preBalances, meta.postBalances, volumeByAsset);
2979
+ }
2980
+ function accumulateSplDeltas(pre, post, volumeByAsset) {
2981
+ for (const postEntry of post) {
2982
+ const preEntry = pre.find((entry) => entry.accountIndex === postEntry.accountIndex);
2983
+ const preAmount = preEntry ? BigInt(preEntry.uiTokenAmount.amount) : 0n;
2984
+ const postAmount = BigInt(postEntry.uiTokenAmount.amount);
2985
+ const delta = postAmount - preAmount;
2986
+ if (delta > 0n) {
2987
+ volumeByAsset[postEntry.mint] = (volumeByAsset[postEntry.mint] ?? 0n) + delta;
2988
+ }
2989
+ }
2990
+ }
2991
+ function accumulateNativeDeltas(pre, post, volumeByAsset) {
2992
+ for (let i = 1; i < post.length; i++) {
2993
+ const preValue = pre[i] ?? 0n;
2994
+ const postValue = post[i] ?? 0n;
2995
+ const delta = BigInt(postValue) - BigInt(preValue);
2996
+ if (delta > 0n) {
2997
+ volumeByAsset[NATIVE_KEY] = (volumeByAsset[NATIVE_KEY] ?? 0n) + delta;
2998
+ }
2999
+ }
3000
+ }
2904
3001
  var SessionSpendLimitEntrySchema = zod.z.object({
2905
3002
  chain: zod.z.enum(["solana"]),
2906
3003
  token: zod.z.string().min(1).max(16).regex(/^[a-z0-9]+$/, "token must be lowercase alphanumeric"),
@@ -3106,6 +3203,7 @@ exports.DEFAULTS = DEFAULTS;
3106
3203
  exports.DEFAULT_KIND_OFFSET = DEFAULT_KIND_OFFSET;
3107
3204
  exports.DEFAULT_REDACT_PATHS = DEFAULT_REDACT_PATHS;
3108
3205
  exports.DiscoveryService = DiscoveryService;
3206
+ exports.ELISYM_PROTOCOL_TAG = ELISYM_PROTOCOL_TAG;
3109
3207
  exports.ElisymClient = ElisymClient;
3110
3208
  exports.ElisymIdentity = ElisymIdentity;
3111
3209
  exports.GlobalConfigSchema = GlobalConfigSchema;
@@ -3135,6 +3233,7 @@ exports.SECRET_REDACT_PATHS = SECRET_REDACT_PATHS;
3135
3233
  exports.SessionSpendLimitEntrySchema = SessionSpendLimitEntrySchema;
3136
3234
  exports.SolanaPaymentStrategy = SolanaPaymentStrategy;
3137
3235
  exports.USDC_SOLANA_DEVNET = USDC_SOLANA_DEVNET;
3236
+ exports.aggregateNetworkStats = aggregateNetworkStats;
3138
3237
  exports.assertExpiry = assertExpiry;
3139
3238
  exports.assertLamports = assertLamports;
3140
3239
  exports.assetByKey = assetByKey;