@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
@@ -0,0 +1,284 @@
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 { InvalidDecimalError } from "@gala-chain/chaincode";
25
+ import { currency, fixture, transactionError, users } from "@gala-chain/test";
26
+ import BigNumber from "bignumber.js";
27
+ import { plainToInstance } from "class-transformer";
28
+
29
+ import { ExactTokenQuantityDto, LaunchpadSale } from "../../api/types";
30
+ import { LaunchpadContract } from "../LaunchpadContract";
31
+ import launchpadgala from "../test/launchpadgala";
32
+
33
+ describe("sellExactToken", () => {
34
+ let currencyClass: TokenClass;
35
+ let currencyInstance: TokenInstance;
36
+ let launchpadGalaClass: TokenClass;
37
+ let launchpadGalaInstance: TokenInstance;
38
+ let launchpadGalaClassKey: TokenClassKey;
39
+ let vaultAddress: UserAlias;
40
+ let sale: LaunchpadSale;
41
+ let salelaunchpadGalaBalance: TokenBalance;
42
+ let saleCurrencyBalance: TokenBalance;
43
+ let userlaunchpadGalaBalance: TokenBalance;
44
+ let userCurrencyBalance: TokenBalance;
45
+
46
+ beforeEach(() => {
47
+ currencyClass = currency.tokenClass();
48
+ currencyInstance = currency.tokenInstance();
49
+ launchpadGalaClass = launchpadgala.tokenClass();
50
+ launchpadGalaInstance = launchpadgala.tokenInstance();
51
+ launchpadGalaClassKey = launchpadgala.tokenClassKey();
52
+
53
+ vaultAddress = asValidUserAlias(`service|${launchpadGalaClassKey.toStringKey()}$launchpad`);
54
+
55
+ // Initialize sale with manual values
56
+ sale = new LaunchpadSale(
57
+ vaultAddress,
58
+ currencyInstance.instanceKeyObj(),
59
+ undefined,
60
+ users.testUser1.identityKey
61
+ );
62
+
63
+ // Create sale balances - sale needs tokens to pay out
64
+ salelaunchpadGalaBalance = plainToInstance(TokenBalance, {
65
+ ...launchpadgala.tokenBalance(),
66
+ owner: vaultAddress,
67
+ quantity: new BigNumber("1000000") // Large amount in vault
68
+ });
69
+ saleCurrencyBalance = plainToInstance(TokenBalance, {
70
+ ...currency.tokenBalance(),
71
+ owner: vaultAddress,
72
+ quantity: new BigNumber("1000000") // Large amount in vault
73
+ });
74
+
75
+ // Create user balances - user needs tokens to sell
76
+ userlaunchpadGalaBalance = plainToInstance(TokenBalance, {
77
+ ...launchpadgala.tokenBalance(),
78
+ owner: users.testUser1.identityKey,
79
+ quantity: new BigNumber("10000") // User has tokens to sell
80
+ });
81
+ userCurrencyBalance = plainToInstance(TokenBalance, {
82
+ ...currency.tokenBalance(),
83
+ owner: users.testUser1.identityKey,
84
+ quantity: new BigNumber("1000") // User has some CURRENCY tokens
85
+ });
86
+ });
87
+
88
+ it("should sell exact token amount successfully", async () => {
89
+ // Given
90
+ sale.buyToken(new BigNumber("1000"), new BigNumber("50")); // Users bought tokens, sale now has GALA
91
+ const { ctx, contract } = fixture(LaunchpadContract)
92
+ .registeredUsers(users.testUser1)
93
+ .savedState(
94
+ currencyClass,
95
+ currencyInstance,
96
+ launchpadGalaClass,
97
+ launchpadGalaInstance,
98
+ sale,
99
+ salelaunchpadGalaBalance,
100
+ saleCurrencyBalance,
101
+ userlaunchpadGalaBalance,
102
+ userCurrencyBalance
103
+ );
104
+
105
+ const sellDto = new ExactTokenQuantityDto(vaultAddress, new BigNumber("100"));
106
+ sellDto.uniqueKey = randomUniqueKey();
107
+ const signedDto = sellDto.signed(users.testUser1.privateKey);
108
+
109
+ // When
110
+ const response = await contract.SellExactToken(ctx, signedDto);
111
+
112
+ // Then
113
+ expect(response.Status).toBe(1);
114
+ expect(response.Data).toHaveProperty("outputQuantity");
115
+ expect(response.Data).toHaveProperty("inputQuantity", "100");
116
+ expect(response.Data).toHaveProperty("isFinalized");
117
+ });
118
+
119
+ it("should reject sell when native token has 0 decimals and bonding curve produces fractional quantity", async () => {
120
+ // Given
121
+ const zeroDecimalNativeClass = plainToInstance(TokenClass, {
122
+ ...launchpadgala.tokenClassPlain(),
123
+ decimals: 0 // Integer-only native token
124
+ });
125
+
126
+ // Simulate prior buys to establish sale state
127
+ sale.buyToken(new BigNumber("5000"), new BigNumber("100"));
128
+
129
+ const { ctx, contract } = fixture(LaunchpadContract)
130
+ .registeredUsers(users.testUser1)
131
+ .savedState(
132
+ currencyClass,
133
+ currencyInstance,
134
+ zeroDecimalNativeClass,
135
+ launchpadGalaInstance,
136
+ sale,
137
+ salelaunchpadGalaBalance,
138
+ saleCurrencyBalance,
139
+ userlaunchpadGalaBalance,
140
+ userCurrencyBalance
141
+ );
142
+
143
+ // Choose a token quantity that will produce fractional native tokens from bonding curve
144
+ const sellDto = new ExactTokenQuantityDto(vaultAddress, new BigNumber("100"));
145
+ sellDto.uniqueKey = randomUniqueKey();
146
+ const signedDto = sellDto.signed(users.testUser1.privateKey);
147
+
148
+ // When
149
+ const response = await contract.SellExactToken(ctx, signedDto);
150
+
151
+ // Then - Expect error due to decimal precision mismatch
152
+ expect(response).toEqual(
153
+ transactionError(new InvalidDecimalError(new BigNumber("0.00166022"), zeroDecimalNativeClass.decimals))
154
+ );
155
+ });
156
+
157
+ it("should reject sell when meme token has 0 decimals and input dto contains greater fractional precision", async () => {
158
+ // Given
159
+ const zeroDecimalMemeClass = plainToInstance(TokenClass, {
160
+ ...currency.tokenClassPlain(),
161
+ decimals: 0 // Integer-only native token
162
+ });
163
+
164
+ // Simulate prior buys to establish sale state
165
+ sale.buyToken(new BigNumber("5000"), new BigNumber("100"));
166
+
167
+ const { ctx, contract } = fixture(LaunchpadContract)
168
+ .registeredUsers(users.testUser1)
169
+ .savedState(
170
+ zeroDecimalMemeClass,
171
+ currencyInstance,
172
+ launchpadGalaClass,
173
+ launchpadGalaInstance,
174
+ sale,
175
+ salelaunchpadGalaBalance,
176
+ saleCurrencyBalance,
177
+ userlaunchpadGalaBalance,
178
+ userCurrencyBalance
179
+ );
180
+
181
+ // Choose a token quantity that will produce fractional native tokens from bonding curve
182
+ const sellDto = new ExactTokenQuantityDto(vaultAddress, new BigNumber("100.555"));
183
+ sellDto.uniqueKey = randomUniqueKey();
184
+ const signedDto = sellDto.signed(users.testUser1.privateKey);
185
+
186
+ // When
187
+ const response = await contract.SellExactToken(ctx, signedDto);
188
+
189
+ // Then - Expect error due to decimal precision mismatch
190
+ expect(response).toEqual(
191
+ transactionError(new InvalidDecimalError(sellDto.tokenQuantity, zeroDecimalMemeClass.decimals))
192
+ );
193
+ });
194
+
195
+ it("should handle small token sell amount", async () => {
196
+ // Given
197
+ sale.buyToken(new BigNumber("500"), new BigNumber("25")); // Users bought tokens, sale now has GALA
198
+ const { ctx, contract } = fixture(LaunchpadContract)
199
+ .registeredUsers(users.testUser1)
200
+ .savedState(
201
+ currencyClass,
202
+ currencyInstance,
203
+ launchpadGalaClass,
204
+ launchpadGalaInstance,
205
+ sale,
206
+ salelaunchpadGalaBalance,
207
+ saleCurrencyBalance,
208
+ userlaunchpadGalaBalance,
209
+ userCurrencyBalance
210
+ );
211
+
212
+ const sellDto = new ExactTokenQuantityDto(vaultAddress, new BigNumber("1"));
213
+ sellDto.uniqueKey = randomUniqueKey();
214
+ const signedDto = sellDto.signed(users.testUser1.privateKey);
215
+
216
+ // When
217
+ const response = await contract.SellExactToken(ctx, signedDto);
218
+
219
+ // Then
220
+ expect(response.Status).toBe(1);
221
+ expect(response.Data?.inputQuantity).toBe("1");
222
+ expect(new BigNumber(response.Data?.outputQuantity || "0").isPositive()).toBe(true);
223
+ });
224
+
225
+ it("should handle sell with expected native token parameter", async () => {
226
+ // Given
227
+ sale.buyToken(new BigNumber("800"), new BigNumber("40")); // Users bought tokens, sale now has GALA
228
+ const { ctx, contract } = fixture(LaunchpadContract)
229
+ .registeredUsers(users.testUser1)
230
+ .savedState(
231
+ currencyClass,
232
+ currencyInstance,
233
+ launchpadGalaClass,
234
+ launchpadGalaInstance,
235
+ sale,
236
+ salelaunchpadGalaBalance,
237
+ saleCurrencyBalance,
238
+ userlaunchpadGalaBalance,
239
+ userCurrencyBalance
240
+ );
241
+
242
+ const sellDto = new ExactTokenQuantityDto(vaultAddress, new BigNumber("50"));
243
+ sellDto.expectedNativeToken = new BigNumber("0.0001"); // Set realistic expectation for slippage protection
244
+ sellDto.uniqueKey = randomUniqueKey();
245
+ const signedDto = sellDto.signed(users.testUser1.privateKey);
246
+
247
+ // When
248
+ const response = await contract.SellExactToken(ctx, signedDto);
249
+
250
+ // Then
251
+ expect(response.Status).toBe(1);
252
+ expect(response.Data?.inputQuantity).toBe("50");
253
+ });
254
+
255
+ it("should handle large token sell amount", async () => {
256
+ // Given
257
+ sale.buyToken(new BigNumber("2000"), new BigNumber("100")); // Users bought tokens, sale now has GALA
258
+ const { ctx, contract } = fixture(LaunchpadContract)
259
+ .registeredUsers(users.testUser1)
260
+ .savedState(
261
+ currencyClass,
262
+ currencyInstance,
263
+ launchpadGalaClass,
264
+ launchpadGalaInstance,
265
+ sale,
266
+ salelaunchpadGalaBalance,
267
+ saleCurrencyBalance,
268
+ userlaunchpadGalaBalance,
269
+ userCurrencyBalance
270
+ );
271
+
272
+ const sellDto = new ExactTokenQuantityDto(vaultAddress, new BigNumber("500"));
273
+ sellDto.uniqueKey = randomUniqueKey();
274
+ const signedDto = sellDto.signed(users.testUser1.privateKey);
275
+
276
+ // When
277
+ const response = await contract.SellExactToken(ctx, signedDto);
278
+
279
+ // Then
280
+ expect(response.Status).toBe(1);
281
+ expect(response.Data?.inputQuantity).toBe("500");
282
+ expect(new BigNumber(response.Data?.outputQuantity || "0").isPositive()).toBe(true);
283
+ });
284
+ });
@@ -0,0 +1,329 @@
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 { InvalidDecimalError } from "@gala-chain/chaincode";
25
+ import { currency, fixture, transactionError, transactionSuccess, users } from "@gala-chain/test";
26
+ import BigNumber from "bignumber.js";
27
+ import { plainToInstance } from "class-transformer";
28
+
29
+ import { LaunchpadSale, NativeTokenQuantityDto, TradeResDto } from "../../api/types";
30
+ import { LaunchpadContract } from "../LaunchpadContract";
31
+ import launchpadgala from "../test/launchpadgala";
32
+
33
+ describe("sellWithNative", () => {
34
+ let currencyClass: TokenClass;
35
+ let currencyInstance: TokenInstance;
36
+ let launchpadGalaClass: TokenClass;
37
+ let launchpadGalaInstance: TokenInstance;
38
+ let launchpadGalaClassKey: TokenClassKey;
39
+ let vaultAddress: UserAlias;
40
+ let sale: LaunchpadSale;
41
+ let salelaunchpadGalaBalance: TokenBalance;
42
+ let saleCurrencyBalance: TokenBalance;
43
+ let userlaunchpadGalaBalance: TokenBalance;
44
+ let userCurrencyBalance: TokenBalance;
45
+
46
+ beforeEach(() => {
47
+ currencyClass = currency.tokenClass();
48
+ currencyInstance = currency.tokenInstance();
49
+ launchpadGalaClass = launchpadgala.tokenClass();
50
+ launchpadGalaInstance = launchpadgala.tokenInstance();
51
+ launchpadGalaClassKey = launchpadgala.tokenClassKey();
52
+
53
+ vaultAddress = asValidUserAlias(`service|${launchpadGalaClassKey.toStringKey()}$launchpad`);
54
+
55
+ // Initialize sale with manual values
56
+ sale = new LaunchpadSale(
57
+ vaultAddress,
58
+ currencyInstance.instanceKeyObj(),
59
+ undefined,
60
+ users.testUser1.identityKey
61
+ );
62
+
63
+ // Create sale balances - sale needs tokens to pay out
64
+ salelaunchpadGalaBalance = plainToInstance(TokenBalance, {
65
+ ...launchpadgala.tokenBalance(),
66
+ owner: vaultAddress,
67
+ quantity: new BigNumber("1000000") // Large amount in vault
68
+ });
69
+ saleCurrencyBalance = plainToInstance(TokenBalance, {
70
+ ...currency.tokenBalance(),
71
+ owner: vaultAddress,
72
+ quantity: new BigNumber("1000000") // Large amount in vault
73
+ });
74
+
75
+ // Create user balances - user needs tokens to sell
76
+ userlaunchpadGalaBalance = plainToInstance(TokenBalance, {
77
+ ...launchpadgala.tokenBalance(),
78
+ owner: users.testUser1.identityKey,
79
+ quantity: new BigNumber("10000") // User has tokens to sell
80
+ });
81
+ userCurrencyBalance = plainToInstance(TokenBalance, {
82
+ ...currency.tokenBalance(),
83
+ owner: users.testUser1.identityKey,
84
+ quantity: new BigNumber("10000") // User has some CURRENCY tokens
85
+ });
86
+ });
87
+
88
+ test("Round sell qty when bonding curve produces fractional precision than meme TokenClass.decimals", async () => {
89
+ // Given - Setup meme token with 0 decimals to force decimal precision error
90
+ const zeroDecimalMemeTokenClass = plainToInstance(TokenClass, {
91
+ ...currency.tokenClassPlain(),
92
+ decimals: 0 // Integer-only meme token
93
+ });
94
+
95
+ // Simulate prior buys to establish sale state with native tokens
96
+ sale.buyToken(new BigNumber("10000"), new BigNumber("10"));
97
+
98
+ const { ctx, contract } = fixture(LaunchpadContract)
99
+ .registeredUsers(users.testUser1)
100
+ .savedState(
101
+ currencyInstance,
102
+ zeroDecimalMemeTokenClass,
103
+ launchpadGalaClass,
104
+ launchpadGalaInstance,
105
+ sale,
106
+ salelaunchpadGalaBalance,
107
+ saleCurrencyBalance,
108
+ userlaunchpadGalaBalance,
109
+ userCurrencyBalance
110
+ );
111
+
112
+ // Request native tokens that will require fractional meme tokens from bonding curve
113
+ const sellDto = new NativeTokenQuantityDto(vaultAddress, new BigNumber("0.001"));
114
+ sellDto.uniqueKey = randomUniqueKey();
115
+ sellDto.sign(users.testUser1.privateKey);
116
+
117
+ // When
118
+ const response = await contract.SellWithNative(ctx, sellDto);
119
+
120
+ // Then - Expect error due to decimal precision mismatch
121
+ expect(response).not.toEqual(
122
+ transactionError(
123
+ new InvalidDecimalError(new BigNumber("9940.1186641108"), zeroDecimalMemeTokenClass.decimals)
124
+ )
125
+ );
126
+ });
127
+
128
+ it("should reject sell when dto contains fractional precision greater than native TokenClass.decimals", async () => {
129
+ // Given - Setup meme token with 0 decimals to force decimal precision error
130
+ const zeroDecimalLaunchpadGalaClass = plainToInstance(TokenClass, {
131
+ ...launchpadgala.tokenClassPlain(),
132
+ decimals: 8 // This codebase currently hard-codes 8 as NATIVE_TOKEN_DECIMALS...
133
+ });
134
+
135
+ // Simulate prior buys to establish sale state with native tokens
136
+ sale.buyToken(new BigNumber("100"), new BigNumber("100"));
137
+
138
+ const { ctx, contract } = fixture(LaunchpadContract)
139
+ .registeredUsers(users.testUser1)
140
+ .savedState(
141
+ currencyClass,
142
+ currencyInstance,
143
+ zeroDecimalLaunchpadGalaClass,
144
+ launchpadGalaInstance,
145
+ sale,
146
+ salelaunchpadGalaBalance,
147
+ saleCurrencyBalance,
148
+ userlaunchpadGalaBalance,
149
+ userCurrencyBalance
150
+ );
151
+
152
+ // Request native tokens that will require fractional meme tokens from bonding curve
153
+ const sellDto = new NativeTokenQuantityDto(vaultAddress, new BigNumber("0.123456789"));
154
+ sellDto.uniqueKey = randomUniqueKey();
155
+ sellDto.sign(users.testUser1.privateKey);
156
+
157
+ // When
158
+ const response = await contract.SellWithNative(ctx, sellDto);
159
+
160
+ // Then - Expect error due to decimal precision mismatch
161
+ expect(response).toEqual(
162
+ transactionError(
163
+ new InvalidDecimalError(sellDto.nativeTokenQuantity, zeroDecimalLaunchpadGalaClass.decimals)
164
+ )
165
+ );
166
+ });
167
+
168
+ it("should sell tokens for native currency successfully", async () => {
169
+ // Given
170
+ sale.buyToken(new BigNumber("10000"), new BigNumber("10")); // Users bought tokens, sale now has GALA
171
+ const { ctx, contract } = fixture(LaunchpadContract)
172
+ .registeredUsers(users.testUser1)
173
+ .savedState(
174
+ currencyClass,
175
+ currencyInstance,
176
+ launchpadGalaClass,
177
+ launchpadGalaInstance,
178
+ sale,
179
+ salelaunchpadGalaBalance,
180
+ saleCurrencyBalance,
181
+ userlaunchpadGalaBalance,
182
+ userCurrencyBalance
183
+ );
184
+
185
+ const sellDto = new NativeTokenQuantityDto(vaultAddress, new BigNumber("0.1"));
186
+ sellDto.uniqueKey = randomUniqueKey();
187
+ const signedDto = sellDto.signed(users.testUser1.privateKey);
188
+
189
+ const expectedResponse = plainToInstance(TradeResDto, {
190
+ functionName: "SellWithNative",
191
+ inputQuantity: "6008.9271949682",
192
+ isFinalized: false,
193
+ outputQuantity: "0.1",
194
+ tokenName: "AUTOMATEDTESTCOIN",
195
+ totalFees: "0",
196
+ totalTokenSold: "3991.0728050318",
197
+ tradeType: "Sell",
198
+ uniqueKey: sellDto.uniqueKey,
199
+ userAddress: "client|testUser1",
200
+ vaultAddress: "service|GALA$Unit$none$none$launchpad"
201
+ });
202
+
203
+ // When
204
+ const response = await contract.SellWithNative(ctx, signedDto);
205
+
206
+ // Then
207
+ expect(response).toEqual(transactionSuccess(expectedResponse));
208
+ });
209
+
210
+ it("should handle small native token sell amount", async () => {
211
+ // Given
212
+ sale.buyToken(new BigNumber("5000"), new BigNumber("5")); // Users bought tokens, sale now has GALA
213
+ const { ctx, contract } = fixture(LaunchpadContract)
214
+ .registeredUsers(users.testUser1)
215
+ .savedState(
216
+ currencyClass,
217
+ currencyInstance,
218
+ launchpadGalaClass,
219
+ launchpadGalaInstance,
220
+ sale,
221
+ salelaunchpadGalaBalance,
222
+ saleCurrencyBalance,
223
+ userlaunchpadGalaBalance,
224
+ userCurrencyBalance
225
+ );
226
+
227
+ const sellDto = new NativeTokenQuantityDto(vaultAddress, new BigNumber("0.001"));
228
+ sellDto.uniqueKey = randomUniqueKey();
229
+ const signedDto = sellDto.signed(users.testUser1.privateKey);
230
+
231
+ // When
232
+ const response = await contract.SellWithNative(ctx, signedDto);
233
+
234
+ // Then
235
+ expect(response).toEqual(transactionSuccess());
236
+ });
237
+
238
+ it("should handle sell with expected token parameter", async () => {
239
+ // Given
240
+ sale.buyToken(new BigNumber("8000"), new BigNumber("8")); // Users bought tokens, sale now has GALA
241
+ const { ctx, contract } = fixture(LaunchpadContract)
242
+ .registeredUsers(users.testUser1)
243
+ .savedState(
244
+ currencyClass,
245
+ currencyInstance,
246
+ launchpadGalaClass,
247
+ launchpadGalaInstance,
248
+ sale,
249
+ salelaunchpadGalaBalance,
250
+ saleCurrencyBalance,
251
+ userlaunchpadGalaBalance,
252
+ userCurrencyBalance
253
+ );
254
+
255
+ const sellDto = new NativeTokenQuantityDto(vaultAddress, new BigNumber("0.001"));
256
+
257
+ // expectedToken value, if provided, must be greater than or equal to actual amount received
258
+ // in other words, if the transaction would sell the buyer less than the buyer expects, it fails.
259
+ sellDto.expectedToken = new BigNumber("100"); // Set expectation for slippage protection
260
+
261
+ sellDto.uniqueKey = randomUniqueKey();
262
+ const signedDto = sellDto.signed(users.testUser1.privateKey);
263
+
264
+ // When
265
+ const response = await contract.SellWithNative(ctx, signedDto);
266
+
267
+ // Then
268
+ expect(response).toEqual(transactionSuccess());
269
+ });
270
+
271
+ it("should handle large native token sell amount", async () => {
272
+ // Given
273
+ sale.buyToken(new BigNumber("2000000000"), new BigNumber("200000000")); // Users bought tokens, sale now has GALA
274
+ const { ctx, contract } = fixture(LaunchpadContract)
275
+ .registeredUsers(users.testUser1)
276
+ .savedState(
277
+ currencyClass,
278
+ currencyInstance,
279
+ launchpadGalaClass,
280
+ launchpadGalaInstance,
281
+ sale,
282
+ salelaunchpadGalaBalance,
283
+ saleCurrencyBalance,
284
+ userlaunchpadGalaBalance,
285
+ userCurrencyBalance
286
+ );
287
+
288
+ const sellDto = new NativeTokenQuantityDto(vaultAddress, new BigNumber("1000"));
289
+ sellDto.uniqueKey = randomUniqueKey();
290
+ const signedDto = sellDto.signed(users.testUser1.privateKey);
291
+
292
+ // When
293
+ const response = await contract.SellWithNative(ctx, signedDto);
294
+
295
+ // Then
296
+ expect(response).toEqual(transactionSuccess());
297
+ });
298
+
299
+ it("should handle edge case with vault balance limits", async () => {
300
+ // Given
301
+ sale.buyToken(new BigNumber("10000"), new BigNumber("1000")); // Users bought tokens, sale has GALA amount
302
+ const { ctx, contract } = fixture(LaunchpadContract)
303
+ .registeredUsers(users.testUser1)
304
+ .savedState(
305
+ currencyClass,
306
+ currencyInstance,
307
+ launchpadGalaClass,
308
+ launchpadGalaInstance,
309
+ sale,
310
+ salelaunchpadGalaBalance,
311
+ saleCurrencyBalance,
312
+ userlaunchpadGalaBalance,
313
+ userCurrencyBalance
314
+ );
315
+
316
+ // Try to sell amount that exceeds what's in the vault
317
+ const sellDto = new NativeTokenQuantityDto(vaultAddress, new BigNumber("0.1"));
318
+ sellDto.uniqueKey = randomUniqueKey();
319
+ const signedDto = sellDto.signed(users.testUser1.privateKey);
320
+
321
+ // When
322
+ const response = await contract.SellWithNative(ctx, signedDto);
323
+
324
+ // Then
325
+ expect(response).toEqual(transactionSuccess());
326
+
327
+ // todo: check writes map, verify vault balance
328
+ });
329
+ });
@@ -12,11 +12,17 @@
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";
16
- import { GalaChainContext, fetchTokenClass, putChainObject, transferToken } from "@gala-chain/chaincode";
15
+ import { TokenClass, ValidationFailedError } from "@gala-chain/api";
16
+ import {
17
+ GalaChainContext,
18
+ fetchTokenClass,
19
+ getObjectByKey,
20
+ putChainObject,
21
+ transferToken
22
+ } from "@gala-chain/chaincode";
17
23
  import BigNumber from "bignumber.js";
18
24
 
19
- import { NativeTokenQuantityDto, TradeResDto } from "../../api/types";
25
+ import { LaunchpadSale, NativeTokenQuantityDto, TradeResDto } from "../../api/types";
20
26
  import { SlippageToleranceExceededError } from "../../api/utils/error";
21
27
  import { fetchAndValidateSale, fetchLaunchpadFeeAddress } from "../utils";
22
28
  import { callMemeTokenIn } from "./callMemeTokenIn";
@@ -46,7 +52,16 @@ export async function sellWithNative(
46
52
  sellTokenDTO: NativeTokenQuantityDto
47
53
  ): Promise<TradeResDto> {
48
54
  // Fetch and validate the sale object
49
- const sale = await fetchAndValidateSale(ctx, sellTokenDTO.vaultAddress);
55
+ const sale: LaunchpadSale = await fetchAndValidateSale(ctx, sellTokenDTO.vaultAddress);
56
+
57
+ const { collection, category, type, additionalKey } = sale.sellingToken;
58
+ const sellingTokenCompositeKey = TokenClass.getCompositeKeyFromParts(TokenClass.INDEX_KEY, [
59
+ collection,
60
+ category,
61
+ type,
62
+ additionalKey
63
+ ]);
64
+ const sellingToken = await getObjectByKey(ctx, TokenClass, sellingTokenCompositeKey);
50
65
 
51
66
  const nativeTokensLeftInVault = new BigNumber(sale.nativeTokenQuantity);
52
67
 
@@ -58,7 +73,9 @@ export async function sellWithNative(
58
73
  // Calculate how many tokens need to be sold to get the requested native amount
59
74
  const callMemeTokenInResult = await callMemeTokenIn(ctx, sellTokenDTO);
60
75
  const transactionFees = callMemeTokenInResult.extraFees.transactionFees;
61
- const tokensToSell = new BigNumber(callMemeTokenInResult.calculatedQuantity);
76
+ const tokensToSell = new BigNumber(callMemeTokenInResult.calculatedQuantity).decimalPlaces(
77
+ sellingToken.decimals
78
+ );
62
79
 
63
80
  const nativeToken = sale.fetchNativeTokenInstanceKey();
64
81
  const memeToken = sale.fetchSellingTokenInstanceKey();
@@ -41,7 +41,7 @@ const tokenClassKeyPlain = createPlainFn({
41
41
  const tokenClassPlain = createPlainFn({
42
42
  ...tokenClassKeyPlain(),
43
43
  description: "Generated via automated test suite.",
44
- decimals: 10,
44
+ decimals: 8, // This codebase currently hard-codes 8 as NATIVE_TOKEN_DECIMALS
45
45
  image: "https://app.gala.games/test-image-placeholder-url.png",
46
46
  isNonFungible: false,
47
47
  maxCapacity: new BigNumber(100000000000000),