@gala-chain/launchpad 1.0.15 → 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 +2 -1
- package/lib/src/api/types/LaunchpadDtos.d.ts.map +1 -1
- package/lib/src/api/types/LaunchpadDtos.js +11 -1
- 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 -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/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 -1
- 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.ts +10 -5
- 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
|
@@ -125,6 +125,12 @@ export class CreateTokenSaleDTO extends SubmitCallDTO {
|
|
|
125
125
|
@Type(() => ReverseBondingCurveConfigurationDto)
|
|
126
126
|
public reverseBondingCurveConfiguration?: ReverseBondingCurveConfigurationDto;
|
|
127
127
|
|
|
128
|
+
@IsOptional()
|
|
129
|
+
@IsNumber()
|
|
130
|
+
@Min(100)
|
|
131
|
+
@Max(100)
|
|
132
|
+
public adjustableSupplyMultiplier?: number;
|
|
133
|
+
|
|
128
134
|
constructor(
|
|
129
135
|
tokenName: string,
|
|
130
136
|
tokenSymbol: string,
|
|
@@ -134,7 +140,8 @@ export class CreateTokenSaleDTO extends SubmitCallDTO {
|
|
|
134
140
|
tokenCollection: string,
|
|
135
141
|
tokenCategory: string,
|
|
136
142
|
reverseBondingCurveConfiguration?: ReverseBondingCurveConfigurationDto,
|
|
137
|
-
saleStartTime?: number
|
|
143
|
+
saleStartTime?: number,
|
|
144
|
+
adjustableSupplyMultiplier?: number
|
|
138
145
|
) {
|
|
139
146
|
super();
|
|
140
147
|
this.tokenName = tokenName;
|
|
@@ -149,6 +156,10 @@ export class CreateTokenSaleDTO extends SubmitCallDTO {
|
|
|
149
156
|
if (saleStartTime !== undefined) {
|
|
150
157
|
this.saleStartTime = saleStartTime;
|
|
151
158
|
}
|
|
159
|
+
|
|
160
|
+
if (adjustableSupplyMultiplier !== undefined) {
|
|
161
|
+
this.adjustableSupplyMultiplier = adjustableSupplyMultiplier;
|
|
162
|
+
}
|
|
152
163
|
}
|
|
153
164
|
}
|
|
154
165
|
|
|
@@ -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
|
|
|
@@ -86,6 +86,8 @@ export async function createSale(
|
|
|
86
86
|
throw new ConflictError("This token and a sale associated with it already exists");
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
const supplyCapMultiplier = launchpadDetails.adjustableSupplyMultiplier ?? 1;
|
|
90
|
+
|
|
89
91
|
// Call createTokenClass
|
|
90
92
|
await createTokenClass(ctx, {
|
|
91
93
|
network: "GC",
|
|
@@ -96,8 +98,8 @@ export async function createSale(
|
|
|
96
98
|
symbol: launchpadDetails.tokenSymbol,
|
|
97
99
|
description: launchpadDetails.tokenDescription,
|
|
98
100
|
image: launchpadDetails.tokenImage,
|
|
99
|
-
maxSupply: new BigNumber("2e+7"),
|
|
100
|
-
maxCapacity: new BigNumber("2e+7"),
|
|
101
|
+
maxSupply: new BigNumber("2e+7").times(supplyCapMultiplier),
|
|
102
|
+
maxCapacity: new BigNumber("2e+7").times(supplyCapMultiplier),
|
|
101
103
|
totalMintAllowance: new BigNumber(0),
|
|
102
104
|
totalSupply: new BigNumber(0),
|
|
103
105
|
totalBurned: new BigNumber(0),
|
|
@@ -109,12 +111,13 @@ export async function createSale(
|
|
|
109
111
|
tokenClassKey: tokenInstanceKey.getTokenClassKey(),
|
|
110
112
|
tokenInstance: new BigNumber(0),
|
|
111
113
|
owner: vaultAddress,
|
|
112
|
-
quantity: new BigNumber("2e+7")
|
|
114
|
+
quantity: new BigNumber("2e+7").times(supplyCapMultiplier)
|
|
113
115
|
});
|
|
114
116
|
|
|
115
|
-
//Update token class to remove the calling user as an authority in the token class
|
|
117
|
+
// Update token class to remove the calling user as an authority in the token class
|
|
116
118
|
await updateTokenClass(ctx, {
|
|
117
119
|
tokenClass: tokenInstanceKey.getTokenClassKey(),
|
|
120
|
+
overwriteAuthorities: true,
|
|
118
121
|
authorities: [vaultAddress]
|
|
119
122
|
});
|
|
120
123
|
|
|
@@ -123,7 +126,9 @@ export async function createSale(
|
|
|
123
126
|
vaultAddress,
|
|
124
127
|
tokenInstanceKey,
|
|
125
128
|
launchpadDetails.reverseBondingCurveConfiguration?.toChainObject(),
|
|
126
|
-
ctx.callingUser
|
|
129
|
+
ctx.callingUser,
|
|
130
|
+
undefined,
|
|
131
|
+
launchpadDetails.adjustableSupplyMultiplier
|
|
127
132
|
);
|
|
128
133
|
|
|
129
134
|
await putChainObject(ctx, launchpad);
|