@gala-chain/launchpad 1.0.8 → 1.0.10

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 (33) hide show
  1. package/CLAUDE.md +279 -0
  2. package/lib/package.json +2 -2
  3. package/lib/src/chaincode/launchpad/buyExactToken.js +1 -1
  4. package/lib/src/chaincode/launchpad/buyExactToken.js.map +1 -1
  5. package/lib/src/chaincode/launchpad/buyWithNative.d.ts.map +1 -1
  6. package/lib/src/chaincode/launchpad/buyWithNative.js +11 -4
  7. package/lib/src/chaincode/launchpad/buyWithNative.js.map +1 -1
  8. package/lib/src/chaincode/launchpad/finaliseSale.js +1 -1
  9. package/lib/src/chaincode/launchpad/finaliseSale.js.map +1 -1
  10. package/lib/src/chaincode/launchpad/sellWithNative.d.ts.map +1 -1
  11. package/lib/src/chaincode/launchpad/sellWithNative.js +9 -1
  12. package/lib/src/chaincode/launchpad/sellWithNative.js.map +1 -1
  13. package/lib/src/chaincode/test/launchpadgala.js +1 -1
  14. package/lib/src/chaincode/test/launchpadgala.js.map +1 -1
  15. package/lib/tsconfig.tsbuildinfo +1 -1
  16. package/package.json +2 -2
  17. package/src/chaincode/launchpad/buyExactToken.spec.ts +140 -20
  18. package/src/chaincode/launchpad/buyExactToken.ts +1 -1
  19. package/src/chaincode/launchpad/buyWithNative.spec.ts +99 -25
  20. package/src/chaincode/launchpad/buyWithNative.ts +20 -5
  21. package/src/chaincode/launchpad/callMemeTokenIn.spec.ts +244 -0
  22. package/src/chaincode/launchpad/callMemeTokenOut.spec.ts +1 -1
  23. package/src/chaincode/launchpad/callNativeTokenIn.spec.ts +269 -0
  24. package/src/chaincode/launchpad/callNativeTokenOut.spec.ts +276 -0
  25. package/src/chaincode/launchpad/configureLaunchpadFeeConfig.spec.ts +202 -0
  26. package/src/chaincode/launchpad/createSale.spec.ts +259 -0
  27. package/src/chaincode/launchpad/fetchSaleDetails.spec.ts +141 -0
  28. package/src/chaincode/launchpad/finaliseSale.ts +1 -1
  29. package/src/chaincode/launchpad/finalizeTokenAllocation.spec.ts +126 -0
  30. package/src/chaincode/launchpad/sellExactToken.spec.ts +284 -0
  31. package/src/chaincode/launchpad/sellWithNative.spec.ts +329 -0
  32. package/src/chaincode/launchpad/sellWithNative.ts +22 -5
  33. package/src/chaincode/test/launchpadgala.ts +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gala-chain/launchpad",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "description": "GalaChain Launchpad Chaincode",
5
5
  "main": "lib/src/index.js",
6
6
  "types": "lib/src/index.d.ts",
@@ -25,7 +25,7 @@
25
25
  "dependencies": {
26
26
  "@gala-chain/api": "~2.3.4",
27
27
  "@gala-chain/chaincode": "~2.3.4",
28
- "@gala-chain/dex": "1.0.4",
28
+ "@gala-chain/dex": "1.0.19",
29
29
  "@grpc/grpc-js": "1.10.10",
30
30
  "decimal.js": "^10.5.0",
31
31
  "dotenv": "^16.0.1",
@@ -23,7 +23,8 @@ import {
23
23
  asValidUserAlias,
24
24
  randomUniqueKey
25
25
  } from "@gala-chain/api";
26
- import { currency, fixture, users } from "@gala-chain/test";
26
+ import { InvalidDecimalError } from "@gala-chain/chaincode";
27
+ import { currency, fixture, transactionError, transactionSuccess, users } from "@gala-chain/test";
27
28
  import BigNumber from "bignumber.js";
28
29
  import { plainToInstance } from "class-transformer";
29
30
 
@@ -31,7 +32,8 @@ import {
31
32
  ExactTokenQuantityDto,
32
33
  LaunchpadFeeConfig,
33
34
  LaunchpadSale,
34
- NativeTokenQuantityDto
35
+ NativeTokenQuantityDto,
36
+ TradeResDto
35
37
  } from "../../api/types";
36
38
  import { LaunchpadContract } from "../LaunchpadContract";
37
39
  import launchpadgala from "../test/launchpadgala";
@@ -59,7 +61,7 @@ describe("buyWithNative", () => {
59
61
 
60
62
  launchpadGalaClass = plainToInstance(TokenClass, {
61
63
  ...launchpadgala.tokenClassPlain(),
62
- decimals: 18
64
+ decimals: 8
63
65
  });
64
66
 
65
67
  vaultAddress = asValidUserAlias(`service|${launchpadGalaClassKey.toStringKey()}$launchpad`);
@@ -67,7 +69,7 @@ describe("buyWithNative", () => {
67
69
  // Initialize sale with manual values
68
70
  sale = new LaunchpadSale(
69
71
  vaultAddress,
70
- launchpadGalaInstance.instanceKeyObj(),
72
+ currencyInstance.instanceKeyObj(),
71
73
  undefined,
72
74
  users.testUser1.identityKey
73
75
  );
@@ -97,8 +99,8 @@ describe("buyWithNative", () => {
97
99
  });
98
100
  });
99
101
 
100
- test("User should be able to buy exact tokens, without fee configured", async () => {
101
- //Given
102
+ it("should properly round buy qty to native token decimals limit when bonding curve produces greater fractional precision", async () => {
103
+ // Given
102
104
  const { ctx, contract } = fixture(LaunchpadContract)
103
105
  .registeredUsers(users.testUser1)
104
106
  .savedState(
@@ -113,32 +115,97 @@ describe("buyWithNative", () => {
113
115
  userCurrencyBalance
114
116
  );
115
117
 
118
+ // Choose a token quantity that will produce fractional native tokens from bonding curve
119
+ // The bonding curve calculation will produce a value like 0.00825575
116
120
  const dto = new ExactTokenQuantityDto(vaultAddress, new BigNumber("500"));
121
+ dto.uniqueKey = randomUniqueKey();
122
+ dto.sign(users.testUser1.privateKey);
123
+
124
+ // When
125
+ const buyTokenRes = await contract.BuyExactToken(ctx, dto);
126
+
127
+ // Then
128
+ expect(buyTokenRes).toEqual(transactionSuccess());
129
+ });
130
+
131
+ it("should reject buy when meme token has 0 decimals and input dto contains fractional quantity", async () => {
132
+ // Given - Setup token with 0 decimals to force decimal precision error
133
+ const zeroDecimalCurrencyClass = plainToInstance(TokenClass, {
134
+ ...currency.tokenClassPlain(),
135
+ decimals: 0 // Integer-only token
136
+ });
137
+
138
+ const { ctx, contract } = fixture(LaunchpadContract)
139
+ .registeredUsers(users.testUser1)
140
+ .savedState(
141
+ zeroDecimalCurrencyClass,
142
+ currencyInstance,
143
+ launchpadGalaClass,
144
+ launchpadGalaInstance,
145
+ sale,
146
+ salelaunchpadGalaBalance,
147
+ saleCurrencyBalance,
148
+ userlaunchpadGalaBalance,
149
+ userCurrencyBalance
150
+ );
117
151
 
152
+ const dto = new ExactTokenQuantityDto(vaultAddress, new BigNumber("500.555"));
118
153
  dto.uniqueKey = randomUniqueKey();
119
154
  dto.sign(users.testUser1.privateKey);
120
155
 
121
- //When
156
+ // When
122
157
  const buyTokenRes = await contract.BuyExactToken(ctx, dto);
123
158
 
124
- //Then
125
- expect(buyTokenRes.Data).toMatchObject({
159
+ // Then
160
+ expect(buyTokenRes).toEqual(
161
+ transactionError(new InvalidDecimalError(dto.tokenQuantity, zeroDecimalCurrencyClass.decimals))
162
+ );
163
+ });
164
+
165
+ test("User should be able to buy exact tokens, without fee configured", async () => {
166
+ // Given
167
+ const { ctx, contract } = fixture(LaunchpadContract)
168
+ .registeredUsers(users.testUser1)
169
+ .savedState(
170
+ currencyClass,
171
+ currencyInstance,
172
+ launchpadGalaClass,
173
+ launchpadGalaInstance,
174
+ sale,
175
+ salelaunchpadGalaBalance,
176
+ saleCurrencyBalance,
177
+ userlaunchpadGalaBalance,
178
+ userCurrencyBalance
179
+ );
180
+
181
+ const dto = new ExactTokenQuantityDto(vaultAddress, new BigNumber("500"));
182
+
183
+ dto.uniqueKey = randomUniqueKey();
184
+ dto.sign(users.testUser1.privateKey);
185
+
186
+ const expectedResponse = plainToInstance(TradeResDto, {
126
187
  inputQuantity: "0.00825575",
127
188
  totalFees: "0.00000000",
189
+ totalTokenSold: "500",
128
190
  outputQuantity: "500",
129
191
  tokenName: "AUTOMATEDTESTCOIN",
130
192
  tradeType: "Buy",
193
+ uniqueKey: dto.uniqueKey,
131
194
  vaultAddress: "service|GALA$Unit$none$none$launchpad",
132
195
  userAddress: "client|testUser1",
133
196
  isFinalized: false,
134
197
  functionName: "BuyExactToken"
135
198
  });
136
- expect(buyTokenRes.Data?.inputQuantity).toEqual("0.00825575");
137
- expect(buyTokenRes.Data?.outputQuantity).toEqual("500");
199
+
200
+ // When
201
+ const buyTokenRes = await contract.BuyExactToken(ctx, dto);
202
+
203
+ // Then
204
+ expect(buyTokenRes).toEqual(transactionSuccess(expectedResponse));
138
205
  });
139
206
 
140
207
  test("User should be able to buy tokens , fee configured check", async () => {
141
- //Given
208
+ // Given
142
209
  const launchpadConfig = new LaunchpadFeeConfig(users.testUser2.identityKey, Number("0.32"), [
143
210
  users.testUser2.identityKey
144
211
  ]);
@@ -163,23 +230,76 @@ describe("buyWithNative", () => {
163
230
  dto.uniqueKey = randomUniqueKey();
164
231
  dto.sign(users.testUser1.privateKey);
165
232
 
166
- //When
167
- const buyTokenRes = await contract.BuyExactToken(ctx, dto);
168
-
169
- //Then
170
- expect(buyTokenRes.Data).toMatchObject({
233
+ const expectedResponse = plainToInstance(TradeResDto, {
171
234
  inputQuantity: "0.08991559",
172
235
  totalFees: "0.02877299",
236
+ totalTokenSold: "5430",
173
237
  outputQuantity: "5430",
174
238
  tokenName: "AUTOMATEDTESTCOIN",
175
239
  tradeType: "Buy",
176
240
  vaultAddress: "service|GALA$Unit$none$none$launchpad",
177
241
  userAddress: "client|testUser1",
178
242
  isFinalized: false,
179
- functionName: "BuyExactToken"
243
+ functionName: "BuyExactToken",
244
+ uniqueKey: dto.uniqueKey
245
+ });
246
+
247
+ // When
248
+ const buyTokenRes = await contract.BuyExactToken(ctx, dto);
249
+
250
+ // Then
251
+ expect(buyTokenRes).toEqual(transactionSuccess(expectedResponse));
252
+ });
253
+
254
+ test("User should be able to finalize sale , if fee is configured", async () => {
255
+ //Given
256
+ salelaunchpadGalaBalance = plainToInstance(TokenBalance, {
257
+ ...launchpadgala.tokenBalance(),
258
+ owner: vaultAddress,
259
+ quantity: new BigNumber("0")
180
260
  });
181
- expect(buyTokenRes.Data?.inputQuantity).toEqual("0.08991559");
182
- expect(buyTokenRes.Data?.outputQuantity).toEqual("5430");
261
+
262
+ saleCurrencyBalance = plainToInstance(TokenBalance, {
263
+ ...currency.tokenBalance(),
264
+ owner: vaultAddress,
265
+ quantity: new BigNumber("2e+7")
266
+ });
267
+ userlaunchpadGalaBalance = plainToInstance(TokenBalance, {
268
+ ...launchpadgala.tokenBalance(),
269
+ owner: users.testUser1.identityKey,
270
+ quantity: new BigNumber("100000000")
271
+ });
272
+
273
+ const launchpadConfig = new LaunchpadFeeConfig(users.testUser2.identityKey, Number("0.001"), [
274
+ users.testUser2.identityKey
275
+ ]);
276
+
277
+ const { ctx, contract } = fixture(LaunchpadContract)
278
+ .registeredUsers(users.testUser1)
279
+ .savedState(
280
+ currencyClass,
281
+ currencyInstance,
282
+ launchpadGalaClass,
283
+ launchpadGalaInstance,
284
+ sale,
285
+ salelaunchpadGalaBalance,
286
+ saleCurrencyBalance,
287
+ userlaunchpadGalaBalance,
288
+ userCurrencyBalance,
289
+ launchpadConfig
290
+ );
291
+
292
+ const dto = new ExactTokenQuantityDto(vaultAddress, new BigNumber("10000000"));
293
+
294
+ dto.uniqueKey = randomUniqueKey();
295
+ dto.sign(users.testUser1.privateKey);
296
+
297
+ //When
298
+ const buyTokenRes = await contract.BuyExactToken(ctx, dto);
299
+
300
+ //Then
301
+ expect(buyTokenRes).toEqual(transactionSuccess());
302
+ expect(buyTokenRes.Data?.isFinalized).toBe(true);
183
303
  });
184
304
 
185
305
  it("should throw error if user has insufficient funds incuding the transaction fees", async () => {
@@ -89,7 +89,7 @@ export async function buyExactToken(
89
89
  // Transfer transaction fees
90
90
  const launchpadFeeAddressConfiguration = await fetchLaunchpadFeeAddress(ctx);
91
91
  if (launchpadFeeAddressConfiguration && transactionFees) {
92
- const totalRequired = new BigNumber(buyTokenDTO.tokenQuantity).plus(transactionFees);
92
+ const totalRequired = nativeTokensToBuy.plus(new BigNumber(transactionFees));
93
93
 
94
94
  const buyerBalance = await fetchOrCreateBalance(ctx, ctx.callingUser, sale.nativeToken);
95
95
  if (buyerBalance.getQuantityTotal().lt(totalRequired)) {
@@ -23,11 +23,12 @@ import {
23
23
  asValidUserAlias,
24
24
  randomUniqueKey
25
25
  } from "@gala-chain/api";
26
- import { currency, fixture, users } from "@gala-chain/test";
26
+ import { InvalidDecimalError } from "@gala-chain/chaincode";
27
+ import { currency, fixture, transactionError, transactionSuccess, users } from "@gala-chain/test";
27
28
  import BigNumber from "bignumber.js";
28
29
  import { plainToInstance } from "class-transformer";
29
30
 
30
- import { LaunchpadFeeConfig, LaunchpadSale, NativeTokenQuantityDto } from "../../api/types";
31
+ import { LaunchpadFeeConfig, LaunchpadSale, NativeTokenQuantityDto, TradeResDto } from "../../api/types";
31
32
  import { LaunchpadContract } from "../LaunchpadContract";
32
33
  import launchpadgala from "../test/launchpadgala";
33
34
 
@@ -63,7 +64,7 @@ describe("buyWithNative", () => {
63
64
  // Initialize sale with manual values
64
65
  sale = new LaunchpadSale(
65
66
  vaultAddress,
66
- launchpadGalaInstance.instanceKeyObj(),
67
+ currencyInstance.instanceKeyObj(),
67
68
  undefined,
68
69
  users.testUser1.identityKey
69
70
  );
@@ -93,7 +94,79 @@ describe("buyWithNative", () => {
93
94
  });
94
95
  });
95
96
 
96
- test("User should be able to buy tokens with providing native gala , without fee configured", async () => {
97
+ it("should round buy when meme token has 0 decimals and bonding curve produces fractional quantity", async () => {
98
+ // Given
99
+ const zeroDecimalCurrencyClass = plainToInstance(TokenClass, {
100
+ ...currency.tokenClassPlain(),
101
+ decimals: 0 // Integer-only meme token
102
+ });
103
+
104
+ const { ctx, contract } = fixture(LaunchpadContract)
105
+ .registeredUsers(users.testUser1)
106
+ .savedState(
107
+ currencyInstance,
108
+ zeroDecimalCurrencyClass,
109
+ launchpadGalaInstance,
110
+ launchpadGalaClass,
111
+ sale,
112
+ salelaunchpadGalaBalance,
113
+ saleCurrencyBalance,
114
+ userlaunchpadGalaBalance,
115
+ userCurrencyBalance
116
+ );
117
+
118
+ // Use a native token amount that will produce fractional meme tokens from bonding curve
119
+ const dto = new NativeTokenQuantityDto(vaultAddress, new BigNumber("0.01"));
120
+ dto.uniqueKey = randomUniqueKey();
121
+ dto.sign(users.testUser1.privateKey);
122
+
123
+ // When
124
+ const buyTokenRes = await contract.BuyWithNative(ctx, dto);
125
+
126
+ // Then - Expect code to round transferToken qty to decimal limit specified by TokenClass
127
+ expect(buyTokenRes).not.toEqual(
128
+ transactionError(
129
+ new InvalidDecimalError(new BigNumber("605.60177406237267161"), zeroDecimalCurrencyClass.decimals)
130
+ )
131
+ );
132
+ });
133
+
134
+ it("should reject buy when input dto has higher fractional precision than GALA TokenClass", async () => {
135
+ // Given
136
+ const zeroDecimalLaunchpadClass = plainToInstance(TokenClass, {
137
+ ...launchpadgala.tokenClassPlain(),
138
+ decimals: 0 // Integer-only meme token
139
+ });
140
+
141
+ const { ctx, contract } = fixture(LaunchpadContract)
142
+ .registeredUsers(users.testUser1)
143
+ .savedState(
144
+ currencyInstance,
145
+ currencyClass,
146
+ zeroDecimalLaunchpadClass,
147
+ launchpadGalaInstance,
148
+ sale,
149
+ salelaunchpadGalaBalance,
150
+ saleCurrencyBalance,
151
+ userlaunchpadGalaBalance,
152
+ userCurrencyBalance
153
+ );
154
+
155
+ // Use a native token amount that will produce fractional meme tokens from bonding curve
156
+ const dto = new NativeTokenQuantityDto(vaultAddress, new BigNumber("0.01"));
157
+ dto.uniqueKey = randomUniqueKey();
158
+ dto.sign(users.testUser1.privateKey);
159
+
160
+ // When
161
+ const buyTokenRes = await contract.BuyWithNative(ctx, dto);
162
+
163
+ // Then - Expect error due to decimal precision mismatch
164
+ expect(buyTokenRes).toEqual(
165
+ transactionError(new InvalidDecimalError(dto.nativeTokenQuantity, zeroDecimalLaunchpadClass.decimals))
166
+ );
167
+ });
168
+
169
+ test("User buys tokens by providing native gala, without fee needing to be configured", async () => {
97
170
  //Given
98
171
  const { ctx, contract } = fixture(LaunchpadContract)
99
172
  .registeredUsers(users.testUser1)
@@ -114,27 +187,28 @@ describe("buyWithNative", () => {
114
187
  dto.uniqueKey = randomUniqueKey();
115
188
  dto.sign(users.testUser1.privateKey);
116
189
 
117
- //When
118
- const buyTokenRes = await contract.BuyWithNative(ctx, dto);
119
-
120
- //Then
121
- expect(buyTokenRes.Data).toMatchObject({
190
+ const expectedResponse = plainToInstance(TradeResDto, {
122
191
  inputQuantity: "150",
123
192
  totalFees: "0.00000000",
124
- outputQuantity: "2101667.8890651635002",
193
+ totalTokenSold: "2101667.8890651635",
194
+ outputQuantity: "2101667.8890651635",
125
195
  tokenName: "AUTOMATEDTESTCOIN",
126
196
  tradeType: "Buy",
127
197
  vaultAddress: "service|GALA$Unit$none$none$launchpad",
128
198
  userAddress: "client|testUser1",
129
199
  isFinalized: false,
130
- functionName: "BuyWithNative"
200
+ functionName: "BuyWithNative",
201
+ uniqueKey: dto.uniqueKey
131
202
  });
132
203
 
133
- expect(buyTokenRes.Data?.inputQuantity).toEqual("150");
134
- expect(buyTokenRes.Data?.outputQuantity).toEqual("2101667.8890651635002");
204
+ //When
205
+ const buyTokenRes = await contract.BuyWithNative(ctx, dto);
206
+
207
+ //Then
208
+ expect(buyTokenRes).toEqual(transactionSuccess(expectedResponse));
135
209
  });
136
210
 
137
- test("User should be able to buy tokens , fee configured check", async () => {
211
+ test("User buys tokens, configured fee is checked", async () => {
138
212
  //Given
139
213
  const launchpadConfig = new LaunchpadFeeConfig(users.testUser2.identityKey, Number("0.32"), [
140
214
  users.testUser2.identityKey
@@ -159,25 +233,25 @@ describe("buyWithNative", () => {
159
233
  dto.uniqueKey = randomUniqueKey();
160
234
  dto.sign(users.testUser1.privateKey);
161
235
 
162
- //When
163
- const buyTokenRes = await contract.BuyWithNative(ctx, dto);
164
-
165
- //Then
166
- expect(buyTokenRes.Data).toMatchObject({
236
+ const expectedResponse = plainToInstance(TradeResDto, {
167
237
  inputQuantity: "1000",
168
238
  totalFees: "320.00000000",
169
- outputQuantity: "3663321.3628130557168",
239
+ totalTokenSold: "3663321.3628130557",
240
+ outputQuantity: "3663321.3628130557",
170
241
  tokenName: "AUTOMATEDTESTCOIN",
171
242
  tradeType: "Buy",
172
243
  vaultAddress: "service|GALA$Unit$none$none$launchpad",
173
244
  userAddress: "client|testUser1",
174
245
  isFinalized: false,
175
- functionName: "BuyWithNative"
246
+ functionName: "BuyWithNative",
247
+ uniqueKey: dto.uniqueKey
176
248
  });
177
249
 
178
- expect(buyTokenRes.Data?.totalFees).toEqual("320.00000000");
179
- expect(buyTokenRes.Data?.inputQuantity).toEqual("1000");
180
- expect(buyTokenRes.Data?.outputQuantity).toEqual("3663321.3628130557168");
250
+ //When
251
+ const buyTokenRes = await contract.BuyWithNative(ctx, dto);
252
+
253
+ //Then
254
+ expect(buyTokenRes).toEqual(transactionSuccess(expectedResponse));
181
255
  });
182
256
 
183
257
  it("should throw error if user has insufficient funds incuding the transaction fees", async () => {
@@ -211,7 +285,7 @@ describe("buyWithNative", () => {
211
285
 
212
286
  //Then
213
287
  expect(buyTokenRes).toEqual(
214
- GalaChainResponse.Error(
288
+ transactionError(
215
289
  new ValidationFailedError(
216
290
  "Insufficient balance: Total amount required including fee is 2166101.31430784"
217
291
  )
@@ -12,11 +12,12 @@
12
12
  * See the License for the specific language governing permissions and
13
13
  * limitations under the License.
14
14
  */
15
- import { ValidationFailedError } from "@gala-chain/api";
15
+ import { TokenClass, ValidationFailedError } from "@gala-chain/api";
16
16
  import {
17
17
  GalaChainContext,
18
18
  fetchOrCreateBalance,
19
19
  fetchTokenClass,
20
+ getObjectByKey,
20
21
  putChainObject,
21
22
  transferToken
22
23
  } from "@gala-chain/chaincode";
@@ -65,11 +66,24 @@ export async function buyWithNative(
65
66
  const nativeToken = sale.fetchNativeTokenInstanceKey();
66
67
  const memeToken = sale.fetchSellingTokenInstanceKey();
67
68
 
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
+
68
82
  // If vault has fewer tokens than what user wants to buy, cap the purchase
69
83
  if (tokensLeftInVault.comparedTo(tokensToBuy) <= 0) {
70
- tokensToBuy = tokensLeftInVault;
71
- const nativeTokensrequiredToBuyDto = new ExactTokenQuantityDto(buyTokenDTO.vaultAddress, tokensToBuy);
72
- const callNativeTokenInResult = await callNativeTokenIn(ctx, nativeTokensrequiredToBuyDto);
84
+ tokensToBuy = tokensLeftInVault.decimalPlaces(memeTokenClass.decimals);
85
+ const nativeTokensRequiredToBuyDto = new ExactTokenQuantityDto(buyTokenDTO.vaultAddress, tokensToBuy);
86
+ const callNativeTokenInResult = await callNativeTokenIn(ctx, nativeTokensRequiredToBuyDto);
73
87
  transactionFees = callMemeTokenOutResult.extraFees.transactionFees;
74
88
  buyTokenDTO.nativeTokenQuantity = new BigNumber(callNativeTokenInResult.calculatedQuantity);
75
89
  isSaleFinalized = true;
@@ -80,8 +94,9 @@ export async function buyWithNative(
80
94
  buyTokenDTO.nativeTokenQuantity
81
95
  .plus(new BigNumber(sale.nativeTokenQuantity))
82
96
  .gte(new BigNumber(LaunchpadSale.MARKET_CAP))
83
- )
97
+ ) {
84
98
  isSaleFinalized = true;
99
+ }
85
100
 
86
101
  // Check for slippage condition
87
102
  if (buyTokenDTO.expectedToken && buyTokenDTO.expectedToken.comparedTo(tokensToBuy) > 0) {