@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.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { getTransferSolInstruction } from '@solana-program/system';
2
+ import { findAssociatedTokenPda, TOKEN_PROGRAM_ADDRESS, getCreateAssociatedTokenIdempotentInstruction, ASSOCIATED_TOKEN_PROGRAM_ADDRESS, getTransferCheckedInstruction } from '@solana-program/token';
2
3
  import { pipe, createTransactionMessage, setTransactionMessageFeePayerSigner, setTransactionMessageLifetimeUsingBlockhash, setTransactionMessageComputeUnitLimit, setTransactionMessageComputeUnitPrice, appendTransactionMessageInstructions, signTransactionMessageWithSigners, address, AccountRole, getProgramDerivedAddress, assertAccountExists, isAddress, getAddressDecoder, fetchEncodedAccount, decodeAccount, getStructDecoder, fixDecoderSize, getBytesDecoder, getU8Decoder, getOptionDecoder, getU16Decoder, getBooleanDecoder, getI64Decoder } from '@solana/kit';
3
4
  import Decimal2 from 'decimal.js-light';
4
5
  import { z } from 'zod';
@@ -93,13 +94,13 @@ function decodeConfig(encodedAccount) {
93
94
  getConfigDecoder()
94
95
  );
95
96
  }
96
- async function fetchConfig(rpc, address2, config) {
97
- const maybeAccount = await fetchMaybeConfig(rpc, address2, config);
97
+ async function fetchConfig(rpc, address3, config) {
98
+ const maybeAccount = await fetchMaybeConfig(rpc, address3, config);
98
99
  assertAccountExists(maybeAccount);
99
100
  return maybeAccount;
100
101
  }
101
- async function fetchMaybeConfig(rpc, address2, config) {
102
- const maybeAccount = await fetchEncodedAccount(rpc, address2, config);
102
+ async function fetchMaybeConfig(rpc, address3, config) {
103
+ const maybeAccount = await fetchEncodedAccount(rpc, address3, config);
103
104
  return decodeConfig(maybeAccount);
104
105
  }
105
106
  if (process.env.NODE_ENV !== "production") ;
@@ -150,6 +151,99 @@ async function getProtocolConfig(rpc, programId, options) {
150
151
  );
151
152
  }
152
153
  }
154
+
155
+ // src/payment/assets.ts
156
+ var NATIVE_SOL = {
157
+ chain: "solana",
158
+ token: "sol",
159
+ decimals: 9,
160
+ symbol: "SOL"
161
+ };
162
+ var USDC_SOLANA_DEVNET = {
163
+ chain: "solana",
164
+ token: "usdc",
165
+ mint: "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
166
+ decimals: 6,
167
+ symbol: "USDC"
168
+ };
169
+ var KNOWN_ASSETS = [NATIVE_SOL, USDC_SOLANA_DEVNET];
170
+ function assetKey(a) {
171
+ return a.mint ? `${a.chain}:${a.token}:${a.mint}` : `${a.chain}:${a.token}`;
172
+ }
173
+ function resolveKnownAsset(chain, token, mint) {
174
+ const key = mint ? `${chain}:${token}:${mint}` : `${chain}:${token}`;
175
+ return KNOWN_ASSETS.find((asset) => assetKey(asset) === key);
176
+ }
177
+ function assetByKey(key) {
178
+ return KNOWN_ASSETS.find((asset) => assetKey(asset) === key);
179
+ }
180
+ function resolveAssetFromPaymentRequest(request) {
181
+ if (!request.asset) {
182
+ return NATIVE_SOL;
183
+ }
184
+ const found = resolveKnownAsset(request.asset.chain, request.asset.token, request.asset.mint);
185
+ if (!found) {
186
+ const display = request.asset.mint ? `${request.asset.chain}:${request.asset.token}:${request.asset.mint}` : `${request.asset.chain}:${request.asset.token}`;
187
+ throw new Error(
188
+ `Unknown asset in payment request: ${display}. Known assets: ${KNOWN_ASSETS.map(assetKey).join(", ")}`
189
+ );
190
+ }
191
+ return found;
192
+ }
193
+ var DECIMAL_RE = /^(\d+\.\d*|\d*\.\d+|\d+)$/;
194
+ function parseAssetAmount(asset, human) {
195
+ const trimmed = human.trim();
196
+ if (!trimmed) {
197
+ throw new Error(`${asset.symbol} amount is empty`);
198
+ }
199
+ if (trimmed.startsWith("-")) {
200
+ throw new Error(`${asset.symbol} amount cannot be negative`);
201
+ }
202
+ if (!DECIMAL_RE.test(trimmed)) {
203
+ throw new Error(
204
+ `${asset.symbol} amount must be a non-negative decimal (e.g. "0.5", "1"); got "${human}"`
205
+ );
206
+ }
207
+ const dotPos = trimmed.indexOf(".");
208
+ let wholePart;
209
+ if (dotPos === -1) {
210
+ wholePart = trimmed;
211
+ } else if (dotPos === 0) {
212
+ wholePart = "0";
213
+ } else {
214
+ wholePart = trimmed.slice(0, dotPos);
215
+ }
216
+ const fracPart = dotPos === -1 ? "" : trimmed.slice(dotPos + 1);
217
+ if (fracPart.length > asset.decimals) {
218
+ throw new Error(
219
+ `${asset.symbol} amount has too many decimals (max ${asset.decimals}); got "${human}"`
220
+ );
221
+ }
222
+ const unit = 10n ** BigInt(asset.decimals);
223
+ const whole = BigInt(wholePart);
224
+ const frac = fracPart ? BigInt(fracPart.padEnd(asset.decimals, "0")) : 0n;
225
+ const raw = whole * unit + frac;
226
+ if (raw === 0n) {
227
+ throw new Error(`${asset.symbol} amount must be positive; got "${human}"`);
228
+ }
229
+ if (raw > BigInt(Number.MAX_SAFE_INTEGER)) {
230
+ throw new Error(
231
+ `${asset.symbol} amount exceeds safe range (max ${Number.MAX_SAFE_INTEGER} subunits)`
232
+ );
233
+ }
234
+ return raw;
235
+ }
236
+ function formatAssetAmount(asset, raw) {
237
+ const sign = raw < 0n ? "-" : "";
238
+ const abs = raw < 0n ? -raw : raw;
239
+ const unit = 10n ** BigInt(asset.decimals);
240
+ const whole = abs / unit;
241
+ const frac = abs % unit;
242
+ if (asset.decimals === 0) {
243
+ return `${sign}${whole} ${asset.symbol}`;
244
+ }
245
+ return `${sign}${whole}.${frac.toString().padStart(asset.decimals, "0")} ${asset.symbol}`;
246
+ }
153
247
  var BPS_DENOMINATOR = 1e4;
154
248
  function assertLamports(value, field) {
155
249
  if (!Number.isInteger(value) || value < 0) {
@@ -259,6 +353,12 @@ var SOLANA_ADDRESS_LENGTH_RE = /^.{32,44}$/;
259
353
  var lamportsSchema = z.number().int().positive().max(MAX_SAFE_LAMPORTS, `amount must be <= ${MAX_SAFE_LAMPORTS}`);
260
354
  var feeAmountSchema = z.number().int().nonnegative().max(MAX_SAFE_LAMPORTS, `fee_amount must be <= ${MAX_SAFE_LAMPORTS}`);
261
355
  var solanaAddressSchema = z.string().regex(BASE58_RE, "must be base58").regex(SOLANA_ADDRESS_LENGTH_RE, "must be 32-44 base58 chars");
356
+ var paymentAssetRefSchema = z.object({
357
+ chain: z.string().min(1),
358
+ token: z.string().min(1),
359
+ mint: solanaAddressSchema.optional(),
360
+ decimals: z.number().int().min(0).max(18)
361
+ });
262
362
  var PaymentRequestSchema = z.object({
263
363
  recipient: solanaAddressSchema,
264
364
  amount: lamportsSchema,
@@ -267,7 +367,8 @@ var PaymentRequestSchema = z.object({
267
367
  fee_address: solanaAddressSchema.optional(),
268
368
  fee_amount: feeAmountSchema.optional(),
269
369
  created_at: z.number().int().positive(),
270
- expiry_secs: z.number().int().positive().max(MAX_EXPIRY_SECS_SCHEMA, `expiry_secs must be <= ${MAX_EXPIRY_SECS_SCHEMA}`)
370
+ expiry_secs: z.number().int().positive().max(MAX_EXPIRY_SECS_SCHEMA, `expiry_secs must be <= ${MAX_EXPIRY_SECS_SCHEMA}`),
371
+ asset: paymentAssetRefSchema.optional()
271
372
  });
272
373
  function parsePaymentRequest(input, options) {
273
374
  let parsed;
@@ -349,6 +450,12 @@ var SolanaPaymentStrategy = class {
349
450
  assertExpirySecs(expirySecs);
350
451
  const feeAmount = calculateProtocolFee(amount, config.feeBps);
351
452
  const reference = generateReference();
453
+ const assetRef = options?.asset && options.asset !== NATIVE_SOL ? {
454
+ chain: options.asset.chain,
455
+ token: options.asset.token,
456
+ mint: options.asset.mint,
457
+ decimals: options.asset.decimals
458
+ } : void 0;
352
459
  return {
353
460
  recipient: recipientAddress,
354
461
  amount,
@@ -356,7 +463,8 @@ var SolanaPaymentStrategy = class {
356
463
  fee_address: config.treasury,
357
464
  fee_amount: feeAmount,
358
465
  created_at: Math.floor(Date.now() / 1e3),
359
- expiry_secs: expirySecs
466
+ expiry_secs: expirySecs,
467
+ ...assetRef ? { asset: assetRef } : {}
360
468
  };
361
469
  }
362
470
  validatePaymentRequest(requestJson, config, expectedRecipient, options) {
@@ -374,6 +482,16 @@ var SolanaPaymentStrategy = class {
374
482
  return { code: "invalid_amount", message: parsed.error.message };
375
483
  }
376
484
  const data = parsed.data;
485
+ if (data.asset) {
486
+ try {
487
+ resolveAssetFromPaymentRequest(data);
488
+ } catch (error) {
489
+ return {
490
+ code: "invalid_asset",
491
+ message: error instanceof Error ? error.message : String(error)
492
+ };
493
+ }
494
+ }
377
495
  if (!isValidSolanaAddress(data.recipient)) {
378
496
  return {
379
497
  code: "invalid_recipient_address",
@@ -462,7 +580,7 @@ var SolanaPaymentStrategy = class {
462
580
  if (!Number.isInteger(computeUnitLimit) || computeUnitLimit <= 0) {
463
581
  throw new Error(`Invalid computeUnitLimit: ${computeUnitLimit}. Must be a positive integer.`);
464
582
  }
465
- const paymentInstructions = buildPaymentInstructions(paymentRequest, payerSigner);
583
+ const paymentInstructions = await buildPaymentInstructions(paymentRequest, payerSigner);
466
584
  const priorityFeeMicroLamports = options?.priorityFeeMicroLamports ?? await estimatePriorityFeeMicroLamports(rpc, {
467
585
  percentile: options?.priorityFeePercentile ?? DEFAULT_PRIORITY_FEE_PERCENTILE
468
586
  });
@@ -524,6 +642,16 @@ var SolanaPaymentStrategy = class {
524
642
  error: `Fee amount (${feeAmount}) exceeds or equals total amount (${paymentRequest.amount})`
525
643
  };
526
644
  }
645
+ let asset;
646
+ try {
647
+ asset = resolveAssetFromPaymentRequest(paymentRequest);
648
+ } catch (error) {
649
+ return {
650
+ verified: false,
651
+ error: error instanceof Error ? error.message : String(error)
652
+ };
653
+ }
654
+ const mint = asset.mint;
527
655
  if (options?.txSignature) {
528
656
  return this._verifyBySignature(
529
657
  rpc,
@@ -533,6 +661,7 @@ var SolanaPaymentStrategy = class {
533
661
  treasury,
534
662
  expectedNet,
535
663
  feeAmount,
664
+ mint,
536
665
  options?.retries ?? DEFAULTS.VERIFY_RETRIES,
537
666
  options?.intervalMs ?? DEFAULTS.VERIFY_INTERVAL_MS
538
667
  );
@@ -544,11 +673,12 @@ var SolanaPaymentStrategy = class {
544
673
  treasury,
545
674
  expectedNet,
546
675
  feeAmount,
676
+ mint,
547
677
  options?.retries ?? DEFAULTS.VERIFY_BY_REF_RETRIES,
548
678
  options?.intervalMs ?? DEFAULTS.VERIFY_BY_REF_INTERVAL_MS
549
679
  );
550
680
  }
551
- async _verifyBySignature(rpc, txSignature, referenceKey, recipientAddress, treasuryAddress, expectedNet, expectedFee, retries, intervalMs) {
681
+ async _verifyBySignature(rpc, txSignature, referenceKey, recipientAddress, treasuryAddress, expectedNet, expectedFee, mint, retries, intervalMs) {
552
682
  let lastError;
553
683
  for (let attempt = 0; attempt < retries; attempt++) {
554
684
  try {
@@ -567,15 +697,18 @@ var SolanaPaymentStrategy = class {
567
697
  error: tx?.meta?.err ? "Transaction failed on-chain" : "Transaction not found"
568
698
  };
569
699
  }
570
- const verdict = checkBalanceDiff({
700
+ const verdict = checkTxDiff({
571
701
  accountKeys: tx.transaction.message.accountKeys,
572
702
  preBalances: tx.meta.preBalances,
573
703
  postBalances: tx.meta.postBalances,
704
+ preTokenBalances: tx.meta.preTokenBalances,
705
+ postTokenBalances: tx.meta.postTokenBalances,
574
706
  referenceKey,
575
707
  recipientAddress,
576
708
  treasuryAddress,
577
709
  expectedNet,
578
- expectedFee
710
+ expectedFee,
711
+ mint
579
712
  });
580
713
  if (verdict.ok) {
581
714
  return { verified: true, txSignature };
@@ -593,7 +726,7 @@ var SolanaPaymentStrategy = class {
593
726
  error: `Verification failed after ${retries} retries: ${lastError instanceof Error ? lastError.message : "unknown error"}`
594
727
  };
595
728
  }
596
- async _verifyByReference(rpc, referenceKey, recipientAddress, treasuryAddress, expectedNet, expectedFee, retries, intervalMs) {
729
+ async _verifyByReference(rpc, referenceKey, recipientAddress, treasuryAddress, expectedNet, expectedFee, mint, retries, intervalMs) {
597
730
  let lastError;
598
731
  const reference = address(referenceKey);
599
732
  for (let attempt = 0; attempt < retries; attempt++) {
@@ -617,15 +750,18 @@ var SolanaPaymentStrategy = class {
617
750
  if (!tx?.meta || tx.meta.err) {
618
751
  continue;
619
752
  }
620
- const verdict = checkBalanceDiff({
753
+ const verdict = checkTxDiff({
621
754
  accountKeys: tx.transaction.message.accountKeys,
622
755
  preBalances: tx.meta.preBalances,
623
756
  postBalances: tx.meta.postBalances,
757
+ preTokenBalances: tx.meta.preTokenBalances,
758
+ postTokenBalances: tx.meta.postTokenBalances,
624
759
  referenceKey,
625
760
  recipientAddress,
626
761
  treasuryAddress,
627
762
  expectedNet,
628
- expectedFee
763
+ expectedFee,
764
+ mint
629
765
  });
630
766
  if (verdict.ok) {
631
767
  return { verified: true, txSignature: sig };
@@ -645,7 +781,7 @@ var SolanaPaymentStrategy = class {
645
781
  };
646
782
  }
647
783
  };
648
- function checkBalanceDiff(input) {
784
+ function checkTxDiff(input) {
649
785
  const balanceCount = input.preBalances.length;
650
786
  const keyToIdx = /* @__PURE__ */ new Map();
651
787
  for (let i = 0; i < Math.min(input.accountKeys.length, balanceCount); i++) {
@@ -657,6 +793,9 @@ function checkBalanceDiff(input) {
657
793
  if (!keyToIdx.has(input.referenceKey)) {
658
794
  return { ok: false, reason: "Reference key not found in transaction - possible replay" };
659
795
  }
796
+ if (input.mint) {
797
+ return checkTokenBalanceDiff(input);
798
+ }
660
799
  const recipientIdx = keyToIdx.get(input.recipientAddress);
661
800
  if (recipientIdx === void 0) {
662
801
  return { ok: false, reason: "Recipient not found in transaction" };
@@ -689,6 +828,47 @@ function checkBalanceDiff(input) {
689
828
  }
690
829
  return { ok: true };
691
830
  }
831
+ function checkTokenBalanceDiff(input) {
832
+ const mint = input.mint;
833
+ if (!mint) {
834
+ return { ok: false, reason: "Expected mint for SPL verification, got none" };
835
+ }
836
+ const pre = input.preTokenBalances ?? [];
837
+ const post = input.postTokenBalances ?? [];
838
+ const tokenDelta = (ownerAddress) => {
839
+ const preEntry = pre.find((entry) => entry.owner === ownerAddress && entry.mint === mint);
840
+ const postEntry = post.find((entry) => entry.owner === ownerAddress && entry.mint === mint);
841
+ if (!postEntry) {
842
+ return -1n;
843
+ }
844
+ const preAmount = preEntry ? BigInt(preEntry.uiTokenAmount.amount) : 0n;
845
+ const postAmount = BigInt(postEntry.uiTokenAmount.amount);
846
+ return postAmount - preAmount;
847
+ };
848
+ const recipientDelta = tokenDelta(input.recipientAddress);
849
+ if (recipientDelta === -1n) {
850
+ return { ok: false, reason: "Recipient token account not found in transaction" };
851
+ }
852
+ if (recipientDelta < BigInt(input.expectedNet)) {
853
+ return {
854
+ ok: false,
855
+ reason: `Recipient received ${recipientDelta.toString()} tokens, expected >= ${input.expectedNet}`
856
+ };
857
+ }
858
+ if (input.expectedFee > 0) {
859
+ const treasuryDelta = tokenDelta(input.treasuryAddress);
860
+ if (treasuryDelta === -1n) {
861
+ return { ok: false, reason: "Treasury token account not found in transaction" };
862
+ }
863
+ if (treasuryDelta < BigInt(input.expectedFee)) {
864
+ return {
865
+ ok: false,
866
+ reason: `Treasury received ${treasuryDelta.toString()} tokens, expected >= ${input.expectedFee}`
867
+ };
868
+ }
869
+ }
870
+ return { ok: true };
871
+ }
692
872
  function bigIntDelta(post, pre) {
693
873
  const postValue = post === void 0 ? 0n : BigInt(post);
694
874
  const preValue = pre === void 0 ? 0n : BigInt(pre);
@@ -697,7 +877,7 @@ function bigIntDelta(post, pre) {
697
877
  function waitMs(ms) {
698
878
  return new Promise((resolve) => setTimeout(resolve, ms));
699
879
  }
700
- function buildPaymentInstructions(paymentRequest, payerSigner) {
880
+ async function buildPaymentInstructions(paymentRequest, payerSigner) {
701
881
  const recipient = address(paymentRequest.recipient);
702
882
  const reference = address(paymentRequest.reference);
703
883
  const feeAmount = paymentRequest.fee_amount ?? 0;
@@ -707,22 +887,98 @@ function buildPaymentInstructions(paymentRequest, payerSigner) {
707
887
  `Fee amount (${feeAmount}) exceeds or equals total amount (${paymentRequest.amount}). Cannot create transaction with non-positive provider amount.`
708
888
  );
709
889
  }
710
- const providerTransferIx = getTransferSolInstruction({
711
- source: payerSigner,
712
- destination: recipient,
713
- amount: BigInt(providerAmount)
890
+ const asset = resolveAssetFromPaymentRequest(paymentRequest);
891
+ if (!asset.mint) {
892
+ const providerTransferIx2 = getTransferSolInstruction({
893
+ source: payerSigner,
894
+ destination: recipient,
895
+ amount: BigInt(providerAmount)
896
+ });
897
+ const providerTransferIxWithReference2 = {
898
+ ...providerTransferIx2,
899
+ accounts: [
900
+ ...providerTransferIx2.accounts,
901
+ { address: reference, role: AccountRole.READONLY }
902
+ ]
903
+ };
904
+ const instructions2 = [providerTransferIxWithReference2];
905
+ if (paymentRequest.fee_address && feeAmount > 0) {
906
+ instructions2.push(
907
+ getTransferSolInstruction({
908
+ source: payerSigner,
909
+ destination: address(paymentRequest.fee_address),
910
+ amount: BigInt(feeAmount)
911
+ })
912
+ );
913
+ }
914
+ return instructions2;
915
+ }
916
+ const mint = address(asset.mint);
917
+ const payerAddress = payerSigner.address;
918
+ const [payerAta] = await findAssociatedTokenPda({
919
+ owner: payerAddress,
920
+ tokenProgram: TOKEN_PROGRAM_ADDRESS,
921
+ mint
922
+ });
923
+ const [recipientAta] = await findAssociatedTokenPda({
924
+ owner: recipient,
925
+ tokenProgram: TOKEN_PROGRAM_ADDRESS,
926
+ mint
927
+ });
928
+ const instructions = [];
929
+ instructions.push(
930
+ getCreateAssociatedTokenIdempotentInstruction(
931
+ {
932
+ payer: payerSigner,
933
+ ata: recipientAta,
934
+ owner: recipient,
935
+ mint
936
+ },
937
+ { programAddress: ASSOCIATED_TOKEN_PROGRAM_ADDRESS }
938
+ )
939
+ );
940
+ let treasuryAta;
941
+ if (paymentRequest.fee_address && feeAmount > 0) {
942
+ const treasuryOwner = address(paymentRequest.fee_address);
943
+ [treasuryAta] = await findAssociatedTokenPda({
944
+ owner: treasuryOwner,
945
+ tokenProgram: TOKEN_PROGRAM_ADDRESS,
946
+ mint
947
+ });
948
+ instructions.push(
949
+ getCreateAssociatedTokenIdempotentInstruction(
950
+ {
951
+ payer: payerSigner,
952
+ ata: treasuryAta,
953
+ owner: treasuryOwner,
954
+ mint
955
+ },
956
+ { programAddress: ASSOCIATED_TOKEN_PROGRAM_ADDRESS }
957
+ )
958
+ );
959
+ }
960
+ const providerTransferIx = getTransferCheckedInstruction({
961
+ source: payerAta,
962
+ mint,
963
+ destination: recipientAta,
964
+ authority: payerSigner,
965
+ amount: BigInt(providerAmount),
966
+ decimals: asset.decimals
714
967
  });
715
968
  const providerTransferIxWithReference = {
716
969
  ...providerTransferIx,
717
970
  accounts: [...providerTransferIx.accounts, { address: reference, role: AccountRole.READONLY }]
718
971
  };
719
- const instructions = [providerTransferIxWithReference];
720
- if (paymentRequest.fee_address && feeAmount > 0) {
972
+ instructions.push(providerTransferIxWithReference);
973
+ if (treasuryAta && paymentRequest.fee_address && feeAmount > 0) {
721
974
  instructions.push(
722
- getTransferSolInstruction({
723
- source: payerSigner,
724
- destination: address(paymentRequest.fee_address),
725
- amount: BigInt(feeAmount)
975
+ getTransferCheckedInstruction({
976
+ source: payerAta,
977
+ mint,
978
+ destination: treasuryAta,
979
+ authority: payerSigner,
980
+ amount: BigInt(feeAmount),
981
+ decimals: asset.decimals
726
982
  })
727
983
  );
728
984
  }
@@ -2331,6 +2587,133 @@ var ElisymClient = class {
2331
2587
  this.pool.close();
2332
2588
  }
2333
2589
  };
2590
+ var DEFAULT_COMPUTE_UNIT_LIMIT2 = 2e5;
2591
+ var DEFAULT_PRIORITY_FEE_PERCENTILE2 = 75;
2592
+ var BASE_FEE_LAMPORTS_PER_SIGNATURE = 5000n;
2593
+ var FALLBACK_ATA_RENT_LAMPORTS = 2039280n;
2594
+ var SPL_TOKEN_ACCOUNT_SIZE = 165;
2595
+ async function estimateSolFeeLamports(rpc, paymentRequest, _payerAddress, options) {
2596
+ const numSignatures = options?.numSignatures ?? 1;
2597
+ const computeUnitLimit = options?.computeUnitLimit ?? DEFAULT_COMPUTE_UNIT_LIMIT2;
2598
+ const priorityFeeMicroLamports = options?.priorityFeeMicroLamports ?? await estimatePriorityFeeMicroLamports(rpc, {
2599
+ percentile: options?.priorityFeePercentile ?? DEFAULT_PRIORITY_FEE_PERCENTILE2
2600
+ });
2601
+ const baseFeeLamports = BASE_FEE_LAMPORTS_PER_SIGNATURE * BigInt(numSignatures);
2602
+ const priorityFeeLamports = ceilDiv(
2603
+ priorityFeeMicroLamports * BigInt(computeUnitLimit),
2604
+ 1000000n
2605
+ );
2606
+ const asset = resolveAssetFromPaymentRequest(paymentRequest);
2607
+ let rentLamports = 0n;
2608
+ let rentPerAtaLamports = 0n;
2609
+ let missingAtaCount = 0;
2610
+ if (asset.mint) {
2611
+ rentPerAtaLamports = await fetchAtaRent(rpc);
2612
+ const mint = address(asset.mint);
2613
+ const ataAccountsToCheck = [];
2614
+ const [recipientAta] = await findAssociatedTokenPda({
2615
+ owner: address(paymentRequest.recipient),
2616
+ tokenProgram: TOKEN_PROGRAM_ADDRESS,
2617
+ mint
2618
+ });
2619
+ ataAccountsToCheck.push(recipientAta);
2620
+ const feeAmount = paymentRequest.fee_amount ?? 0;
2621
+ if (paymentRequest.fee_address && feeAmount > 0) {
2622
+ const [treasuryAta] = await findAssociatedTokenPda({
2623
+ owner: address(paymentRequest.fee_address),
2624
+ tokenProgram: TOKEN_PROGRAM_ADDRESS,
2625
+ mint
2626
+ });
2627
+ ataAccountsToCheck.push(treasuryAta);
2628
+ }
2629
+ missingAtaCount = await countMissingAccounts(rpc, ataAccountsToCheck);
2630
+ rentLamports = rentPerAtaLamports * BigInt(missingAtaCount);
2631
+ }
2632
+ const totalLamports = baseFeeLamports + priorityFeeLamports + rentLamports;
2633
+ return {
2634
+ baseFeeLamports,
2635
+ priorityFeeLamports,
2636
+ rentLamports,
2637
+ totalLamports,
2638
+ breakdown: {
2639
+ numSignatures,
2640
+ priorityFeeMicroLamports,
2641
+ computeUnitLimit,
2642
+ rentPerAtaLamports,
2643
+ missingAtaCount
2644
+ }
2645
+ };
2646
+ }
2647
+ async function fetchAtaRent(rpc) {
2648
+ try {
2649
+ const lamports = await rpc.getMinimumBalanceForRentExemption(BigInt(SPL_TOKEN_ACCOUNT_SIZE)).send();
2650
+ if (typeof lamports === "bigint") {
2651
+ return lamports;
2652
+ }
2653
+ if (typeof lamports === "number" && Number.isFinite(lamports) && lamports > 0) {
2654
+ return BigInt(lamports);
2655
+ }
2656
+ return FALLBACK_ATA_RENT_LAMPORTS;
2657
+ } catch {
2658
+ return FALLBACK_ATA_RENT_LAMPORTS;
2659
+ }
2660
+ }
2661
+ async function countMissingAccounts(rpc, accounts) {
2662
+ if (accounts.length === 0) {
2663
+ return 0;
2664
+ }
2665
+ let missing = 0;
2666
+ for (const acct of accounts) {
2667
+ try {
2668
+ const res = await rpc.getAccountInfo(acct, { encoding: "base64" }).send();
2669
+ if (!res || !res.value) {
2670
+ missing++;
2671
+ }
2672
+ } catch {
2673
+ missing++;
2674
+ }
2675
+ }
2676
+ return missing;
2677
+ }
2678
+ function ceilDiv(num, denom) {
2679
+ if (denom === 0n) {
2680
+ throw new Error("division by zero in ceilDiv");
2681
+ }
2682
+ const q = num / denom;
2683
+ const r = num % denom;
2684
+ return r === 0n ? q : q + 1n;
2685
+ }
2686
+ function formatFeeBreakdown(estimate) {
2687
+ const line = (label, lamports) => {
2688
+ const label16 = label.padEnd(14);
2689
+ return ` ${label16}${lamports.toString()} lamports (${lamportsToSol(lamports)} SOL)`;
2690
+ };
2691
+ const lines = [
2692
+ "Estimated SOL cost for this transaction:",
2693
+ line("Base fee:", estimate.baseFeeLamports),
2694
+ line("Priority fee:", estimate.priorityFeeLamports)
2695
+ ];
2696
+ if (estimate.rentLamports > 0n) {
2697
+ lines.push(line("ATA rent:", estimate.rentLamports));
2698
+ }
2699
+ lines.push(line("Total:", estimate.totalLamports));
2700
+ return lines.join("\n");
2701
+ }
2702
+ function lamportsToSol(lamports) {
2703
+ const LAMPORTS_PER_SOL2 = 1000000000n;
2704
+ const whole = lamports / LAMPORTS_PER_SOL2;
2705
+ const frac = lamports % LAMPORTS_PER_SOL2;
2706
+ return `${whole}.${frac.toString().padStart(9, "0")}`;
2707
+ }
2708
+ var SessionSpendLimitEntrySchema = z.object({
2709
+ chain: z.enum(["solana"]),
2710
+ token: z.string().min(1).max(16).regex(/^[a-z0-9]+$/, "token must be lowercase alphanumeric"),
2711
+ mint: z.string().min(1).max(64).optional(),
2712
+ amount: z.number().positive().finite()
2713
+ }).strict();
2714
+ var GlobalConfigSchema = z.object({
2715
+ session_spend_limits: z.array(SessionSpendLimitEntrySchema).max(16).optional()
2716
+ }).strict();
2334
2717
  function formatSol(lamports) {
2335
2718
  const sol = new Decimal2(lamports).div(LAMPORTS_PER_SOL);
2336
2719
  if (sol.gte(1e6)) {
@@ -2522,6 +2905,6 @@ function makeCensor() {
2522
2905
  };
2523
2906
  }
2524
2907
 
2525
- export { BoundedSet, DEFAULTS, DEFAULT_KIND_OFFSET, DEFAULT_REDACT_PATHS, DiscoveryService, ElisymClient, ElisymIdentity, INPUT_REDACT_PATHS, KIND_APP_HANDLER, KIND_JOB_FEEDBACK, KIND_JOB_REQUEST, KIND_JOB_REQUEST_BASE, KIND_JOB_RESULT, KIND_JOB_RESULT_BASE, KIND_PING, KIND_PONG, LAMPORTS_PER_SOL, LIMITS, MarketplaceService, MediaService, NostrPool, PROTOCOL_FEE_BPS, PROTOCOL_PROGRAM_ID_DEVNET, PROTOCOL_TREASURY, PaymentRequestSchema, PingService, RELAYS, SECRET_REDACT_PATHS, SolanaPaymentStrategy, assertExpiry, assertLamports, buildPaymentInstructions, calculateProtocolFee, clearPriorityFeeCache, clearProtocolConfigCache, createPaymentRequestWithOnchainConfig, createSlidingWindowLimiter, estimatePriorityFeeMicroLamports, formatSol, getProtocolConfig, getProtocolProgramId, jobRequestKind, jobResultKind, makeCensor, nip44Decrypt, nip44Encrypt, parsePaymentRequest, pickPercentileFee, timeAgo, toDTag, truncateKey, validateAgentName, validateExpiry };
2908
+ export { BoundedSet, DEFAULTS, DEFAULT_KIND_OFFSET, DEFAULT_REDACT_PATHS, DiscoveryService, ElisymClient, ElisymIdentity, GlobalConfigSchema, INPUT_REDACT_PATHS, KIND_APP_HANDLER, KIND_JOB_FEEDBACK, KIND_JOB_REQUEST, KIND_JOB_REQUEST_BASE, KIND_JOB_RESULT, KIND_JOB_RESULT_BASE, KIND_PING, KIND_PONG, KNOWN_ASSETS, LAMPORTS_PER_SOL, LIMITS, MarketplaceService, MediaService, NATIVE_SOL, NostrPool, PROTOCOL_FEE_BPS, PROTOCOL_PROGRAM_ID_DEVNET, PROTOCOL_TREASURY, PaymentRequestSchema, PingService, RELAYS, SECRET_REDACT_PATHS, SessionSpendLimitEntrySchema, SolanaPaymentStrategy, USDC_SOLANA_DEVNET, assertExpiry, assertLamports, assetByKey, assetKey, buildPaymentInstructions, calculateProtocolFee, clearPriorityFeeCache, clearProtocolConfigCache, createPaymentRequestWithOnchainConfig, createSlidingWindowLimiter, estimatePriorityFeeMicroLamports, estimateSolFeeLamports, formatAssetAmount, formatFeeBreakdown, formatSol, getProtocolConfig, getProtocolProgramId, jobRequestKind, jobResultKind, makeCensor, nip44Decrypt, nip44Encrypt, parseAssetAmount, parsePaymentRequest, pickPercentileFee, resolveAssetFromPaymentRequest, resolveKnownAsset, timeAgo, toDTag, truncateKey, validateAgentName, validateExpiry };
2526
2909
  //# sourceMappingURL=index.js.map
2527
2910
  //# sourceMappingURL=index.js.map