@gala-chain/launchpad 1.0.13 → 1.0.14

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.
Files changed (86) hide show
  1. package/lib/package.json +1 -1
  2. package/lib/src/api/types/LaunchpadDtos.d.ts +1 -0
  3. package/lib/src/api/types/LaunchpadDtos.d.ts.map +1 -1
  4. package/lib/src/api/types/LaunchpadDtos.js +5 -0
  5. package/lib/src/api/types/LaunchpadDtos.js.map +1 -1
  6. package/lib/src/api/types/LaunchpadSale.d.ts +2 -1
  7. package/lib/src/api/types/LaunchpadSale.d.ts.map +1 -1
  8. package/lib/src/api/types/LaunchpadSale.js +18 -6
  9. package/lib/src/api/types/LaunchpadSale.js.map +1 -1
  10. package/lib/src/chaincode/LaunchpadContract.js +6 -6
  11. package/lib/src/chaincode/LaunchpadContract.js.map +1 -1
  12. package/lib/src/chaincode/launchpad/buyExactToken.d.ts.map +1 -1
  13. package/lib/src/chaincode/launchpad/buyExactToken.js +18 -42
  14. package/lib/src/chaincode/launchpad/buyExactToken.js.map +1 -1
  15. package/lib/src/chaincode/launchpad/buyWithNative.d.ts.map +1 -1
  16. package/lib/src/chaincode/launchpad/buyWithNative.js +12 -43
  17. package/lib/src/chaincode/launchpad/buyWithNative.js.map +1 -1
  18. package/lib/src/chaincode/launchpad/callMemeTokenIn.d.ts +4 -10
  19. package/lib/src/chaincode/launchpad/callMemeTokenIn.d.ts.map +1 -1
  20. package/lib/src/chaincode/launchpad/callMemeTokenIn.js +13 -9
  21. package/lib/src/chaincode/launchpad/callMemeTokenIn.js.map +1 -1
  22. package/lib/src/chaincode/launchpad/callMemeTokenOut.d.ts +4 -10
  23. package/lib/src/chaincode/launchpad/callMemeTokenOut.d.ts.map +1 -1
  24. package/lib/src/chaincode/launchpad/callMemeTokenOut.js +46 -22
  25. package/lib/src/chaincode/launchpad/callMemeTokenOut.js.map +1 -1
  26. package/lib/src/chaincode/launchpad/callNativeTokenIn.d.ts +4 -10
  27. package/lib/src/chaincode/launchpad/callNativeTokenIn.d.ts.map +1 -1
  28. package/lib/src/chaincode/launchpad/callNativeTokenIn.js +35 -14
  29. package/lib/src/chaincode/launchpad/callNativeTokenIn.js.map +1 -1
  30. package/lib/src/chaincode/launchpad/callNativeTokenOut.d.ts +4 -10
  31. package/lib/src/chaincode/launchpad/callNativeTokenOut.d.ts.map +1 -1
  32. package/lib/src/chaincode/launchpad/callNativeTokenOut.js +17 -12
  33. package/lib/src/chaincode/launchpad/callNativeTokenOut.js.map +1 -1
  34. package/lib/src/chaincode/launchpad/configureLaunchpadFeeConfig.js +1 -1
  35. package/lib/src/chaincode/launchpad/configureLaunchpadFeeConfig.js.map +1 -1
  36. package/lib/src/chaincode/launchpad/createSale.js +1 -1
  37. package/lib/src/chaincode/launchpad/createSale.js.map +1 -1
  38. package/lib/src/chaincode/launchpad/fees.d.ts +11 -0
  39. package/lib/src/chaincode/launchpad/fees.d.ts.map +1 -1
  40. package/lib/src/chaincode/launchpad/fees.js +42 -3
  41. package/lib/src/chaincode/launchpad/fees.js.map +1 -1
  42. package/lib/src/chaincode/launchpad/finaliseSale.d.ts.map +1 -1
  43. package/lib/src/chaincode/launchpad/finaliseSale.js +7 -4
  44. package/lib/src/chaincode/launchpad/finaliseSale.js.map +1 -1
  45. package/lib/src/chaincode/launchpad/sellExactToken.d.ts.map +1 -1
  46. package/lib/src/chaincode/launchpad/sellExactToken.js +14 -23
  47. package/lib/src/chaincode/launchpad/sellExactToken.js.map +1 -1
  48. package/lib/src/chaincode/launchpad/sellWithNative.d.ts.map +1 -1
  49. package/lib/src/chaincode/launchpad/sellWithNative.js +10 -19
  50. package/lib/src/chaincode/launchpad/sellWithNative.js.map +1 -1
  51. package/lib/src/chaincode/test/launchpadgala.d.ts.map +1 -1
  52. package/lib/src/chaincode/test/launchpadgala.js +2 -1
  53. package/lib/src/chaincode/test/launchpadgala.js.map +1 -1
  54. package/lib/src/chaincode/utils/launchpadSaleUtils.d.ts +4 -0
  55. package/lib/src/chaincode/utils/launchpadSaleUtils.d.ts.map +1 -1
  56. package/lib/src/chaincode/utils/launchpadSaleUtils.js +10 -1
  57. package/lib/src/chaincode/utils/launchpadSaleUtils.js.map +1 -1
  58. package/lib/src/cli.js +3 -1
  59. package/lib/src/cli.js.map +1 -1
  60. package/lib/tsconfig.tsbuildinfo +1 -1
  61. package/package.json +1 -1
  62. package/src/api/types/LaunchpadDtos.ts +4 -0
  63. package/src/api/types/LaunchpadSale.ts +14 -7
  64. package/src/chaincode/LaunchpadContract.ts +3 -3
  65. package/src/chaincode/launchpad/buyExactToken.spec.ts +2 -37
  66. package/src/chaincode/launchpad/buyExactToken.ts +22 -56
  67. package/src/chaincode/launchpad/buyWithNative.spec.ts +2 -37
  68. package/src/chaincode/launchpad/buyWithNative.ts +15 -65
  69. package/src/chaincode/launchpad/callMemeTokenIn.ts +36 -12
  70. package/src/chaincode/launchpad/callMemeTokenOut.spec.ts +1 -1
  71. package/src/chaincode/launchpad/callMemeTokenOut.ts +72 -26
  72. package/src/chaincode/launchpad/callNativeTokenIn.ts +62 -19
  73. package/src/chaincode/launchpad/callNativeTokenOut.ts +38 -15
  74. package/src/chaincode/launchpad/configureLaunchpadFeeConfig.spec.ts +0 -1
  75. package/src/chaincode/launchpad/configureLaunchpadFeeConfig.ts +1 -1
  76. package/src/chaincode/launchpad/createSale.ts +1 -1
  77. package/src/chaincode/launchpad/fees.ts +55 -3
  78. package/src/chaincode/launchpad/finaliseSale.ts +7 -4
  79. package/src/chaincode/launchpad/launchpadBatchSubmitAuthorizations.spec.ts +150 -68
  80. package/src/chaincode/launchpad/sellExactToken.spec.ts +4 -81
  81. package/src/chaincode/launchpad/sellExactToken.ts +16 -25
  82. package/src/chaincode/launchpad/sellWithNative.spec.ts +2 -42
  83. package/src/chaincode/launchpad/sellWithNative.ts +14 -23
  84. package/src/chaincode/test/launchpadgala.ts +3 -1
  85. package/src/chaincode/utils/launchpadSaleUtils.ts +13 -1
  86. package/src/cli.ts +3 -1
@@ -16,10 +16,53 @@ import { GalaChainContext } from "@gala-chain/chaincode";
16
16
  import BigNumber from "bignumber.js";
17
17
  import Decimal from "decimal.js";
18
18
 
19
- import { LaunchpadSale, NativeTokenQuantityDto } from "../../api/types";
20
- import { fetchAndValidateSale, fetchLaunchpadFeeAddress, getBondingConstants } from "../utils";
19
+ import { LaunchpadSale, NativeTokenQuantityDto, TradeCalculationResDto } from "../../api/types";
20
+ import {
21
+ fetchAndValidateSale,
22
+ fetchLaunchpadFeeAddress,
23
+ fetchTokenDecimals,
24
+ getBondingConstants
25
+ } from "../utils";
21
26
  import { calculateTransactionFee } from "./fees";
22
27
 
28
+ function calculateTokensPurchasable(
29
+ nativeTokens: Decimal,
30
+ totalTokensSold: Decimal,
31
+ nativeTokenDecimals: number,
32
+ sellingTokenDecimals: number
33
+ ): [string, string] {
34
+ const basePrice = new Decimal(LaunchpadSale.BASE_PRICE);
35
+ const { exponentFactor, euler, decimals } = getBondingConstants();
36
+
37
+ // Round native tokens, then calculate tokens based on that rounded amount
38
+ const roundedNativeTokens = nativeTokens.toDecimalPlaces(nativeTokenDecimals, Decimal.ROUND_UP);
39
+
40
+ // Calculate tokens purchasable: newTokens = (decimals / exponentFactor) * ln((nativeTokens * exponentFactor / basePrice) + e^(exponentFactor * totalTokensSold / decimals)) - totalTokensSold
41
+ // Where:
42
+ // constant = nativeTokens * exponentFactor / basePrice
43
+ // exponent1 = exponentFactor * totalTokensSold / decimals
44
+ // eResult1 = e^(exponent1) = e^(exponentFactor * totalTokensSold / decimals)
45
+ // ethScaled = constant + eResult1 = (nativeTokens * exponentFactor / basePrice) + e^(exponentFactor * totalTokensSold / decimals)
46
+ // lnEthScaled = ln(ethScaled) * decimals
47
+ // lnEthScaledBase = lnEthScaled / exponentFactor = (decimals / exponentFactor) * ln(ethScaled)
48
+ // result = lnEthScaledBase - totalTokensSold
49
+ const constant = roundedNativeTokens.mul(exponentFactor).div(basePrice);
50
+ const exponent1 = exponentFactor.mul(totalTokensSold).div(decimals);
51
+ const eResult1 = euler.pow(exponent1);
52
+ const ethScaled = constant.add(eResult1);
53
+ const lnEthScaled = ethScaled.ln().mul(decimals);
54
+ const lnEthScaledBase = lnEthScaled.div(exponentFactor);
55
+ const result = lnEthScaledBase.minus(totalTokensSold);
56
+ let roundedResult = result.toDecimalPlaces(sellingTokenDecimals, Decimal.ROUND_DOWN);
57
+
58
+ // Cap total supply to 10 million
59
+ if (roundedResult.add(totalTokensSold).greaterThan(new Decimal("1e+7"))) {
60
+ roundedResult = new Decimal("1e+7").minus(new Decimal(totalTokensSold));
61
+ }
62
+
63
+ return [roundedNativeTokens.toFixed(), roundedResult.toFixed()];
64
+ }
65
+
23
66
  /**
24
67
  * Calculates the number of tokens that can be purchased using a specified amount
25
68
  * of native tokens based on a bonding curve mechanism.
@@ -31,12 +74,15 @@ import { calculateTransactionFee } from "./fees";
31
74
  * @param buyTokenDTO - The data transfer object containing the sale address
32
75
  * and the amount of native tokens to spend for the purchase.
33
76
  *
34
- * @returns A promise that resolves to a string representing the calculated amount of
35
- * tokens to be received, rounded down to 18 decimal places.
77
+ * @returns A promise that resolves to a TradeCalculationResDto object containing the calculated
78
+ * quantity of tokens to be received, the original quantity of native tokens used, and extra fees.
36
79
  *
37
80
  * @throws Error if the calculation results in an invalid state.
38
81
  */
39
- export async function callMemeTokenOut(ctx: GalaChainContext, buyTokenDTO: NativeTokenQuantityDto) {
82
+ export async function callMemeTokenOut(
83
+ ctx: GalaChainContext,
84
+ buyTokenDTO: NativeTokenQuantityDto
85
+ ): Promise<TradeCalculationResDto> {
40
86
  // Convert input amount to Decimal
41
87
  let nativeTokens = new Decimal(buyTokenDTO.nativeTokenQuantity.toString());
42
88
 
@@ -44,11 +90,13 @@ export async function callMemeTokenOut(ctx: GalaChainContext, buyTokenDTO: Nativ
44
90
  let totalTokensSold = new Decimal(0);
45
91
 
46
92
  // Fetch sale details and update parameters if this is not a sale premint calculation
93
+ let sale: LaunchpadSale | undefined;
47
94
  if (!buyTokenDTO.IsPreMint) {
48
- const sale = await fetchAndValidateSale(ctx, buyTokenDTO.vaultAddress);
95
+ sale = await fetchAndValidateSale(ctx, buyTokenDTO.vaultAddress);
49
96
  totalTokensSold = new Decimal(sale.fetchTokensSold());
50
97
 
51
- // Enforce market cap limit
98
+ // Enforce market cap limit, adjust number of native tokens that will be used in the transaction
99
+ // if total GALA tokens will exceed the market cap
52
100
  if (
53
101
  nativeTokens
54
102
  .add(new Decimal(sale.nativeTokenQuantity))
@@ -58,34 +106,32 @@ export async function callMemeTokenOut(ctx: GalaChainContext, buyTokenDTO: Nativ
58
106
  }
59
107
  }
60
108
 
61
- // Load bonding curve constants
62
- const basePrice = new Decimal(LaunchpadSale.BASE_PRICE);
63
- const { exponentFactor, euler, decimals } = getBondingConstants();
109
+ // Get token decimals for rounding
110
+ const { nativeTokenDecimals, sellingTokenDecimals } = sale
111
+ ? await fetchTokenDecimals(ctx, sale)
112
+ : {
113
+ nativeTokenDecimals: LaunchpadSale.NATIVE_TOKEN_DECIMALS,
114
+ sellingTokenDecimals: LaunchpadSale.SELLING_TOKEN_DECIMALS
115
+ };
64
116
 
65
- // Apply bonding curve math
66
- const constant = nativeTokens.mul(exponentFactor).div(basePrice);
67
- const exponent1 = exponentFactor.mul(totalTokensSold).div(decimals);
68
- const eResult1 = euler.pow(exponent1);
69
- const ethScaled = constant.add(eResult1);
70
- const lnEthScaled = ethScaled.ln().mul(decimals);
71
- const lnEthScaledBase = lnEthScaled.div(exponentFactor);
72
- const result = lnEthScaledBase.minus(totalTokensSold);
73
- let roundedResult = result.toDecimalPlaces(18, Decimal.ROUND_DOWN);
74
-
75
- // Cap total supply to 10 million
76
- if (roundedResult.add(totalTokensSold).greaterThan(new Decimal("1e+7"))) {
77
- roundedResult = new Decimal("1e+7").minus(new Decimal(totalTokensSold));
78
- }
117
+ // Calculate tokens purchasable using bonding curve math
118
+ const [originalQuantity, calculatedQuantity] = calculateTokensPurchasable(
119
+ nativeTokens,
120
+ totalTokensSold,
121
+ nativeTokenDecimals,
122
+ sellingTokenDecimals
123
+ );
79
124
 
80
125
  // Fetch fee configuration and return result
81
126
  const launchpadFeeAddressConfiguration = await fetchLaunchpadFeeAddress(ctx);
82
127
 
83
128
  return {
84
- calculatedQuantity: roundedResult.toFixed(),
129
+ originalQuantity: originalQuantity,
130
+ calculatedQuantity: calculatedQuantity,
85
131
  extraFees: {
86
132
  reverseBondingCurve: "0",
87
133
  transactionFees: calculateTransactionFee(
88
- BigNumber(nativeTokens.toFixed()),
134
+ BigNumber(originalQuantity),
89
135
  launchpadFeeAddressConfiguration?.feeAmount
90
136
  )
91
137
  }
@@ -16,10 +16,51 @@ import { GalaChainContext } from "@gala-chain/chaincode";
16
16
  import BigNumber from "bignumber.js";
17
17
  import Decimal from "decimal.js";
18
18
 
19
- import { ExactTokenQuantityDto } from "../../api/types";
20
- import { fetchAndValidateSale, fetchLaunchpadFeeAddress, getBondingConstants } from "../utils";
19
+ import { ExactTokenQuantityDto, LaunchpadSale, TradeCalculationResDto } from "../../api/types";
20
+ import {
21
+ fetchAndValidateSale,
22
+ fetchLaunchpadFeeAddress,
23
+ fetchTokenDecimals,
24
+ getBondingConstants
25
+ } from "../utils";
21
26
  import { calculateTransactionFee } from "./fees";
22
27
 
28
+ function calculateNativeTokensRequired(
29
+ tokensToBuy: Decimal,
30
+ totalTokensSold: Decimal,
31
+ sellingTokenDecimals: number,
32
+ nativeTokenDecimals: number
33
+ ): [string, string] {
34
+ const basePrice = new Decimal(LaunchpadSale.BASE_PRICE);
35
+ const { exponentFactor, euler, decimals } = getBondingConstants();
36
+
37
+ // Round tokens first, then calculate native tokens based on that rounded amount
38
+ const roundedTokensToBuy = tokensToBuy.toDecimalPlaces(sellingTokenDecimals, Decimal.ROUND_DOWN);
39
+
40
+ // Calculate native tokens required: price = (basePrice / exponentFactor) * (e^(exponentFactor * (totalTokensSold + tokensToBuy) / decimals) - e^(exponentFactor * totalTokensSold / decimals))
41
+ // Where:
42
+ // exponent1 = exponentFactor * (totalTokensSold + tokensToBuy) / decimals
43
+ // exponent2 = exponentFactor * totalTokensSold / decimals
44
+ // eResult1 = e^(exponent1) = e^(exponentFactor * (totalTokensSold + tokensToBuy) / decimals)
45
+ // eResult2 = e^(exponent2) = e^(exponentFactor * totalTokensSold / decimals)
46
+ // constantFactor = basePrice / exponentFactor
47
+ // differenceOfExponentials = eResult1 - eResult2
48
+ // price = constantFactor * differenceOfExponentials
49
+ const exponent1 = exponentFactor.mul(totalTokensSold.add(roundedTokensToBuy)).div(decimals);
50
+ const exponent2 = exponentFactor.mul(totalTokensSold).div(decimals);
51
+
52
+ const eResult1 = euler.pow(exponent1);
53
+ const eResult2 = euler.pow(exponent2);
54
+
55
+ const constantFactor = basePrice.div(exponentFactor);
56
+ const differenceOfExponentials = eResult1.minus(eResult2);
57
+
58
+ const price = constantFactor.mul(differenceOfExponentials);
59
+ const roundedPrice = price.toDecimalPlaces(nativeTokenDecimals, Decimal.ROUND_UP);
60
+
61
+ return [roundedTokensToBuy.toFixed(), roundedPrice.toFixed()];
62
+ }
63
+
23
64
  /**
24
65
  * Calculates the amount of native tokens required to purchase a specified amount
25
66
  * of tokens using a bonding curve mechanism.
@@ -31,43 +72,45 @@ import { calculateTransactionFee } from "./fees";
31
72
  * @param buyTokenDTO - The data transfer object containing the sale address
32
73
  * and the exact amount of tokens to be purchased.
33
74
  *
34
- * @returns A promise that resolves to a string representing the calculated amount of
35
- * native tokens required for the purchase, rounded up to 8 decimal places.
75
+ * @returns A promise that resolves to a TradeCalculationResDto object containing the calculated
76
+ * quantity of native tokens required for the purchase, the original quantity of tokens used, and extra fees.
36
77
  *
37
78
  * @throws Error if the calculation encounters an invalid state or data.
38
79
  */
39
- export async function callNativeTokenIn(ctx: GalaChainContext, buyTokenDTO: ExactTokenQuantityDto) {
80
+ export async function callNativeTokenIn(
81
+ ctx: GalaChainContext,
82
+ buyTokenDTO: ExactTokenQuantityDto
83
+ ): Promise<TradeCalculationResDto> {
40
84
  const sale = await fetchAndValidateSale(ctx, buyTokenDTO.vaultAddress);
41
85
  const totalTokensSold = new Decimal(sale.fetchTokensSold());
42
86
 
43
87
  let tokensToBuy = new Decimal(buyTokenDTO.tokenQuantity.toString());
44
- const basePrice = new Decimal(sale.fetchBasePrice());
45
- const { exponentFactor, euler, decimals } = getBondingConstants();
46
88
 
89
+ // Adjust tokensToBuy if user is trying to buy more tokens than the total supply
47
90
  if (tokensToBuy.add(totalTokensSold).greaterThan(new Decimal("1e+7"))) {
48
91
  tokensToBuy = new Decimal(sale.sellingTokenQuantity);
49
92
  }
50
93
 
51
- const exponent1 = exponentFactor.mul(totalTokensSold.add(tokensToBuy)).div(decimals);
52
- const exponent2 = exponentFactor.mul(totalTokensSold).div(decimals);
53
-
54
- const eResult1 = euler.pow(exponent1);
55
- const eResult2 = euler.pow(exponent2);
56
-
57
- const constantFactor = basePrice.div(exponentFactor);
58
- const differenceOfExponentials = eResult1.minus(eResult2);
94
+ // Get token decimals for rounding
95
+ const { nativeTokenDecimals, sellingTokenDecimals } = await fetchTokenDecimals(ctx, sale);
59
96
 
60
- const price = constantFactor.mul(differenceOfExponentials);
97
+ // Calculate native tokens required using bonding curve math
98
+ const [originalQuantity, calculatedQuantity] = calculateNativeTokensRequired(
99
+ tokensToBuy,
100
+ totalTokensSold,
101
+ sellingTokenDecimals,
102
+ nativeTokenDecimals
103
+ );
61
104
 
62
105
  const launchpadFeeAddressConfiguration = await fetchLaunchpadFeeAddress(ctx);
63
106
 
64
- const roundedPrice = price.toDecimalPlaces(8, Decimal.ROUND_UP).toFixed();
65
107
  return {
66
- calculatedQuantity: roundedPrice,
108
+ originalQuantity: originalQuantity,
109
+ calculatedQuantity: calculatedQuantity,
67
110
  extraFees: {
68
111
  reverseBondingCurve: "0",
69
112
  transactionFees: calculateTransactionFee(
70
- BigNumber(roundedPrice),
113
+ BigNumber(calculatedQuantity),
71
114
  launchpadFeeAddressConfiguration?.feeAmount
72
115
  )
73
116
  }
@@ -16,25 +16,38 @@ import { GalaChainContext } from "@gala-chain/chaincode";
16
16
  import BigNumber from "bignumber.js";
17
17
  import Decimal from "decimal.js";
18
18
 
19
- import { ExactTokenQuantityDto, LaunchpadSale } from "../../api/types";
20
- import { fetchAndValidateSale, fetchLaunchpadFeeAddress, getBondingConstants } from "../utils";
19
+ import { ExactTokenQuantityDto, LaunchpadSale, TradeCalculationResDto } from "../../api/types";
20
+ import {
21
+ fetchAndValidateSale,
22
+ fetchLaunchpadFeeAddress,
23
+ fetchTokenDecimals,
24
+ getBondingConstants
25
+ } from "../utils";
21
26
  import { calculateReverseBondingCurveFee, calculateTransactionFee } from "./fees";
22
27
 
23
- function calculateNativeTokensReceived(sale: LaunchpadSale, tokensToSellBn: BigNumber) {
28
+ function calculateNativeTokensReceived(
29
+ sale: LaunchpadSale,
30
+ tokensToSellBn: BigNumber,
31
+ sellingTokenDecimals: number,
32
+ nativeTokenDecimals: number
33
+ ): [string, string] {
24
34
  const totalTokensSold = new Decimal(sale.fetchTokensSold());
25
35
 
26
36
  let tokensToSell = new Decimal(tokensToSellBn.toString());
27
- const basePrice = new Decimal(sale.fetchBasePrice());
37
+ const basePrice = new Decimal(LaunchpadSale.BASE_PRICE);
28
38
  const { exponentFactor, euler, decimals } = getBondingConstants();
29
39
 
30
40
  let newTotalTokensSold = totalTokensSold.minus(tokensToSell);
31
41
 
32
- if (newTotalTokensSold.comparedTo(0) < 0) {
42
+ if (newTotalTokensSold.lessThan(0)) {
33
43
  tokensToSell = totalTokensSold;
34
44
  newTotalTokensSold = new Decimal(0);
35
45
  }
36
46
 
37
- const exponent1 = exponentFactor.mul(newTotalTokensSold.add(tokensToSell)).div(decimals);
47
+ // Round tokens first, then calculate native tokens based on that rounded amount
48
+ const roundedTokensToSell = tokensToSell.toDecimalPlaces(sellingTokenDecimals, Decimal.ROUND_UP);
49
+
50
+ const exponent1 = exponentFactor.mul(newTotalTokensSold.add(roundedTokensToSell)).div(decimals);
38
51
  const exponent2 = exponentFactor.mul(newTotalTokensSold).div(decimals);
39
52
 
40
53
  const eResult1 = euler.pow(exponent1);
@@ -43,9 +56,9 @@ function calculateNativeTokensReceived(sale: LaunchpadSale, tokensToSellBn: BigN
43
56
  const constantFactor = basePrice.div(exponentFactor);
44
57
  const differenceOfExponentials = eResult1.minus(eResult2);
45
58
  const price = constantFactor.mul(differenceOfExponentials);
46
- const roundedPrice = price.toDecimalPlaces(8, Decimal.ROUND_DOWN);
59
+ const roundedPrice = price.toDecimalPlaces(nativeTokenDecimals, Decimal.ROUND_DOWN);
47
60
 
48
- return roundedPrice.toFixed();
61
+ return [roundedTokensToSell.toFixed(), roundedPrice.toFixed()];
49
62
  }
50
63
 
51
64
  /**
@@ -59,23 +72,33 @@ function calculateNativeTokensReceived(sale: LaunchpadSale, tokensToSellBn: BigN
59
72
  * @param sellTokenDTO - The data transfer object containing the sale address
60
73
  * and the exact amount of tokens to be sold.
61
74
  *
62
- * @returns A promise that resolves to a string representing the calculated amount of
63
- * native tokens to be received, rounded down to 8 decimal places.
75
+ * @returns A promise that resolves to a TradeCalculationResDto object containing the calculated
76
+ * quantity of native tokens received, the quantity of tokens required, and extra fees.
64
77
  *
65
78
  * @throws DefaultError if the calculated new total tokens sold is less than zero
66
79
  * or if the input amount is invalid.
67
80
  */
68
- export async function callNativeTokenOut(ctx: GalaChainContext, sellTokenDTO: ExactTokenQuantityDto) {
81
+ export async function callNativeTokenOut(
82
+ ctx: GalaChainContext,
83
+ sellTokenDTO: ExactTokenQuantityDto
84
+ ): Promise<TradeCalculationResDto> {
69
85
  const sale = await fetchAndValidateSale(ctx, sellTokenDTO.vaultAddress);
70
- const nativeTokensReceived = calculateNativeTokensReceived(sale, sellTokenDTO.tokenQuantity);
86
+ const { nativeTokenDecimals, sellingTokenDecimals } = await fetchTokenDecimals(ctx, sale);
87
+ const [originalQuantity, calculatedQuantity] = calculateNativeTokensReceived(
88
+ sale,
89
+ sellTokenDTO.tokenQuantity,
90
+ sellingTokenDecimals,
91
+ nativeTokenDecimals
92
+ );
71
93
  const launchpadFeeAddressConfiguration = await fetchLaunchpadFeeAddress(ctx);
72
94
 
73
95
  return {
74
- calculatedQuantity: nativeTokensReceived,
96
+ originalQuantity: originalQuantity,
97
+ calculatedQuantity: calculatedQuantity,
75
98
  extraFees: {
76
- reverseBondingCurve: calculateReverseBondingCurveFee(sale, BigNumber(nativeTokensReceived)).toString(),
99
+ reverseBondingCurve: calculateReverseBondingCurveFee(sale, BigNumber(calculatedQuantity)).toString(),
77
100
  transactionFees: calculateTransactionFee(
78
- BigNumber(nativeTokensReceived),
101
+ BigNumber(calculatedQuantity),
79
102
  launchpadFeeAddressConfiguration?.feeAmount
80
103
  )
81
104
  }
@@ -155,7 +155,6 @@ describe("configureLaunchpadFeeAddress", () => {
155
155
 
156
156
  // Update with zero fee amount
157
157
  const configDto = new ConfigureLaunchpadFeeAddressDto();
158
- configDto.newPlatformFeeAddress = asValidUserAlias("client|feeAddress"); // Keep same address
159
158
  configDto.newFeeAmount = 0;
160
159
  configDto.uniqueKey = randomUniqueKey();
161
160
  const signedDto = configDto.signed(users.admin.privateKey);
@@ -35,7 +35,7 @@ export async function configureLaunchpadFeeAddress(
35
35
  dto: ConfigureLaunchpadFeeAddressDto
36
36
  ): Promise<LaunchpadFeeConfig> {
37
37
  // Validate input: at least one field must be present
38
- if (!dto.newPlatformFeeAddress && !dto.newAuthorities?.length && !dto.newFeeAmount) {
38
+ if (!dto.newPlatformFeeAddress && !dto.newAuthorities?.length && dto.newFeeAmount === undefined) {
39
39
  throw new ValidationFailedError("None of the input fields are present.");
40
40
  }
41
41
 
@@ -85,7 +85,7 @@ export async function createSale(
85
85
  network: "GC",
86
86
  tokenClass: tokenInstanceKey.getTokenClassKey(),
87
87
  isNonFungible: false,
88
- decimals: 18,
88
+ decimals: LaunchpadSale.SELLING_TOKEN_DECIMALS,
89
89
  name: launchpadDetails.tokenName,
90
90
  symbol: launchpadDetails.tokenSymbol,
91
91
  description: launchpadDetails.tokenDescription,
@@ -12,9 +12,10 @@
12
12
  * See the License for the specific language governing permissions and
13
13
  * limitations under the License.
14
14
  */
15
- import { FeeReceiptStatus } from "@gala-chain/api";
15
+ import { FeeReceiptStatus, ValidationFailedError } from "@gala-chain/api";
16
16
  import {
17
17
  GalaChainContext,
18
+ fetchOrCreateBalance,
18
19
  transferToken,
19
20
  txUnixTimeToDateIndexKeys,
20
21
  writeChannelPaymentReceipt,
@@ -27,7 +28,7 @@ import { SlippageToleranceExceededError } from "../../api/utils/error";
27
28
  import { fetchLaunchpadFeeAddress } from "../utils";
28
29
 
29
30
  const REVERSE_BONDING_CURVE_FEE_CODE = "LaunchpadReverseBondingCurveFee";
30
- const NATIVE_TOKEN_DECIMALS = 8;
31
+ const NATIVE_TOKEN_DECIMALS = LaunchpadSale.NATIVE_TOKEN_DECIMALS;
31
32
 
32
33
  export function calculateReverseBondingCurveFee(sale: LaunchpadSale, nativeTokensToReceive: BigNumber) {
33
34
  if (
@@ -104,5 +105,56 @@ export async function payReverseBondingCurveFee(
104
105
  }
105
106
 
106
107
  export function calculateTransactionFee(tokensBeingTraded: BigNumber, feeAmount?: number) {
107
- return tokensBeingTraded.multipliedBy(feeAmount ?? 0).toFixed(8, BigNumber.ROUND_UP);
108
+ return tokensBeingTraded.multipliedBy(feeAmount ?? 0).toFixed(NATIVE_TOKEN_DECIMALS, BigNumber.ROUND_UP);
109
+ }
110
+
111
+ /**
112
+ * Transfers transaction fees to the launchpad fee address if applicable.
113
+ * Optionally validates that the user has sufficient balance when nativeTokensRequired is provided.
114
+ *
115
+ * @param ctx - The context object providing access to the GalaChain environment.
116
+ * @param sale - The launchpad sale object.
117
+ * @param transactionFees - The transaction fees amount (as BigNumber or string).
118
+ * @param nativeToken - The native token instance key (returned from sale.fetchNativeTokenInstanceKey()).
119
+ * @param nativeTokensRequired - Optional. If provided, validates user has sufficient balance for fees + required tokens.
120
+ */
121
+ export async function transferTransactionFees(
122
+ ctx: GalaChainContext,
123
+ sale: LaunchpadSale,
124
+ transactionFees: BigNumber | string,
125
+ nativeToken: ReturnType<LaunchpadSale["fetchNativeTokenInstanceKey"]>,
126
+ nativeTokensRequired?: BigNumber
127
+ ): Promise<void> {
128
+ const launchpadFeeAddressConfiguration = await fetchLaunchpadFeeAddress(ctx);
129
+ const transactionFeesBn =
130
+ typeof transactionFees === "string" ? new BigNumber(transactionFees) : transactionFees;
131
+
132
+ // Check if transaction fees is greater than 0 and if the launchpad fee address configuration where
133
+ // the fees are sent to is defined
134
+ if (!launchpadFeeAddressConfiguration || !transactionFeesBn.isGreaterThan(0)) {
135
+ return;
136
+ }
137
+
138
+ // If nativeTokensRequired is provided, validate user has sufficient balance
139
+ if (nativeTokensRequired) {
140
+ const totalRequired = nativeTokensRequired.plus(transactionFeesBn);
141
+ const buyerBalance = await fetchOrCreateBalance(ctx, ctx.callingUser, sale.nativeToken);
142
+
143
+ // Check if the buyer has sufficient balance to pay the transaction fees
144
+ if (buyerBalance.getQuantityTotal().isLessThan(totalRequired)) {
145
+ throw new ValidationFailedError(
146
+ `Insufficient balance: Total amount required including fee is ${totalRequired}`
147
+ );
148
+ }
149
+ }
150
+
151
+ // Transfer transaction fees to the launchpad fee address
152
+ await transferToken(ctx, {
153
+ from: ctx.callingUser,
154
+ to: launchpadFeeAddressConfiguration.feeAddress,
155
+ tokenInstanceKey: nativeToken,
156
+ quantity: transactionFeesBn,
157
+ allowancesToUse: [],
158
+ authorizedOnBehalf: undefined
159
+ });
108
160
  }
@@ -65,7 +65,7 @@ export async function finalizeSale(ctx: GalaChainContext, sale: LaunchpadSale):
65
65
  tokenInstanceKey: nativeToken,
66
66
  quantity: new BigNumber(sale.nativeTokenQuantity)
67
67
  .times(ownerAllocationPercentage)
68
- .decimalPlaces(8, BigNumber.ROUND_DOWN),
68
+ .decimalPlaces(LaunchpadSale.NATIVE_TOKEN_DECIMALS, BigNumber.ROUND_DOWN),
69
69
  allowancesToUse: [],
70
70
  authorizedOnBehalf: {
71
71
  callingOnBehalf: vaultAddressAlias,
@@ -79,7 +79,7 @@ export async function finalizeSale(ctx: GalaChainContext, sale: LaunchpadSale):
79
79
  tokenInstanceKey: nativeToken,
80
80
  quantity: new BigNumber(sale.nativeTokenQuantity)
81
81
  .times(platformFeePercentage)
82
- .decimalPlaces(8, BigNumber.ROUND_DOWN),
82
+ .decimalPlaces(LaunchpadSale.NATIVE_TOKEN_DECIMALS, BigNumber.ROUND_DOWN),
83
83
  allowancesToUse: [],
84
84
  authorizedOnBehalf: {
85
85
  callingOnBehalf: vaultAddressAlias,
@@ -110,7 +110,10 @@ export async function finalizeSale(ctx: GalaChainContext, sale: LaunchpadSale):
110
110
  const poolInfo = await getSlot0(ctx, poolDTO);
111
111
 
112
112
  // Proceed normally if price in the pool is within an acceptable range
113
- const priceCloseEnough = sqrtPrice.minus(poolInfo.sqrtPrice).abs().lte(sqrtPrice.multipliedBy(0.05));
113
+ const priceCloseEnough = sqrtPrice
114
+ .minus(poolInfo.sqrtPrice)
115
+ .abs()
116
+ .isLessThanOrEqualTo(sqrtPrice.multipliedBy(0.05));
114
117
  const expectedNativeTokenRequired = new BigNumber(sale.nativeTokenQuantity).times(
115
118
  liquidityAllocationPercentage
116
119
  );
@@ -200,7 +203,7 @@ function calculateFinalLaunchpadPrice(
200
203
  areTokensSorted: boolean
201
204
  ): { sqrtPrice: BigNumber; finalPrice: BigNumber } {
202
205
  const totalTokensSold = new Decimal(sale.fetchTokensSold());
203
- const basePrice = new Decimal(sale.fetchBasePrice());
206
+ const basePrice = new Decimal(LaunchpadSale.BASE_PRICE);
204
207
  const { exponentFactor, euler, decimals } = getBondingConstants();
205
208
 
206
209
  const exponent = exponentFactor.mul(totalTokensSold).div(decimals);