@gala-chain/launchpad 1.0.15 → 1.0.17
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 +2 -7
- package/lib/src/api/types/LaunchpadDtos.d.ts.map +1 -1
- package/lib/src/api/types/LaunchpadDtos.js +11 -28
- package/lib/src/api/types/LaunchpadDtos.js.map +1 -1
- package/lib/src/api/types/LaunchpadSale.d.ts +6 -1
- package/lib/src/api/types/LaunchpadSale.d.ts.map +1 -1
- package/lib/src/api/types/LaunchpadSale.js +21 -6
- 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 +8 -13
- 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/utils/launchpadSaleUtils.d.ts +1 -1
- package/lib/src/chaincode/utils/launchpadSaleUtils.d.ts.map +1 -1
- package/lib/src/chaincode/utils/launchpadSaleUtils.js +5 -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 +12 -22
- package/src/api/types/LaunchpadSale.ts +34 -6
- package/src/chaincode/launchpad/buyExactToken.spec.ts +233 -0
- package/src/chaincode/launchpad/buyWithNative.spec.ts +61 -1
- package/src/chaincode/launchpad/buyWithNative.ts +5 -1
- package/src/chaincode/launchpad/callMemeTokenIn.ts +11 -4
- 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.spec.ts +1 -87
- package/src/chaincode/launchpad/createSale.ts +11 -14
- package/src/chaincode/launchpad/finaliseSale.ts +2 -2
- package/src/chaincode/launchpad/sellExactToken.spec.ts +139 -2
- package/src/chaincode/launchpad/sellWithNative.spec.ts +157 -1
- package/src/chaincode/utils/launchpadSaleUtils.ts +6 -2
package/package.json
CHANGED
|
@@ -104,18 +104,6 @@ export class CreateTokenSaleDTO extends SubmitCallDTO {
|
|
|
104
104
|
@BigNumberProperty()
|
|
105
105
|
public preBuyQuantity: BigNumber;
|
|
106
106
|
|
|
107
|
-
@IsString()
|
|
108
|
-
@IsOptional()
|
|
109
|
-
public websiteUrl?: string;
|
|
110
|
-
|
|
111
|
-
@IsString()
|
|
112
|
-
@IsOptional()
|
|
113
|
-
public telegramUrl?: string;
|
|
114
|
-
|
|
115
|
-
@IsString()
|
|
116
|
-
@IsOptional()
|
|
117
|
-
public twitterUrl?: string;
|
|
118
|
-
|
|
119
107
|
@IsOptional()
|
|
120
108
|
@IsInt()
|
|
121
109
|
public saleStartTime?: number;
|
|
@@ -125,6 +113,12 @@ export class CreateTokenSaleDTO extends SubmitCallDTO {
|
|
|
125
113
|
@Type(() => ReverseBondingCurveConfigurationDto)
|
|
126
114
|
public reverseBondingCurveConfiguration?: ReverseBondingCurveConfigurationDto;
|
|
127
115
|
|
|
116
|
+
@IsOptional()
|
|
117
|
+
@IsNumber()
|
|
118
|
+
@Min(100)
|
|
119
|
+
@Max(100)
|
|
120
|
+
public adjustableSupplyMultiplier?: number;
|
|
121
|
+
|
|
128
122
|
constructor(
|
|
129
123
|
tokenName: string,
|
|
130
124
|
tokenSymbol: string,
|
|
@@ -134,7 +128,8 @@ export class CreateTokenSaleDTO extends SubmitCallDTO {
|
|
|
134
128
|
tokenCollection: string,
|
|
135
129
|
tokenCategory: string,
|
|
136
130
|
reverseBondingCurveConfiguration?: ReverseBondingCurveConfigurationDto,
|
|
137
|
-
saleStartTime?: number
|
|
131
|
+
saleStartTime?: number,
|
|
132
|
+
adjustableSupplyMultiplier?: number
|
|
138
133
|
) {
|
|
139
134
|
super();
|
|
140
135
|
this.tokenName = tokenName;
|
|
@@ -149,6 +144,10 @@ export class CreateTokenSaleDTO extends SubmitCallDTO {
|
|
|
149
144
|
if (saleStartTime !== undefined) {
|
|
150
145
|
this.saleStartTime = saleStartTime;
|
|
151
146
|
}
|
|
147
|
+
|
|
148
|
+
if (adjustableSupplyMultiplier !== undefined) {
|
|
149
|
+
this.adjustableSupplyMultiplier = adjustableSupplyMultiplier;
|
|
150
|
+
}
|
|
152
151
|
}
|
|
153
152
|
}
|
|
154
153
|
|
|
@@ -165,15 +164,6 @@ export class CreateSaleResDto {
|
|
|
165
164
|
@IsNotEmpty()
|
|
166
165
|
public description: string;
|
|
167
166
|
|
|
168
|
-
@IsOptional()
|
|
169
|
-
public websiteUrl?: string;
|
|
170
|
-
|
|
171
|
-
@IsOptional()
|
|
172
|
-
public telegramUrl?: string;
|
|
173
|
-
|
|
174
|
-
@IsOptional()
|
|
175
|
-
public twitterUrl?: string;
|
|
176
|
-
|
|
177
167
|
@IsNotEmpty()
|
|
178
168
|
public initialBuyQuantity: string;
|
|
179
169
|
|
|
@@ -23,7 +23,16 @@ 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";
|
|
@@ -93,6 +102,15 @@ export class LaunchpadSale extends ChainObject {
|
|
|
93
102
|
@Type(() => ReverseBondingCurveConfigurationChainObject)
|
|
94
103
|
public reverseBondingCurveConfiguration?: ReverseBondingCurveConfigurationChainObject;
|
|
95
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
|
+
|
|
96
114
|
@JSONSchema({
|
|
97
115
|
description:
|
|
98
116
|
"The market cap has been calculated using the bonding curve equations to approximate a specific final price."
|
|
@@ -121,7 +139,8 @@ export class LaunchpadSale extends ChainObject {
|
|
|
121
139
|
sellingToken: TokenInstanceKey,
|
|
122
140
|
reverseBondingCurveConfiguration: ReverseBondingCurveConfigurationChainObject | undefined,
|
|
123
141
|
saleOwner: UserAlias,
|
|
124
|
-
saleStartTime?: number | undefined
|
|
142
|
+
saleStartTime?: number | undefined,
|
|
143
|
+
adjustableSupplyMultiplier?: number
|
|
125
144
|
) {
|
|
126
145
|
super();
|
|
127
146
|
|
|
@@ -129,7 +148,6 @@ export class LaunchpadSale extends ChainObject {
|
|
|
129
148
|
this.saleOwner = saleOwner;
|
|
130
149
|
|
|
131
150
|
this.sellingToken = sellingToken;
|
|
132
|
-
this.sellingTokenQuantity = "1e+7";
|
|
133
151
|
|
|
134
152
|
if (saleStartTime) {
|
|
135
153
|
this.saleStartTime = saleStartTime;
|
|
@@ -141,9 +159,19 @@ export class LaunchpadSale extends ChainObject {
|
|
|
141
159
|
this.saleStatus = SaleStatus.ONGOING;
|
|
142
160
|
}
|
|
143
161
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
+
|
|
147
175
|
this.euler = new BigNumber("2.7182818284590452353602874713527");
|
|
148
176
|
|
|
149
177
|
const nativeTokenInstance = new TokenInstanceKey();
|
|
@@ -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";
|
|
@@ -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
|
});
|
|
@@ -347,8 +347,68 @@ describe("buyWithNative", () => {
|
|
|
347
347
|
expect(difference.isLessThanOrEqualTo(tolerance)).toBe(true);
|
|
348
348
|
}
|
|
349
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
|
+
});
|
|
350
410
|
});
|
|
351
|
-
function roundToDecimal(value, decimals) {
|
|
411
|
+
function roundToDecimal(value: number, decimals: number) {
|
|
352
412
|
const factor = Math.pow(10, decimals);
|
|
353
413
|
return Math.round(value * factor) / factor;
|
|
354
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 {
|
|
@@ -29,15 +29,26 @@ function calculateTokensPurchasable(
|
|
|
29
29
|
nativeTokens: Decimal,
|
|
30
30
|
totalTokensSold: Decimal,
|
|
31
31
|
nativeTokenDecimals: number,
|
|
32
|
-
sellingTokenDecimals: number
|
|
32
|
+
sellingTokenDecimals: number,
|
|
33
|
+
adjustableSupplyMultiplier?: number
|
|
33
34
|
): [string, string] {
|
|
34
|
-
const basePrice =
|
|
35
|
-
|
|
35
|
+
const basePrice =
|
|
36
|
+
adjustableSupplyMultiplier && adjustableSupplyMultiplier > 0
|
|
37
|
+
? new Decimal(LaunchpadSale.BASE_PRICE).dividedBy(adjustableSupplyMultiplier)
|
|
38
|
+
: new Decimal(LaunchpadSale.BASE_PRICE);
|
|
39
|
+
|
|
40
|
+
const { exponentFactor, euler, decimals } = getBondingConstants(adjustableSupplyMultiplier);
|
|
36
41
|
|
|
37
42
|
// Round native tokens, then calculate tokens based on that rounded amount
|
|
38
43
|
const roundedNativeTokens = nativeTokens.toDecimalPlaces(nativeTokenDecimals, Decimal.ROUND_UP);
|
|
39
44
|
|
|
40
|
-
// Calculate tokens purchasable:
|
|
45
|
+
// Calculate tokens purchasable:
|
|
46
|
+
// newTokens = (decimals / exponentFactor) *
|
|
47
|
+
// ln(
|
|
48
|
+
// (nativeTokens * exponentFactor / basePrice) +
|
|
49
|
+
// e^(exponentFactor * totalTokensSold / decimals)
|
|
50
|
+
// ) -
|
|
51
|
+
// totalTokensSold
|
|
41
52
|
// Where:
|
|
42
53
|
// constant = nativeTokens * exponentFactor / basePrice
|
|
43
54
|
// exponent1 = exponentFactor * totalTokensSold / decimals
|
|
@@ -55,9 +66,13 @@ function calculateTokensPurchasable(
|
|
|
55
66
|
const result = lnEthScaledBase.minus(totalTokensSold);
|
|
56
67
|
let roundedResult = result.toDecimalPlaces(sellingTokenDecimals, Decimal.ROUND_DOWN);
|
|
57
68
|
|
|
58
|
-
// Cap total supply
|
|
59
|
-
|
|
60
|
-
|
|
69
|
+
// Cap total supply
|
|
70
|
+
const supplyCap = adjustableSupplyMultiplier
|
|
71
|
+
? new BigNumber(1e7).times(adjustableSupplyMultiplier).toString()
|
|
72
|
+
: "1e+7";
|
|
73
|
+
|
|
74
|
+
if (roundedResult.add(totalTokensSold).greaterThan(new Decimal(supplyCap))) {
|
|
75
|
+
roundedResult = new Decimal(supplyCap).minus(new Decimal(totalTokensSold));
|
|
61
76
|
}
|
|
62
77
|
|
|
63
78
|
return [roundedNativeTokens.toFixed(), roundedResult.toFixed()];
|
|
@@ -119,7 +134,8 @@ export async function callMemeTokenOut(
|
|
|
119
134
|
nativeTokens,
|
|
120
135
|
totalTokensSold,
|
|
121
136
|
nativeTokenDecimals,
|
|
122
|
-
sellingTokenDecimals
|
|
137
|
+
sellingTokenDecimals,
|
|
138
|
+
sale?.adjustableSupplyMultiplier
|
|
123
139
|
);
|
|
124
140
|
|
|
125
141
|
// Fetch fee configuration and return result
|
|
@@ -29,15 +29,25 @@ function calculateNativeTokensRequired(
|
|
|
29
29
|
tokensToBuy: Decimal,
|
|
30
30
|
totalTokensSold: Decimal,
|
|
31
31
|
sellingTokenDecimals: number,
|
|
32
|
-
nativeTokenDecimals: number
|
|
32
|
+
nativeTokenDecimals: number,
|
|
33
|
+
adjustableSupplyMultiplier?: number
|
|
33
34
|
): [string, string] {
|
|
34
|
-
const basePrice =
|
|
35
|
-
|
|
35
|
+
const basePrice =
|
|
36
|
+
adjustableSupplyMultiplier && adjustableSupplyMultiplier > 0
|
|
37
|
+
? new Decimal(LaunchpadSale.BASE_PRICE).dividedBy(adjustableSupplyMultiplier)
|
|
38
|
+
: new Decimal(LaunchpadSale.BASE_PRICE);
|
|
39
|
+
|
|
40
|
+
const { exponentFactor, euler, decimals } = getBondingConstants(adjustableSupplyMultiplier);
|
|
36
41
|
|
|
37
42
|
// Round tokens first, then calculate native tokens based on that rounded amount
|
|
38
43
|
const roundedTokensToBuy = tokensToBuy.toDecimalPlaces(sellingTokenDecimals, Decimal.ROUND_DOWN);
|
|
39
44
|
|
|
40
|
-
// Calculate native tokens required:
|
|
45
|
+
// Calculate native tokens required:
|
|
46
|
+
// price = (basePrice / exponentFactor) *
|
|
47
|
+
// (
|
|
48
|
+
// e^(exponentFactor * (totalTokensSold + tokensToBuy) / decimals) -
|
|
49
|
+
// e^(exponentFactor * totalTokensSold / decimals)
|
|
50
|
+
// )
|
|
41
51
|
// Where:
|
|
42
52
|
// exponent1 = exponentFactor * (totalTokensSold + tokensToBuy) / decimals
|
|
43
53
|
// exponent2 = exponentFactor * totalTokensSold / decimals
|
|
@@ -81,13 +91,13 @@ export async function callNativeTokenIn(
|
|
|
81
91
|
ctx: GalaChainContext,
|
|
82
92
|
buyTokenDTO: ExactTokenQuantityDto
|
|
83
93
|
): Promise<TradeCalculationResDto> {
|
|
84
|
-
const sale = await fetchAndValidateSale(ctx, buyTokenDTO.vaultAddress);
|
|
94
|
+
const sale: LaunchpadSale = await fetchAndValidateSale(ctx, buyTokenDTO.vaultAddress);
|
|
85
95
|
const totalTokensSold = new Decimal(sale.fetchTokensSold());
|
|
86
96
|
|
|
87
97
|
let tokensToBuy = new Decimal(buyTokenDTO.tokenQuantity.toString());
|
|
88
98
|
|
|
89
99
|
// Adjust tokensToBuy if user is trying to buy more tokens than the total supply
|
|
90
|
-
if (tokensToBuy.add(totalTokensSold).greaterThan(new Decimal(
|
|
100
|
+
if (tokensToBuy.add(totalTokensSold).greaterThan(new Decimal(sale.maxSupply.toString()))) {
|
|
91
101
|
tokensToBuy = new Decimal(sale.sellingTokenQuantity);
|
|
92
102
|
}
|
|
93
103
|
|
|
@@ -99,7 +109,8 @@ export async function callNativeTokenIn(
|
|
|
99
109
|
tokensToBuy,
|
|
100
110
|
totalTokensSold,
|
|
101
111
|
sellingTokenDecimals,
|
|
102
|
-
nativeTokenDecimals
|
|
112
|
+
nativeTokenDecimals,
|
|
113
|
+
sale.adjustableSupplyMultiplier
|
|
103
114
|
);
|
|
104
115
|
|
|
105
116
|
const launchpadFeeAddressConfiguration = await fetchLaunchpadFeeAddress(ctx);
|
|
@@ -29,13 +29,18 @@ function calculateNativeTokensReceived(
|
|
|
29
29
|
sale: LaunchpadSale,
|
|
30
30
|
tokensToSellBn: BigNumber,
|
|
31
31
|
sellingTokenDecimals: number,
|
|
32
|
-
nativeTokenDecimals: number
|
|
32
|
+
nativeTokenDecimals: number,
|
|
33
|
+
adjustableSupplyMultiplier?: number
|
|
33
34
|
): [string, string] {
|
|
34
35
|
const totalTokensSold = new Decimal(sale.fetchTokensSold());
|
|
35
36
|
|
|
36
37
|
let tokensToSell = new Decimal(tokensToSellBn.toString());
|
|
37
|
-
const basePrice =
|
|
38
|
-
|
|
38
|
+
const basePrice =
|
|
39
|
+
adjustableSupplyMultiplier && adjustableSupplyMultiplier > 0
|
|
40
|
+
? new Decimal(LaunchpadSale.BASE_PRICE).dividedBy(adjustableSupplyMultiplier)
|
|
41
|
+
: new Decimal(LaunchpadSale.BASE_PRICE);
|
|
42
|
+
|
|
43
|
+
const { exponentFactor, euler, decimals } = getBondingConstants(adjustableSupplyMultiplier);
|
|
39
44
|
|
|
40
45
|
let newTotalTokensSold = totalTokensSold.minus(tokensToSell);
|
|
41
46
|
|
|
@@ -88,7 +93,8 @@ export async function callNativeTokenOut(
|
|
|
88
93
|
sale,
|
|
89
94
|
sellTokenDTO.tokenQuantity,
|
|
90
95
|
sellingTokenDecimals,
|
|
91
|
-
nativeTokenDecimals
|
|
96
|
+
nativeTokenDecimals,
|
|
97
|
+
sale.adjustableSupplyMultiplier
|
|
92
98
|
);
|
|
93
99
|
const launchpadFeeAddressConfiguration = await fetchLaunchpadFeeAddress(ctx);
|
|
94
100
|
|