@gala-chain/launchpad 1.0.9 → 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 (27) hide show
  1. package/CLAUDE.md +279 -0
  2. package/lib/package.json +2 -2
  3. package/lib/src/chaincode/launchpad/buyWithNative.d.ts.map +1 -1
  4. package/lib/src/chaincode/launchpad/buyWithNative.js +11 -4
  5. package/lib/src/chaincode/launchpad/buyWithNative.js.map +1 -1
  6. package/lib/src/chaincode/launchpad/sellWithNative.d.ts.map +1 -1
  7. package/lib/src/chaincode/launchpad/sellWithNative.js +9 -1
  8. package/lib/src/chaincode/launchpad/sellWithNative.js.map +1 -1
  9. package/lib/src/chaincode/test/launchpadgala.js +1 -1
  10. package/lib/src/chaincode/test/launchpadgala.js.map +1 -1
  11. package/lib/tsconfig.tsbuildinfo +1 -1
  12. package/package.json +2 -2
  13. package/src/chaincode/launchpad/buyExactToken.spec.ts +90 -20
  14. package/src/chaincode/launchpad/buyWithNative.spec.ts +99 -25
  15. package/src/chaincode/launchpad/buyWithNative.ts +20 -5
  16. package/src/chaincode/launchpad/callMemeTokenIn.spec.ts +244 -0
  17. package/src/chaincode/launchpad/callMemeTokenOut.spec.ts +1 -1
  18. package/src/chaincode/launchpad/callNativeTokenIn.spec.ts +269 -0
  19. package/src/chaincode/launchpad/callNativeTokenOut.spec.ts +276 -0
  20. package/src/chaincode/launchpad/configureLaunchpadFeeConfig.spec.ts +202 -0
  21. package/src/chaincode/launchpad/createSale.spec.ts +259 -0
  22. package/src/chaincode/launchpad/fetchSaleDetails.spec.ts +141 -0
  23. package/src/chaincode/launchpad/finalizeTokenAllocation.spec.ts +126 -0
  24. package/src/chaincode/launchpad/sellExactToken.spec.ts +284 -0
  25. package/src/chaincode/launchpad/sellWithNative.spec.ts +329 -0
  26. package/src/chaincode/launchpad/sellWithNative.ts +22 -5
  27. 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.9",
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`);
@@ -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
+ });
117
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
+ );
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,26 +230,28 @@ 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
180
245
  });
181
- expect(buyTokenRes.Data?.inputQuantity).toEqual("0.08991559");
182
- expect(buyTokenRes.Data?.outputQuantity).toEqual("5430");
246
+
247
+ // When
248
+ const buyTokenRes = await contract.BuyExactToken(ctx, dto);
249
+
250
+ // Then
251
+ expect(buyTokenRes).toEqual(transactionSuccess(expectedResponse));
183
252
  });
184
253
 
185
- test("User should be able to finalise sale , if fee is configured", async () => {
254
+ test("User should be able to finalize sale , if fee is configured", async () => {
186
255
  //Given
187
256
  salelaunchpadGalaBalance = plainToInstance(TokenBalance, {
188
257
  ...launchpadgala.tokenBalance(),
@@ -229,6 +298,7 @@ describe("buyWithNative", () => {
229
298
  const buyTokenRes = await contract.BuyExactToken(ctx, dto);
230
299
 
231
300
  //Then
301
+ expect(buyTokenRes).toEqual(transactionSuccess());
232
302
  expect(buyTokenRes.Data?.isFinalized).toBe(true);
233
303
  });
234
304
 
@@ -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) {
@@ -0,0 +1,244 @@
1
+ /*
2
+ * Copyright (c) Gala Games Inc. All rights reserved.
3
+ * Licensed under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License.
5
+ * You may obtain a copy of the License at
6
+ *
7
+ * http://www.apache.org/licenses/LICENSE-2.0
8
+ *
9
+ * Unless required by applicable law or agreed to in writing, software
10
+ * distributed under the License is distributed on an "AS IS" BASIS,
11
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ * See the License for the specific language governing permissions and
13
+ * limitations under the License.
14
+ */
15
+ import {
16
+ TokenBalance,
17
+ TokenClass,
18
+ TokenClassKey,
19
+ TokenInstance,
20
+ UserAlias,
21
+ asValidUserAlias,
22
+ randomUniqueKey
23
+ } from "@gala-chain/api";
24
+ import { currency, fixture, users } from "@gala-chain/test";
25
+ import BigNumber from "bignumber.js";
26
+ import { plainToInstance } from "class-transformer";
27
+
28
+ import { LaunchpadSale, NativeTokenQuantityDto } from "../../api/types";
29
+ import { LaunchpadContract } from "../LaunchpadContract";
30
+ import launchpadgala from "../test/launchpadgala";
31
+
32
+ describe("callMemeTokenIn", () => {
33
+ let currencyClass: TokenClass;
34
+ let currencyInstance: TokenInstance;
35
+ let launchpadGalaClass: TokenClass;
36
+ let launchpadGalaInstance: TokenInstance;
37
+ let launchpadGalaClassKey: TokenClassKey;
38
+ let vaultAddress: UserAlias;
39
+ let sale: LaunchpadSale;
40
+ let salelaunchpadGalaBalance: TokenBalance;
41
+ let saleCurrencyBalance: TokenBalance;
42
+ let userlaunchpadGalaBalance: TokenBalance;
43
+ let userCurrencyBalance: TokenBalance;
44
+
45
+ beforeEach(() => {
46
+ currencyClass = currency.tokenClass();
47
+ currencyInstance = currency.tokenInstance();
48
+ launchpadGalaClass = launchpadgala.tokenClass();
49
+ launchpadGalaInstance = launchpadgala.tokenInstance();
50
+ launchpadGalaClassKey = launchpadgala.tokenClassKey();
51
+
52
+ vaultAddress = asValidUserAlias(`service|${launchpadGalaClassKey.toStringKey()}$launchpad`);
53
+
54
+ // Initialize sale with manual values
55
+ sale = new LaunchpadSale(
56
+ vaultAddress,
57
+ currencyInstance.instanceKeyObj(),
58
+ undefined,
59
+ users.testUser1.identityKey
60
+ );
61
+
62
+ // Create sale balances - sale needs tokens to pay out
63
+ salelaunchpadGalaBalance = plainToInstance(TokenBalance, {
64
+ ...launchpadgala.tokenBalance(),
65
+ owner: vaultAddress,
66
+ quantity: new BigNumber("97.238975330345368866")
67
+ });
68
+ saleCurrencyBalance = plainToInstance(TokenBalance, {
69
+ ...currency.tokenBalance(),
70
+ owner: vaultAddress,
71
+ quantity: new BigNumber("188809.790718")
72
+ });
73
+
74
+ // Create user balances - user needs tokens to swap
75
+ userlaunchpadGalaBalance = plainToInstance(TokenBalance, {
76
+ ...launchpadgala.tokenBalance(),
77
+ owner: users.testUser1.identityKey,
78
+ quantity: new BigNumber("10000") // User has 10k launchpadGala tokens
79
+ });
80
+ userCurrencyBalance = plainToInstance(TokenBalance, {
81
+ ...currency.tokenBalance(),
82
+ owner: users.testUser1.identityKey,
83
+ quantity: new BigNumber("10000") // User has 10k CURRENCY tokens
84
+ });
85
+ });
86
+
87
+ it("should calculate meme token required amount for selling", async () => {
88
+ // Given
89
+ sale.buyToken(new BigNumber("605.60177406237267161"), new BigNumber(0.01));
90
+ const { ctx, contract } = fixture(LaunchpadContract)
91
+ .registeredUsers(users.testUser1)
92
+ .savedState(
93
+ currencyClass,
94
+ currencyInstance,
95
+ launchpadGalaClass,
96
+ launchpadGalaInstance,
97
+ sale,
98
+ salelaunchpadGalaBalance,
99
+ saleCurrencyBalance,
100
+ userlaunchpadGalaBalance,
101
+ userCurrencyBalance
102
+ );
103
+
104
+ const callMemeTokenInDto = new NativeTokenQuantityDto(vaultAddress, new BigNumber(1));
105
+ callMemeTokenInDto.uniqueKey = randomUniqueKey();
106
+
107
+ const signedDto = callMemeTokenInDto.signed(users.testUser1.privateKey);
108
+
109
+ // When
110
+ const response = await contract.CallMemeTokenIn(ctx, signedDto);
111
+
112
+ // Then
113
+ expect(response.Status).toBe(1);
114
+ expect(response.Data).toHaveProperty("calculatedQuantity");
115
+ expect(response.Data).toHaveProperty("extraFees");
116
+ expect(response.Data?.extraFees).toHaveProperty("reverseBondingCurve");
117
+ expect(response.Data?.extraFees).toHaveProperty("transactionFees");
118
+ });
119
+
120
+ it("should calculate tokens required for native token amount of 10", async () => {
121
+ // Given
122
+ sale.buyToken(new BigNumber("605.60177406237267161"), new BigNumber(0.01));
123
+ const { ctx, contract } = fixture(LaunchpadContract)
124
+ .registeredUsers(users.testUser1)
125
+ .savedState(
126
+ currencyClass,
127
+ currencyInstance,
128
+ launchpadGalaClass,
129
+ launchpadGalaInstance,
130
+ sale,
131
+ salelaunchpadGalaBalance,
132
+ saleCurrencyBalance,
133
+ userlaunchpadGalaBalance,
134
+ userCurrencyBalance
135
+ );
136
+
137
+ const callMemeTokenInDto = new NativeTokenQuantityDto(vaultAddress, new BigNumber(10));
138
+ callMemeTokenInDto.uniqueKey = randomUniqueKey();
139
+
140
+ const signedDto = callMemeTokenInDto.signed(users.testUser1.privateKey);
141
+
142
+ // When
143
+ const response = await contract.CallMemeTokenIn(ctx, signedDto);
144
+
145
+ // Then
146
+ expect(response.Status).toBe(1);
147
+ expect(response.Data?.calculatedQuantity).toBeDefined();
148
+ expect(new BigNumber(response.Data?.calculatedQuantity || "0").isPositive()).toBe(true);
149
+ });
150
+
151
+ it("should handle edge case with minimal native token amount", async () => {
152
+ // Given
153
+ sale.buyToken(new BigNumber("100"), new BigNumber(0.01));
154
+ const { ctx, contract } = fixture(LaunchpadContract)
155
+ .registeredUsers(users.testUser1)
156
+ .savedState(
157
+ currencyClass,
158
+ currencyInstance,
159
+ launchpadGalaClass,
160
+ launchpadGalaInstance,
161
+ sale,
162
+ salelaunchpadGalaBalance,
163
+ saleCurrencyBalance,
164
+ userlaunchpadGalaBalance,
165
+ userCurrencyBalance
166
+ );
167
+
168
+ const callMemeTokenInDto = new NativeTokenQuantityDto(vaultAddress, new BigNumber("0.001"));
169
+ callMemeTokenInDto.uniqueKey = randomUniqueKey();
170
+
171
+ const signedDto = callMemeTokenInDto.signed(users.testUser1.privateKey);
172
+
173
+ // When
174
+ const response = await contract.CallMemeTokenIn(ctx, signedDto);
175
+
176
+ // Then
177
+ expect(response.Status).toBe(1);
178
+ expect(response.Data?.calculatedQuantity).toBeDefined();
179
+ });
180
+
181
+ it("should handle calculation when native token amount exceeds vault balance", async () => {
182
+ // Given
183
+ sale.buyToken(new BigNumber("100"), new BigNumber(0.01));
184
+ const { ctx, contract } = fixture(LaunchpadContract)
185
+ .registeredUsers(users.testUser1)
186
+ .savedState(
187
+ currencyClass,
188
+ currencyInstance,
189
+ launchpadGalaClass,
190
+ launchpadGalaInstance,
191
+ sale,
192
+ salelaunchpadGalaBalance,
193
+ saleCurrencyBalance,
194
+ userlaunchpadGalaBalance,
195
+ userCurrencyBalance
196
+ );
197
+
198
+ // Request more native tokens than available in vault
199
+ const callMemeTokenInDto = new NativeTokenQuantityDto(vaultAddress, new BigNumber("1000000"));
200
+ callMemeTokenInDto.uniqueKey = randomUniqueKey();
201
+
202
+ const signedDto = callMemeTokenInDto.signed(users.testUser1.privateKey);
203
+
204
+ // When
205
+ const response = await contract.CallMemeTokenIn(ctx, signedDto);
206
+
207
+ // Then
208
+ expect(response.Status).toBe(1);
209
+ expect(response.Data?.calculatedQuantity).toBeDefined();
210
+ });
211
+
212
+ it("should calculate correct fees for transaction", async () => {
213
+ // Given
214
+ sale.buyToken(new BigNumber("500"), new BigNumber(0.01));
215
+ const { ctx, contract } = fixture(LaunchpadContract)
216
+ .registeredUsers(users.testUser1)
217
+ .savedState(
218
+ currencyClass,
219
+ currencyInstance,
220
+ launchpadGalaClass,
221
+ launchpadGalaInstance,
222
+ sale,
223
+ salelaunchpadGalaBalance,
224
+ saleCurrencyBalance,
225
+ userlaunchpadGalaBalance,
226
+ userCurrencyBalance
227
+ );
228
+
229
+ const callMemeTokenInDto = new NativeTokenQuantityDto(vaultAddress, new BigNumber(5));
230
+ callMemeTokenInDto.uniqueKey = randomUniqueKey();
231
+
232
+ const signedDto = callMemeTokenInDto.signed(users.testUser1.privateKey);
233
+
234
+ // When
235
+ const response = await contract.CallMemeTokenIn(ctx, signedDto);
236
+
237
+ // Then
238
+ expect(response.Status).toBe(1);
239
+ expect(response.Data?.extraFees.reverseBondingCurve).toBeDefined();
240
+ expect(response.Data?.extraFees.transactionFees).toBeDefined();
241
+ expect(new BigNumber(response.Data?.extraFees.reverseBondingCurve || "0").isFinite()).toBe(true);
242
+ expect(new BigNumber(response.Data?.extraFees.transactionFees || "0").isFinite()).toBe(true);
243
+ });
244
+ });