@elisym/sdk 0.7.0 → 0.9.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,6 +1,7 @@
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');
@@ -118,13 +119,13 @@ function decodeConfig(encodedAccount) {
118
119
  getConfigDecoder()
119
120
  );
120
121
  }
121
- async function fetchConfig(rpc, address2, config) {
122
- const maybeAccount = await fetchMaybeConfig(rpc, address2, config);
122
+ async function fetchConfig(rpc, address3, config) {
123
+ const maybeAccount = await fetchMaybeConfig(rpc, address3, config);
123
124
  kit.assertAccountExists(maybeAccount);
124
125
  return maybeAccount;
125
126
  }
126
- async function fetchMaybeConfig(rpc, address2, config) {
127
- const maybeAccount = await kit.fetchEncodedAccount(rpc, address2, config);
127
+ async function fetchMaybeConfig(rpc, address3, config) {
128
+ const maybeAccount = await kit.fetchEncodedAccount(rpc, address3, config);
128
129
  return decodeConfig(maybeAccount);
129
130
  }
130
131
  if (process.env.NODE_ENV !== "production") ;
@@ -175,6 +176,99 @@ async function getProtocolConfig(rpc, programId, options) {
175
176
  );
176
177
  }
177
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
+ }
178
272
  var BPS_DENOMINATOR = 1e4;
179
273
  function assertLamports(value, field) {
180
274
  if (!Number.isInteger(value) || value < 0) {
@@ -284,6 +378,12 @@ var SOLANA_ADDRESS_LENGTH_RE = /^.{32,44}$/;
284
378
  var lamportsSchema = zod.z.number().int().positive().max(MAX_SAFE_LAMPORTS, `amount must be <= ${MAX_SAFE_LAMPORTS}`);
285
379
  var feeAmountSchema = zod.z.number().int().nonnegative().max(MAX_SAFE_LAMPORTS, `fee_amount must be <= ${MAX_SAFE_LAMPORTS}`);
286
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
+ });
287
387
  var PaymentRequestSchema = zod.z.object({
288
388
  recipient: solanaAddressSchema,
289
389
  amount: lamportsSchema,
@@ -292,7 +392,8 @@ var PaymentRequestSchema = zod.z.object({
292
392
  fee_address: solanaAddressSchema.optional(),
293
393
  fee_amount: feeAmountSchema.optional(),
294
394
  created_at: zod.z.number().int().positive(),
295
- 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()
296
397
  });
297
398
  function parsePaymentRequest(input, options) {
298
399
  let parsed;
@@ -374,6 +475,12 @@ var SolanaPaymentStrategy = class {
374
475
  assertExpirySecs(expirySecs);
375
476
  const feeAmount = calculateProtocolFee(amount, config.feeBps);
376
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;
377
484
  return {
378
485
  recipient: recipientAddress,
379
486
  amount,
@@ -381,7 +488,8 @@ var SolanaPaymentStrategy = class {
381
488
  fee_address: config.treasury,
382
489
  fee_amount: feeAmount,
383
490
  created_at: Math.floor(Date.now() / 1e3),
384
- expiry_secs: expirySecs
491
+ expiry_secs: expirySecs,
492
+ ...assetRef ? { asset: assetRef } : {}
385
493
  };
386
494
  }
387
495
  validatePaymentRequest(requestJson, config, expectedRecipient, options) {
@@ -399,6 +507,16 @@ var SolanaPaymentStrategy = class {
399
507
  return { code: "invalid_amount", message: parsed.error.message };
400
508
  }
401
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
+ }
402
520
  if (!isValidSolanaAddress(data.recipient)) {
403
521
  return {
404
522
  code: "invalid_recipient_address",
@@ -487,7 +605,7 @@ var SolanaPaymentStrategy = class {
487
605
  if (!Number.isInteger(computeUnitLimit) || computeUnitLimit <= 0) {
488
606
  throw new Error(`Invalid computeUnitLimit: ${computeUnitLimit}. Must be a positive integer.`);
489
607
  }
490
- const paymentInstructions = buildPaymentInstructions(paymentRequest, payerSigner);
608
+ const paymentInstructions = await buildPaymentInstructions(paymentRequest, payerSigner);
491
609
  const priorityFeeMicroLamports = options?.priorityFeeMicroLamports ?? await estimatePriorityFeeMicroLamports(rpc, {
492
610
  percentile: options?.priorityFeePercentile ?? DEFAULT_PRIORITY_FEE_PERCENTILE
493
611
  });
@@ -549,6 +667,16 @@ var SolanaPaymentStrategy = class {
549
667
  error: `Fee amount (${feeAmount}) exceeds or equals total amount (${paymentRequest.amount})`
550
668
  };
551
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;
552
680
  if (options?.txSignature) {
553
681
  return this._verifyBySignature(
554
682
  rpc,
@@ -558,6 +686,7 @@ var SolanaPaymentStrategy = class {
558
686
  treasury,
559
687
  expectedNet,
560
688
  feeAmount,
689
+ mint,
561
690
  options?.retries ?? DEFAULTS.VERIFY_RETRIES,
562
691
  options?.intervalMs ?? DEFAULTS.VERIFY_INTERVAL_MS
563
692
  );
@@ -569,11 +698,12 @@ var SolanaPaymentStrategy = class {
569
698
  treasury,
570
699
  expectedNet,
571
700
  feeAmount,
701
+ mint,
572
702
  options?.retries ?? DEFAULTS.VERIFY_BY_REF_RETRIES,
573
703
  options?.intervalMs ?? DEFAULTS.VERIFY_BY_REF_INTERVAL_MS
574
704
  );
575
705
  }
576
- async _verifyBySignature(rpc, txSignature, referenceKey, recipientAddress, treasuryAddress, expectedNet, expectedFee, retries, intervalMs) {
706
+ async _verifyBySignature(rpc, txSignature, referenceKey, recipientAddress, treasuryAddress, expectedNet, expectedFee, mint, retries, intervalMs) {
577
707
  let lastError;
578
708
  for (let attempt = 0; attempt < retries; attempt++) {
579
709
  try {
@@ -592,15 +722,18 @@ var SolanaPaymentStrategy = class {
592
722
  error: tx?.meta?.err ? "Transaction failed on-chain" : "Transaction not found"
593
723
  };
594
724
  }
595
- const verdict = checkBalanceDiff({
725
+ const verdict = checkTxDiff({
596
726
  accountKeys: tx.transaction.message.accountKeys,
597
727
  preBalances: tx.meta.preBalances,
598
728
  postBalances: tx.meta.postBalances,
729
+ preTokenBalances: tx.meta.preTokenBalances,
730
+ postTokenBalances: tx.meta.postTokenBalances,
599
731
  referenceKey,
600
732
  recipientAddress,
601
733
  treasuryAddress,
602
734
  expectedNet,
603
- expectedFee
735
+ expectedFee,
736
+ mint
604
737
  });
605
738
  if (verdict.ok) {
606
739
  return { verified: true, txSignature };
@@ -618,7 +751,7 @@ var SolanaPaymentStrategy = class {
618
751
  error: `Verification failed after ${retries} retries: ${lastError instanceof Error ? lastError.message : "unknown error"}`
619
752
  };
620
753
  }
621
- async _verifyByReference(rpc, referenceKey, recipientAddress, treasuryAddress, expectedNet, expectedFee, retries, intervalMs) {
754
+ async _verifyByReference(rpc, referenceKey, recipientAddress, treasuryAddress, expectedNet, expectedFee, mint, retries, intervalMs) {
622
755
  let lastError;
623
756
  const reference = kit.address(referenceKey);
624
757
  for (let attempt = 0; attempt < retries; attempt++) {
@@ -642,15 +775,18 @@ var SolanaPaymentStrategy = class {
642
775
  if (!tx?.meta || tx.meta.err) {
643
776
  continue;
644
777
  }
645
- const verdict = checkBalanceDiff({
778
+ const verdict = checkTxDiff({
646
779
  accountKeys: tx.transaction.message.accountKeys,
647
780
  preBalances: tx.meta.preBalances,
648
781
  postBalances: tx.meta.postBalances,
782
+ preTokenBalances: tx.meta.preTokenBalances,
783
+ postTokenBalances: tx.meta.postTokenBalances,
649
784
  referenceKey,
650
785
  recipientAddress,
651
786
  treasuryAddress,
652
787
  expectedNet,
653
- expectedFee
788
+ expectedFee,
789
+ mint
654
790
  });
655
791
  if (verdict.ok) {
656
792
  return { verified: true, txSignature: sig };
@@ -670,7 +806,7 @@ var SolanaPaymentStrategy = class {
670
806
  };
671
807
  }
672
808
  };
673
- function checkBalanceDiff(input) {
809
+ function checkTxDiff(input) {
674
810
  const balanceCount = input.preBalances.length;
675
811
  const keyToIdx = /* @__PURE__ */ new Map();
676
812
  for (let i = 0; i < Math.min(input.accountKeys.length, balanceCount); i++) {
@@ -682,6 +818,9 @@ function checkBalanceDiff(input) {
682
818
  if (!keyToIdx.has(input.referenceKey)) {
683
819
  return { ok: false, reason: "Reference key not found in transaction - possible replay" };
684
820
  }
821
+ if (input.mint) {
822
+ return checkTokenBalanceDiff(input);
823
+ }
685
824
  const recipientIdx = keyToIdx.get(input.recipientAddress);
686
825
  if (recipientIdx === void 0) {
687
826
  return { ok: false, reason: "Recipient not found in transaction" };
@@ -714,6 +853,47 @@ function checkBalanceDiff(input) {
714
853
  }
715
854
  return { ok: true };
716
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
+ }
717
897
  function bigIntDelta(post, pre) {
718
898
  const postValue = post === void 0 ? 0n : BigInt(post);
719
899
  const preValue = pre === void 0 ? 0n : BigInt(pre);
@@ -722,7 +902,7 @@ function bigIntDelta(post, pre) {
722
902
  function waitMs(ms) {
723
903
  return new Promise((resolve) => setTimeout(resolve, ms));
724
904
  }
725
- function buildPaymentInstructions(paymentRequest, payerSigner) {
905
+ async function buildPaymentInstructions(paymentRequest, payerSigner) {
726
906
  const recipient = kit.address(paymentRequest.recipient);
727
907
  const reference = kit.address(paymentRequest.reference);
728
908
  const feeAmount = paymentRequest.fee_amount ?? 0;
@@ -732,22 +912,98 @@ function buildPaymentInstructions(paymentRequest, payerSigner) {
732
912
  `Fee amount (${feeAmount}) exceeds or equals total amount (${paymentRequest.amount}). Cannot create transaction with non-positive provider amount.`
733
913
  );
734
914
  }
735
- const providerTransferIx = system.getTransferSolInstruction({
736
- source: payerSigner,
737
- destination: recipient,
738
- 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
739
992
  });
740
993
  const providerTransferIxWithReference = {
741
994
  ...providerTransferIx,
742
995
  accounts: [...providerTransferIx.accounts, { address: reference, role: kit.AccountRole.READONLY }]
743
996
  };
744
- const instructions = [providerTransferIxWithReference];
745
- if (paymentRequest.fee_address && feeAmount > 0) {
997
+ instructions.push(providerTransferIxWithReference);
998
+ if (treasuryAta && paymentRequest.fee_address && feeAmount > 0) {
746
999
  instructions.push(
747
- system.getTransferSolInstruction({
748
- source: payerSigner,
749
- destination: kit.address(paymentRequest.fee_address),
750
- 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
751
1007
  })
752
1008
  );
753
1009
  }
@@ -2356,6 +2612,133 @@ var ElisymClient = class {
2356
2612
  this.pool.close();
2357
2613
  }
2358
2614
  };
2615
+ var DEFAULT_COMPUTE_UNIT_LIMIT2 = 2e5;
2616
+ var DEFAULT_PRIORITY_FEE_PERCENTILE2 = 75;
2617
+ var BASE_FEE_LAMPORTS_PER_SIGNATURE = 5000n;
2618
+ var FALLBACK_ATA_RENT_LAMPORTS = 2039280n;
2619
+ var SPL_TOKEN_ACCOUNT_SIZE = 165;
2620
+ async function estimateSolFeeLamports(rpc, paymentRequest, _payerAddress, options) {
2621
+ const numSignatures = options?.numSignatures ?? 1;
2622
+ const computeUnitLimit = options?.computeUnitLimit ?? DEFAULT_COMPUTE_UNIT_LIMIT2;
2623
+ const priorityFeeMicroLamports = options?.priorityFeeMicroLamports ?? await estimatePriorityFeeMicroLamports(rpc, {
2624
+ percentile: options?.priorityFeePercentile ?? DEFAULT_PRIORITY_FEE_PERCENTILE2
2625
+ });
2626
+ const baseFeeLamports = BASE_FEE_LAMPORTS_PER_SIGNATURE * BigInt(numSignatures);
2627
+ const priorityFeeLamports = ceilDiv(
2628
+ priorityFeeMicroLamports * BigInt(computeUnitLimit),
2629
+ 1000000n
2630
+ );
2631
+ const asset = resolveAssetFromPaymentRequest(paymentRequest);
2632
+ let rentLamports = 0n;
2633
+ let rentPerAtaLamports = 0n;
2634
+ let missingAtaCount = 0;
2635
+ if (asset.mint) {
2636
+ rentPerAtaLamports = await fetchAtaRent(rpc);
2637
+ const mint = kit.address(asset.mint);
2638
+ const ataAccountsToCheck = [];
2639
+ const [recipientAta] = await token.findAssociatedTokenPda({
2640
+ owner: kit.address(paymentRequest.recipient),
2641
+ tokenProgram: token.TOKEN_PROGRAM_ADDRESS,
2642
+ mint
2643
+ });
2644
+ ataAccountsToCheck.push(recipientAta);
2645
+ const feeAmount = paymentRequest.fee_amount ?? 0;
2646
+ if (paymentRequest.fee_address && feeAmount > 0) {
2647
+ const [treasuryAta] = await token.findAssociatedTokenPda({
2648
+ owner: kit.address(paymentRequest.fee_address),
2649
+ tokenProgram: token.TOKEN_PROGRAM_ADDRESS,
2650
+ mint
2651
+ });
2652
+ ataAccountsToCheck.push(treasuryAta);
2653
+ }
2654
+ missingAtaCount = await countMissingAccounts(rpc, ataAccountsToCheck);
2655
+ rentLamports = rentPerAtaLamports * BigInt(missingAtaCount);
2656
+ }
2657
+ const totalLamports = baseFeeLamports + priorityFeeLamports + rentLamports;
2658
+ return {
2659
+ baseFeeLamports,
2660
+ priorityFeeLamports,
2661
+ rentLamports,
2662
+ totalLamports,
2663
+ breakdown: {
2664
+ numSignatures,
2665
+ priorityFeeMicroLamports,
2666
+ computeUnitLimit,
2667
+ rentPerAtaLamports,
2668
+ missingAtaCount
2669
+ }
2670
+ };
2671
+ }
2672
+ async function fetchAtaRent(rpc) {
2673
+ try {
2674
+ const lamports = await rpc.getMinimumBalanceForRentExemption(BigInt(SPL_TOKEN_ACCOUNT_SIZE)).send();
2675
+ if (typeof lamports === "bigint") {
2676
+ return lamports;
2677
+ }
2678
+ if (typeof lamports === "number" && Number.isFinite(lamports) && lamports > 0) {
2679
+ return BigInt(lamports);
2680
+ }
2681
+ return FALLBACK_ATA_RENT_LAMPORTS;
2682
+ } catch {
2683
+ return FALLBACK_ATA_RENT_LAMPORTS;
2684
+ }
2685
+ }
2686
+ async function countMissingAccounts(rpc, accounts) {
2687
+ if (accounts.length === 0) {
2688
+ return 0;
2689
+ }
2690
+ let missing = 0;
2691
+ for (const acct of accounts) {
2692
+ try {
2693
+ const res = await rpc.getAccountInfo(acct, { encoding: "base64" }).send();
2694
+ if (!res || !res.value) {
2695
+ missing++;
2696
+ }
2697
+ } catch {
2698
+ missing++;
2699
+ }
2700
+ }
2701
+ return missing;
2702
+ }
2703
+ function ceilDiv(num, denom) {
2704
+ if (denom === 0n) {
2705
+ throw new Error("division by zero in ceilDiv");
2706
+ }
2707
+ const q = num / denom;
2708
+ const r = num % denom;
2709
+ return r === 0n ? q : q + 1n;
2710
+ }
2711
+ function formatFeeBreakdown(estimate) {
2712
+ const line = (label, lamports) => {
2713
+ const label16 = label.padEnd(14);
2714
+ return ` ${label16}${lamports.toString()} lamports (${lamportsToSol(lamports)} SOL)`;
2715
+ };
2716
+ const lines = [
2717
+ "Estimated SOL cost for this transaction:",
2718
+ line("Base fee:", estimate.baseFeeLamports),
2719
+ line("Priority fee:", estimate.priorityFeeLamports)
2720
+ ];
2721
+ if (estimate.rentLamports > 0n) {
2722
+ lines.push(line("ATA rent:", estimate.rentLamports));
2723
+ }
2724
+ lines.push(line("Total:", estimate.totalLamports));
2725
+ return lines.join("\n");
2726
+ }
2727
+ function lamportsToSol(lamports) {
2728
+ const LAMPORTS_PER_SOL2 = 1000000000n;
2729
+ const whole = lamports / LAMPORTS_PER_SOL2;
2730
+ const frac = lamports % LAMPORTS_PER_SOL2;
2731
+ return `${whole}.${frac.toString().padStart(9, "0")}`;
2732
+ }
2733
+ var SessionSpendLimitEntrySchema = zod.z.object({
2734
+ chain: zod.z.enum(["solana"]),
2735
+ token: zod.z.string().min(1).max(16).regex(/^[a-z0-9]+$/, "token must be lowercase alphanumeric"),
2736
+ mint: zod.z.string().min(1).max(64).optional(),
2737
+ amount: zod.z.number().positive().finite()
2738
+ }).strict();
2739
+ var GlobalConfigSchema = zod.z.object({
2740
+ session_spend_limits: zod.z.array(SessionSpendLimitEntrySchema).max(16).optional()
2741
+ }).strict();
2359
2742
  function formatSol(lamports) {
2360
2743
  const sol = new Decimal2__default.default(lamports).div(LAMPORTS_PER_SOL);
2361
2744
  if (sol.gte(1e6)) {
@@ -2554,6 +2937,7 @@ exports.DEFAULT_REDACT_PATHS = DEFAULT_REDACT_PATHS;
2554
2937
  exports.DiscoveryService = DiscoveryService;
2555
2938
  exports.ElisymClient = ElisymClient;
2556
2939
  exports.ElisymIdentity = ElisymIdentity;
2940
+ exports.GlobalConfigSchema = GlobalConfigSchema;
2557
2941
  exports.INPUT_REDACT_PATHS = INPUT_REDACT_PATHS;
2558
2942
  exports.KIND_APP_HANDLER = KIND_APP_HANDLER;
2559
2943
  exports.KIND_JOB_FEEDBACK = KIND_JOB_FEEDBACK;
@@ -2563,10 +2947,12 @@ exports.KIND_JOB_RESULT = KIND_JOB_RESULT;
2563
2947
  exports.KIND_JOB_RESULT_BASE = KIND_JOB_RESULT_BASE;
2564
2948
  exports.KIND_PING = KIND_PING;
2565
2949
  exports.KIND_PONG = KIND_PONG;
2950
+ exports.KNOWN_ASSETS = KNOWN_ASSETS;
2566
2951
  exports.LAMPORTS_PER_SOL = LAMPORTS_PER_SOL;
2567
2952
  exports.LIMITS = LIMITS;
2568
2953
  exports.MarketplaceService = MarketplaceService;
2569
2954
  exports.MediaService = MediaService;
2955
+ exports.NATIVE_SOL = NATIVE_SOL;
2570
2956
  exports.NostrPool = NostrPool;
2571
2957
  exports.PROTOCOL_FEE_BPS = PROTOCOL_FEE_BPS;
2572
2958
  exports.PROTOCOL_PROGRAM_ID_DEVNET = PROTOCOL_PROGRAM_ID_DEVNET;
@@ -2575,9 +2961,13 @@ exports.PaymentRequestSchema = PaymentRequestSchema;
2575
2961
  exports.PingService = PingService;
2576
2962
  exports.RELAYS = RELAYS;
2577
2963
  exports.SECRET_REDACT_PATHS = SECRET_REDACT_PATHS;
2964
+ exports.SessionSpendLimitEntrySchema = SessionSpendLimitEntrySchema;
2578
2965
  exports.SolanaPaymentStrategy = SolanaPaymentStrategy;
2966
+ exports.USDC_SOLANA_DEVNET = USDC_SOLANA_DEVNET;
2579
2967
  exports.assertExpiry = assertExpiry;
2580
2968
  exports.assertLamports = assertLamports;
2969
+ exports.assetByKey = assetByKey;
2970
+ exports.assetKey = assetKey;
2581
2971
  exports.buildPaymentInstructions = buildPaymentInstructions;
2582
2972
  exports.calculateProtocolFee = calculateProtocolFee;
2583
2973
  exports.clearPriorityFeeCache = clearPriorityFeeCache;
@@ -2585,6 +2975,9 @@ exports.clearProtocolConfigCache = clearProtocolConfigCache;
2585
2975
  exports.createPaymentRequestWithOnchainConfig = createPaymentRequestWithOnchainConfig;
2586
2976
  exports.createSlidingWindowLimiter = createSlidingWindowLimiter;
2587
2977
  exports.estimatePriorityFeeMicroLamports = estimatePriorityFeeMicroLamports;
2978
+ exports.estimateSolFeeLamports = estimateSolFeeLamports;
2979
+ exports.formatAssetAmount = formatAssetAmount;
2980
+ exports.formatFeeBreakdown = formatFeeBreakdown;
2588
2981
  exports.formatSol = formatSol;
2589
2982
  exports.getProtocolConfig = getProtocolConfig;
2590
2983
  exports.getProtocolProgramId = getProtocolProgramId;
@@ -2593,8 +2986,11 @@ exports.jobResultKind = jobResultKind;
2593
2986
  exports.makeCensor = makeCensor;
2594
2987
  exports.nip44Decrypt = nip44Decrypt;
2595
2988
  exports.nip44Encrypt = nip44Encrypt;
2989
+ exports.parseAssetAmount = parseAssetAmount;
2596
2990
  exports.parsePaymentRequest = parsePaymentRequest;
2597
2991
  exports.pickPercentileFee = pickPercentileFee;
2992
+ exports.resolveAssetFromPaymentRequest = resolveAssetFromPaymentRequest;
2993
+ exports.resolveKnownAsset = resolveKnownAsset;
2598
2994
  exports.timeAgo = timeAgo;
2599
2995
  exports.toDTag = toDTag;
2600
2996
  exports.truncateKey = truncateKey;