@gala-chain/launchpad 1.0.14 → 1.0.16
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/lib/package.json +1 -1
- package/lib/src/api/types/LaunchpadDtos.d.ts +3 -1
- package/lib/src/api/types/LaunchpadDtos.d.ts.map +1 -1
- package/lib/src/api/types/LaunchpadDtos.js +19 -1
- package/lib/src/api/types/LaunchpadDtos.js.map +1 -1
- package/lib/src/api/types/LaunchpadSale.d.ts +8 -1
- package/lib/src/api/types/LaunchpadSale.d.ts.map +1 -1
- package/lib/src/api/types/LaunchpadSale.js +37 -8
- package/lib/src/api/types/LaunchpadSale.js.map +1 -1
- package/lib/src/chaincode/launchpad/buyWithNative.d.ts.map +1 -1
- package/lib/src/chaincode/launchpad/buyWithNative.js +4 -1
- package/lib/src/chaincode/launchpad/buyWithNative.js.map +1 -1
- package/lib/src/chaincode/launchpad/callMemeTokenIn.d.ts.map +1 -1
- package/lib/src/chaincode/launchpad/callMemeTokenIn.js +6 -4
- package/lib/src/chaincode/launchpad/callMemeTokenIn.js.map +1 -1
- package/lib/src/chaincode/launchpad/callMemeTokenOut.d.ts.map +1 -1
- package/lib/src/chaincode/launchpad/callMemeTokenOut.js +19 -8
- package/lib/src/chaincode/launchpad/callMemeTokenOut.js.map +1 -1
- package/lib/src/chaincode/launchpad/callNativeTokenIn.d.ts.map +1 -1
- package/lib/src/chaincode/launchpad/callNativeTokenIn.js +13 -6
- package/lib/src/chaincode/launchpad/callNativeTokenIn.js.map +1 -1
- package/lib/src/chaincode/launchpad/callNativeTokenOut.d.ts.map +1 -1
- package/lib/src/chaincode/launchpad/callNativeTokenOut.js +6 -4
- package/lib/src/chaincode/launchpad/callNativeTokenOut.js.map +1 -1
- package/lib/src/chaincode/launchpad/createSale.d.ts.map +1 -1
- package/lib/src/chaincode/launchpad/createSale.js +22 -6
- package/lib/src/chaincode/launchpad/createSale.js.map +1 -1
- package/lib/src/chaincode/launchpad/finaliseSale.js +2 -2
- package/lib/src/chaincode/launchpad/finaliseSale.js.map +1 -1
- package/lib/src/chaincode/launchpad/sellExactToken.js +1 -1
- package/lib/src/chaincode/launchpad/sellExactToken.js.map +1 -1
- package/lib/src/chaincode/utils/launchpadSaleUtils.d.ts +1 -1
- package/lib/src/chaincode/utils/launchpadSaleUtils.d.ts.map +1 -1
- package/lib/src/chaincode/utils/launchpadSaleUtils.js +14 -2
- package/lib/src/chaincode/utils/launchpadSaleUtils.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/api/types/LaunchpadDtos.ts +22 -1
- package/src/api/types/LaunchpadSale.ts +51 -8
- package/src/chaincode/launchpad/buyExactToken.spec.ts +234 -1
- package/src/chaincode/launchpad/buyWithNative.spec.ts +163 -8
- package/src/chaincode/launchpad/buyWithNative.ts +5 -1
- package/src/chaincode/launchpad/callMemeTokenIn.ts +11 -4
- package/src/chaincode/launchpad/callMemeTokenOut.spec.ts +1 -1
- package/src/chaincode/launchpad/callMemeTokenOut.ts +24 -8
- package/src/chaincode/launchpad/callNativeTokenIn.ts +18 -7
- package/src/chaincode/launchpad/callNativeTokenOut.ts +10 -4
- package/src/chaincode/launchpad/createSale.ts +35 -7
- package/src/chaincode/launchpad/finaliseSale.ts +2 -2
- package/src/chaincode/launchpad/sellExactToken.spec.ts +139 -2
- package/src/chaincode/launchpad/sellExactToken.ts +1 -1
- package/src/chaincode/launchpad/sellWithNative.spec.ts +157 -1
- package/src/chaincode/utils/launchpadSaleUtils.ts +18 -2
package/package.json
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
ArrayNotEmpty,
|
|
28
28
|
IsArray,
|
|
29
29
|
IsBoolean,
|
|
30
|
+
IsInt,
|
|
30
31
|
IsNotEmpty,
|
|
31
32
|
IsNumber,
|
|
32
33
|
IsOptional,
|
|
@@ -115,11 +116,21 @@ export class CreateTokenSaleDTO extends SubmitCallDTO {
|
|
|
115
116
|
@IsOptional()
|
|
116
117
|
public twitterUrl?: string;
|
|
117
118
|
|
|
119
|
+
@IsOptional()
|
|
120
|
+
@IsInt()
|
|
121
|
+
public saleStartTime?: number;
|
|
122
|
+
|
|
118
123
|
@IsOptional()
|
|
119
124
|
@ValidateNested()
|
|
120
125
|
@Type(() => ReverseBondingCurveConfigurationDto)
|
|
121
126
|
public reverseBondingCurveConfiguration?: ReverseBondingCurveConfigurationDto;
|
|
122
127
|
|
|
128
|
+
@IsOptional()
|
|
129
|
+
@IsNumber()
|
|
130
|
+
@Min(100)
|
|
131
|
+
@Max(100)
|
|
132
|
+
public adjustableSupplyMultiplier?: number;
|
|
133
|
+
|
|
123
134
|
constructor(
|
|
124
135
|
tokenName: string,
|
|
125
136
|
tokenSymbol: string,
|
|
@@ -128,7 +139,9 @@ export class CreateTokenSaleDTO extends SubmitCallDTO {
|
|
|
128
139
|
preBuyQuantity: BigNumber,
|
|
129
140
|
tokenCollection: string,
|
|
130
141
|
tokenCategory: string,
|
|
131
|
-
reverseBondingCurveConfiguration?: ReverseBondingCurveConfigurationDto
|
|
142
|
+
reverseBondingCurveConfiguration?: ReverseBondingCurveConfigurationDto,
|
|
143
|
+
saleStartTime?: number,
|
|
144
|
+
adjustableSupplyMultiplier?: number
|
|
132
145
|
) {
|
|
133
146
|
super();
|
|
134
147
|
this.tokenName = tokenName;
|
|
@@ -139,6 +152,14 @@ export class CreateTokenSaleDTO extends SubmitCallDTO {
|
|
|
139
152
|
this.tokenCollection = tokenCollection;
|
|
140
153
|
this.tokenCategory = tokenCategory;
|
|
141
154
|
this.reverseBondingCurveConfiguration = reverseBondingCurveConfiguration;
|
|
155
|
+
|
|
156
|
+
if (saleStartTime !== undefined) {
|
|
157
|
+
this.saleStartTime = saleStartTime;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (adjustableSupplyMultiplier !== undefined) {
|
|
161
|
+
this.adjustableSupplyMultiplier = adjustableSupplyMultiplier;
|
|
162
|
+
}
|
|
142
163
|
}
|
|
143
164
|
}
|
|
144
165
|
|
|
@@ -23,12 +23,22 @@ import {
|
|
|
23
23
|
} from "@gala-chain/api";
|
|
24
24
|
import BigNumber from "bignumber.js";
|
|
25
25
|
import { Exclude, Type } from "class-transformer";
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
IsInt,
|
|
28
|
+
IsNotEmpty,
|
|
29
|
+
IsNumber,
|
|
30
|
+
IsOptional,
|
|
31
|
+
IsPositive,
|
|
32
|
+
IsString,
|
|
33
|
+
Min,
|
|
34
|
+
ValidateNested
|
|
35
|
+
} from "class-validator";
|
|
27
36
|
import { JSONSchema } from "class-validator-jsonschema";
|
|
28
37
|
|
|
29
38
|
import { ReverseBondingCurveConfigurationChainObject } from "./LaunchpadDtos";
|
|
30
39
|
|
|
31
40
|
export enum SaleStatus {
|
|
41
|
+
UPCOMING = "Upcoming",
|
|
32
42
|
ONGOING = "Ongoing",
|
|
33
43
|
END = "Finished"
|
|
34
44
|
}
|
|
@@ -49,6 +59,10 @@ export class LaunchpadSale extends ChainObject {
|
|
|
49
59
|
@IsNotEmpty()
|
|
50
60
|
public saleStatus: SaleStatus;
|
|
51
61
|
|
|
62
|
+
@IsOptional()
|
|
63
|
+
@IsInt()
|
|
64
|
+
public saleStartTime?: number;
|
|
65
|
+
|
|
52
66
|
@IsNotEmpty()
|
|
53
67
|
@ValidateNested()
|
|
54
68
|
@Type(() => TokenInstanceKey)
|
|
@@ -88,6 +102,15 @@ export class LaunchpadSale extends ChainObject {
|
|
|
88
102
|
@Type(() => ReverseBondingCurveConfigurationChainObject)
|
|
89
103
|
public reverseBondingCurveConfiguration?: ReverseBondingCurveConfigurationChainObject;
|
|
90
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Optional multiplier used to scale token output (both in bonding curve calculations and initial
|
|
107
|
+
* minting) while preserving the same economics and curve shape.
|
|
108
|
+
*/
|
|
109
|
+
@IsOptional()
|
|
110
|
+
@IsNumber()
|
|
111
|
+
@Min(0)
|
|
112
|
+
public adjustableSupplyMultiplier?: number;
|
|
113
|
+
|
|
91
114
|
@JSONSchema({
|
|
92
115
|
description:
|
|
93
116
|
"The market cap has been calculated using the bonding curve equations to approximate a specific final price."
|
|
@@ -104,7 +127,7 @@ export class LaunchpadSale extends ChainObject {
|
|
|
104
127
|
@JSONSchema({
|
|
105
128
|
description: "The decimals of the selling token."
|
|
106
129
|
})
|
|
107
|
-
public static SELLING_TOKEN_DECIMALS =
|
|
130
|
+
public static SELLING_TOKEN_DECIMALS = 9;
|
|
108
131
|
|
|
109
132
|
@JSONSchema({
|
|
110
133
|
description: "The decimals of the native token."
|
|
@@ -115,7 +138,9 @@ export class LaunchpadSale extends ChainObject {
|
|
|
115
138
|
vaultAddress: UserAlias,
|
|
116
139
|
sellingToken: TokenInstanceKey,
|
|
117
140
|
reverseBondingCurveConfiguration: ReverseBondingCurveConfigurationChainObject | undefined,
|
|
118
|
-
saleOwner: UserAlias
|
|
141
|
+
saleOwner: UserAlias,
|
|
142
|
+
saleStartTime?: number | undefined,
|
|
143
|
+
adjustableSupplyMultiplier?: number
|
|
119
144
|
) {
|
|
120
145
|
super();
|
|
121
146
|
|
|
@@ -123,13 +148,31 @@ export class LaunchpadSale extends ChainObject {
|
|
|
123
148
|
this.saleOwner = saleOwner;
|
|
124
149
|
|
|
125
150
|
this.sellingToken = sellingToken;
|
|
126
|
-
this.sellingTokenQuantity = "1e+7";
|
|
127
151
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
152
|
+
if (saleStartTime) {
|
|
153
|
+
this.saleStartTime = saleStartTime;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (this.saleStartTime !== undefined && this.saleStartTime > 0) {
|
|
157
|
+
this.saleStatus = SaleStatus.UPCOMING;
|
|
158
|
+
} else {
|
|
159
|
+
this.saleStatus = SaleStatus.ONGOING;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (adjustableSupplyMultiplier !== undefined) {
|
|
163
|
+
this.adjustableSupplyMultiplier = adjustableSupplyMultiplier;
|
|
164
|
+
this.basePrice = new BigNumber(LaunchpadSale.BASE_PRICE).dividedBy(adjustableSupplyMultiplier);
|
|
165
|
+
this.exponentFactor = new BigNumber("1166069000000");
|
|
166
|
+
this.maxSupply = new BigNumber("1e+7").times(adjustableSupplyMultiplier);
|
|
167
|
+
this.sellingTokenQuantity = new BigNumber("1e+7").times(adjustableSupplyMultiplier).toString();
|
|
168
|
+
} else {
|
|
169
|
+
this.basePrice = new BigNumber(LaunchpadSale.BASE_PRICE);
|
|
170
|
+
this.exponentFactor = new BigNumber("1166069000000");
|
|
171
|
+
this.maxSupply = new BigNumber("1e+7");
|
|
172
|
+
this.sellingTokenQuantity = "1e+7";
|
|
173
|
+
}
|
|
174
|
+
|
|
131
175
|
this.euler = new BigNumber("2.7182818284590452353602874713527");
|
|
132
|
-
this.saleStatus = SaleStatus.ONGOING;
|
|
133
176
|
|
|
134
177
|
const nativeTokenInstance = new TokenInstanceKey();
|
|
135
178
|
nativeTokenInstance.collection = "GALA";
|
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
LaunchpadFeeConfig,
|
|
33
33
|
LaunchpadSale,
|
|
34
34
|
NativeTokenQuantityDto,
|
|
35
|
+
SaleStatus,
|
|
35
36
|
TradeResDto
|
|
36
37
|
} from "../../api/types";
|
|
37
38
|
import { LaunchpadContract } from "../LaunchpadContract";
|
|
@@ -60,7 +61,7 @@ describe("buyWithNative", () => {
|
|
|
60
61
|
|
|
61
62
|
launchpadGalaClass = plainToInstance(TokenClass, {
|
|
62
63
|
...launchpadgala.tokenClassPlain(),
|
|
63
|
-
decimals:
|
|
64
|
+
decimals: LaunchpadSale.NATIVE_TOKEN_DECIMALS
|
|
64
65
|
});
|
|
65
66
|
|
|
66
67
|
vaultAddress = asValidUserAlias(`service|${launchpadGalaClassKey.toStringKey()}$launchpad`);
|
|
@@ -304,4 +305,236 @@ describe("buyWithNative", () => {
|
|
|
304
305
|
)
|
|
305
306
|
);
|
|
306
307
|
});
|
|
308
|
+
|
|
309
|
+
test("Full sale purchase yields expected totals for 10 Million token sale", async () => {
|
|
310
|
+
// Given
|
|
311
|
+
salelaunchpadGalaBalance = plainToInstance(TokenBalance, {
|
|
312
|
+
...launchpadgala.tokenBalance(),
|
|
313
|
+
owner: vaultAddress,
|
|
314
|
+
quantity: new BigNumber("0")
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
saleCurrencyBalance = plainToInstance(TokenBalance, {
|
|
318
|
+
...currency.tokenBalance(),
|
|
319
|
+
owner: vaultAddress,
|
|
320
|
+
quantity: new BigNumber("2e+7")
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
userlaunchpadGalaBalance = plainToInstance(TokenBalance, {
|
|
324
|
+
...launchpadgala.tokenBalance(),
|
|
325
|
+
owner: users.testUser1.identityKey,
|
|
326
|
+
quantity: new BigNumber("100000000")
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
userCurrencyBalance = plainToInstance(TokenBalance, {
|
|
330
|
+
...currency.tokenBalance(),
|
|
331
|
+
owner: users.testUser1.identityKey,
|
|
332
|
+
quantity: new BigNumber("0")
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
const launchpadConfig = new LaunchpadFeeConfig(users.testUser2.identityKey, Number("0.001"), [
|
|
336
|
+
users.testUser2.identityKey
|
|
337
|
+
]);
|
|
338
|
+
|
|
339
|
+
const userStartingGalaQuantity = userlaunchpadGalaBalance.getQuantityTotal();
|
|
340
|
+
|
|
341
|
+
const { ctx, contract, getWrites } = fixture(LaunchpadContract)
|
|
342
|
+
.registeredUsers(users.testUser1)
|
|
343
|
+
.savedState(
|
|
344
|
+
currencyClass,
|
|
345
|
+
currencyInstance,
|
|
346
|
+
launchpadConfig,
|
|
347
|
+
launchpadGalaClass,
|
|
348
|
+
launchpadGalaInstance,
|
|
349
|
+
sale,
|
|
350
|
+
salelaunchpadGalaBalance,
|
|
351
|
+
saleCurrencyBalance,
|
|
352
|
+
userlaunchpadGalaBalance,
|
|
353
|
+
userCurrencyBalance
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
// When
|
|
357
|
+
for (let i = 0; i < 50; i++) {
|
|
358
|
+
const dto = new ExactTokenQuantityDto(vaultAddress, new BigNumber("200000"));
|
|
359
|
+
|
|
360
|
+
dto.uniqueKey = randomUniqueKey();
|
|
361
|
+
dto.sign(users.testUser1.privateKey);
|
|
362
|
+
|
|
363
|
+
const buyTokenRes = await contract.BuyExactToken(ctx, dto);
|
|
364
|
+
|
|
365
|
+
expect(buyTokenRes).toEqual(transactionSuccess());
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Then
|
|
369
|
+
const saleKey = sale.getCompositeKey();
|
|
370
|
+
const userGalaBalanceKey = userlaunchpadGalaBalance.getCompositeKey();
|
|
371
|
+
const userMemeBalanceKey = userCurrencyBalance.getCompositeKey();
|
|
372
|
+
|
|
373
|
+
const writes = getWrites();
|
|
374
|
+
|
|
375
|
+
const finalSaleData = JSON.parse(writes[saleKey]);
|
|
376
|
+
const finalUserGalaBalanceData = JSON.parse(writes[userGalaBalanceKey]);
|
|
377
|
+
const finalUserMemeBalanceData = JSON.parse(writes[userMemeBalanceKey]);
|
|
378
|
+
|
|
379
|
+
expect(finalSaleData).toEqual(
|
|
380
|
+
expect.objectContaining({
|
|
381
|
+
saleStatus: SaleStatus.END
|
|
382
|
+
})
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
const finalUserGalaQuantity = new BigNumber(finalUserGalaBalanceData.quantity);
|
|
386
|
+
const finalUserMemeQuantity = new BigNumber(finalUserMemeBalanceData.quantity);
|
|
387
|
+
|
|
388
|
+
// user bought full sale quantity of ten million
|
|
389
|
+
expect(finalUserMemeQuantity).toEqual(new BigNumber("10000000"));
|
|
390
|
+
expect(userStartingGalaQuantity.minus(finalUserGalaQuantity)).toEqual(new BigNumber("1560577.53780865"));
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
test("Adjustable supply: single transaction", async () => {
|
|
394
|
+
// Given
|
|
395
|
+
const multiplier = 100;
|
|
396
|
+
|
|
397
|
+
sale = new LaunchpadSale(
|
|
398
|
+
vaultAddress,
|
|
399
|
+
currencyInstance.instanceKeyObj(),
|
|
400
|
+
undefined,
|
|
401
|
+
users.testUser1.identityKey,
|
|
402
|
+
undefined,
|
|
403
|
+
multiplier
|
|
404
|
+
);
|
|
405
|
+
|
|
406
|
+
const { ctx, contract } = fixture(LaunchpadContract)
|
|
407
|
+
.registeredUsers(users.testUser1)
|
|
408
|
+
.savedState(
|
|
409
|
+
currencyClass,
|
|
410
|
+
currencyInstance,
|
|
411
|
+
launchpadGalaClass,
|
|
412
|
+
launchpadGalaInstance,
|
|
413
|
+
sale,
|
|
414
|
+
salelaunchpadGalaBalance,
|
|
415
|
+
saleCurrencyBalance,
|
|
416
|
+
userlaunchpadGalaBalance,
|
|
417
|
+
userCurrencyBalance
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
const dto = new ExactTokenQuantityDto(vaultAddress, new BigNumber("500").times(multiplier));
|
|
421
|
+
|
|
422
|
+
dto.uniqueKey = randomUniqueKey();
|
|
423
|
+
dto.sign(users.testUser1.privateKey);
|
|
424
|
+
|
|
425
|
+
const expectedResponse = plainToInstance(TradeResDto, {
|
|
426
|
+
inputQuantity: "0.00825575",
|
|
427
|
+
totalFees: "0",
|
|
428
|
+
totalTokenSold: new BigNumber("500").times(multiplier).toString(),
|
|
429
|
+
outputQuantity: new BigNumber("500").times(multiplier).toString(),
|
|
430
|
+
tokenName: "AUTOMATEDTESTCOIN",
|
|
431
|
+
tradeType: "Buy",
|
|
432
|
+
uniqueKey: dto.uniqueKey,
|
|
433
|
+
vaultAddress: "service|GALA$Unit$none$none$launchpad",
|
|
434
|
+
userAddress: "client|testUser1",
|
|
435
|
+
isFinalized: false,
|
|
436
|
+
functionName: "BuyExactToken"
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
// When
|
|
440
|
+
const buyTokenRes = await contract.BuyExactToken(ctx, dto);
|
|
441
|
+
|
|
442
|
+
// Then
|
|
443
|
+
expect(buyTokenRes).toEqual(transactionSuccess(expectedResponse));
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
test("Adjustable supply: Full sale purchase yields expected totals for 1 billion token sale", async () => {
|
|
447
|
+
// Given
|
|
448
|
+
const multiplier = 100;
|
|
449
|
+
|
|
450
|
+
sale = new LaunchpadSale(
|
|
451
|
+
vaultAddress,
|
|
452
|
+
currencyInstance.instanceKeyObj(),
|
|
453
|
+
undefined,
|
|
454
|
+
users.testUser1.identityKey,
|
|
455
|
+
undefined,
|
|
456
|
+
multiplier
|
|
457
|
+
);
|
|
458
|
+
|
|
459
|
+
salelaunchpadGalaBalance = plainToInstance(TokenBalance, {
|
|
460
|
+
...launchpadgala.tokenBalance(),
|
|
461
|
+
owner: vaultAddress,
|
|
462
|
+
quantity: new BigNumber("0")
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
saleCurrencyBalance = plainToInstance(TokenBalance, {
|
|
466
|
+
...currency.tokenBalance(),
|
|
467
|
+
owner: vaultAddress,
|
|
468
|
+
quantity: new BigNumber("2e+7").times(multiplier)
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
userlaunchpadGalaBalance = plainToInstance(TokenBalance, {
|
|
472
|
+
...launchpadgala.tokenBalance(),
|
|
473
|
+
owner: users.testUser1.identityKey,
|
|
474
|
+
quantity: new BigNumber("100000000")
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
userCurrencyBalance = plainToInstance(TokenBalance, {
|
|
478
|
+
...currency.tokenBalance(),
|
|
479
|
+
owner: users.testUser1.identityKey,
|
|
480
|
+
quantity: new BigNumber("0")
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
const launchpadConfig = new LaunchpadFeeConfig(users.testUser2.identityKey, Number("0.001"), [
|
|
484
|
+
users.testUser2.identityKey
|
|
485
|
+
]);
|
|
486
|
+
|
|
487
|
+
const userStartingGalaQuantity = userlaunchpadGalaBalance.getQuantityTotal();
|
|
488
|
+
|
|
489
|
+
const { ctx, contract, getWrites } = fixture(LaunchpadContract)
|
|
490
|
+
.registeredUsers(users.testUser1)
|
|
491
|
+
.savedState(
|
|
492
|
+
currencyClass,
|
|
493
|
+
currencyInstance,
|
|
494
|
+
launchpadConfig,
|
|
495
|
+
launchpadGalaClass,
|
|
496
|
+
launchpadGalaInstance,
|
|
497
|
+
sale,
|
|
498
|
+
salelaunchpadGalaBalance,
|
|
499
|
+
saleCurrencyBalance,
|
|
500
|
+
userlaunchpadGalaBalance,
|
|
501
|
+
userCurrencyBalance
|
|
502
|
+
);
|
|
503
|
+
|
|
504
|
+
// When
|
|
505
|
+
for (let i = 0; i < 50; i++) {
|
|
506
|
+
const dto = new ExactTokenQuantityDto(vaultAddress, new BigNumber("200000").times(multiplier));
|
|
507
|
+
|
|
508
|
+
dto.uniqueKey = randomUniqueKey();
|
|
509
|
+
dto.sign(users.testUser1.privateKey);
|
|
510
|
+
|
|
511
|
+
const buyTokenRes = await contract.BuyExactToken(ctx, dto);
|
|
512
|
+
|
|
513
|
+
expect(buyTokenRes).toEqual(transactionSuccess());
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Then
|
|
517
|
+
const saleKey = sale.getCompositeKey();
|
|
518
|
+
const userGalaBalanceKey = userlaunchpadGalaBalance.getCompositeKey();
|
|
519
|
+
const userMemeBalanceKey = userCurrencyBalance.getCompositeKey();
|
|
520
|
+
|
|
521
|
+
const writes = getWrites();
|
|
522
|
+
|
|
523
|
+
const finalSaleData = JSON.parse(writes[saleKey]);
|
|
524
|
+
const finalUserGalaBalanceData = JSON.parse(writes[userGalaBalanceKey]);
|
|
525
|
+
const finalUserMemeBalanceData = JSON.parse(writes[userMemeBalanceKey]);
|
|
526
|
+
|
|
527
|
+
expect(finalSaleData).toEqual(
|
|
528
|
+
expect.objectContaining({
|
|
529
|
+
saleStatus: SaleStatus.END
|
|
530
|
+
})
|
|
531
|
+
);
|
|
532
|
+
|
|
533
|
+
const finalUserGalaQuantity = new BigNumber(finalUserGalaBalanceData.quantity);
|
|
534
|
+
const finalUserMemeQuantity = new BigNumber(finalUserMemeBalanceData.quantity);
|
|
535
|
+
|
|
536
|
+
// user bought full sale quantity of ten million
|
|
537
|
+
expect(finalUserMemeQuantity).toEqual(new BigNumber("10000000").times(multiplier));
|
|
538
|
+
expect(userStartingGalaQuantity.minus(finalUserGalaQuantity)).toEqual(new BigNumber("1560577.53780865"));
|
|
539
|
+
});
|
|
307
540
|
});
|
|
@@ -27,7 +27,13 @@ import { currency, fixture, transactionError, transactionSuccess, users } from "
|
|
|
27
27
|
import BigNumber from "bignumber.js";
|
|
28
28
|
import { plainToInstance } from "class-transformer";
|
|
29
29
|
|
|
30
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
ExactTokenQuantityDto,
|
|
32
|
+
LaunchpadFeeConfig,
|
|
33
|
+
LaunchpadSale,
|
|
34
|
+
NativeTokenQuantityDto,
|
|
35
|
+
TradeResDto
|
|
36
|
+
} from "../../api/types";
|
|
31
37
|
import { LaunchpadContract } from "../LaunchpadContract";
|
|
32
38
|
import launchpadgala from "../test/launchpadgala";
|
|
33
39
|
|
|
@@ -46,13 +52,15 @@ describe("buyWithNative", () => {
|
|
|
46
52
|
|
|
47
53
|
beforeEach(() => {
|
|
48
54
|
//Given
|
|
49
|
-
currencyClass =
|
|
55
|
+
currencyClass = plainToInstance(TokenClass, {
|
|
56
|
+
...currency.tokenClassPlain(),
|
|
57
|
+
decimals: LaunchpadSale.SELLING_TOKEN_DECIMALS
|
|
58
|
+
});
|
|
50
59
|
currencyInstance = currency.tokenInstance();
|
|
51
|
-
launchpadGalaClass = launchpadgala.tokenClass();
|
|
52
60
|
|
|
53
61
|
launchpadGalaClass = plainToInstance(TokenClass, {
|
|
54
62
|
...launchpadgala.tokenClassPlain(),
|
|
55
|
-
decimals:
|
|
63
|
+
decimals: LaunchpadSale.NATIVE_TOKEN_DECIMALS
|
|
56
64
|
});
|
|
57
65
|
|
|
58
66
|
launchpadGalaInstance = launchpadgala.tokenInstance();
|
|
@@ -154,8 +162,8 @@ describe("buyWithNative", () => {
|
|
|
154
162
|
const expectedResponse = plainToInstance(TradeResDto, {
|
|
155
163
|
inputQuantity: "150",
|
|
156
164
|
totalFees: "0",
|
|
157
|
-
totalTokenSold: "2101667.
|
|
158
|
-
outputQuantity: "2101667.
|
|
165
|
+
totalTokenSold: "2101667.889065163",
|
|
166
|
+
outputQuantity: "2101667.889065163",
|
|
159
167
|
tokenName: "AUTOMATEDTESTCOIN",
|
|
160
168
|
tradeType: "Buy",
|
|
161
169
|
vaultAddress: "service|GALA$Unit$none$none$launchpad",
|
|
@@ -200,8 +208,8 @@ describe("buyWithNative", () => {
|
|
|
200
208
|
const expectedResponse = plainToInstance(TradeResDto, {
|
|
201
209
|
inputQuantity: "1000",
|
|
202
210
|
totalFees: "320",
|
|
203
|
-
totalTokenSold: "3663321.
|
|
204
|
-
outputQuantity: "3663321.
|
|
211
|
+
totalTokenSold: "3663321.362813055",
|
|
212
|
+
outputQuantity: "3663321.362813055",
|
|
205
213
|
tokenName: "AUTOMATEDTESTCOIN",
|
|
206
214
|
tradeType: "Buy",
|
|
207
215
|
vaultAddress: "service|GALA$Unit$none$none$launchpad",
|
|
@@ -256,4 +264,151 @@ describe("buyWithNative", () => {
|
|
|
256
264
|
)
|
|
257
265
|
);
|
|
258
266
|
});
|
|
267
|
+
|
|
268
|
+
it("should return inverse native tokens when buying and selling tokens", async () => {
|
|
269
|
+
//Given
|
|
270
|
+
salelaunchpadGalaBalance.subtractQuantity(new BigNumber("1e+7"), 0);
|
|
271
|
+
saleCurrencyBalance.addQuantity(new BigNumber("1e+7"));
|
|
272
|
+
userlaunchpadGalaBalance.addQuantity(new BigNumber("1e+7"));
|
|
273
|
+
const { ctx, contract } = fixture(LaunchpadContract)
|
|
274
|
+
.registeredUsers(users.testUser1)
|
|
275
|
+
.savedState(
|
|
276
|
+
currencyClass,
|
|
277
|
+
currencyInstance,
|
|
278
|
+
launchpadGalaClass,
|
|
279
|
+
launchpadGalaInstance,
|
|
280
|
+
sale,
|
|
281
|
+
salelaunchpadGalaBalance,
|
|
282
|
+
saleCurrencyBalance,
|
|
283
|
+
userlaunchpadGalaBalance,
|
|
284
|
+
userCurrencyBalance
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
const arr: string[] = [
|
|
288
|
+
"31.27520343",
|
|
289
|
+
"100.3731319",
|
|
290
|
+
"322.1326962",
|
|
291
|
+
"1033.837164",
|
|
292
|
+
"3317.947211",
|
|
293
|
+
"10648.46001",
|
|
294
|
+
"34174.65481",
|
|
295
|
+
"109678.4915",
|
|
296
|
+
"351996.8694",
|
|
297
|
+
"1129679.88999"
|
|
298
|
+
];
|
|
299
|
+
|
|
300
|
+
const sellingArr: string[] = [];
|
|
301
|
+
const sellArr: string[] = [
|
|
302
|
+
"1000000.000060721",
|
|
303
|
+
"1000000.000017497",
|
|
304
|
+
"999999.999868935",
|
|
305
|
+
"1000000.000187964",
|
|
306
|
+
"999999.999879254",
|
|
307
|
+
"999999.999929098",
|
|
308
|
+
"1000000.000096442",
|
|
309
|
+
"999999.999800915",
|
|
310
|
+
"1000000.000112742",
|
|
311
|
+
"999999.000293124"
|
|
312
|
+
];
|
|
313
|
+
|
|
314
|
+
// When - Buy tokens using native token amounts from arr
|
|
315
|
+
for (let i = 0; i < arr.length; i++) {
|
|
316
|
+
let nativeCoins = Number(arr[i]);
|
|
317
|
+
nativeCoins = roundToDecimal(nativeCoins, 8);
|
|
318
|
+
|
|
319
|
+
const dto = new NativeTokenQuantityDto(vaultAddress, new BigNumber(nativeCoins));
|
|
320
|
+
dto.uniqueKey = randomUniqueKey();
|
|
321
|
+
dto.sign(users.testUser1.privateKey);
|
|
322
|
+
|
|
323
|
+
const buyTokenRes = await contract.BuyWithNative(ctx, dto);
|
|
324
|
+
expect(buyTokenRes).toEqual(transactionSuccess());
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// When - Sell tokens back in reverse order
|
|
328
|
+
for (let i = sellArr.length - 1; i >= 0; i--) {
|
|
329
|
+
const sellDto = new ExactTokenQuantityDto(vaultAddress, new BigNumber(sellArr[i]));
|
|
330
|
+
sellDto.uniqueKey = randomUniqueKey();
|
|
331
|
+
sellDto.sign(users.testUser1.privateKey);
|
|
332
|
+
|
|
333
|
+
const sellTokenRes = await contract.SellExactToken(ctx, sellDto);
|
|
334
|
+
expect(sellTokenRes).toEqual(transactionSuccess());
|
|
335
|
+
sellingArr.push(sellTokenRes.Data?.outputQuantity || "0");
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Then - Verify sellingArr is the inverse of arr with the extra ""
|
|
339
|
+
const expectedSellingArr = [...arr].reverse();
|
|
340
|
+
|
|
341
|
+
// Compare each element, checking that values match up to 4 decimal places
|
|
342
|
+
const tolerance = new BigNumber("0.0001"); // 4 decimal places tolerance
|
|
343
|
+
for (let i = 1; i < sellingArr.length; i++) {
|
|
344
|
+
const sellingValue = new BigNumber(sellingArr[i]);
|
|
345
|
+
const expectedValue = new BigNumber(expectedSellingArr[i]);
|
|
346
|
+
const difference = sellingValue.minus(expectedValue).abs();
|
|
347
|
+
expect(difference.isLessThanOrEqualTo(tolerance)).toBe(true);
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
test("Adjustable supply: Single transaction", async () => {
|
|
352
|
+
//Given
|
|
353
|
+
const multiplier = 100;
|
|
354
|
+
|
|
355
|
+
sale = new LaunchpadSale(
|
|
356
|
+
vaultAddress,
|
|
357
|
+
currencyInstance.instanceKeyObj(),
|
|
358
|
+
undefined,
|
|
359
|
+
users.testUser1.identityKey,
|
|
360
|
+
undefined,
|
|
361
|
+
multiplier
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
saleCurrencyBalance = plainToInstance(TokenBalance, {
|
|
365
|
+
...currency.tokenBalance(),
|
|
366
|
+
owner: vaultAddress,
|
|
367
|
+
quantity: new BigNumber("2e+7").times(multiplier)
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
const { ctx, contract } = fixture(LaunchpadContract)
|
|
371
|
+
.registeredUsers(users.testUser1)
|
|
372
|
+
.savedState(
|
|
373
|
+
currencyClass,
|
|
374
|
+
currencyInstance,
|
|
375
|
+
launchpadGalaClass,
|
|
376
|
+
launchpadGalaInstance,
|
|
377
|
+
sale,
|
|
378
|
+
salelaunchpadGalaBalance,
|
|
379
|
+
saleCurrencyBalance,
|
|
380
|
+
userlaunchpadGalaBalance,
|
|
381
|
+
userCurrencyBalance
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
const dto = new NativeTokenQuantityDto(vaultAddress, new BigNumber("150"));
|
|
385
|
+
|
|
386
|
+
dto.uniqueKey = randomUniqueKey();
|
|
387
|
+
dto.sign(users.testUser1.privateKey);
|
|
388
|
+
|
|
389
|
+
const expectedOutput = new BigNumber("2101667.8890651635").times(multiplier).toString();
|
|
390
|
+
const expectedResponse = plainToInstance(TradeResDto, {
|
|
391
|
+
inputQuantity: "150",
|
|
392
|
+
totalFees: "0",
|
|
393
|
+
totalTokenSold: expectedOutput,
|
|
394
|
+
outputQuantity: expectedOutput,
|
|
395
|
+
tokenName: "AUTOMATEDTESTCOIN",
|
|
396
|
+
tradeType: "Buy",
|
|
397
|
+
vaultAddress: "service|GALA$Unit$none$none$launchpad",
|
|
398
|
+
userAddress: "client|testUser1",
|
|
399
|
+
isFinalized: false,
|
|
400
|
+
functionName: "BuyWithNative",
|
|
401
|
+
uniqueKey: dto.uniqueKey
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
//When
|
|
405
|
+
const buyTokenRes = await contract.BuyWithNative(ctx, dto);
|
|
406
|
+
|
|
407
|
+
//Then
|
|
408
|
+
expect(buyTokenRes).toEqual(transactionSuccess(expectedResponse));
|
|
409
|
+
});
|
|
259
410
|
});
|
|
411
|
+
function roundToDecimal(value: number, decimals: number) {
|
|
412
|
+
const factor = Math.pow(10, decimals);
|
|
413
|
+
return Math.round(value * factor) / factor;
|
|
414
|
+
}
|
|
@@ -59,10 +59,14 @@ export async function buyWithNative(
|
|
|
59
59
|
const memeToken = sale.fetchSellingTokenInstanceKey();
|
|
60
60
|
|
|
61
61
|
// If native tokens required exceeds the market cap, the sale can be finalized
|
|
62
|
+
const supplyCap =
|
|
63
|
+
sale.adjustableSupplyMultiplier !== undefined
|
|
64
|
+
? new BigNumber(LaunchpadSale.MARKET_CAP).times(sale.adjustableSupplyMultiplier).toString()
|
|
65
|
+
: LaunchpadSale.MARKET_CAP;
|
|
62
66
|
if (
|
|
63
67
|
buyTokenDTO.nativeTokenQuantity
|
|
64
68
|
.plus(new BigNumber(sale.nativeTokenQuantity))
|
|
65
|
-
.isGreaterThanOrEqualTo(new BigNumber(
|
|
69
|
+
.isGreaterThanOrEqualTo(new BigNumber(supplyCap))
|
|
66
70
|
) {
|
|
67
71
|
isSaleFinalized = true;
|
|
68
72
|
}
|
|
@@ -30,15 +30,21 @@ function calculateMemeTokensRequired(
|
|
|
30
30
|
sale: LaunchpadSale,
|
|
31
31
|
requestedNativeTokenQuantity: BigNumber,
|
|
32
32
|
nativeTokenDecimals: number,
|
|
33
|
-
sellingTokenDecimals: number
|
|
33
|
+
sellingTokenDecimals: number,
|
|
34
|
+
adjustableSupplyMultiplier?: number
|
|
34
35
|
): [string, string] {
|
|
35
36
|
const totalTokensSold = new Decimal(sale.fetchTokensSold()); // current tokens sold / x
|
|
36
37
|
let nativeTokens = new Decimal(requestedNativeTokenQuantity.toString()).toDecimalPlaces(
|
|
37
38
|
nativeTokenDecimals,
|
|
38
39
|
Decimal.ROUND_DOWN
|
|
39
40
|
);
|
|
40
|
-
|
|
41
|
-
const
|
|
41
|
+
|
|
42
|
+
const basePrice =
|
|
43
|
+
adjustableSupplyMultiplier && adjustableSupplyMultiplier > 0
|
|
44
|
+
? new Decimal(LaunchpadSale.BASE_PRICE).dividedBy(adjustableSupplyMultiplier)
|
|
45
|
+
: new Decimal(LaunchpadSale.BASE_PRICE);
|
|
46
|
+
|
|
47
|
+
const { exponentFactor, euler, decimals } = getBondingConstants(adjustableSupplyMultiplier);
|
|
42
48
|
|
|
43
49
|
const nativeTokenInVault = new Decimal(sale.nativeTokenQuantity);
|
|
44
50
|
if (nativeTokens.greaterThan(nativeTokenInVault)) {
|
|
@@ -90,7 +96,8 @@ export async function callMemeTokenIn(
|
|
|
90
96
|
sale,
|
|
91
97
|
sellTokenDTO.nativeTokenQuantity,
|
|
92
98
|
nativeTokenDecimals,
|
|
93
|
-
sellingTokenDecimals
|
|
99
|
+
sellingTokenDecimals,
|
|
100
|
+
sale.adjustableSupplyMultiplier
|
|
94
101
|
);
|
|
95
102
|
|
|
96
103
|
return {
|
|
@@ -143,7 +143,7 @@ describe("callMemeTokenOut", () => {
|
|
|
143
143
|
|
|
144
144
|
// Then
|
|
145
145
|
expect(response.Data).toMatchObject({
|
|
146
|
-
calculatedQuantity: "458291.
|
|
146
|
+
calculatedQuantity: "458291.302953644",
|
|
147
147
|
extraFees: { reverseBondingCurve: "0", transactionFees: "0.00000000" }
|
|
148
148
|
});
|
|
149
149
|
});
|