@continuumdao/ctm-mpc-defi 0.2.54 → 0.2.56

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.
@@ -226,6 +226,12 @@ function cctpSupportedRoutes(network) {
226
226
  }
227
227
 
228
228
  // src/protocols/evm/circle-cctp/fees.ts
229
+ function cctpProtocolFeeBaseUnits(transferAmount, minimumFeeBps) {
230
+ if (transferAmount <= 0n || !Number.isFinite(minimumFeeBps) || minimumFeeBps <= 0) return 0n;
231
+ const scaledBps = BigInt(Math.round(minimumFeeBps * 100));
232
+ if (scaledBps <= 0n) return 0n;
233
+ return transferAmount * scaledBps / 1000000n;
234
+ }
229
235
  function pickForwardFeeTier(row, tier) {
230
236
  const forwardFee = row.forwardFee;
231
237
  if (forwardFee && typeof forwardFee === "object") {
@@ -234,11 +240,25 @@ function pickForwardFeeTier(row, tier) {
234
240
  if (v != null) return BigInt(String(v));
235
241
  }
236
242
  const maxFee = row.maximumFee ?? row.maxFee ?? row.forwardFee;
237
- if (maxFee != null) return BigInt(String(maxFee));
243
+ if (maxFee != null && typeof maxFee !== "object") return BigInt(String(maxFee));
238
244
  throw new Error("Iris fee response missing forwardFee tiers.");
239
245
  }
246
+ function feeRows(json) {
247
+ if (Array.isArray(json)) return json.filter((row) => row && typeof row === "object");
248
+ if (json && typeof json === "object") {
249
+ const data = json.data;
250
+ if (Array.isArray(data)) return data.filter((row) => row && typeof row === "object");
251
+ return [json];
252
+ }
253
+ return [];
254
+ }
255
+ function selectCctpIrisFeeRow(rows, finalityThreshold) {
256
+ const match = rows.find((row) => Number(row.finalityThreshold) === finalityThreshold);
257
+ return match ?? rows[0] ?? {};
258
+ }
240
259
  async function fetchCctpBurnFees(args) {
241
260
  const tier = args.feeTier ?? "med";
261
+ const finalityThreshold = args.minFinalityThreshold ?? CCTP_DEFAULT_MIN_FINALITY_THRESHOLD;
242
262
  const base = irisApiBaseForChainId(args.sourceChainId);
243
263
  const url = `${base}/v2/burn/USDC/fees/${args.sourceDomain}/${args.destDomain}?forward=true`;
244
264
  const res = await fetch(url, { headers: { Accept: "application/json" } });
@@ -246,20 +266,28 @@ async function fetchCctpBurnFees(args) {
246
266
  const text = await res.text().catch(() => "");
247
267
  throw new Error(`Iris fee quote failed (${res.status}): ${text.slice(0, 200)}`);
248
268
  }
249
- const json = await res.json();
250
- const rows = Array.isArray(json) ? json : Array.isArray(json.data) ? json.data : [json];
251
- const row = rows[0] ?? {};
269
+ const row = selectCctpIrisFeeRow(feeRows(await res.json()), finalityThreshold);
252
270
  const low = pickForwardFeeTier(row, "low");
253
271
  const med = pickForwardFeeTier(row, "med");
254
272
  const high = pickForwardFeeTier(row, "high");
255
- const maxFee = tier === "low" ? low : tier === "high" ? high : med;
273
+ const minimumFeeRaw = row.minimumFee;
274
+ const minimumFeeBps = typeof minimumFeeRaw === "number" ? minimumFeeRaw : Number(minimumFeeRaw ?? 0);
275
+ const transferAmount = args.transferAmount ?? 0n;
276
+ const protocolFee = cctpProtocolFeeBaseUnits(transferAmount, minimumFeeBps);
277
+ const forwardForTier = tier === "low" ? low : tier === "high" ? high : med;
256
278
  return {
257
279
  sourceDomain: args.sourceDomain,
258
280
  destDomain: args.destDomain,
259
- maxFee,
281
+ finalityThreshold: Number(row.finalityThreshold ?? finalityThreshold),
282
+ minimumFeeBps: Number.isFinite(minimumFeeBps) ? minimumFeeBps : 0,
283
+ protocolFee,
284
+ maxFee: forwardForTier + protocolFee,
260
285
  forwardFeeLow: low,
261
286
  forwardFeeMed: med,
262
287
  forwardFeeHigh: high,
288
+ maxFeeLow: low + protocolFee,
289
+ maxFeeMed: med + protocolFee,
290
+ maxFeeHigh: high + protocolFee,
263
291
  feeTierUsed: tier
264
292
  };
265
293
  }
@@ -623,6 +651,8 @@ async function buildEvmMultisignBodyCctpBurnBatch(args) {
623
651
  const executor = getAddress(args.executorAddress);
624
652
  const mintRecipient = getAddress(args.mintRecipient ?? executor);
625
653
  const minFinality = args.minFinalityThreshold ?? CCTP_DEFAULT_MIN_FINALITY_THRESHOLD;
654
+ const transferAmountForFee = parseUnits(args.transferAmountHuman.trim(), CCTP_USDC_DECIMALS);
655
+ if (transferAmountForFee <= 0n) throw new Error("Transfer amount must be greater than zero.");
626
656
  let maxFee = args.maxFee;
627
657
  let proposalMaxFee = args.proposalMaxFee ?? maxFee;
628
658
  if (maxFee == null) {
@@ -630,7 +660,9 @@ async function buildEvmMultisignBodyCctpBurnBatch(args) {
630
660
  sourceChainId: args.sourceChainId,
631
661
  sourceDomain: src.domain,
632
662
  destDomain: dst.domain,
633
- feeTier: args.feeTier
663
+ feeTier: args.feeTier,
664
+ transferAmount: transferAmountForFee,
665
+ minFinalityThreshold: minFinality
634
666
  });
635
667
  maxFee = fees.maxFee;
636
668
  proposalMaxFee = fees.maxFee;
@@ -720,11 +752,22 @@ async function buildEvmMultisignBodyCctpBurnBatch(args) {
720
752
  return result;
721
753
  }
722
754
  async function buildEvmMultisignBodyCctpBurnBatchFromMcp(args) {
723
- let maxFee;
724
- let proposalMaxFee;
755
+ const src = cctpChainConfig(args.chainId);
756
+ const dst = cctpChainConfig(args.destChainId);
757
+ if (!src || !dst) throw new Error("Unsupported CCTP source or destination chain.");
758
+ const transferAmount = parseUnits(args.transferAmountHuman.trim(), CCTP_USDC_DECIMALS);
759
+ const fees = await fetchCctpBurnFees({
760
+ sourceChainId: args.chainId,
761
+ sourceDomain: src.domain,
762
+ destDomain: dst.domain,
763
+ feeTier: args.feeTier,
764
+ transferAmount,
765
+ minFinalityThreshold: args.minFinalityThreshold
766
+ });
767
+ let maxFee = fees.maxFee;
725
768
  if (args.feeSnapshot?.maxFee != null) {
726
- maxFee = BigInt(String(args.feeSnapshot.maxFee));
727
- proposalMaxFee = maxFee;
769
+ const snapshotMaxFee = BigInt(String(args.feeSnapshot.maxFee));
770
+ if (snapshotMaxFee > maxFee) maxFee = snapshotMaxFee;
728
771
  }
729
772
  return buildEvmMultisignBodyCctpBurnBatch({
730
773
  keyGen: args.keyGen,
@@ -739,7 +782,7 @@ async function buildEvmMultisignBodyCctpBurnBatchFromMcp(args) {
739
782
  executorAddress: args.executorAddress,
740
783
  purposeText: args.purposeText,
741
784
  maxFee,
742
- proposalMaxFee,
785
+ proposalMaxFee: maxFee,
743
786
  feeTier: args.feeTier,
744
787
  minFinalityThreshold: args.minFinalityThreshold
745
788
  });
@@ -853,14 +896,15 @@ function buildCctpMcpFeeFields(args) {
853
896
  const forwardFeeLowHuman = formatCctpUsdcHuman(fees.forwardFeeLow);
854
897
  const forwardFeeMedHuman = formatCctpUsdcHuman(fees.forwardFeeMed);
855
898
  const forwardFeeHighHuman = formatCctpUsdcHuman(fees.forwardFeeHigh);
899
+ const protocolFeeHuman = formatCctpUsdcHuman(fees.protocolFee);
856
900
  let totalBurn;
857
901
  let totalBurnHuman;
858
- if (transferAmount != null) {
902
+ if (transferAmount != null && transferAmount > 0n) {
859
903
  totalBurn = transferAmount + fees.maxFee;
860
904
  totalBurnHuman = formatCctpUsdcHuman(totalBurn);
861
905
  }
862
- const recipientPart = transferAmountHuman != null ? ` Recipient receives ${transferAmountHuman} USDC; wallet burns ${totalBurnHuman} USDC total (transfer + max fee).` : "";
863
- const feeSummary = `Forwarding Service max fee (${fees.feeTierUsed} tier): ${maxFeeHuman} USDC.${recipientPart} Fee tiers \u2014 low: ${forwardFeeLowHuman}, med: ${forwardFeeMedHuman}, high: ${forwardFeeHighHuman} USDC. Fees refresh again at Get Sig.`;
906
+ const recipientPart = transferAmountHuman != null && totalBurnHuman != null ? ` Recipient receives ${transferAmountHuman} USDC; wallet burns ${totalBurnHuman} USDC total (transfer + max fee).` : " Pass transferAmountHuman so maxFee includes the fast-transfer protocol fee.";
907
+ const feeSummary = `CCTP max fee (${fees.feeTierUsed} tier): ${maxFeeHuman} USDC = forwarding ${formatCctpUsdcHuman(fees.feeTierUsed === "low" ? fees.forwardFeeLow : fees.feeTierUsed === "high" ? fees.forwardFeeHigh : fees.forwardFeeMed)} + protocol ${protocolFeeHuman} (${fees.minimumFeeBps} bps, finality ${fees.finalityThreshold}).${recipientPart} Combined tiers \u2014 low: ${formatCctpUsdcHuman(fees.maxFeeLow)}, med: ${formatCctpUsdcHuman(fees.maxFeeMed)}, high: ${formatCctpUsdcHuman(fees.maxFeeHigh)} USDC. Fees refresh again at Get Sig.`;
864
908
  return {
865
909
  feeTierUsed: fees.feeTierUsed,
866
910
  maxFee: fees.maxFee.toString(),
@@ -871,6 +915,16 @@ function buildCctpMcpFeeFields(args) {
871
915
  forwardFeeMedHuman,
872
916
  forwardFeeHigh: fees.forwardFeeHigh.toString(),
873
917
  forwardFeeHighHuman,
918
+ maxFeeLow: fees.maxFeeLow.toString(),
919
+ maxFeeLowHuman: formatCctpUsdcHuman(fees.maxFeeLow),
920
+ maxFeeMed: fees.maxFeeMed.toString(),
921
+ maxFeeMedHuman: formatCctpUsdcHuman(fees.maxFeeMed),
922
+ maxFeeHigh: fees.maxFeeHigh.toString(),
923
+ maxFeeHighHuman: formatCctpUsdcHuman(fees.maxFeeHigh),
924
+ minimumFeeBps: fees.minimumFeeBps,
925
+ protocolFee: fees.protocolFee.toString(),
926
+ protocolFeeHuman,
927
+ finalityThreshold: fees.finalityThreshold,
874
928
  ...transferAmount != null ? { transferAmount: transferAmount.toString() } : {},
875
929
  ...transferAmountHuman != null ? { transferAmountHuman } : {},
876
930
  ...totalBurn != null ? { totalBurn: totalBurn.toString() } : {},
@@ -968,14 +1022,16 @@ async function cctpFetchBurnFeesForRoute(args) {
968
1022
  if (!src || !dst) throw new Error("Unsupported source or destination chain for CCTP.");
969
1023
  if (src.network !== dst.network) throw new Error("Source and destination must be on the same network (mainnet or testnet).");
970
1024
  if (src.chainId === dst.chainId) throw new Error("Source and destination must differ.");
1025
+ const transferHuman = args.transferAmountHuman?.trim();
1026
+ const transferAmount = transferHuman ? parseUnits(transferHuman, CCTP_USDC_DECIMALS) : void 0;
971
1027
  const fees = await fetchCctpBurnFees({
972
1028
  sourceChainId: args.sourceChainId,
973
1029
  sourceDomain: src.domain,
974
1030
  destDomain: dst.domain,
975
- feeTier: args.feeTier
1031
+ feeTier: args.feeTier,
1032
+ transferAmount,
1033
+ minFinalityThreshold: args.minFinalityThreshold
976
1034
  });
977
- const transferHuman = args.transferAmountHuman?.trim();
978
- const transferAmount = transferHuman ? parseUnits(transferHuman, CCTP_USDC_DECIMALS) : void 0;
979
1035
  const feeFields = buildCctpMcpFeeFields({
980
1036
  fees,
981
1037
  transferAmount,
@@ -1020,7 +1076,7 @@ var circleCctpProtocolModule = {
1020
1076
  id: "circle-cctp.fetch-fees",
1021
1077
  protocolId: CIRCLE_CCTP_PROTOCOL_ID,
1022
1078
  chainCategory: "evm",
1023
- description: "Quote Iris Forwarding Service burn fees for a route",
1079
+ description: "Quote Iris forwarding fee plus fast-transfer protocol fee for a route",
1024
1080
  commonParams: [],
1025
1081
  params: {
1026
1082
  sourceChainId: { type: "number", required: true, description: "Source EIP-155 chain id" },
@@ -1045,6 +1101,6 @@ var circleCctpProtocolModule = {
1045
1101
  };
1046
1102
  registerProtocolModule(circleCctpProtocolModule);
1047
1103
 
1048
- export { CCTP_DEFAULT_MIN_FINALITY_THRESHOLD, CCTP_DEPOSIT_FOR_BURN_FALLBACK_GAS, CCTP_ERC20_APPROVE_FALLBACK_GAS, CCTP_EVM_CHAINS, CCTP_USDC_DECIMALS, CIRCLE_CCTP_PROTOCOL_ID, FORWARDING_SERVICE_HOOK_DATA, IRIS_API_MAINNET, IRIS_API_SANDBOX, TOKEN_MESSENGER_V2_MAINNET, TOKEN_MESSENGER_V2_TESTNET, buildCctpBurnExtraJSON, buildCctpBurnSteps, buildEvmMultisignBodyCctpBurnBatch, buildEvmMultisignBodyCctpBurnBatchFromMcp, cctpAssertBurnPreconditions, cctpCanonicalUsdcAddress, cctpChainConfig, cctpFeeSummaryFromBodyForSign, cctpFetchBurnFeesForRoute, cctpFetchSupportedRoutes, cctpFetchTransferStatus, cctpFetchUsdcBalance, cctpMintRecipientAddressFromExtra, cctpSupportedRoutes, chainIdToDomain, circleCctpProtocolModule, domainToChainId, fetchCctpBurnFees, formatCctpUsdcHuman, irisApiBaseForChainId, isCctpBurnEvmSignRequest, isCctpCanonicalUsdcRow, isCctpErc20ApproveEvmSignRequest, isCctpRouteSupported, isCctpSourceChainSupported, parseCctpBurnExtraJSON, parseCctpEvmChainId, rebuildCctpBurnCalldataAtGetSig, resolveCctpBatchStepGasFromSignRequest };
1104
+ export { CCTP_DEFAULT_MIN_FINALITY_THRESHOLD, CCTP_DEPOSIT_FOR_BURN_FALLBACK_GAS, CCTP_ERC20_APPROVE_FALLBACK_GAS, CCTP_EVM_CHAINS, CCTP_USDC_DECIMALS, CIRCLE_CCTP_PROTOCOL_ID, FORWARDING_SERVICE_HOOK_DATA, IRIS_API_MAINNET, IRIS_API_SANDBOX, TOKEN_MESSENGER_V2_MAINNET, TOKEN_MESSENGER_V2_TESTNET, buildCctpBurnExtraJSON, buildCctpBurnSteps, buildEvmMultisignBodyCctpBurnBatch, buildEvmMultisignBodyCctpBurnBatchFromMcp, cctpAssertBurnPreconditions, cctpCanonicalUsdcAddress, cctpChainConfig, cctpFeeSummaryFromBodyForSign, cctpFetchBurnFeesForRoute, cctpFetchSupportedRoutes, cctpFetchTransferStatus, cctpFetchUsdcBalance, cctpMintRecipientAddressFromExtra, cctpProtocolFeeBaseUnits, cctpSupportedRoutes, chainIdToDomain, circleCctpProtocolModule, domainToChainId, fetchCctpBurnFees, formatCctpUsdcHuman, irisApiBaseForChainId, isCctpBurnEvmSignRequest, isCctpCanonicalUsdcRow, isCctpErc20ApproveEvmSignRequest, isCctpRouteSupported, isCctpSourceChainSupported, parseCctpBurnExtraJSON, parseCctpEvmChainId, rebuildCctpBurnCalldataAtGetSig, resolveCctpBatchStepGasFromSignRequest, selectCctpIrisFeeRow };
1049
1105
  //# sourceMappingURL=index.js.map
1050
1106
  //# sourceMappingURL=index.js.map