@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.
- package/CLAUDE.md +279 -0
- package/lib/package.json +2 -2
- package/lib/src/chaincode/launchpad/buyExactToken.js +1 -1
- package/lib/src/chaincode/launchpad/buyExactToken.js.map +1 -1
- package/lib/src/chaincode/launchpad/buyWithNative.d.ts.map +1 -1
- package/lib/src/chaincode/launchpad/buyWithNative.js +11 -4
- package/lib/src/chaincode/launchpad/buyWithNative.js.map +1 -1
- package/lib/src/chaincode/launchpad/finaliseSale.js +1 -1
- package/lib/src/chaincode/launchpad/finaliseSale.js.map +1 -1
- package/lib/src/chaincode/launchpad/sellWithNative.d.ts.map +1 -1
- package/lib/src/chaincode/launchpad/sellWithNative.js +9 -1
- package/lib/src/chaincode/launchpad/sellWithNative.js.map +1 -1
- package/lib/src/chaincode/test/launchpadgala.js +1 -1
- package/lib/src/chaincode/test/launchpadgala.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/chaincode/launchpad/buyExactToken.spec.ts +140 -20
- package/src/chaincode/launchpad/buyExactToken.ts +1 -1
- package/src/chaincode/launchpad/buyWithNative.spec.ts +99 -25
- package/src/chaincode/launchpad/buyWithNative.ts +20 -5
- package/src/chaincode/launchpad/callMemeTokenIn.spec.ts +244 -0
- package/src/chaincode/launchpad/callMemeTokenOut.spec.ts +1 -1
- package/src/chaincode/launchpad/callNativeTokenIn.spec.ts +269 -0
- package/src/chaincode/launchpad/callNativeTokenOut.spec.ts +276 -0
- package/src/chaincode/launchpad/configureLaunchpadFeeConfig.spec.ts +202 -0
- package/src/chaincode/launchpad/createSale.spec.ts +259 -0
- package/src/chaincode/launchpad/fetchSaleDetails.spec.ts +141 -0
- package/src/chaincode/launchpad/finaliseSale.ts +1 -1
- package/src/chaincode/launchpad/finalizeTokenAllocation.spec.ts +126 -0
- package/src/chaincode/launchpad/sellExactToken.spec.ts +284 -0
- package/src/chaincode/launchpad/sellWithNative.spec.ts +329 -0
- package/src/chaincode/launchpad/sellWithNative.ts +22 -5
- 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.
|
|
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.
|
|
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 {
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
137
|
-
|
|
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
|
-
|
|
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
|
-
|
|
182
|
-
|
|
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(
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
134
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
|
|
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
|
|
72
|
-
const callNativeTokenInResult = await callNativeTokenIn(ctx,
|
|
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) {
|