@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.
Files changed (53) hide show
  1. package/lib/package.json +1 -1
  2. package/lib/src/api/types/LaunchpadDtos.d.ts +3 -1
  3. package/lib/src/api/types/LaunchpadDtos.d.ts.map +1 -1
  4. package/lib/src/api/types/LaunchpadDtos.js +19 -1
  5. package/lib/src/api/types/LaunchpadDtos.js.map +1 -1
  6. package/lib/src/api/types/LaunchpadSale.d.ts +8 -1
  7. package/lib/src/api/types/LaunchpadSale.d.ts.map +1 -1
  8. package/lib/src/api/types/LaunchpadSale.js +37 -8
  9. package/lib/src/api/types/LaunchpadSale.js.map +1 -1
  10. package/lib/src/chaincode/launchpad/buyWithNative.d.ts.map +1 -1
  11. package/lib/src/chaincode/launchpad/buyWithNative.js +4 -1
  12. package/lib/src/chaincode/launchpad/buyWithNative.js.map +1 -1
  13. package/lib/src/chaincode/launchpad/callMemeTokenIn.d.ts.map +1 -1
  14. package/lib/src/chaincode/launchpad/callMemeTokenIn.js +6 -4
  15. package/lib/src/chaincode/launchpad/callMemeTokenIn.js.map +1 -1
  16. package/lib/src/chaincode/launchpad/callMemeTokenOut.d.ts.map +1 -1
  17. package/lib/src/chaincode/launchpad/callMemeTokenOut.js +19 -8
  18. package/lib/src/chaincode/launchpad/callMemeTokenOut.js.map +1 -1
  19. package/lib/src/chaincode/launchpad/callNativeTokenIn.d.ts.map +1 -1
  20. package/lib/src/chaincode/launchpad/callNativeTokenIn.js +13 -6
  21. package/lib/src/chaincode/launchpad/callNativeTokenIn.js.map +1 -1
  22. package/lib/src/chaincode/launchpad/callNativeTokenOut.d.ts.map +1 -1
  23. package/lib/src/chaincode/launchpad/callNativeTokenOut.js +6 -4
  24. package/lib/src/chaincode/launchpad/callNativeTokenOut.js.map +1 -1
  25. package/lib/src/chaincode/launchpad/createSale.d.ts.map +1 -1
  26. package/lib/src/chaincode/launchpad/createSale.js +22 -6
  27. package/lib/src/chaincode/launchpad/createSale.js.map +1 -1
  28. package/lib/src/chaincode/launchpad/finaliseSale.js +2 -2
  29. package/lib/src/chaincode/launchpad/finaliseSale.js.map +1 -1
  30. package/lib/src/chaincode/launchpad/sellExactToken.js +1 -1
  31. package/lib/src/chaincode/launchpad/sellExactToken.js.map +1 -1
  32. package/lib/src/chaincode/utils/launchpadSaleUtils.d.ts +1 -1
  33. package/lib/src/chaincode/utils/launchpadSaleUtils.d.ts.map +1 -1
  34. package/lib/src/chaincode/utils/launchpadSaleUtils.js +14 -2
  35. package/lib/src/chaincode/utils/launchpadSaleUtils.js.map +1 -1
  36. package/lib/tsconfig.tsbuildinfo +1 -1
  37. package/package.json +1 -1
  38. package/src/api/types/LaunchpadDtos.ts +22 -1
  39. package/src/api/types/LaunchpadSale.ts +51 -8
  40. package/src/chaincode/launchpad/buyExactToken.spec.ts +234 -1
  41. package/src/chaincode/launchpad/buyWithNative.spec.ts +163 -8
  42. package/src/chaincode/launchpad/buyWithNative.ts +5 -1
  43. package/src/chaincode/launchpad/callMemeTokenIn.ts +11 -4
  44. package/src/chaincode/launchpad/callMemeTokenOut.spec.ts +1 -1
  45. package/src/chaincode/launchpad/callMemeTokenOut.ts +24 -8
  46. package/src/chaincode/launchpad/callNativeTokenIn.ts +18 -7
  47. package/src/chaincode/launchpad/callNativeTokenOut.ts +10 -4
  48. package/src/chaincode/launchpad/createSale.ts +35 -7
  49. package/src/chaincode/launchpad/finaliseSale.ts +2 -2
  50. package/src/chaincode/launchpad/sellExactToken.spec.ts +139 -2
  51. package/src/chaincode/launchpad/sellExactToken.ts +1 -1
  52. package/src/chaincode/launchpad/sellWithNative.spec.ts +157 -1
  53. package/src/chaincode/utils/launchpadSaleUtils.ts +18 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gala-chain/launchpad",
3
- "version": "1.0.14",
3
+ "version": "1.0.16",
4
4
  "description": "GalaChain Launchpad Chaincode",
5
5
  "main": "lib/src/index.js",
6
6
  "types": "lib/src/index.d.ts",
@@ -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 { IsNotEmpty, IsOptional, IsString, ValidateNested } from "class-validator";
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 = 18;
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
- this.basePrice = new BigNumber(LaunchpadSale.BASE_PRICE);
129
- this.exponentFactor = new BigNumber("1166069000000");
130
- this.maxSupply = new BigNumber("1e+7");
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: 8
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 { LaunchpadFeeConfig, LaunchpadSale, NativeTokenQuantityDto, TradeResDto } from "../../api/types";
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 = currency.tokenClass();
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: 18
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.8890651635",
158
- outputQuantity: "2101667.8890651635",
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.3628130557",
204
- outputQuantity: "3663321.3628130557",
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(LaunchpadSale.MARKET_CAP))
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
- const basePrice = new Decimal(LaunchpadSale.BASE_PRICE); // base price / a
41
- const { exponentFactor, euler, decimals } = getBondingConstants();
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.30295364487969",
146
+ calculatedQuantity: "458291.302953644",
147
147
  extraFees: { reverseBondingCurve: "0", transactionFees: "0.00000000" }
148
148
  });
149
149
  });