@elisym/sdk 0.8.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/index.cjs CHANGED
@@ -1,15 +1,12 @@
1
1
  'use strict';
2
2
 
3
3
  var system = require('@solana-program/system');
4
+ var token = require('@solana-program/token');
4
5
  var kit = require('@solana/kit');
5
6
  var Decimal2 = require('decimal.js-light');
6
7
  var zod = require('zod');
7
8
  var nostrTools = require('nostr-tools');
8
9
  var nip44 = require('nostr-tools/nip44');
9
- var promises = require('fs/promises');
10
- var YAML2 = require('yaml');
11
- var crypto$1 = require('crypto');
12
- var path = require('path');
13
10
 
14
11
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
15
12
 
@@ -33,7 +30,6 @@ function _interopNamespace(e) {
33
30
 
34
31
  var Decimal2__default = /*#__PURE__*/_interopDefault(Decimal2);
35
32
  var nip44__namespace = /*#__PURE__*/_interopNamespace(nip44);
36
- var YAML2__default = /*#__PURE__*/_interopDefault(YAML2);
37
33
 
38
34
  // src/constants.ts
39
35
  var RELAYS = [
@@ -79,7 +75,7 @@ function getProtocolProgramId(cluster) {
79
75
  }
80
76
  var DEFAULTS = {
81
77
  SUBSCRIPTION_TIMEOUT_MS: 12e4,
82
- PING_TIMEOUT_MS: 15e3,
78
+ PING_TIMEOUT_MS: 3e3,
83
79
  PING_RETRIES: 2,
84
80
  PING_CACHE_TTL_MS: 3e4,
85
81
  PAYMENT_EXPIRY_SECS: 600,
@@ -123,13 +119,13 @@ function decodeConfig(encodedAccount) {
123
119
  getConfigDecoder()
124
120
  );
125
121
  }
126
- async function fetchConfig(rpc, address2, config) {
127
- const maybeAccount = await fetchMaybeConfig(rpc, address2, config);
122
+ async function fetchConfig(rpc, address3, config) {
123
+ const maybeAccount = await fetchMaybeConfig(rpc, address3, config);
128
124
  kit.assertAccountExists(maybeAccount);
129
125
  return maybeAccount;
130
126
  }
131
- async function fetchMaybeConfig(rpc, address2, config) {
132
- const maybeAccount = await kit.fetchEncodedAccount(rpc, address2, config);
127
+ async function fetchMaybeConfig(rpc, address3, config) {
128
+ const maybeAccount = await kit.fetchEncodedAccount(rpc, address3, config);
133
129
  return decodeConfig(maybeAccount);
134
130
  }
135
131
  if (process.env.NODE_ENV !== "production") ;
@@ -180,6 +176,99 @@ async function getProtocolConfig(rpc, programId, options) {
180
176
  );
181
177
  }
182
178
  }
179
+
180
+ // src/payment/assets.ts
181
+ var NATIVE_SOL = {
182
+ chain: "solana",
183
+ token: "sol",
184
+ decimals: 9,
185
+ symbol: "SOL"
186
+ };
187
+ var USDC_SOLANA_DEVNET = {
188
+ chain: "solana",
189
+ token: "usdc",
190
+ mint: "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
191
+ decimals: 6,
192
+ symbol: "USDC"
193
+ };
194
+ var KNOWN_ASSETS = [NATIVE_SOL, USDC_SOLANA_DEVNET];
195
+ function assetKey(a) {
196
+ return a.mint ? `${a.chain}:${a.token}:${a.mint}` : `${a.chain}:${a.token}`;
197
+ }
198
+ function resolveKnownAsset(chain, token, mint) {
199
+ const key = mint ? `${chain}:${token}:${mint}` : `${chain}:${token}`;
200
+ return KNOWN_ASSETS.find((asset) => assetKey(asset) === key);
201
+ }
202
+ function assetByKey(key) {
203
+ return KNOWN_ASSETS.find((asset) => assetKey(asset) === key);
204
+ }
205
+ function resolveAssetFromPaymentRequest(request) {
206
+ if (!request.asset) {
207
+ return NATIVE_SOL;
208
+ }
209
+ const found = resolveKnownAsset(request.asset.chain, request.asset.token, request.asset.mint);
210
+ if (!found) {
211
+ const display = request.asset.mint ? `${request.asset.chain}:${request.asset.token}:${request.asset.mint}` : `${request.asset.chain}:${request.asset.token}`;
212
+ throw new Error(
213
+ `Unknown asset in payment request: ${display}. Known assets: ${KNOWN_ASSETS.map(assetKey).join(", ")}`
214
+ );
215
+ }
216
+ return found;
217
+ }
218
+ var DECIMAL_RE = /^(\d+\.\d*|\d*\.\d+|\d+)$/;
219
+ function parseAssetAmount(asset, human) {
220
+ const trimmed = human.trim();
221
+ if (!trimmed) {
222
+ throw new Error(`${asset.symbol} amount is empty`);
223
+ }
224
+ if (trimmed.startsWith("-")) {
225
+ throw new Error(`${asset.symbol} amount cannot be negative`);
226
+ }
227
+ if (!DECIMAL_RE.test(trimmed)) {
228
+ throw new Error(
229
+ `${asset.symbol} amount must be a non-negative decimal (e.g. "0.5", "1"); got "${human}"`
230
+ );
231
+ }
232
+ const dotPos = trimmed.indexOf(".");
233
+ let wholePart;
234
+ if (dotPos === -1) {
235
+ wholePart = trimmed;
236
+ } else if (dotPos === 0) {
237
+ wholePart = "0";
238
+ } else {
239
+ wholePart = trimmed.slice(0, dotPos);
240
+ }
241
+ const fracPart = dotPos === -1 ? "" : trimmed.slice(dotPos + 1);
242
+ if (fracPart.length > asset.decimals) {
243
+ throw new Error(
244
+ `${asset.symbol} amount has too many decimals (max ${asset.decimals}); got "${human}"`
245
+ );
246
+ }
247
+ const unit = 10n ** BigInt(asset.decimals);
248
+ const whole = BigInt(wholePart);
249
+ const frac = fracPart ? BigInt(fracPart.padEnd(asset.decimals, "0")) : 0n;
250
+ const raw = whole * unit + frac;
251
+ if (raw === 0n) {
252
+ throw new Error(`${asset.symbol} amount must be positive; got "${human}"`);
253
+ }
254
+ if (raw > BigInt(Number.MAX_SAFE_INTEGER)) {
255
+ throw new Error(
256
+ `${asset.symbol} amount exceeds safe range (max ${Number.MAX_SAFE_INTEGER} subunits)`
257
+ );
258
+ }
259
+ return raw;
260
+ }
261
+ function formatAssetAmount(asset, raw) {
262
+ const sign = raw < 0n ? "-" : "";
263
+ const abs = raw < 0n ? -raw : raw;
264
+ const unit = 10n ** BigInt(asset.decimals);
265
+ const whole = abs / unit;
266
+ const frac = abs % unit;
267
+ if (asset.decimals === 0) {
268
+ return `${sign}${whole} ${asset.symbol}`;
269
+ }
270
+ return `${sign}${whole}.${frac.toString().padStart(asset.decimals, "0")} ${asset.symbol}`;
271
+ }
183
272
  var BPS_DENOMINATOR = 1e4;
184
273
  function assertLamports(value, field) {
185
274
  if (!Number.isInteger(value) || value < 0) {
@@ -289,6 +378,12 @@ var SOLANA_ADDRESS_LENGTH_RE = /^.{32,44}$/;
289
378
  var lamportsSchema = zod.z.number().int().positive().max(MAX_SAFE_LAMPORTS, `amount must be <= ${MAX_SAFE_LAMPORTS}`);
290
379
  var feeAmountSchema = zod.z.number().int().nonnegative().max(MAX_SAFE_LAMPORTS, `fee_amount must be <= ${MAX_SAFE_LAMPORTS}`);
291
380
  var solanaAddressSchema = zod.z.string().regex(BASE58_RE, "must be base58").regex(SOLANA_ADDRESS_LENGTH_RE, "must be 32-44 base58 chars");
381
+ var paymentAssetRefSchema = zod.z.object({
382
+ chain: zod.z.string().min(1),
383
+ token: zod.z.string().min(1),
384
+ mint: solanaAddressSchema.optional(),
385
+ decimals: zod.z.number().int().min(0).max(18)
386
+ });
292
387
  var PaymentRequestSchema = zod.z.object({
293
388
  recipient: solanaAddressSchema,
294
389
  amount: lamportsSchema,
@@ -297,7 +392,8 @@ var PaymentRequestSchema = zod.z.object({
297
392
  fee_address: solanaAddressSchema.optional(),
298
393
  fee_amount: feeAmountSchema.optional(),
299
394
  created_at: zod.z.number().int().positive(),
300
- expiry_secs: zod.z.number().int().positive().max(MAX_EXPIRY_SECS_SCHEMA, `expiry_secs must be <= ${MAX_EXPIRY_SECS_SCHEMA}`)
395
+ expiry_secs: zod.z.number().int().positive().max(MAX_EXPIRY_SECS_SCHEMA, `expiry_secs must be <= ${MAX_EXPIRY_SECS_SCHEMA}`),
396
+ asset: paymentAssetRefSchema.optional()
301
397
  });
302
398
  function parsePaymentRequest(input, options) {
303
399
  let parsed;
@@ -379,6 +475,12 @@ var SolanaPaymentStrategy = class {
379
475
  assertExpirySecs(expirySecs);
380
476
  const feeAmount = calculateProtocolFee(amount, config.feeBps);
381
477
  const reference = generateReference();
478
+ const assetRef = options?.asset && options.asset !== NATIVE_SOL ? {
479
+ chain: options.asset.chain,
480
+ token: options.asset.token,
481
+ mint: options.asset.mint,
482
+ decimals: options.asset.decimals
483
+ } : void 0;
382
484
  return {
383
485
  recipient: recipientAddress,
384
486
  amount,
@@ -386,7 +488,8 @@ var SolanaPaymentStrategy = class {
386
488
  fee_address: config.treasury,
387
489
  fee_amount: feeAmount,
388
490
  created_at: Math.floor(Date.now() / 1e3),
389
- expiry_secs: expirySecs
491
+ expiry_secs: expirySecs,
492
+ ...assetRef ? { asset: assetRef } : {}
390
493
  };
391
494
  }
392
495
  validatePaymentRequest(requestJson, config, expectedRecipient, options) {
@@ -404,6 +507,16 @@ var SolanaPaymentStrategy = class {
404
507
  return { code: "invalid_amount", message: parsed.error.message };
405
508
  }
406
509
  const data = parsed.data;
510
+ if (data.asset) {
511
+ try {
512
+ resolveAssetFromPaymentRequest(data);
513
+ } catch (error) {
514
+ return {
515
+ code: "invalid_asset",
516
+ message: error instanceof Error ? error.message : String(error)
517
+ };
518
+ }
519
+ }
407
520
  if (!isValidSolanaAddress(data.recipient)) {
408
521
  return {
409
522
  code: "invalid_recipient_address",
@@ -492,7 +605,7 @@ var SolanaPaymentStrategy = class {
492
605
  if (!Number.isInteger(computeUnitLimit) || computeUnitLimit <= 0) {
493
606
  throw new Error(`Invalid computeUnitLimit: ${computeUnitLimit}. Must be a positive integer.`);
494
607
  }
495
- const paymentInstructions = buildPaymentInstructions(paymentRequest, payerSigner);
608
+ const paymentInstructions = await buildPaymentInstructions(paymentRequest, payerSigner);
496
609
  const priorityFeeMicroLamports = options?.priorityFeeMicroLamports ?? await estimatePriorityFeeMicroLamports(rpc, {
497
610
  percentile: options?.priorityFeePercentile ?? DEFAULT_PRIORITY_FEE_PERCENTILE
498
611
  });
@@ -554,6 +667,16 @@ var SolanaPaymentStrategy = class {
554
667
  error: `Fee amount (${feeAmount}) exceeds or equals total amount (${paymentRequest.amount})`
555
668
  };
556
669
  }
670
+ let asset;
671
+ try {
672
+ asset = resolveAssetFromPaymentRequest(paymentRequest);
673
+ } catch (error) {
674
+ return {
675
+ verified: false,
676
+ error: error instanceof Error ? error.message : String(error)
677
+ };
678
+ }
679
+ const mint = asset.mint;
557
680
  if (options?.txSignature) {
558
681
  return this._verifyBySignature(
559
682
  rpc,
@@ -563,6 +686,7 @@ var SolanaPaymentStrategy = class {
563
686
  treasury,
564
687
  expectedNet,
565
688
  feeAmount,
689
+ mint,
566
690
  options?.retries ?? DEFAULTS.VERIFY_RETRIES,
567
691
  options?.intervalMs ?? DEFAULTS.VERIFY_INTERVAL_MS
568
692
  );
@@ -574,11 +698,12 @@ var SolanaPaymentStrategy = class {
574
698
  treasury,
575
699
  expectedNet,
576
700
  feeAmount,
701
+ mint,
577
702
  options?.retries ?? DEFAULTS.VERIFY_BY_REF_RETRIES,
578
703
  options?.intervalMs ?? DEFAULTS.VERIFY_BY_REF_INTERVAL_MS
579
704
  );
580
705
  }
581
- async _verifyBySignature(rpc, txSignature, referenceKey, recipientAddress, treasuryAddress, expectedNet, expectedFee, retries, intervalMs) {
706
+ async _verifyBySignature(rpc, txSignature, referenceKey, recipientAddress, treasuryAddress, expectedNet, expectedFee, mint, retries, intervalMs) {
582
707
  let lastError;
583
708
  for (let attempt = 0; attempt < retries; attempt++) {
584
709
  try {
@@ -597,15 +722,18 @@ var SolanaPaymentStrategy = class {
597
722
  error: tx?.meta?.err ? "Transaction failed on-chain" : "Transaction not found"
598
723
  };
599
724
  }
600
- const verdict = checkBalanceDiff({
725
+ const verdict = checkTxDiff({
601
726
  accountKeys: tx.transaction.message.accountKeys,
602
727
  preBalances: tx.meta.preBalances,
603
728
  postBalances: tx.meta.postBalances,
729
+ preTokenBalances: tx.meta.preTokenBalances,
730
+ postTokenBalances: tx.meta.postTokenBalances,
604
731
  referenceKey,
605
732
  recipientAddress,
606
733
  treasuryAddress,
607
734
  expectedNet,
608
- expectedFee
735
+ expectedFee,
736
+ mint
609
737
  });
610
738
  if (verdict.ok) {
611
739
  return { verified: true, txSignature };
@@ -623,7 +751,7 @@ var SolanaPaymentStrategy = class {
623
751
  error: `Verification failed after ${retries} retries: ${lastError instanceof Error ? lastError.message : "unknown error"}`
624
752
  };
625
753
  }
626
- async _verifyByReference(rpc, referenceKey, recipientAddress, treasuryAddress, expectedNet, expectedFee, retries, intervalMs) {
754
+ async _verifyByReference(rpc, referenceKey, recipientAddress, treasuryAddress, expectedNet, expectedFee, mint, retries, intervalMs) {
627
755
  let lastError;
628
756
  const reference = kit.address(referenceKey);
629
757
  for (let attempt = 0; attempt < retries; attempt++) {
@@ -647,15 +775,18 @@ var SolanaPaymentStrategy = class {
647
775
  if (!tx?.meta || tx.meta.err) {
648
776
  continue;
649
777
  }
650
- const verdict = checkBalanceDiff({
778
+ const verdict = checkTxDiff({
651
779
  accountKeys: tx.transaction.message.accountKeys,
652
780
  preBalances: tx.meta.preBalances,
653
781
  postBalances: tx.meta.postBalances,
782
+ preTokenBalances: tx.meta.preTokenBalances,
783
+ postTokenBalances: tx.meta.postTokenBalances,
654
784
  referenceKey,
655
785
  recipientAddress,
656
786
  treasuryAddress,
657
787
  expectedNet,
658
- expectedFee
788
+ expectedFee,
789
+ mint
659
790
  });
660
791
  if (verdict.ok) {
661
792
  return { verified: true, txSignature: sig };
@@ -675,7 +806,7 @@ var SolanaPaymentStrategy = class {
675
806
  };
676
807
  }
677
808
  };
678
- function checkBalanceDiff(input) {
809
+ function checkTxDiff(input) {
679
810
  const balanceCount = input.preBalances.length;
680
811
  const keyToIdx = /* @__PURE__ */ new Map();
681
812
  for (let i = 0; i < Math.min(input.accountKeys.length, balanceCount); i++) {
@@ -687,6 +818,9 @@ function checkBalanceDiff(input) {
687
818
  if (!keyToIdx.has(input.referenceKey)) {
688
819
  return { ok: false, reason: "Reference key not found in transaction - possible replay" };
689
820
  }
821
+ if (input.mint) {
822
+ return checkTokenBalanceDiff(input);
823
+ }
690
824
  const recipientIdx = keyToIdx.get(input.recipientAddress);
691
825
  if (recipientIdx === void 0) {
692
826
  return { ok: false, reason: "Recipient not found in transaction" };
@@ -719,6 +853,47 @@ function checkBalanceDiff(input) {
719
853
  }
720
854
  return { ok: true };
721
855
  }
856
+ function checkTokenBalanceDiff(input) {
857
+ const mint = input.mint;
858
+ if (!mint) {
859
+ return { ok: false, reason: "Expected mint for SPL verification, got none" };
860
+ }
861
+ const pre = input.preTokenBalances ?? [];
862
+ const post = input.postTokenBalances ?? [];
863
+ const tokenDelta = (ownerAddress) => {
864
+ const preEntry = pre.find((entry) => entry.owner === ownerAddress && entry.mint === mint);
865
+ const postEntry = post.find((entry) => entry.owner === ownerAddress && entry.mint === mint);
866
+ if (!postEntry) {
867
+ return -1n;
868
+ }
869
+ const preAmount = preEntry ? BigInt(preEntry.uiTokenAmount.amount) : 0n;
870
+ const postAmount = BigInt(postEntry.uiTokenAmount.amount);
871
+ return postAmount - preAmount;
872
+ };
873
+ const recipientDelta = tokenDelta(input.recipientAddress);
874
+ if (recipientDelta === -1n) {
875
+ return { ok: false, reason: "Recipient token account not found in transaction" };
876
+ }
877
+ if (recipientDelta < BigInt(input.expectedNet)) {
878
+ return {
879
+ ok: false,
880
+ reason: `Recipient received ${recipientDelta.toString()} tokens, expected >= ${input.expectedNet}`
881
+ };
882
+ }
883
+ if (input.expectedFee > 0) {
884
+ const treasuryDelta = tokenDelta(input.treasuryAddress);
885
+ if (treasuryDelta === -1n) {
886
+ return { ok: false, reason: "Treasury token account not found in transaction" };
887
+ }
888
+ if (treasuryDelta < BigInt(input.expectedFee)) {
889
+ return {
890
+ ok: false,
891
+ reason: `Treasury received ${treasuryDelta.toString()} tokens, expected >= ${input.expectedFee}`
892
+ };
893
+ }
894
+ }
895
+ return { ok: true };
896
+ }
722
897
  function bigIntDelta(post, pre) {
723
898
  const postValue = post === void 0 ? 0n : BigInt(post);
724
899
  const preValue = pre === void 0 ? 0n : BigInt(pre);
@@ -727,7 +902,7 @@ function bigIntDelta(post, pre) {
727
902
  function waitMs(ms) {
728
903
  return new Promise((resolve) => setTimeout(resolve, ms));
729
904
  }
730
- function buildPaymentInstructions(paymentRequest, payerSigner) {
905
+ async function buildPaymentInstructions(paymentRequest, payerSigner) {
731
906
  const recipient = kit.address(paymentRequest.recipient);
732
907
  const reference = kit.address(paymentRequest.reference);
733
908
  const feeAmount = paymentRequest.fee_amount ?? 0;
@@ -737,22 +912,98 @@ function buildPaymentInstructions(paymentRequest, payerSigner) {
737
912
  `Fee amount (${feeAmount}) exceeds or equals total amount (${paymentRequest.amount}). Cannot create transaction with non-positive provider amount.`
738
913
  );
739
914
  }
740
- const providerTransferIx = system.getTransferSolInstruction({
741
- source: payerSigner,
742
- destination: recipient,
743
- amount: BigInt(providerAmount)
915
+ const asset = resolveAssetFromPaymentRequest(paymentRequest);
916
+ if (!asset.mint) {
917
+ const providerTransferIx2 = system.getTransferSolInstruction({
918
+ source: payerSigner,
919
+ destination: recipient,
920
+ amount: BigInt(providerAmount)
921
+ });
922
+ const providerTransferIxWithReference2 = {
923
+ ...providerTransferIx2,
924
+ accounts: [
925
+ ...providerTransferIx2.accounts,
926
+ { address: reference, role: kit.AccountRole.READONLY }
927
+ ]
928
+ };
929
+ const instructions2 = [providerTransferIxWithReference2];
930
+ if (paymentRequest.fee_address && feeAmount > 0) {
931
+ instructions2.push(
932
+ system.getTransferSolInstruction({
933
+ source: payerSigner,
934
+ destination: kit.address(paymentRequest.fee_address),
935
+ amount: BigInt(feeAmount)
936
+ })
937
+ );
938
+ }
939
+ return instructions2;
940
+ }
941
+ const mint = kit.address(asset.mint);
942
+ const payerAddress = payerSigner.address;
943
+ const [payerAta] = await token.findAssociatedTokenPda({
944
+ owner: payerAddress,
945
+ tokenProgram: token.TOKEN_PROGRAM_ADDRESS,
946
+ mint
947
+ });
948
+ const [recipientAta] = await token.findAssociatedTokenPda({
949
+ owner: recipient,
950
+ tokenProgram: token.TOKEN_PROGRAM_ADDRESS,
951
+ mint
952
+ });
953
+ const instructions = [];
954
+ instructions.push(
955
+ token.getCreateAssociatedTokenIdempotentInstruction(
956
+ {
957
+ payer: payerSigner,
958
+ ata: recipientAta,
959
+ owner: recipient,
960
+ mint
961
+ },
962
+ { programAddress: token.ASSOCIATED_TOKEN_PROGRAM_ADDRESS }
963
+ )
964
+ );
965
+ let treasuryAta;
966
+ if (paymentRequest.fee_address && feeAmount > 0) {
967
+ const treasuryOwner = kit.address(paymentRequest.fee_address);
968
+ [treasuryAta] = await token.findAssociatedTokenPda({
969
+ owner: treasuryOwner,
970
+ tokenProgram: token.TOKEN_PROGRAM_ADDRESS,
971
+ mint
972
+ });
973
+ instructions.push(
974
+ token.getCreateAssociatedTokenIdempotentInstruction(
975
+ {
976
+ payer: payerSigner,
977
+ ata: treasuryAta,
978
+ owner: treasuryOwner,
979
+ mint
980
+ },
981
+ { programAddress: token.ASSOCIATED_TOKEN_PROGRAM_ADDRESS }
982
+ )
983
+ );
984
+ }
985
+ const providerTransferIx = token.getTransferCheckedInstruction({
986
+ source: payerAta,
987
+ mint,
988
+ destination: recipientAta,
989
+ authority: payerSigner,
990
+ amount: BigInt(providerAmount),
991
+ decimals: asset.decimals
744
992
  });
745
993
  const providerTransferIxWithReference = {
746
994
  ...providerTransferIx,
747
995
  accounts: [...providerTransferIx.accounts, { address: reference, role: kit.AccountRole.READONLY }]
748
996
  };
749
- const instructions = [providerTransferIxWithReference];
750
- if (paymentRequest.fee_address && feeAmount > 0) {
997
+ instructions.push(providerTransferIxWithReference);
998
+ if (treasuryAta && paymentRequest.fee_address && feeAmount > 0) {
751
999
  instructions.push(
752
- system.getTransferSolInstruction({
753
- source: payerSigner,
754
- destination: kit.address(paymentRequest.fee_address),
755
- amount: BigInt(feeAmount)
1000
+ token.getTransferCheckedInstruction({
1001
+ source: payerAta,
1002
+ mint,
1003
+ destination: treasuryAta,
1004
+ authority: payerSigner,
1005
+ amount: BigInt(feeAmount),
1006
+ decimals: asset.decimals
756
1007
  })
757
1008
  );
758
1009
  }
@@ -768,6 +1019,95 @@ async function createPaymentRequestWithOnchainConfig(rpc, programId, recipient,
768
1019
  options
769
1020
  );
770
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;
771
1111
  function toDTag(name) {
772
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, "");
773
1113
  if (!tag) {
@@ -775,6 +1115,28 @@ function toDTag(name) {
775
1115
  }
776
1116
  return tag;
777
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
+ }
778
1140
  function buildAgentsFromEvents(events, network) {
779
1141
  const latestByDTag = /* @__PURE__ */ new Map();
780
1142
  for (const event of events) {
@@ -869,9 +1231,37 @@ function buildAgentsFromEvents(events, network) {
869
1231
  }
870
1232
  return agentMap;
871
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
+ }
872
1257
  var DiscoveryService = class {
873
- constructor(pool) {
1258
+ constructor(pool, defaultRpc) {
874
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;
875
1265
  }
876
1266
  /** Count elisym agents (kind:31990 with "elisym" tag). */
877
1267
  async fetchAllAgentCount() {
@@ -959,8 +1349,21 @@ var DiscoveryService = class {
959
1349
  }
960
1350
  return agents;
961
1351
  }
962
- /** Fetch elisym agents filtered by network. */
963
- async fetchAgents(network = "devnet", limit) {
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) {
964
1367
  const filter = {
965
1368
  kinds: [KIND_APP_HANDLER],
966
1369
  "#t": ["elisym"]
@@ -970,40 +1373,100 @@ var DiscoveryService = class {
970
1373
  }
971
1374
  const events = await this.pool.querySync(filter);
972
1375
  const agentMap = buildAgentsFromEvents(events, network);
973
- const agents = Array.from(agentMap.values()).sort((a, b) => b.lastSeen - a.lastSeen);
1376
+ const agents = Array.from(agentMap.values());
974
1377
  const agentPubkeys = Array.from(agentMap.keys());
975
- if (agentPubkeys.length > 0) {
976
- const activitySince = Math.floor(Date.now() / 1e3) - 24 * 60 * 60;
977
- const resultKinds = /* @__PURE__ */ new Set();
978
- for (const agent of agentMap.values()) {
979
- for (const k of agent.supportedKinds) {
980
- if (k >= KIND_JOB_REQUEST_BASE && k < KIND_JOB_RESULT_BASE) {
981
- resultKinds.add(KIND_JOB_RESULT_BASE + (k - KIND_JOB_REQUEST_BASE));
982
- }
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));
983
1387
  }
984
1388
  }
985
- resultKinds.add(jobResultKind(DEFAULT_KIND_OFFSET));
986
- const [activityEvents] = await Promise.all([
987
- this.pool.queryBatched(
988
- {
989
- kinds: [...resultKinds, KIND_JOB_FEEDBACK],
990
- since: activitySince
991
- },
992
- agentPubkeys
993
- ),
994
- this.enrichWithMetadata(agents)
995
- ]);
996
- for (const ev of activityEvents) {
997
- if (!nostrTools.verifyEvent(ev)) {
998
- continue;
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;
999
1436
  }
1000
- const agent = agentMap.get(ev.pubkey);
1001
- if (agent && ev.created_at > agent.lastSeen) {
1002
- agent.lastSeen = ev.created_at;
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]);
1003
1448
  }
1004
1449
  }
1005
- agents.sort((a, b) => b.lastSeen - a.lastSeen);
1006
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);
1007
1470
  return agents;
1008
1471
  }
1009
1472
  /**
@@ -2351,7 +2814,7 @@ var ElisymClient = class {
2351
2814
  payment;
2352
2815
  constructor(config = {}) {
2353
2816
  this.pool = new NostrPool(config.relays ?? RELAYS);
2354
- this.discovery = new DiscoveryService(this.pool);
2817
+ this.discovery = new DiscoveryService(this.pool, config.rpc);
2355
2818
  this.marketplace = new MarketplaceService(this.pool);
2356
2819
  this.ping = new PingService(this.pool);
2357
2820
  this.media = new MediaService(config.uploadUrl);
@@ -2361,96 +2824,124 @@ var ElisymClient = class {
2361
2824
  this.pool.close();
2362
2825
  }
2363
2826
  };
2364
-
2365
- // src/payment/assets.ts
2366
- var NATIVE_SOL = {
2367
- chain: "solana",
2368
- token: "sol",
2369
- decimals: 9,
2370
- symbol: "SOL"
2371
- };
2372
- var KNOWN_ASSETS = [NATIVE_SOL];
2373
- function assetKey(a) {
2374
- return a.mint ? `${a.chain}:${a.token}:${a.mint}` : `${a.chain}:${a.token}`;
2375
- }
2376
- function resolveKnownAsset(chain, token, mint) {
2377
- const key = mint ? `${chain}:${token}:${mint}` : `${chain}:${token}`;
2378
- return KNOWN_ASSETS.find((asset) => assetKey(asset) === key);
2379
- }
2380
- function assetByKey(key) {
2381
- return KNOWN_ASSETS.find((asset) => assetKey(asset) === key);
2382
- }
2383
- var DECIMAL_RE = /^(\d+\.\d*|\d*\.\d+|\d+)$/;
2384
- function parseAssetAmount(asset, human) {
2385
- const trimmed = human.trim();
2386
- if (!trimmed) {
2387
- throw new Error(`${asset.symbol} amount is empty`);
2388
- }
2389
- if (trimmed.startsWith("-")) {
2390
- throw new Error(`${asset.symbol} amount cannot be negative`);
2391
- }
2392
- if (!DECIMAL_RE.test(trimmed)) {
2393
- throw new Error(
2394
- `${asset.symbol} amount must be a non-negative decimal (e.g. "0.5", "1"); got "${human}"`
2395
- );
2396
- }
2397
- const dotPos = trimmed.indexOf(".");
2398
- let wholePart;
2399
- if (dotPos === -1) {
2400
- wholePart = trimmed;
2401
- } else if (dotPos === 0) {
2402
- wholePart = "0";
2403
- } else {
2404
- wholePart = trimmed.slice(0, dotPos);
2405
- }
2406
- const fracPart = dotPos === -1 ? "" : trimmed.slice(dotPos + 1);
2407
- if (fracPart.length > asset.decimals) {
2408
- throw new Error(
2409
- `${asset.symbol} amount has too many decimals (max ${asset.decimals}); got "${human}"`
2410
- );
2411
- }
2412
- const unit = 10n ** BigInt(asset.decimals);
2413
- const whole = BigInt(wholePart);
2414
- const frac = fracPart ? BigInt(fracPart.padEnd(asset.decimals, "0")) : 0n;
2415
- const raw = whole * unit + frac;
2416
- if (raw === 0n) {
2417
- throw new Error(`${asset.symbol} amount must be positive; got "${human}"`);
2418
- }
2419
- if (raw > BigInt(Number.MAX_SAFE_INTEGER)) {
2420
- throw new Error(
2421
- `${asset.symbol} amount exceeds safe range (max ${Number.MAX_SAFE_INTEGER} subunits)`
2422
- );
2827
+ var DEFAULT_COMPUTE_UNIT_LIMIT2 = 2e5;
2828
+ var DEFAULT_PRIORITY_FEE_PERCENTILE2 = 75;
2829
+ var BASE_FEE_LAMPORTS_PER_SIGNATURE = 5000n;
2830
+ var FALLBACK_ATA_RENT_LAMPORTS = 2039280n;
2831
+ var SPL_TOKEN_ACCOUNT_SIZE = 165;
2832
+ async function estimateSolFeeLamports(rpc, paymentRequest, _payerAddress, options) {
2833
+ const numSignatures = options?.numSignatures ?? 1;
2834
+ const computeUnitLimit = options?.computeUnitLimit ?? DEFAULT_COMPUTE_UNIT_LIMIT2;
2835
+ const priorityFeeMicroLamports = options?.priorityFeeMicroLamports ?? await estimatePriorityFeeMicroLamports(rpc, {
2836
+ percentile: options?.priorityFeePercentile ?? DEFAULT_PRIORITY_FEE_PERCENTILE2
2837
+ });
2838
+ const baseFeeLamports = BASE_FEE_LAMPORTS_PER_SIGNATURE * BigInt(numSignatures);
2839
+ const priorityFeeLamports = ceilDiv(
2840
+ priorityFeeMicroLamports * BigInt(computeUnitLimit),
2841
+ 1000000n
2842
+ );
2843
+ const asset = resolveAssetFromPaymentRequest(paymentRequest);
2844
+ let rentLamports = 0n;
2845
+ let rentPerAtaLamports = 0n;
2846
+ let missingAtaCount = 0;
2847
+ if (asset.mint) {
2848
+ rentPerAtaLamports = await fetchAtaRent(rpc);
2849
+ const mint = kit.address(asset.mint);
2850
+ const ataAccountsToCheck = [];
2851
+ const [recipientAta] = await token.findAssociatedTokenPda({
2852
+ owner: kit.address(paymentRequest.recipient),
2853
+ tokenProgram: token.TOKEN_PROGRAM_ADDRESS,
2854
+ mint
2855
+ });
2856
+ ataAccountsToCheck.push(recipientAta);
2857
+ const feeAmount = paymentRequest.fee_amount ?? 0;
2858
+ if (paymentRequest.fee_address && feeAmount > 0) {
2859
+ const [treasuryAta] = await token.findAssociatedTokenPda({
2860
+ owner: kit.address(paymentRequest.fee_address),
2861
+ tokenProgram: token.TOKEN_PROGRAM_ADDRESS,
2862
+ mint
2863
+ });
2864
+ ataAccountsToCheck.push(treasuryAta);
2865
+ }
2866
+ missingAtaCount = await countMissingAccounts(rpc, ataAccountsToCheck);
2867
+ rentLamports = rentPerAtaLamports * BigInt(missingAtaCount);
2423
2868
  }
2424
- return raw;
2869
+ const totalLamports = baseFeeLamports + priorityFeeLamports + rentLamports;
2870
+ return {
2871
+ baseFeeLamports,
2872
+ priorityFeeLamports,
2873
+ rentLamports,
2874
+ totalLamports,
2875
+ breakdown: {
2876
+ numSignatures,
2877
+ priorityFeeMicroLamports,
2878
+ computeUnitLimit,
2879
+ rentPerAtaLamports,
2880
+ missingAtaCount
2881
+ }
2882
+ };
2425
2883
  }
2426
- function formatAssetAmount(asset, raw) {
2427
- const sign = raw < 0n ? "-" : "";
2428
- const abs = raw < 0n ? -raw : raw;
2429
- const unit = 10n ** BigInt(asset.decimals);
2430
- const whole = abs / unit;
2431
- const frac = abs % unit;
2432
- if (asset.decimals === 0) {
2433
- return `${sign}${whole} ${asset.symbol}`;
2884
+ async function fetchAtaRent(rpc) {
2885
+ try {
2886
+ const lamports = await rpc.getMinimumBalanceForRentExemption(BigInt(SPL_TOKEN_ACCOUNT_SIZE)).send();
2887
+ if (typeof lamports === "bigint") {
2888
+ return lamports;
2889
+ }
2890
+ if (typeof lamports === "number" && Number.isFinite(lamports) && lamports > 0) {
2891
+ return BigInt(lamports);
2892
+ }
2893
+ return FALLBACK_ATA_RENT_LAMPORTS;
2894
+ } catch {
2895
+ return FALLBACK_ATA_RENT_LAMPORTS;
2434
2896
  }
2435
- return `${sign}${whole}.${frac.toString().padStart(asset.decimals, "0")} ${asset.symbol}`;
2436
2897
  }
2437
- async function writeFileAtomic(path$1, data, mode) {
2438
- await promises.mkdir(path.dirname(path$1), { recursive: true });
2439
- const tmpPath = `${path$1}.tmp.${crypto$1.randomBytes(6).toString("hex")}`;
2440
- await promises.writeFile(tmpPath, data, { mode });
2441
- try {
2442
- await promises.rename(tmpPath, path$1);
2443
- } catch (e) {
2898
+ async function countMissingAccounts(rpc, accounts) {
2899
+ if (accounts.length === 0) {
2900
+ return 0;
2901
+ }
2902
+ let missing = 0;
2903
+ for (const acct of accounts) {
2444
2904
  try {
2445
- const { unlink } = await import('fs/promises');
2446
- await unlink(tmpPath);
2905
+ const res = await rpc.getAccountInfo(acct, { encoding: "base64" }).send();
2906
+ if (!res || !res.value) {
2907
+ missing++;
2908
+ }
2447
2909
  } catch {
2910
+ missing++;
2448
2911
  }
2449
- throw e;
2450
2912
  }
2913
+ return missing;
2914
+ }
2915
+ function ceilDiv(num, denom) {
2916
+ if (denom === 0n) {
2917
+ throw new Error("division by zero in ceilDiv");
2918
+ }
2919
+ const q = num / denom;
2920
+ const r = num % denom;
2921
+ return r === 0n ? q : q + 1n;
2922
+ }
2923
+ function formatFeeBreakdown(estimate) {
2924
+ const line = (label, lamports) => {
2925
+ const label16 = label.padEnd(14);
2926
+ return ` ${label16}${lamports.toString()} lamports (${lamportsToSol(lamports)} SOL)`;
2927
+ };
2928
+ const lines = [
2929
+ "Estimated SOL cost for this transaction:",
2930
+ line("Base fee:", estimate.baseFeeLamports),
2931
+ line("Priority fee:", estimate.priorityFeeLamports)
2932
+ ];
2933
+ if (estimate.rentLamports > 0n) {
2934
+ lines.push(line("ATA rent:", estimate.rentLamports));
2935
+ }
2936
+ lines.push(line("Total:", estimate.totalLamports));
2937
+ return lines.join("\n");
2938
+ }
2939
+ function lamportsToSol(lamports) {
2940
+ const LAMPORTS_PER_SOL2 = 1000000000n;
2941
+ const whole = lamports / LAMPORTS_PER_SOL2;
2942
+ const frac = lamports % LAMPORTS_PER_SOL2;
2943
+ return `${whole}.${frac.toString().padStart(9, "0")}`;
2451
2944
  }
2452
-
2453
- // src/config/global.ts
2454
2945
  var SessionSpendLimitEntrySchema = zod.z.object({
2455
2946
  chain: zod.z.enum(["solana"]),
2456
2947
  token: zod.z.string().min(1).max(16).regex(/^[a-z0-9]+$/, "token must be lowercase alphanumeric"),
@@ -2460,30 +2951,6 @@ var SessionSpendLimitEntrySchema = zod.z.object({
2460
2951
  var GlobalConfigSchema = zod.z.object({
2461
2952
  session_spend_limits: zod.z.array(SessionSpendLimitEntrySchema).max(16).optional()
2462
2953
  }).strict();
2463
- function isEnoent(e) {
2464
- return typeof e === "object" && e !== null && "code" in e && e.code === "ENOENT";
2465
- }
2466
- async function loadGlobalConfig(path) {
2467
- let raw;
2468
- try {
2469
- raw = await promises.readFile(path, "utf-8");
2470
- } catch (e) {
2471
- if (isEnoent(e)) {
2472
- return {};
2473
- }
2474
- throw e;
2475
- }
2476
- if (raw.trim() === "") {
2477
- return {};
2478
- }
2479
- const parsed = YAML2__default.default.parse(raw);
2480
- return GlobalConfigSchema.parse(parsed ?? {});
2481
- }
2482
- async function writeGlobalConfig(path, config) {
2483
- const validated = GlobalConfigSchema.parse(config);
2484
- const body = YAML2__default.default.stringify(validated);
2485
- await writeFileAtomic(path, body, 420);
2486
- }
2487
2954
  function formatSol(lamports) {
2488
2955
  const sol = new Decimal2__default.default(lamports).div(LAMPORTS_PER_SOL);
2489
2956
  if (sol.gte(1e6)) {
@@ -2708,6 +3175,7 @@ exports.RELAYS = RELAYS;
2708
3175
  exports.SECRET_REDACT_PATHS = SECRET_REDACT_PATHS;
2709
3176
  exports.SessionSpendLimitEntrySchema = SessionSpendLimitEntrySchema;
2710
3177
  exports.SolanaPaymentStrategy = SolanaPaymentStrategy;
3178
+ exports.USDC_SOLANA_DEVNET = USDC_SOLANA_DEVNET;
2711
3179
  exports.assertExpiry = assertExpiry;
2712
3180
  exports.assertLamports = assertLamports;
2713
3181
  exports.assetByKey = assetByKey;
@@ -2716,28 +3184,33 @@ exports.buildPaymentInstructions = buildPaymentInstructions;
2716
3184
  exports.calculateProtocolFee = calculateProtocolFee;
2717
3185
  exports.clearPriorityFeeCache = clearPriorityFeeCache;
2718
3186
  exports.clearProtocolConfigCache = clearProtocolConfigCache;
3187
+ exports.clearQuickVerifyCache = clearQuickVerifyCache;
3188
+ exports.compareAgentsByRank = compareAgentsByRank;
3189
+ exports.computeRankKey = computeRankKey;
2719
3190
  exports.createPaymentRequestWithOnchainConfig = createPaymentRequestWithOnchainConfig;
2720
3191
  exports.createSlidingWindowLimiter = createSlidingWindowLimiter;
2721
3192
  exports.estimatePriorityFeeMicroLamports = estimatePriorityFeeMicroLamports;
3193
+ exports.estimateSolFeeLamports = estimateSolFeeLamports;
2722
3194
  exports.formatAssetAmount = formatAssetAmount;
3195
+ exports.formatFeeBreakdown = formatFeeBreakdown;
2723
3196
  exports.formatSol = formatSol;
2724
3197
  exports.getProtocolConfig = getProtocolConfig;
2725
3198
  exports.getProtocolProgramId = getProtocolProgramId;
2726
3199
  exports.jobRequestKind = jobRequestKind;
2727
3200
  exports.jobResultKind = jobResultKind;
2728
- exports.loadGlobalConfig = loadGlobalConfig;
2729
3201
  exports.makeCensor = makeCensor;
2730
3202
  exports.nip44Decrypt = nip44Decrypt;
2731
3203
  exports.nip44Encrypt = nip44Encrypt;
2732
3204
  exports.parseAssetAmount = parseAssetAmount;
2733
3205
  exports.parsePaymentRequest = parsePaymentRequest;
2734
3206
  exports.pickPercentileFee = pickPercentileFee;
3207
+ exports.resolveAssetFromPaymentRequest = resolveAssetFromPaymentRequest;
2735
3208
  exports.resolveKnownAsset = resolveKnownAsset;
2736
3209
  exports.timeAgo = timeAgo;
2737
3210
  exports.toDTag = toDTag;
2738
3211
  exports.truncateKey = truncateKey;
2739
3212
  exports.validateAgentName = validateAgentName;
2740
3213
  exports.validateExpiry = validateExpiry;
2741
- exports.writeGlobalConfig = writeGlobalConfig;
3214
+ exports.verifyJobPaymentQuick = verifyJobPaymentQuick;
2742
3215
  //# sourceMappingURL=index.cjs.map
2743
3216
  //# sourceMappingURL=index.cjs.map