@elisym/sdk 0.8.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,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 = [
@@ -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
  }
@@ -2361,96 +2612,124 @@ var ElisymClient = class {
2361
2612
  this.pool.close();
2362
2613
  }
2363
2614
  };
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
- );
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);
2423
2656
  }
2424
- return raw;
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
+ };
2425
2671
  }
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}`;
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;
2434
2684
  }
2435
- return `${sign}${whole}.${frac.toString().padStart(asset.decimals, "0")} ${asset.symbol}`;
2436
2685
  }
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) {
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) {
2444
2692
  try {
2445
- const { unlink } = await import('fs/promises');
2446
- await unlink(tmpPath);
2693
+ const res = await rpc.getAccountInfo(acct, { encoding: "base64" }).send();
2694
+ if (!res || !res.value) {
2695
+ missing++;
2696
+ }
2447
2697
  } catch {
2698
+ missing++;
2448
2699
  }
2449
- throw e;
2450
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")}`;
2451
2732
  }
2452
-
2453
- // src/config/global.ts
2454
2733
  var SessionSpendLimitEntrySchema = zod.z.object({
2455
2734
  chain: zod.z.enum(["solana"]),
2456
2735
  token: zod.z.string().min(1).max(16).regex(/^[a-z0-9]+$/, "token must be lowercase alphanumeric"),
@@ -2460,30 +2739,6 @@ var SessionSpendLimitEntrySchema = zod.z.object({
2460
2739
  var GlobalConfigSchema = zod.z.object({
2461
2740
  session_spend_limits: zod.z.array(SessionSpendLimitEntrySchema).max(16).optional()
2462
2741
  }).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
2742
  function formatSol(lamports) {
2488
2743
  const sol = new Decimal2__default.default(lamports).div(LAMPORTS_PER_SOL);
2489
2744
  if (sol.gte(1e6)) {
@@ -2708,6 +2963,7 @@ exports.RELAYS = RELAYS;
2708
2963
  exports.SECRET_REDACT_PATHS = SECRET_REDACT_PATHS;
2709
2964
  exports.SessionSpendLimitEntrySchema = SessionSpendLimitEntrySchema;
2710
2965
  exports.SolanaPaymentStrategy = SolanaPaymentStrategy;
2966
+ exports.USDC_SOLANA_DEVNET = USDC_SOLANA_DEVNET;
2711
2967
  exports.assertExpiry = assertExpiry;
2712
2968
  exports.assertLamports = assertLamports;
2713
2969
  exports.assetByKey = assetByKey;
@@ -2719,25 +2975,26 @@ exports.clearProtocolConfigCache = clearProtocolConfigCache;
2719
2975
  exports.createPaymentRequestWithOnchainConfig = createPaymentRequestWithOnchainConfig;
2720
2976
  exports.createSlidingWindowLimiter = createSlidingWindowLimiter;
2721
2977
  exports.estimatePriorityFeeMicroLamports = estimatePriorityFeeMicroLamports;
2978
+ exports.estimateSolFeeLamports = estimateSolFeeLamports;
2722
2979
  exports.formatAssetAmount = formatAssetAmount;
2980
+ exports.formatFeeBreakdown = formatFeeBreakdown;
2723
2981
  exports.formatSol = formatSol;
2724
2982
  exports.getProtocolConfig = getProtocolConfig;
2725
2983
  exports.getProtocolProgramId = getProtocolProgramId;
2726
2984
  exports.jobRequestKind = jobRequestKind;
2727
2985
  exports.jobResultKind = jobResultKind;
2728
- exports.loadGlobalConfig = loadGlobalConfig;
2729
2986
  exports.makeCensor = makeCensor;
2730
2987
  exports.nip44Decrypt = nip44Decrypt;
2731
2988
  exports.nip44Encrypt = nip44Encrypt;
2732
2989
  exports.parseAssetAmount = parseAssetAmount;
2733
2990
  exports.parsePaymentRequest = parsePaymentRequest;
2734
2991
  exports.pickPercentileFee = pickPercentileFee;
2992
+ exports.resolveAssetFromPaymentRequest = resolveAssetFromPaymentRequest;
2735
2993
  exports.resolveKnownAsset = resolveKnownAsset;
2736
2994
  exports.timeAgo = timeAgo;
2737
2995
  exports.toDTag = toDTag;
2738
2996
  exports.truncateKey = truncateKey;
2739
2997
  exports.validateAgentName = validateAgentName;
2740
2998
  exports.validateExpiry = validateExpiry;
2741
- exports.writeGlobalConfig = writeGlobalConfig;
2742
2999
  //# sourceMappingURL=index.cjs.map
2743
3000
  //# sourceMappingURL=index.cjs.map