@gala-chain/launchpad 1.0.13 → 1.0.15

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 (87) hide show
  1. package/lib/package.json +1 -1
  2. package/lib/src/api/types/LaunchpadDtos.d.ts +3 -1
  3. package/lib/src/api/types/LaunchpadDtos.d.ts.map +1 -1
  4. package/lib/src/api/types/LaunchpadDtos.js +14 -1
  5. package/lib/src/api/types/LaunchpadDtos.js.map +1 -1
  6. package/lib/src/api/types/LaunchpadSale.d.ts +5 -2
  7. package/lib/src/api/types/LaunchpadSale.d.ts.map +1 -1
  8. package/lib/src/api/types/LaunchpadSale.js +35 -9
  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.d.ts.map +1 -1
  37. package/lib/src/chaincode/launchpad/createSale.js +15 -1
  38. package/lib/src/chaincode/launchpad/createSale.js.map +1 -1
  39. package/lib/src/chaincode/launchpad/fees.d.ts +11 -0
  40. package/lib/src/chaincode/launchpad/fees.d.ts.map +1 -1
  41. package/lib/src/chaincode/launchpad/fees.js +42 -3
  42. package/lib/src/chaincode/launchpad/fees.js.map +1 -1
  43. package/lib/src/chaincode/launchpad/finaliseSale.d.ts.map +1 -1
  44. package/lib/src/chaincode/launchpad/finaliseSale.js +7 -4
  45. package/lib/src/chaincode/launchpad/finaliseSale.js.map +1 -1
  46. package/lib/src/chaincode/launchpad/sellExactToken.d.ts.map +1 -1
  47. package/lib/src/chaincode/launchpad/sellExactToken.js +14 -23
  48. package/lib/src/chaincode/launchpad/sellExactToken.js.map +1 -1
  49. package/lib/src/chaincode/launchpad/sellWithNative.d.ts.map +1 -1
  50. package/lib/src/chaincode/launchpad/sellWithNative.js +10 -19
  51. package/lib/src/chaincode/launchpad/sellWithNative.js.map +1 -1
  52. package/lib/src/chaincode/test/launchpadgala.d.ts.map +1 -1
  53. package/lib/src/chaincode/test/launchpadgala.js +2 -1
  54. package/lib/src/chaincode/test/launchpadgala.js.map +1 -1
  55. package/lib/src/chaincode/utils/launchpadSaleUtils.d.ts +4 -0
  56. package/lib/src/chaincode/utils/launchpadSaleUtils.d.ts.map +1 -1
  57. package/lib/src/chaincode/utils/launchpadSaleUtils.js +19 -1
  58. package/lib/src/chaincode/utils/launchpadSaleUtils.js.map +1 -1
  59. package/lib/src/cli.js +3 -1
  60. package/lib/src/cli.js.map +1 -1
  61. package/lib/tsconfig.tsbuildinfo +1 -1
  62. package/package.json +1 -1
  63. package/src/api/types/LaunchpadDtos.ts +15 -1
  64. package/src/api/types/LaunchpadSale.ts +32 -10
  65. package/src/chaincode/LaunchpadContract.ts +3 -3
  66. package/src/chaincode/launchpad/buyExactToken.spec.ts +3 -38
  67. package/src/chaincode/launchpad/buyExactToken.ts +22 -56
  68. package/src/chaincode/launchpad/buyWithNative.spec.ts +105 -45
  69. package/src/chaincode/launchpad/buyWithNative.ts +15 -65
  70. package/src/chaincode/launchpad/callMemeTokenIn.ts +36 -12
  71. package/src/chaincode/launchpad/callMemeTokenOut.spec.ts +2 -2
  72. package/src/chaincode/launchpad/callMemeTokenOut.ts +72 -26
  73. package/src/chaincode/launchpad/callNativeTokenIn.ts +62 -19
  74. package/src/chaincode/launchpad/callNativeTokenOut.ts +38 -15
  75. package/src/chaincode/launchpad/configureLaunchpadFeeConfig.spec.ts +0 -1
  76. package/src/chaincode/launchpad/configureLaunchpadFeeConfig.ts +1 -1
  77. package/src/chaincode/launchpad/createSale.ts +26 -3
  78. package/src/chaincode/launchpad/fees.ts +55 -3
  79. package/src/chaincode/launchpad/finaliseSale.ts +7 -4
  80. package/src/chaincode/launchpad/launchpadBatchSubmitAuthorizations.spec.ts +150 -68
  81. package/src/chaincode/launchpad/sellExactToken.spec.ts +4 -81
  82. package/src/chaincode/launchpad/sellExactToken.ts +16 -25
  83. package/src/chaincode/launchpad/sellWithNative.spec.ts +2 -42
  84. package/src/chaincode/launchpad/sellWithNative.ts +14 -23
  85. package/src/chaincode/test/launchpadgala.ts +3 -1
  86. package/src/chaincode/utils/launchpadSaleUtils.ts +25 -1
  87. package/src/cli.ts +3 -1
@@ -12,22 +12,14 @@
12
12
  * See the License for the specific language governing permissions and
13
13
  * limitations under the License.
14
14
  */
15
- import { TokenClass, ValidationFailedError } from "@gala-chain/api";
16
- import {
17
- GalaChainContext,
18
- fetchOrCreateBalance,
19
- fetchTokenClass,
20
- getObjectByKey,
21
- putChainObject,
22
- transferToken
23
- } from "@gala-chain/chaincode";
15
+ import { GalaChainContext, fetchTokenClass, putChainObject, transferToken } from "@gala-chain/chaincode";
24
16
  import BigNumber from "bignumber.js";
25
17
 
26
- import { ExactTokenQuantityDto, LaunchpadSale, NativeTokenQuantityDto, TradeResDto } from "../../api/types";
18
+ import { LaunchpadSale, NativeTokenQuantityDto, TradeResDto } from "../../api/types";
27
19
  import { SlippageToleranceExceededError } from "../../api/utils/error";
28
- import { fetchAndValidateSale, fetchLaunchpadFeeAddress } from "../utils";
20
+ import { fetchAndValidateSale } from "../utils";
29
21
  import { callMemeTokenOut } from "./callMemeTokenOut";
30
- import { callNativeTokenIn } from "./callNativeTokenIn";
22
+ import { transferTransactionFees } from "./fees";
31
23
  import { finalizeSale } from "./finaliseSale";
32
24
 
33
25
  /**
@@ -56,83 +48,41 @@ export async function buyWithNative(
56
48
 
57
49
  // Fetch and validate sale state
58
50
  const sale = await fetchAndValidateSale(ctx, buyTokenDTO.vaultAddress);
59
- const tokensLeftInVault = new BigNumber(sale.sellingTokenQuantity);
60
51
 
61
52
  // Calculate how many tokens the user can buy and fee info
62
53
  const callMemeTokenOutResult = await callMemeTokenOut(ctx, buyTokenDTO);
63
- let transactionFees = callMemeTokenOutResult.extraFees.transactionFees;
64
- let tokensToBuy = new BigNumber(callMemeTokenOutResult.calculatedQuantity);
54
+ const transactionFees = new BigNumber(callMemeTokenOutResult.extraFees.transactionFees); // transaction fees
55
+ const nativeTokensRequired = new BigNumber(callMemeTokenOutResult.originalQuantity); // number of native tokens user wants to spend
56
+ const tokensToBuy = new BigNumber(callMemeTokenOutResult.calculatedQuantity); // number of tokens user will be buying
65
57
 
66
58
  const nativeToken = sale.fetchNativeTokenInstanceKey();
67
59
  const memeToken = sale.fetchSellingTokenInstanceKey();
68
60
 
69
- // Round tokensToBuy based on decimals property of sellToken TokenClass entry,
70
- // because otherwise `transferToken()` call below will fail with
71
- // an INVALID_DECIMALS error.
72
- const { collection, category, type, additionalKey } = sale.sellingToken;
73
-
74
- const memeTokenClass = await getObjectByKey(
75
- ctx,
76
- TokenClass,
77
- TokenClass.getCompositeKeyFromParts(TokenClass.INDEX_KEY, [collection, category, type, additionalKey])
78
- );
79
-
80
- tokensToBuy = tokensToBuy.decimalPlaces(memeTokenClass.decimals);
81
-
82
- // If vault has fewer tokens than what user wants to buy, cap the purchase
83
- if (tokensLeftInVault.comparedTo(tokensToBuy) <= 0) {
84
- tokensToBuy = tokensLeftInVault.decimalPlaces(memeTokenClass.decimals);
85
- const nativeTokensRequiredToBuyDto = new ExactTokenQuantityDto(buyTokenDTO.vaultAddress, tokensToBuy);
86
- const callNativeTokenInResult = await callNativeTokenIn(ctx, nativeTokensRequiredToBuyDto);
87
- transactionFees = callMemeTokenOutResult.extraFees.transactionFees;
88
- buyTokenDTO.nativeTokenQuantity = new BigNumber(callNativeTokenInResult.calculatedQuantity);
89
- isSaleFinalized = true;
90
- }
91
-
92
- // Finalize sale if market cap is reached
61
+ // If native tokens required exceeds the market cap, the sale can be finalized
93
62
  if (
94
63
  buyTokenDTO.nativeTokenQuantity
95
64
  .plus(new BigNumber(sale.nativeTokenQuantity))
96
- .gte(new BigNumber(LaunchpadSale.MARKET_CAP))
65
+ .isGreaterThanOrEqualTo(new BigNumber(LaunchpadSale.MARKET_CAP))
97
66
  ) {
98
67
  isSaleFinalized = true;
99
68
  }
100
69
 
101
70
  // Check for slippage condition
102
- if (buyTokenDTO.expectedToken && buyTokenDTO.expectedToken.comparedTo(tokensToBuy) > 0) {
71
+ if (buyTokenDTO.expectedToken && buyTokenDTO.expectedToken.isGreaterThan(tokensToBuy)) {
103
72
  throw new SlippageToleranceExceededError(
104
73
  `expected ${buyTokenDTO.expectedToken.toString()}, but only ${tokensToBuy.toString()} tokens can be provided. Reduce the expected amount or adjust your slippage tolerance.`
105
74
  );
106
75
  }
107
76
 
108
77
  // Transfer transaction fees to launchpad fee address
109
- const launchpadFeeAddressConfiguration = await fetchLaunchpadFeeAddress(ctx);
110
- if (launchpadFeeAddressConfiguration && transactionFees) {
111
- const totalRequired = new BigNumber(buyTokenDTO.nativeTokenQuantity).plus(transactionFees);
112
-
113
- const buyerBalance = await fetchOrCreateBalance(ctx, ctx.callingUser, sale.nativeToken);
114
- if (buyerBalance.getQuantityTotal().lt(totalRequired)) {
115
- throw new ValidationFailedError(
116
- `Insufficient balance: Total amount required including fee is ${totalRequired}`
117
- );
118
- }
119
-
120
- await transferToken(ctx, {
121
- from: ctx.callingUser,
122
- to: launchpadFeeAddressConfiguration.feeAddress,
123
- tokenInstanceKey: nativeToken,
124
- quantity: new BigNumber(transactionFees),
125
- allowancesToUse: [],
126
- authorizedOnBehalf: undefined
127
- });
128
- }
78
+ await transferTransactionFees(ctx, sale, transactionFees, nativeToken, nativeTokensRequired);
129
79
 
130
80
  // Transfer native tokens from buyer to vault
131
81
  await transferToken(ctx, {
132
82
  from: ctx.callingUser,
133
83
  to: buyTokenDTO.vaultAddress,
134
84
  tokenInstanceKey: nativeToken,
135
- quantity: buyTokenDTO.nativeTokenQuantity,
85
+ quantity: nativeTokensRequired,
136
86
  allowancesToUse: [],
137
87
  authorizedOnBehalf: undefined
138
88
  });
@@ -151,7 +101,7 @@ export async function buyWithNative(
151
101
  });
152
102
 
153
103
  // Update sale object with purchase data
154
- sale.buyToken(tokensToBuy, buyTokenDTO.nativeTokenQuantity);
104
+ sale.buyToken(tokensToBuy, nativeTokensRequired);
155
105
  await putChainObject(ctx, sale);
156
106
 
157
107
  // Finalize sale if it's complete
@@ -161,8 +111,8 @@ export async function buyWithNative(
161
111
 
162
112
  const token = await fetchTokenClass(ctx, sale.sellingToken);
163
113
  return {
164
- inputQuantity: buyTokenDTO.nativeTokenQuantity.toFixed(),
165
- totalFees: transactionFees,
114
+ inputQuantity: nativeTokensRequired.toFixed(),
115
+ totalFees: transactionFees.toFixed(),
166
116
  outputQuantity: tokensToBuy.toFixed(),
167
117
  tokenName: token.name,
168
118
  tradeType: "Buy",
@@ -17,14 +17,27 @@ import { GalaChainContext } from "@gala-chain/chaincode";
17
17
  import BigNumber from "bignumber.js";
18
18
  import Decimal from "decimal.js";
19
19
 
20
- import { LaunchpadSale, NativeTokenQuantityDto } from "../../api/types";
21
- import { fetchAndValidateSale, fetchLaunchpadFeeAddress, getBondingConstants } from "../utils";
20
+ import { LaunchpadSale, NativeTokenQuantityDto, TradeCalculationResDto } from "../../api/types";
21
+ import {
22
+ fetchAndValidateSale,
23
+ fetchLaunchpadFeeAddress,
24
+ fetchTokenDecimals,
25
+ getBondingConstants
26
+ } from "../utils";
22
27
  import { calculateReverseBondingCurveFee, calculateTransactionFee } from "./fees";
23
28
 
24
- function calculateMemeTokensRequired(sale: LaunchpadSale, requestedNativeTokenQuantity: BigNumber) {
29
+ function calculateMemeTokensRequired(
30
+ sale: LaunchpadSale,
31
+ requestedNativeTokenQuantity: BigNumber,
32
+ nativeTokenDecimals: number,
33
+ sellingTokenDecimals: number
34
+ ): [string, string] {
25
35
  const totalTokensSold = new Decimal(sale.fetchTokensSold()); // current tokens sold / x
26
- let nativeTokens = new Decimal(requestedNativeTokenQuantity.toString()); // native tokens used to buy / y
27
- const basePrice = new Decimal(sale.fetchBasePrice()); // base price / a
36
+ let nativeTokens = new Decimal(requestedNativeTokenQuantity.toString()).toDecimalPlaces(
37
+ nativeTokenDecimals,
38
+ Decimal.ROUND_DOWN
39
+ );
40
+ const basePrice = new Decimal(LaunchpadSale.BASE_PRICE); // base price / a
28
41
  const { exponentFactor, euler, decimals } = getBondingConstants();
29
42
 
30
43
  const nativeTokenInVault = new Decimal(sale.nativeTokenQuantity);
@@ -36,16 +49,16 @@ function calculateMemeTokensRequired(sale: LaunchpadSale, requestedNativeTokenQu
36
49
  const exp1 = euler.pow(exponent);
37
50
  const constantFactor = nativeTokens.mul(exponentFactor).div(basePrice);
38
51
 
39
- if (exp1.lte(constantFactor)) {
52
+ if (exp1.lessThanOrEqualTo(constantFactor)) {
40
53
  throw new ValidationFailedError("Cannot sell more tokens than have been bought in this sale.");
41
54
  }
42
55
 
43
56
  const adjustedExp = exp1.minus(constantFactor);
44
57
  const lnAdjustedExp = adjustedExp.ln();
45
58
  const tokensSent = totalTokensSold.minus(lnAdjustedExp.mul(decimals).div(exponentFactor));
46
- const roundedTokenSent = tokensSent.toDecimalPlaces(18, Decimal.ROUND_UP);
59
+ const roundedTokenSent = tokensSent.toDecimalPlaces(sellingTokenDecimals, Decimal.ROUND_UP);
47
60
 
48
- return roundedTokenSent.toFixed();
61
+ return [nativeTokens.toFixed(), roundedTokenSent.toFixed()];
49
62
  }
50
63
 
51
64
  /**
@@ -61,17 +74,28 @@ function calculateMemeTokensRequired(sale: LaunchpadSale, requestedNativeTokenQu
61
74
  * - `nativeTokenAmount`: The amount of native tokens to be recieved from sale.
62
75
  * - `expectedToken` (optional): The expected amount of tokens to be sold.
63
76
  *
64
- * @returns A promise that resolves to a string representing the calculated amount of
65
- * tokens to be sent, rounded up to 18 decimal places.
77
+ * @returns A promise that resolves to a TradeCalculationResDto object containing the calculated
78
+ * quantity of tokens to be sent, the original quantity of native tokens used, and extra fees.
66
79
  *
67
80
  * @throws Error if the calculation results in an invalid amount (e.g., `InvalidAmountError`).
68
81
  */
69
- export async function callMemeTokenIn(ctx: GalaChainContext, sellTokenDTO: NativeTokenQuantityDto) {
82
+ export async function callMemeTokenIn(
83
+ ctx: GalaChainContext,
84
+ sellTokenDTO: NativeTokenQuantityDto
85
+ ): Promise<TradeCalculationResDto> {
70
86
  const sale = await fetchAndValidateSale(ctx, sellTokenDTO.vaultAddress);
71
87
  const launchpadFeeAddressConfiguration = await fetchLaunchpadFeeAddress(ctx);
88
+ const { nativeTokenDecimals, sellingTokenDecimals } = await fetchTokenDecimals(ctx, sale);
89
+ const [originalQuantity, calculatedQuantity] = calculateMemeTokensRequired(
90
+ sale,
91
+ sellTokenDTO.nativeTokenQuantity,
92
+ nativeTokenDecimals,
93
+ sellingTokenDecimals
94
+ );
72
95
 
73
96
  return {
74
- calculatedQuantity: calculateMemeTokensRequired(sale, sellTokenDTO.nativeTokenQuantity),
97
+ originalQuantity: originalQuantity,
98
+ calculatedQuantity: calculatedQuantity,
75
99
  extraFees: {
76
100
  reverseBondingCurve: calculateReverseBondingCurveFee(sale, sellTokenDTO.nativeTokenQuantity).toString(),
77
101
  transactionFees: calculateTransactionFee(
@@ -111,7 +111,7 @@ describe("callMemeTokenOut", () => {
111
111
 
112
112
  // // Then
113
113
  expect(response.Data).toMatchObject({
114
- calculatedQuantity: "58497.530004606452819",
114
+ calculatedQuantity: "58497.5300046064",
115
115
  extraFees: { reverseBondingCurve: "0", transactionFees: "0.00000000" }
116
116
  });
117
117
  });
@@ -143,7 +143,7 @@ describe("callMemeTokenOut", () => {
143
143
 
144
144
  // Then
145
145
  expect(response.Data).toMatchObject({
146
- calculatedQuantity: "458291.30295364487969",
146
+ calculatedQuantity: "458291.302953644",
147
147
  extraFees: { reverseBondingCurve: "0", transactionFees: "0.00000000" }
148
148
  });
149
149
  });
@@ -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