@gala-chain/launchpad 1.0.6 → 1.0.8

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 (55) hide show
  1. package/lib/package.json +1 -1
  2. package/lib/src/api/types/LaunchpadDtos.d.ts +5 -0
  3. package/lib/src/api/types/LaunchpadDtos.d.ts.map +1 -1
  4. package/lib/src/api/types/LaunchpadDtos.js +16 -1
  5. package/lib/src/api/types/LaunchpadDtos.js.map +1 -1
  6. package/lib/src/chaincode/LaunchpadContract.d.ts +4 -3
  7. package/lib/src/chaincode/LaunchpadContract.d.ts.map +1 -1
  8. package/lib/src/chaincode/LaunchpadContract.js +23 -4
  9. package/lib/src/chaincode/LaunchpadContract.js.map +1 -1
  10. package/lib/src/chaincode/launchpad/buyExactToken.d.ts.map +1 -1
  11. package/lib/src/chaincode/launchpad/buyExactToken.js +9 -1
  12. package/lib/src/chaincode/launchpad/buyExactToken.js.map +1 -1
  13. package/lib/src/chaincode/launchpad/buyWithNative.d.ts.map +1 -1
  14. package/lib/src/chaincode/launchpad/buyWithNative.js +10 -2
  15. package/lib/src/chaincode/launchpad/buyWithNative.js.map +1 -1
  16. package/lib/src/chaincode/launchpad/callMemeTokenOut.d.ts.map +1 -1
  17. package/lib/src/chaincode/launchpad/callMemeTokenOut.js.map +1 -1
  18. package/lib/src/chaincode/launchpad/callNativeTokenIn.d.ts.map +1 -1
  19. package/lib/src/chaincode/launchpad/callNativeTokenIn.js.map +1 -1
  20. package/lib/src/chaincode/launchpad/callNativeTokenOut.d.ts.map +1 -1
  21. package/lib/src/chaincode/launchpad/callNativeTokenOut.js.map +1 -1
  22. package/lib/src/chaincode/launchpad/fetchLaunchpadAdressConfig.d.ts +2 -1
  23. package/lib/src/chaincode/launchpad/fetchLaunchpadAdressConfig.d.ts.map +1 -1
  24. package/lib/src/chaincode/launchpad/fetchLaunchpadAdressConfig.js +1 -1
  25. package/lib/src/chaincode/launchpad/fetchLaunchpadAdressConfig.js.map +1 -1
  26. package/lib/src/chaincode/launchpad/fetchLaunchpadFeeAmount.d.ts +12 -0
  27. package/lib/src/chaincode/launchpad/fetchLaunchpadFeeAmount.d.ts.map +1 -0
  28. package/lib/src/chaincode/launchpad/fetchLaunchpadFeeAmount.js +37 -0
  29. package/lib/src/chaincode/launchpad/fetchLaunchpadFeeAmount.js.map +1 -0
  30. package/lib/src/chaincode/launchpad/finaliseSale.js +3 -3
  31. package/lib/src/chaincode/launchpad/finaliseSale.js.map +1 -1
  32. package/lib/src/chaincode/launchpad/sellExactToken.d.ts.map +1 -1
  33. package/lib/src/chaincode/launchpad/sellExactToken.js +3 -1
  34. package/lib/src/chaincode/launchpad/sellExactToken.js.map +1 -1
  35. package/lib/src/chaincode/launchpad/sellWithNative.d.ts.map +1 -1
  36. package/lib/src/chaincode/launchpad/sellWithNative.js +3 -1
  37. package/lib/src/chaincode/launchpad/sellWithNative.js.map +1 -1
  38. package/lib/tsconfig.tsbuildinfo +1 -1
  39. package/package.json +1 -1
  40. package/src/api/types/LaunchpadDtos.ts +11 -0
  41. package/src/chaincode/LaunchpadContract.ts +25 -5
  42. package/src/chaincode/launchpad/buyExactToken.spec.ts +222 -0
  43. package/src/chaincode/launchpad/buyExactToken.ts +19 -2
  44. package/src/chaincode/launchpad/buyWithNative.spec.ts +221 -0
  45. package/src/chaincode/launchpad/buyWithNative.ts +21 -3
  46. package/src/chaincode/launchpad/callMemeTokenOut.spec.ts +1 -1
  47. package/src/chaincode/launchpad/callMemeTokenOut.ts +1 -0
  48. package/src/chaincode/launchpad/callNativeTokenIn.ts +1 -0
  49. package/src/chaincode/launchpad/callNativeTokenOut.ts +1 -0
  50. package/src/chaincode/launchpad/fetchLaunchpadAdressConfig.ts +5 -2
  51. package/src/chaincode/launchpad/fetchLaunchpadFeeAmount.spec.ts +67 -0
  52. package/src/chaincode/launchpad/fetchLaunchpadFeeAmount.ts +40 -0
  53. package/src/chaincode/launchpad/finaliseSale.ts +3 -3
  54. package/src/chaincode/launchpad/sellExactToken.ts +3 -1
  55. package/src/chaincode/launchpad/sellWithNative.ts +3 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gala-chain/launchpad",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "GalaChain Launchpad Chaincode",
5
5
  "main": "lib/src/index.js",
6
6
  "types": "lib/src/index.d.ts",
@@ -306,6 +306,12 @@ export class TradeResDto {
306
306
 
307
307
  @IsNotEmpty()
308
308
  public functionName: string;
309
+
310
+ @IsString()
311
+ public uniqueKey: string;
312
+
313
+ @IsString()
314
+ public totalTokenSold: string;
309
315
  }
310
316
 
311
317
  export class FetchSaleDto extends ChainCallDTO {
@@ -394,3 +400,8 @@ export class BatchSubmitAuthoritiesResDto extends ChainCallDTO {
394
400
  @IsString({ each: true })
395
401
  authorities: string[];
396
402
  }
403
+
404
+ export class TransactionFeeResDto {
405
+ @IsNumber()
406
+ feeAmount: number;
407
+ }
@@ -12,7 +12,7 @@
12
12
  * See the License for the specific language governing permissions and
13
13
  * limitations under the License.
14
14
  */
15
- import { BatchDto, GalaChainResponse, UnauthorizedError } from "@gala-chain/api";
15
+ import { BatchDto, ChainCallDTO, GalaChainResponse, UnauthorizedError } from "@gala-chain/api";
16
16
  import {
17
17
  BatchWriteLimitExceededError,
18
18
  EVALUATE,
@@ -42,7 +42,8 @@ import {
42
42
  LaunchpadSale,
43
43
  NativeTokenQuantityDto,
44
44
  TradeCalculationResDto,
45
- TradeResDto
45
+ TradeResDto,
46
+ TransactionFeeResDto
46
47
  } from "../api/types";
47
48
  import {
48
49
  buyExactTokenFeeGate,
@@ -70,10 +71,13 @@ import {
70
71
  sellExactToken,
71
72
  sellWithNative
72
73
  } from "./launchpad";
74
+ import { fetchLaunchpadFeeAmount } from "./launchpad/fetchLaunchpadFeeAmount";
73
75
 
74
76
  export class LaunchpadContract extends GalaContract {
75
77
  constructor() {
76
- super("Launchpad", version);
78
+ super("Launchpad", version, {
79
+ allowNonRegisteredUsers: true
80
+ });
77
81
  }
78
82
 
79
83
  @Submit({
@@ -203,11 +207,27 @@ export class LaunchpadContract extends GalaContract {
203
207
  }
204
208
 
205
209
  @Evaluate({
210
+ in: ChainCallDTO,
206
211
  out: LaunchpadFeeConfig,
207
212
  allowedOrgs: ["CuratorOrg"]
208
213
  })
209
- public async FetchLaunchpadFeeConfig(ctx: GalaChainContext): Promise<LaunchpadFeeConfig> {
210
- return fetchLaunchpadFeeConfig(ctx);
214
+ public async FetchLaunchpadFeeConfig(
215
+ ctx: GalaChainContext,
216
+ dto: ChainCallDTO
217
+ ): Promise<LaunchpadFeeConfig> {
218
+ return fetchLaunchpadFeeConfig(ctx, dto);
219
+ }
220
+
221
+ @GalaTransaction({
222
+ type: EVALUATE,
223
+ in: ChainCallDTO,
224
+ out: TransactionFeeResDto
225
+ })
226
+ public async FetchLaunchpadFeeAmount(
227
+ ctx: GalaChainContext,
228
+ dto: ChainCallDTO
229
+ ): Promise<TransactionFeeResDto> {
230
+ return fetchLaunchpadFeeAmount(ctx, dto);
211
231
  }
212
232
 
213
233
  @GalaTransaction({
@@ -0,0 +1,222 @@
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
+ GalaChainResponse,
17
+ TokenBalance,
18
+ TokenClass,
19
+ TokenClassKey,
20
+ TokenInstance,
21
+ UserAlias,
22
+ ValidationFailedError,
23
+ asValidUserAlias,
24
+ randomUniqueKey
25
+ } from "@gala-chain/api";
26
+ import { currency, fixture, users } from "@gala-chain/test";
27
+ import BigNumber from "bignumber.js";
28
+ import { plainToInstance } from "class-transformer";
29
+
30
+ import {
31
+ ExactTokenQuantityDto,
32
+ LaunchpadFeeConfig,
33
+ LaunchpadSale,
34
+ NativeTokenQuantityDto
35
+ } from "../../api/types";
36
+ import { LaunchpadContract } from "../LaunchpadContract";
37
+ import launchpadgala from "../test/launchpadgala";
38
+
39
+ describe("buyWithNative", () => {
40
+ let currencyClass: TokenClass;
41
+ let currencyInstance: TokenInstance;
42
+ let launchpadGalaClass: TokenClass;
43
+ let launchpadGalaInstance: TokenInstance;
44
+ let launchpadGalaClassKey: TokenClassKey;
45
+ let vaultAddress: UserAlias;
46
+ let sale: LaunchpadSale;
47
+ let salelaunchpadGalaBalance: TokenBalance;
48
+ let saleCurrencyBalance: TokenBalance;
49
+ let userlaunchpadGalaBalance: TokenBalance;
50
+ let userCurrencyBalance: TokenBalance;
51
+
52
+ beforeEach(() => {
53
+ //Given
54
+ currencyClass = currency.tokenClass();
55
+ currencyInstance = currency.tokenInstance();
56
+ launchpadGalaClass = launchpadgala.tokenClass();
57
+ launchpadGalaInstance = launchpadgala.tokenInstance();
58
+ launchpadGalaClassKey = launchpadgala.tokenClassKey();
59
+
60
+ launchpadGalaClass = plainToInstance(TokenClass, {
61
+ ...launchpadgala.tokenClassPlain(),
62
+ decimals: 18
63
+ });
64
+
65
+ vaultAddress = asValidUserAlias(`service|${launchpadGalaClassKey.toStringKey()}$launchpad`);
66
+
67
+ // Initialize sale with manual values
68
+ sale = new LaunchpadSale(
69
+ vaultAddress,
70
+ launchpadGalaInstance.instanceKeyObj(),
71
+ undefined,
72
+ users.testUser1.identityKey
73
+ );
74
+
75
+ // Create sale balances - sale needs tokens to pay out
76
+ salelaunchpadGalaBalance = plainToInstance(TokenBalance, {
77
+ ...launchpadgala.tokenBalance(),
78
+ owner: vaultAddress,
79
+ quantity: new BigNumber("1e+7")
80
+ });
81
+
82
+ saleCurrencyBalance = plainToInstance(TokenBalance, {
83
+ ...currency.tokenBalance(),
84
+ owner: vaultAddress,
85
+ quantity: new BigNumber("1e+7")
86
+ });
87
+
88
+ // Create user balances - user needs tokens to swap
89
+ userlaunchpadGalaBalance = plainToInstance(TokenBalance, {
90
+ ...launchpadgala.tokenBalance(),
91
+ owner: users.testUser1.identityKey,
92
+ quantity: new BigNumber("1000000") // User has 10k launchpadGala tokens
93
+ });
94
+ userCurrencyBalance = plainToInstance(TokenBalance, {
95
+ ...currency.tokenBalance(),
96
+ owner: users.testUser1.identityKey
97
+ });
98
+ });
99
+
100
+ test("User should be able to buy exact tokens, without fee configured", async () => {
101
+ //Given
102
+ const { ctx, contract } = fixture(LaunchpadContract)
103
+ .registeredUsers(users.testUser1)
104
+ .savedState(
105
+ currencyClass,
106
+ currencyInstance,
107
+ launchpadGalaClass,
108
+ launchpadGalaInstance,
109
+ sale,
110
+ salelaunchpadGalaBalance,
111
+ saleCurrencyBalance,
112
+ userlaunchpadGalaBalance,
113
+ userCurrencyBalance
114
+ );
115
+
116
+ const dto = new ExactTokenQuantityDto(vaultAddress, new BigNumber("500"));
117
+
118
+ dto.uniqueKey = randomUniqueKey();
119
+ dto.sign(users.testUser1.privateKey);
120
+
121
+ //When
122
+ const buyTokenRes = await contract.BuyExactToken(ctx, dto);
123
+
124
+ //Then
125
+ expect(buyTokenRes.Data).toMatchObject({
126
+ inputQuantity: "0.00825575",
127
+ totalFees: "0.00000000",
128
+ outputQuantity: "500",
129
+ tokenName: "AUTOMATEDTESTCOIN",
130
+ tradeType: "Buy",
131
+ vaultAddress: "service|GALA$Unit$none$none$launchpad",
132
+ userAddress: "client|testUser1",
133
+ isFinalized: false,
134
+ functionName: "BuyExactToken"
135
+ });
136
+ expect(buyTokenRes.Data?.inputQuantity).toEqual("0.00825575");
137
+ expect(buyTokenRes.Data?.outputQuantity).toEqual("500");
138
+ });
139
+
140
+ test("User should be able to buy tokens , fee configured check", async () => {
141
+ //Given
142
+ const launchpadConfig = new LaunchpadFeeConfig(users.testUser2.identityKey, Number("0.32"), [
143
+ users.testUser2.identityKey
144
+ ]);
145
+
146
+ const { ctx, contract } = fixture(LaunchpadContract)
147
+ .registeredUsers(users.testUser1)
148
+ .savedState(
149
+ currencyClass,
150
+ currencyInstance,
151
+ launchpadGalaClass,
152
+ launchpadGalaInstance,
153
+ sale,
154
+ salelaunchpadGalaBalance,
155
+ saleCurrencyBalance,
156
+ userlaunchpadGalaBalance,
157
+ userCurrencyBalance,
158
+ launchpadConfig
159
+ );
160
+
161
+ const dto = new ExactTokenQuantityDto(vaultAddress, new BigNumber("5430"));
162
+
163
+ dto.uniqueKey = randomUniqueKey();
164
+ dto.sign(users.testUser1.privateKey);
165
+
166
+ //When
167
+ const buyTokenRes = await contract.BuyExactToken(ctx, dto);
168
+
169
+ //Then
170
+ expect(buyTokenRes.Data).toMatchObject({
171
+ inputQuantity: "0.08991559",
172
+ totalFees: "0.02877299",
173
+ outputQuantity: "5430",
174
+ tokenName: "AUTOMATEDTESTCOIN",
175
+ tradeType: "Buy",
176
+ vaultAddress: "service|GALA$Unit$none$none$launchpad",
177
+ userAddress: "client|testUser1",
178
+ isFinalized: false,
179
+ functionName: "BuyExactToken"
180
+ });
181
+ expect(buyTokenRes.Data?.inputQuantity).toEqual("0.08991559");
182
+ expect(buyTokenRes.Data?.outputQuantity).toEqual("5430");
183
+ });
184
+
185
+ it("should throw error if user has insufficient funds incuding the transaction fees", async () => {
186
+ //Given
187
+ const launchpadConfig = new LaunchpadFeeConfig(users.testUser2.identityKey, Number("0.32"), [
188
+ users.testUser2.identityKey
189
+ ]);
190
+ const { ctx, contract } = fixture(LaunchpadContract)
191
+ .registeredUsers(users.testUser1)
192
+ .savedState(
193
+ currencyClass,
194
+ currencyInstance,
195
+ launchpadGalaClass,
196
+ launchpadGalaInstance,
197
+ sale,
198
+ salelaunchpadGalaBalance,
199
+ saleCurrencyBalance,
200
+ userlaunchpadGalaBalance,
201
+ userCurrencyBalance,
202
+ launchpadConfig
203
+ );
204
+
205
+ const dto = new NativeTokenQuantityDto(vaultAddress, new BigNumber("10000000"));
206
+
207
+ dto.uniqueKey = randomUniqueKey();
208
+ dto.sign(users.testUser1.privateKey);
209
+
210
+ //When
211
+ const buyTokenRes = await contract.BuyWithNative(ctx, dto);
212
+
213
+ //Then
214
+ expect(buyTokenRes).toEqual(
215
+ GalaChainResponse.Error(
216
+ new ValidationFailedError(
217
+ "Insufficient balance: Total amount required including fee is 2166101.31430784"
218
+ )
219
+ )
220
+ );
221
+ });
222
+ });
@@ -12,7 +12,14 @@
12
12
  * See the License for the specific language governing permissions and
13
13
  * limitations under the License.
14
14
  */
15
- import { GalaChainContext, fetchTokenClass, putChainObject, transferToken } from "@gala-chain/chaincode";
15
+ import { ValidationFailedError } from "@gala-chain/api";
16
+ import {
17
+ GalaChainContext,
18
+ fetchOrCreateBalance,
19
+ fetchTokenClass,
20
+ putChainObject,
21
+ transferToken
22
+ } from "@gala-chain/chaincode";
16
23
  import BigNumber from "bignumber.js";
17
24
 
18
25
  import { ExactTokenQuantityDto, LaunchpadSale, TradeResDto } from "../../api/types";
@@ -82,6 +89,14 @@ export async function buyExactToken(
82
89
  // Transfer transaction fees
83
90
  const launchpadFeeAddressConfiguration = await fetchLaunchpadFeeAddress(ctx);
84
91
  if (launchpadFeeAddressConfiguration && transactionFees) {
92
+ const totalRequired = new BigNumber(buyTokenDTO.tokenQuantity).plus(transactionFees);
93
+
94
+ const buyerBalance = await fetchOrCreateBalance(ctx, ctx.callingUser, sale.nativeToken);
95
+ if (buyerBalance.getQuantityTotal().lt(totalRequired)) {
96
+ throw new ValidationFailedError(
97
+ `Insufficient balance: Total amount required including fee is ${totalRequired}`
98
+ );
99
+ }
85
100
  await transferToken(ctx, {
86
101
  from: ctx.callingUser,
87
102
  to: launchpadFeeAddressConfiguration.feeAddress,
@@ -135,6 +150,8 @@ export async function buyExactToken(
135
150
  vaultAddress: buyTokenDTO.vaultAddress,
136
151
  userAddress: ctx.callingUser,
137
152
  isFinalized: isSaleFinalized,
138
- functionName: "BuyExactToken"
153
+ functionName: "BuyExactToken",
154
+ uniqueKey: buyTokenDTO.uniqueKey,
155
+ totalTokenSold: sale.fetchTokensSold()
139
156
  };
140
157
  }
@@ -0,0 +1,221 @@
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
+ GalaChainResponse,
17
+ TokenBalance,
18
+ TokenClass,
19
+ TokenClassKey,
20
+ TokenInstance,
21
+ UserAlias,
22
+ ValidationFailedError,
23
+ asValidUserAlias,
24
+ randomUniqueKey
25
+ } from "@gala-chain/api";
26
+ import { currency, fixture, users } from "@gala-chain/test";
27
+ import BigNumber from "bignumber.js";
28
+ import { plainToInstance } from "class-transformer";
29
+
30
+ import { LaunchpadFeeConfig, LaunchpadSale, NativeTokenQuantityDto } from "../../api/types";
31
+ import { LaunchpadContract } from "../LaunchpadContract";
32
+ import launchpadgala from "../test/launchpadgala";
33
+
34
+ describe("buyWithNative", () => {
35
+ let currencyClass: TokenClass;
36
+ let currencyInstance: TokenInstance;
37
+ let launchpadGalaClass: TokenClass;
38
+ let launchpadGalaInstance: TokenInstance;
39
+ let launchpadGalaClassKey: TokenClassKey;
40
+ let vaultAddress: UserAlias;
41
+ let sale: LaunchpadSale;
42
+ let salelaunchpadGalaBalance: TokenBalance;
43
+ let saleCurrencyBalance: TokenBalance;
44
+ let userlaunchpadGalaBalance: TokenBalance;
45
+ let userCurrencyBalance: TokenBalance;
46
+
47
+ beforeEach(() => {
48
+ //Given
49
+ currencyClass = currency.tokenClass();
50
+ currencyInstance = currency.tokenInstance();
51
+ launchpadGalaClass = launchpadgala.tokenClass();
52
+
53
+ launchpadGalaClass = plainToInstance(TokenClass, {
54
+ ...launchpadgala.tokenClassPlain(),
55
+ decimals: 18
56
+ });
57
+
58
+ launchpadGalaInstance = launchpadgala.tokenInstance();
59
+ launchpadGalaClassKey = launchpadgala.tokenClassKey();
60
+
61
+ vaultAddress = asValidUserAlias(`service|${launchpadGalaClassKey.toStringKey()}$launchpad`);
62
+
63
+ // Initialize sale with manual values
64
+ sale = new LaunchpadSale(
65
+ vaultAddress,
66
+ launchpadGalaInstance.instanceKeyObj(),
67
+ undefined,
68
+ users.testUser1.identityKey
69
+ );
70
+
71
+ // Create sale balances - sale needs tokens to pay out
72
+ salelaunchpadGalaBalance = plainToInstance(TokenBalance, {
73
+ ...launchpadgala.tokenBalance(),
74
+ owner: vaultAddress,
75
+ quantity: new BigNumber("1e+7")
76
+ });
77
+
78
+ saleCurrencyBalance = plainToInstance(TokenBalance, {
79
+ ...currency.tokenBalance(),
80
+ owner: vaultAddress,
81
+ quantity: new BigNumber("1e+7")
82
+ });
83
+
84
+ // Create user balances - user needs tokens to swap
85
+ userlaunchpadGalaBalance = plainToInstance(TokenBalance, {
86
+ ...launchpadgala.tokenBalance(),
87
+ owner: users.testUser1.identityKey,
88
+ quantity: new BigNumber("1000000") // User has 10k launchpadGala tokens
89
+ });
90
+ userCurrencyBalance = plainToInstance(TokenBalance, {
91
+ ...currency.tokenBalance(),
92
+ owner: users.testUser1.identityKey
93
+ });
94
+ });
95
+
96
+ test("User should be able to buy tokens with providing native gala , without fee configured", async () => {
97
+ //Given
98
+ const { ctx, contract } = fixture(LaunchpadContract)
99
+ .registeredUsers(users.testUser1)
100
+ .savedState(
101
+ currencyClass,
102
+ currencyInstance,
103
+ launchpadGalaClass,
104
+ launchpadGalaInstance,
105
+ sale,
106
+ salelaunchpadGalaBalance,
107
+ saleCurrencyBalance,
108
+ userlaunchpadGalaBalance,
109
+ userCurrencyBalance
110
+ );
111
+
112
+ const dto = new NativeTokenQuantityDto(vaultAddress, new BigNumber("150"));
113
+
114
+ dto.uniqueKey = randomUniqueKey();
115
+ dto.sign(users.testUser1.privateKey);
116
+
117
+ //When
118
+ const buyTokenRes = await contract.BuyWithNative(ctx, dto);
119
+
120
+ //Then
121
+ expect(buyTokenRes.Data).toMatchObject({
122
+ inputQuantity: "150",
123
+ totalFees: "0.00000000",
124
+ outputQuantity: "2101667.8890651635002",
125
+ tokenName: "AUTOMATEDTESTCOIN",
126
+ tradeType: "Buy",
127
+ vaultAddress: "service|GALA$Unit$none$none$launchpad",
128
+ userAddress: "client|testUser1",
129
+ isFinalized: false,
130
+ functionName: "BuyWithNative"
131
+ });
132
+
133
+ expect(buyTokenRes.Data?.inputQuantity).toEqual("150");
134
+ expect(buyTokenRes.Data?.outputQuantity).toEqual("2101667.8890651635002");
135
+ });
136
+
137
+ test("User should be able to buy tokens , fee configured check", async () => {
138
+ //Given
139
+ const launchpadConfig = new LaunchpadFeeConfig(users.testUser2.identityKey, Number("0.32"), [
140
+ users.testUser2.identityKey
141
+ ]);
142
+
143
+ const { ctx, contract } = fixture(LaunchpadContract)
144
+ .registeredUsers(users.testUser1)
145
+ .savedState(
146
+ currencyClass,
147
+ currencyInstance,
148
+ launchpadGalaClass,
149
+ launchpadGalaInstance,
150
+ sale,
151
+ salelaunchpadGalaBalance,
152
+ saleCurrencyBalance,
153
+ userlaunchpadGalaBalance,
154
+ userCurrencyBalance,
155
+ launchpadConfig
156
+ );
157
+
158
+ const dto = new NativeTokenQuantityDto(vaultAddress, new BigNumber("1000"));
159
+ dto.uniqueKey = randomUniqueKey();
160
+ dto.sign(users.testUser1.privateKey);
161
+
162
+ //When
163
+ const buyTokenRes = await contract.BuyWithNative(ctx, dto);
164
+
165
+ //Then
166
+ expect(buyTokenRes.Data).toMatchObject({
167
+ inputQuantity: "1000",
168
+ totalFees: "320.00000000",
169
+ outputQuantity: "3663321.3628130557168",
170
+ tokenName: "AUTOMATEDTESTCOIN",
171
+ tradeType: "Buy",
172
+ vaultAddress: "service|GALA$Unit$none$none$launchpad",
173
+ userAddress: "client|testUser1",
174
+ isFinalized: false,
175
+ functionName: "BuyWithNative"
176
+ });
177
+
178
+ expect(buyTokenRes.Data?.totalFees).toEqual("320.00000000");
179
+ expect(buyTokenRes.Data?.inputQuantity).toEqual("1000");
180
+ expect(buyTokenRes.Data?.outputQuantity).toEqual("3663321.3628130557168");
181
+ });
182
+
183
+ it("should throw error if user has insufficient funds incuding the transaction fees", async () => {
184
+ //Given
185
+ const launchpadConfig = new LaunchpadFeeConfig(users.testUser2.identityKey, Number("0.32"), [
186
+ users.testUser2.identityKey
187
+ ]);
188
+
189
+ const { ctx, contract } = fixture(LaunchpadContract)
190
+ .registeredUsers(users.testUser1)
191
+ .savedState(
192
+ currencyClass,
193
+ currencyInstance,
194
+ launchpadGalaClass,
195
+ launchpadGalaInstance,
196
+ sale,
197
+ salelaunchpadGalaBalance,
198
+ saleCurrencyBalance,
199
+ userlaunchpadGalaBalance,
200
+ userCurrencyBalance,
201
+ launchpadConfig
202
+ );
203
+
204
+ const dto = new NativeTokenQuantityDto(vaultAddress, new BigNumber("10000000"));
205
+
206
+ dto.uniqueKey = randomUniqueKey();
207
+ dto.sign(users.testUser1.privateKey);
208
+
209
+ //When
210
+ const buyTokenRes = await contract.BuyWithNative(ctx, dto);
211
+
212
+ //Then
213
+ expect(buyTokenRes).toEqual(
214
+ GalaChainResponse.Error(
215
+ new ValidationFailedError(
216
+ "Insufficient balance: Total amount required including fee is 2166101.31430784"
217
+ )
218
+ )
219
+ );
220
+ });
221
+ });
@@ -12,7 +12,14 @@
12
12
  * See the License for the specific language governing permissions and
13
13
  * limitations under the License.
14
14
  */
15
- import { GalaChainContext, fetchTokenClass, putChainObject, transferToken } from "@gala-chain/chaincode";
15
+ import { ValidationFailedError } from "@gala-chain/api";
16
+ import {
17
+ GalaChainContext,
18
+ fetchOrCreateBalance,
19
+ fetchTokenClass,
20
+ putChainObject,
21
+ transferToken
22
+ } from "@gala-chain/chaincode";
16
23
  import BigNumber from "bignumber.js";
17
24
 
18
25
  import { ExactTokenQuantityDto, LaunchpadSale, NativeTokenQuantityDto, TradeResDto } from "../../api/types";
@@ -79,13 +86,22 @@ export async function buyWithNative(
79
86
  // Check for slippage condition
80
87
  if (buyTokenDTO.expectedToken && buyTokenDTO.expectedToken.comparedTo(tokensToBuy) > 0) {
81
88
  throw new SlippageToleranceExceededError(
82
- "Tokens expected from this operation are more than the the actual amount that will be provided."
89
+ "Tokens expected from this operation are more than the actual amount that will be provided."
83
90
  );
84
91
  }
85
92
 
86
93
  // Transfer transaction fees to launchpad fee address
87
94
  const launchpadFeeAddressConfiguration = await fetchLaunchpadFeeAddress(ctx);
88
95
  if (launchpadFeeAddressConfiguration && transactionFees) {
96
+ const totalRequired = new BigNumber(buyTokenDTO.nativeTokenQuantity).plus(transactionFees);
97
+
98
+ const buyerBalance = await fetchOrCreateBalance(ctx, ctx.callingUser, sale.nativeToken);
99
+ if (buyerBalance.getQuantityTotal().lt(totalRequired)) {
100
+ throw new ValidationFailedError(
101
+ `Insufficient balance: Total amount required including fee is ${totalRequired}`
102
+ );
103
+ }
104
+
89
105
  await transferToken(ctx, {
90
106
  from: ctx.callingUser,
91
107
  to: launchpadFeeAddressConfiguration.feeAddress,
@@ -138,6 +154,8 @@ export async function buyWithNative(
138
154
  vaultAddress: buyTokenDTO.vaultAddress,
139
155
  userAddress: ctx.callingUser,
140
156
  isFinalized: isSaleFinalized,
141
- functionName: "BuyWithNative"
157
+ functionName: "BuyWithNative",
158
+ uniqueKey: buyTokenDTO.uniqueKey,
159
+ totalTokenSold: sale.fetchTokensSold()
142
160
  };
143
161
  }
@@ -141,7 +141,7 @@ describe("callMemeTokenOut", () => {
141
141
  // When
142
142
  const response = await contract.CallMemeTokenOut(ctx, signedDto);
143
143
 
144
- // // Then
144
+ // Then
145
145
  expect(response.Data).toMatchObject({
146
146
  calculatedQuantity: "458291.30295364487969",
147
147
  extraFees: { reverseBondingCurve: "0", transactionFees: "0.00000000" }
@@ -79,6 +79,7 @@ export async function callMemeTokenOut(ctx: GalaChainContext, buyTokenDTO: Nativ
79
79
 
80
80
  // Fetch fee configuration and return result
81
81
  const launchpadFeeAddressConfiguration = await fetchLaunchpadFeeAddress(ctx);
82
+
82
83
  return {
83
84
  calculatedQuantity: roundedResult.toFixed(),
84
85
  extraFees: {
@@ -60,6 +60,7 @@ export async function callNativeTokenIn(ctx: GalaChainContext, buyTokenDTO: Exac
60
60
  const price = constantFactor.mul(differenceOfExponentials);
61
61
 
62
62
  const launchpadFeeAddressConfiguration = await fetchLaunchpadFeeAddress(ctx);
63
+
63
64
  const roundedPrice = price.toDecimalPlaces(8, Decimal.ROUND_UP).toFixed();
64
65
  return {
65
66
  calculatedQuantity: roundedPrice,
@@ -69,6 +69,7 @@ export async function callNativeTokenOut(ctx: GalaChainContext, sellTokenDTO: Ex
69
69
  const sale = await fetchAndValidateSale(ctx, sellTokenDTO.vaultAddress);
70
70
  const nativeTokensReceived = calculateNativeTokensReceived(sale, sellTokenDTO.tokenQuantity);
71
71
  const launchpadFeeAddressConfiguration = await fetchLaunchpadFeeAddress(ctx);
72
+
72
73
  return {
73
74
  calculatedQuantity: nativeTokensReceived,
74
75
  extraFees: {
@@ -12,13 +12,16 @@
12
12
  * See the License for the specific language governing permissions and
13
13
  * limitations under the License.
14
14
  */
15
- import { NotFoundError, UnauthorizedError } from "@gala-chain/api";
15
+ import { ChainCallDTO, NotFoundError, UnauthorizedError } from "@gala-chain/api";
16
16
  import { GalaChainContext } from "@gala-chain/chaincode";
17
17
 
18
18
  import { LaunchpadFeeConfig } from "../../api/types";
19
19
  import { fetchLaunchpadFeeAddress } from "../utils";
20
20
 
21
- export async function fetchLaunchpadFeeConfig(ctx: GalaChainContext): Promise<LaunchpadFeeConfig> {
21
+ export async function fetchLaunchpadFeeConfig(
22
+ ctx: GalaChainContext,
23
+ dto: ChainCallDTO
24
+ ): Promise<LaunchpadFeeConfig> {
22
25
  const curatorOrgMsp = process.env.CURATOR_ORG_MSP ?? "CuratorOrg";
23
26
 
24
27
  const platformFeeAddress = await fetchLaunchpadFeeAddress(ctx);