@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.
Files changed (50) hide show
  1. package/lib/package.json +1 -1
  2. package/lib/src/api/types/LaunchpadDtos.d.ts +2 -7
  3. package/lib/src/api/types/LaunchpadDtos.d.ts.map +1 -1
  4. package/lib/src/api/types/LaunchpadDtos.js +11 -28
  5. package/lib/src/api/types/LaunchpadDtos.js.map +1 -1
  6. package/lib/src/api/types/LaunchpadSale.d.ts +6 -1
  7. package/lib/src/api/types/LaunchpadSale.d.ts.map +1 -1
  8. package/lib/src/api/types/LaunchpadSale.js +21 -6
  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 +8 -13
  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/utils/launchpadSaleUtils.d.ts +1 -1
  31. package/lib/src/chaincode/utils/launchpadSaleUtils.d.ts.map +1 -1
  32. package/lib/src/chaincode/utils/launchpadSaleUtils.js +5 -2
  33. package/lib/src/chaincode/utils/launchpadSaleUtils.js.map +1 -1
  34. package/lib/tsconfig.tsbuildinfo +1 -1
  35. package/package.json +1 -1
  36. package/src/api/types/LaunchpadDtos.ts +12 -22
  37. package/src/api/types/LaunchpadSale.ts +34 -6
  38. package/src/chaincode/launchpad/buyExactToken.spec.ts +233 -0
  39. package/src/chaincode/launchpad/buyWithNative.spec.ts +61 -1
  40. package/src/chaincode/launchpad/buyWithNative.ts +5 -1
  41. package/src/chaincode/launchpad/callMemeTokenIn.ts +11 -4
  42. package/src/chaincode/launchpad/callMemeTokenOut.ts +24 -8
  43. package/src/chaincode/launchpad/callNativeTokenIn.ts +18 -7
  44. package/src/chaincode/launchpad/callNativeTokenOut.ts +10 -4
  45. package/src/chaincode/launchpad/createSale.spec.ts +1 -87
  46. package/src/chaincode/launchpad/createSale.ts +11 -14
  47. package/src/chaincode/launchpad/finaliseSale.ts +2 -2
  48. package/src/chaincode/launchpad/sellExactToken.spec.ts +139 -2
  49. package/src/chaincode/launchpad/sellWithNative.spec.ts +157 -1
  50. package/src/chaincode/utils/launchpadSaleUtils.ts +6 -2
@@ -34,7 +34,6 @@ describe("createSale", () => {
34
34
  "TestCollection",
35
35
  "TestCategory"
36
36
  );
37
- createSaleDto.websiteUrl = "https://example.com";
38
37
  createSaleDto.uniqueKey = randomUniqueKey();
39
38
 
40
39
  const signedDto = createSaleDto.signed(users.testUser1.privateKey);
@@ -54,60 +53,6 @@ describe("createSale", () => {
54
53
  expect(response.Data?.isFinalized).toBe(false);
55
54
  });
56
55
 
57
- it("should create token sale with telegram URL", async () => {
58
- // Given
59
- const { ctx, contract } = fixture(LaunchpadContract).registeredUsers(users.testUser1);
60
-
61
- const createSaleDto = new CreateTokenSaleDTO(
62
- "Telegram Token",
63
- "TG",
64
- "A token with telegram link",
65
- "https://example.com/tg.png",
66
- new BigNumber(0),
67
- "TelegramCollection",
68
- "SocialCategory"
69
- );
70
- createSaleDto.telegramUrl = "https://t.me/testtoken";
71
- createSaleDto.uniqueKey = randomUniqueKey();
72
-
73
- const signedDto = createSaleDto.signed(users.testUser1.privateKey);
74
-
75
- // When
76
- const response = await contract.CreateSale(ctx, signedDto);
77
-
78
- // Then
79
- expect(response.Status).toBe(1);
80
- expect(response.Data?.telegramUrl).toBe("https://t.me/testtoken");
81
- expect(response.Data?.websiteUrl).toBe("");
82
- expect(response.Data?.twitterUrl).toBe("");
83
- });
84
-
85
- it("should create token sale with twitter URL", async () => {
86
- // Given
87
- const { ctx, contract } = fixture(LaunchpadContract).registeredUsers(users.testUser1);
88
-
89
- const createSaleDto = new CreateTokenSaleDTO(
90
- "Twitter Token",
91
- "TWT",
92
- "A token with twitter link",
93
- "https://example.com/twt.png",
94
- new BigNumber(0),
95
- "TwitterCollection",
96
- "SocialCategory"
97
- );
98
- createSaleDto.twitterUrl = "https://twitter.com/testtoken";
99
- createSaleDto.uniqueKey = randomUniqueKey();
100
-
101
- const signedDto = createSaleDto.signed(users.testUser1.privateKey);
102
-
103
- // When
104
- const response = await contract.CreateSale(ctx, signedDto);
105
-
106
- // Then
107
- expect(response.Status).toBe(1);
108
- expect(response.Data?.twitterUrl).toBe("https://twitter.com/testtoken");
109
- });
110
-
111
56
  it("should create token sale with pre-buy amount", async () => {
112
57
  // Given - Setup GALA token for pre-buy (matching LaunchpadSale nativeToken)
113
58
  const galaClass = plainToInstance(TokenClass, {
@@ -162,7 +107,6 @@ describe("createSale", () => {
162
107
  "PreBuyCollection",
163
108
  "PreBuyCategory"
164
109
  );
165
- createSaleDto.websiteUrl = "https://example.com";
166
110
  createSaleDto.uniqueKey = randomUniqueKey();
167
111
 
168
112
  const signedDto = createSaleDto.signed(users.testUser1.privateKey);
@@ -188,7 +132,6 @@ describe("createSale", () => {
188
132
  "LowerCollection",
189
133
  "LowerCategory"
190
134
  );
191
- createSaleDto.websiteUrl = "https://example.com";
192
135
  createSaleDto.uniqueKey = randomUniqueKey();
193
136
 
194
137
  const signedDto = createSaleDto.signed(users.testUser1.privateKey);
@@ -201,35 +144,7 @@ describe("createSale", () => {
201
144
  expect(response.Data?.symbol).toBe("LOWER"); // Should be converted to uppercase
202
145
  });
203
146
 
204
- it("should create sale with all social media URLs", async () => {
205
- // Given
206
- const { ctx, contract } = fixture(LaunchpadContract).registeredUsers(users.testUser1);
207
-
208
- const createSaleDto = new CreateTokenSaleDTO(
209
- "Social Token",
210
- "SOC",
211
- "A fully connected social token",
212
- "https://example.com/soc.png",
213
- new BigNumber(0),
214
- "SocialCollection",
215
- "FullSocialCategory"
216
- );
217
- createSaleDto.websiteUrl = "https://socialtoken.com";
218
- createSaleDto.telegramUrl = "https://t.me/socialtoken";
219
- createSaleDto.twitterUrl = "https://twitter.com/socialtoken";
220
- createSaleDto.uniqueKey = randomUniqueKey();
221
-
222
- const signedDto = createSaleDto.signed(users.testUser1.privateKey);
223
-
224
- // When
225
- const response = await contract.CreateSale(ctx, signedDto);
226
-
227
- // Then
228
- expect(response.Status).toBe(1);
229
- expect(response.Data?.websiteUrl).toBe("https://socialtoken.com");
230
- expect(response.Data?.telegramUrl).toBe("https://t.me/socialtoken");
231
- expect(response.Data?.twitterUrl).toBe("https://twitter.com/socialtoken");
232
- });
147
+ // Social links are now handled off-chain (DB source of truth); chain no longer returns them.
233
148
 
234
149
  it("should create sale with custom token image", async () => {
235
150
  // Given
@@ -244,7 +159,6 @@ describe("createSale", () => {
244
159
  "ImageCollection",
245
160
  "ImageCategory"
246
161
  );
247
- createSaleDto.websiteUrl = "https://example.com";
248
162
  createSaleDto.uniqueKey = randomUniqueKey();
249
163
 
250
164
  const signedDto = createSaleDto.signed(users.testUser1.privateKey);
@@ -12,7 +12,7 @@
12
12
  * See the License for the specific language governing permissions and
13
13
  * limitations under the License.
14
14
  */
15
- import { ConflictError, DefaultError, TokenInstanceKey, asValidUserAlias } from "@gala-chain/api";
15
+ import { ConflictError, TokenInstanceKey, asValidUserAlias } from "@gala-chain/api";
16
16
  import {
17
17
  GalaChainContext,
18
18
  createTokenClass,
@@ -30,7 +30,6 @@ import {
30
30
  NativeTokenQuantityDto,
31
31
  SaleStatus
32
32
  } from "../../api/types";
33
- import { PreConditionFailedError } from "../../api/utils/error";
34
33
  import { buyWithNative } from "./buyWithNative";
35
34
 
36
35
  /**
@@ -62,10 +61,6 @@ export async function createSale(
62
61
  let isSaleFinalized = false;
63
62
  // Validate input parameters
64
63
 
65
- if (!launchpadDetails.websiteUrl && !launchpadDetails.telegramUrl && !launchpadDetails.twitterUrl) {
66
- throw new PreConditionFailedError("Token sale creation requires atleast one social link.");
67
- }
68
-
69
64
  launchpadDetails.tokenSymbol = launchpadDetails.tokenSymbol.toUpperCase();
70
65
 
71
66
  // Define the token class key
@@ -86,6 +81,8 @@ export async function createSale(
86
81
  throw new ConflictError("This token and a sale associated with it already exists");
87
82
  }
88
83
 
84
+ const supplyCapMultiplier = launchpadDetails.adjustableSupplyMultiplier ?? 1;
85
+
89
86
  // Call createTokenClass
90
87
  await createTokenClass(ctx, {
91
88
  network: "GC",
@@ -96,8 +93,8 @@ export async function createSale(
96
93
  symbol: launchpadDetails.tokenSymbol,
97
94
  description: launchpadDetails.tokenDescription,
98
95
  image: launchpadDetails.tokenImage,
99
- maxSupply: new BigNumber("2e+7"),
100
- maxCapacity: new BigNumber("2e+7"),
96
+ maxSupply: new BigNumber("2e+7").times(supplyCapMultiplier),
97
+ maxCapacity: new BigNumber("2e+7").times(supplyCapMultiplier),
101
98
  totalMintAllowance: new BigNumber(0),
102
99
  totalSupply: new BigNumber(0),
103
100
  totalBurned: new BigNumber(0),
@@ -109,12 +106,13 @@ export async function createSale(
109
106
  tokenClassKey: tokenInstanceKey.getTokenClassKey(),
110
107
  tokenInstance: new BigNumber(0),
111
108
  owner: vaultAddress,
112
- quantity: new BigNumber("2e+7")
109
+ quantity: new BigNumber("2e+7").times(supplyCapMultiplier)
113
110
  });
114
111
 
115
- //Update token class to remove the calling user as an authority in the token class
112
+ // Update token class to remove the calling user as an authority in the token class
116
113
  await updateTokenClass(ctx, {
117
114
  tokenClass: tokenInstanceKey.getTokenClassKey(),
115
+ overwriteAuthorities: true,
118
116
  authorities: [vaultAddress]
119
117
  });
120
118
 
@@ -123,7 +121,9 @@ export async function createSale(
123
121
  vaultAddress,
124
122
  tokenInstanceKey,
125
123
  launchpadDetails.reverseBondingCurveConfiguration?.toChainObject(),
126
- ctx.callingUser
124
+ ctx.callingUser,
125
+ undefined,
126
+ launchpadDetails.adjustableSupplyMultiplier
127
127
  );
128
128
 
129
129
  await putChainObject(ctx, launchpad);
@@ -160,9 +160,6 @@ export async function createSale(
160
160
  tokenName: launchpadDetails.tokenName,
161
161
  symbol: launchpadDetails.tokenSymbol,
162
162
  description: launchpadDetails.tokenDescription,
163
- websiteUrl: launchpadDetails.websiteUrl ? launchpadDetails.websiteUrl : "",
164
- telegramUrl: launchpadDetails.telegramUrl ? launchpadDetails.telegramUrl : "",
165
- twitterUrl: launchpadDetails.twitterUrl ? launchpadDetails.twitterUrl : "",
166
163
  initialBuyQuantity: launchpadDetails.preBuyQuantity.toFixed(),
167
164
  vaultAddress: vaultAddress,
168
165
  creatorAddress: ctx.callingUser,
@@ -203,8 +203,8 @@ function calculateFinalLaunchpadPrice(
203
203
  areTokensSorted: boolean
204
204
  ): { sqrtPrice: BigNumber; finalPrice: BigNumber } {
205
205
  const totalTokensSold = new Decimal(sale.fetchTokensSold());
206
- const basePrice = new Decimal(LaunchpadSale.BASE_PRICE);
207
- const { exponentFactor, euler, decimals } = getBondingConstants();
206
+ const basePrice = new Decimal(sale.basePrice.toString());
207
+ const { exponentFactor, euler, decimals } = getBondingConstants(sale.adjustableSupplyMultiplier);
208
208
 
209
209
  const exponent = exponentFactor.mul(totalTokensSold).div(decimals);
210
210
  const multiplicand = euler.pow(exponent);
@@ -21,11 +21,11 @@ import {
21
21
  asValidUserAlias,
22
22
  randomUniqueKey
23
23
  } from "@gala-chain/api";
24
- import { currency, fixture, users } from "@gala-chain/test";
24
+ import { currency, fixture, transactionSuccess, users } from "@gala-chain/test";
25
25
  import BigNumber from "bignumber.js";
26
26
  import { plainToInstance } from "class-transformer";
27
27
 
28
- import { ExactTokenQuantityDto, LaunchpadSale } from "../../api/types";
28
+ import { ExactTokenQuantityDto, LaunchpadSale, TradeResDto } from "../../api/types";
29
29
  import { LaunchpadContract } from "../LaunchpadContract";
30
30
  import launchpadgala from "../test/launchpadgala";
31
31
 
@@ -204,4 +204,141 @@ describe("sellExactToken", () => {
204
204
  expect(response.Data?.inputQuantity).toBe("500");
205
205
  expect(new BigNumber(response.Data?.outputQuantity || "0").isPositive()).toBe(true);
206
206
  });
207
+
208
+ let galaPurchaseQtyDefaultSupply: BigNumber;
209
+
210
+ test("Adjustable supply: Single transaction yields expected value for default 10 Million supply", async () => {
211
+ // Given
212
+ const multiplier = undefined;
213
+
214
+ sale = new LaunchpadSale(
215
+ vaultAddress,
216
+ currencyInstance.instanceKeyObj(),
217
+ undefined,
218
+ users.testUser1.identityKey,
219
+ undefined,
220
+ multiplier
221
+ );
222
+
223
+ const { ctx, contract } = fixture(LaunchpadContract)
224
+ .registeredUsers(users.testUser1)
225
+ .savedState(
226
+ currencyClass,
227
+ currencyInstance,
228
+ launchpadGalaClass,
229
+ launchpadGalaInstance,
230
+ sale,
231
+ salelaunchpadGalaBalance,
232
+ saleCurrencyBalance,
233
+ userlaunchpadGalaBalance,
234
+ userCurrencyBalance
235
+ );
236
+
237
+ const buyDto = new ExactTokenQuantityDto(vaultAddress, new BigNumber("500"));
238
+
239
+ buyDto.uniqueKey = randomUniqueKey();
240
+ buyDto.sign(users.testUser1.privateKey);
241
+
242
+ const expectedBuyResponse = plainToInstance(TradeResDto, {
243
+ inputQuantity: "0.00825575",
244
+ totalFees: "0",
245
+ totalTokenSold: new BigNumber("500").toString(),
246
+ outputQuantity: new BigNumber("500").toString(),
247
+ tokenName: "AUTOMATEDTESTCOIN",
248
+ tradeType: "Buy",
249
+ uniqueKey: buyDto.uniqueKey,
250
+ vaultAddress: "service|GALA$Unit$none$none$launchpad",
251
+ userAddress: "client|testUser1",
252
+ isFinalized: false,
253
+ functionName: "BuyExactToken"
254
+ });
255
+
256
+ const sellDto = new ExactTokenQuantityDto(vaultAddress, new BigNumber("50"));
257
+ sellDto.uniqueKey = randomUniqueKey();
258
+ const signedDto = sellDto.signed(users.testUser1.privateKey);
259
+
260
+ // When
261
+ const buyRes = await contract.BuyExactToken(ctx, buyDto);
262
+
263
+ const sellRes = await contract.SellExactToken(ctx, signedDto);
264
+
265
+ // Then
266
+ expect(buyRes).toEqual(transactionSuccess(expectedBuyResponse));
267
+
268
+ expect(sellRes.Status).toBe(1);
269
+ expect(sellRes.Data?.inputQuantity).toBe("50");
270
+ expect(sellRes.Data?.outputQuantity).toBe("0.00082579");
271
+
272
+ galaPurchaseQtyDefaultSupply = new BigNumber(sellRes.Data?.outputQuantity ?? 0);
273
+ });
274
+
275
+ test("Adjustable supply: Single transaction yields expected quantity for 100x scaled 1 Billion supply", async () => {
276
+ // Given
277
+ const multiplier = 100;
278
+ const inputQty = new BigNumber("50").times(multiplier);
279
+
280
+ sale = new LaunchpadSale(
281
+ vaultAddress,
282
+ currencyInstance.instanceKeyObj(),
283
+ undefined,
284
+ users.testUser1.identityKey,
285
+ undefined,
286
+ multiplier
287
+ );
288
+
289
+ const { ctx, contract } = fixture(LaunchpadContract)
290
+ .registeredUsers(users.testUser1)
291
+ .savedState(
292
+ currencyClass,
293
+ currencyInstance,
294
+ launchpadGalaClass,
295
+ launchpadGalaInstance,
296
+ sale,
297
+ salelaunchpadGalaBalance,
298
+ saleCurrencyBalance,
299
+ userlaunchpadGalaBalance,
300
+ userCurrencyBalance
301
+ );
302
+
303
+ const buyDto = new ExactTokenQuantityDto(vaultAddress, new BigNumber("500").times(multiplier));
304
+
305
+ buyDto.uniqueKey = randomUniqueKey();
306
+ buyDto.sign(users.testUser1.privateKey);
307
+
308
+ const expectedBuyResponse = plainToInstance(TradeResDto, {
309
+ inputQuantity: "0.00825575",
310
+ totalFees: "0",
311
+ totalTokenSold: new BigNumber("500").times(multiplier).toString(),
312
+ outputQuantity: new BigNumber("500").times(multiplier).toString(),
313
+ tokenName: "AUTOMATEDTESTCOIN",
314
+ tradeType: "Buy",
315
+ uniqueKey: buyDto.uniqueKey,
316
+ vaultAddress: "service|GALA$Unit$none$none$launchpad",
317
+ userAddress: "client|testUser1",
318
+ isFinalized: false,
319
+ functionName: "BuyExactToken"
320
+ });
321
+
322
+ const sellDto = new ExactTokenQuantityDto(vaultAddress, inputQty);
323
+ sellDto.uniqueKey = randomUniqueKey();
324
+ const signedDto = sellDto.signed(users.testUser1.privateKey);
325
+
326
+ // When
327
+ const buyRes = await contract.BuyExactToken(ctx, buyDto);
328
+
329
+ const sellRes = await contract.SellExactToken(ctx, signedDto);
330
+
331
+ // Then
332
+ expect(buyRes).toEqual(transactionSuccess(expectedBuyResponse));
333
+
334
+ expect(sellRes.Status).toBe(1);
335
+ expect(sellRes.Data?.inputQuantity).toBe(inputQty.toString());
336
+ expect(sellRes.Data?.outputQuantity).toBe("0.00082579");
337
+
338
+ const galaPurchaseQty100xSupply = new BigNumber(sellRes.Data?.outputQuantity ?? -1);
339
+
340
+ // Compared to the previous test where the Launchpad has the default 10 Million supply,
341
+ // We expect the Meme token Qty to scale 100x and the Gala Qty to remain the same
342
+ expect(galaPurchaseQtyDefaultSupply).toEqual(galaPurchaseQty100xSupply);
343
+ });
207
344
  });
@@ -26,7 +26,7 @@ import { currency, fixture, transactionError, transactionSuccess, users } from "
26
26
  import BigNumber from "bignumber.js";
27
27
  import { plainToInstance } from "class-transformer";
28
28
 
29
- import { LaunchpadSale, NativeTokenQuantityDto, TradeResDto } from "../../api/types";
29
+ import { ExactTokenQuantityDto, LaunchpadSale, NativeTokenQuantityDto, TradeResDto } from "../../api/types";
30
30
  import { LaunchpadContract } from "../LaunchpadContract";
31
31
  import launchpadgala from "../test/launchpadgala";
32
32
 
@@ -286,4 +286,160 @@ describe("sellWithNative", () => {
286
286
 
287
287
  // todo: check writes map, verify vault balance
288
288
  });
289
+
290
+ let galaPurchaseQtyDefaultSupply: BigNumber;
291
+ let memeSaleQtyDefaultSupply: BigNumber;
292
+
293
+ test("Adjustable supply: Single transaction yields expected value for default 10 Million supply", async () => {
294
+ // Given
295
+ const multiplier = undefined;
296
+ galaPurchaseQtyDefaultSupply = new BigNumber("0.00082579");
297
+ memeSaleQtyDefaultSupply = new BigNumber("49.999949130655");
298
+
299
+ sale = new LaunchpadSale(
300
+ vaultAddress,
301
+ currencyInstance.instanceKeyObj(),
302
+ undefined,
303
+ users.testUser1.identityKey,
304
+ undefined,
305
+ multiplier
306
+ );
307
+
308
+ const { ctx, contract } = fixture(LaunchpadContract)
309
+ .registeredUsers(users.testUser1)
310
+ .savedState(
311
+ currencyClass,
312
+ currencyInstance,
313
+ launchpadGalaClass,
314
+ launchpadGalaInstance,
315
+ sale,
316
+ salelaunchpadGalaBalance,
317
+ saleCurrencyBalance,
318
+ userlaunchpadGalaBalance,
319
+ userCurrencyBalance
320
+ );
321
+
322
+ const buyDto = new ExactTokenQuantityDto(vaultAddress, new BigNumber("500"));
323
+
324
+ buyDto.uniqueKey = randomUniqueKey();
325
+ buyDto.sign(users.testUser1.privateKey);
326
+
327
+ const expectedBuyResponse = plainToInstance(TradeResDto, {
328
+ inputQuantity: "0.00825575",
329
+ totalFees: "0",
330
+ totalTokenSold: new BigNumber("500").toString(),
331
+ outputQuantity: new BigNumber("500").toString(),
332
+ tokenName: "AUTOMATEDTESTCOIN",
333
+ tradeType: "Buy",
334
+ uniqueKey: buyDto.uniqueKey,
335
+ vaultAddress: "service|GALA$Unit$none$none$launchpad",
336
+ userAddress: "client|testUser1",
337
+ isFinalized: false,
338
+ functionName: "BuyExactToken"
339
+ });
340
+
341
+ const sellDto = new NativeTokenQuantityDto(vaultAddress, galaPurchaseQtyDefaultSupply);
342
+ sellDto.uniqueKey = randomUniqueKey();
343
+ const signedDto = sellDto.signed(users.testUser1.privateKey);
344
+
345
+ // When
346
+ const buyRes = await contract.BuyExactToken(ctx, buyDto);
347
+
348
+ const sellRes = await contract.SellWithNative(ctx, signedDto);
349
+
350
+ // Then
351
+ expect(buyRes).toEqual(transactionSuccess(expectedBuyResponse));
352
+
353
+ expect(sellRes).toEqual(
354
+ transactionSuccess(
355
+ expect.objectContaining({
356
+ outputQuantity: galaPurchaseQtyDefaultSupply.toString(),
357
+ // extra precision in set constant above accounts for loss of precision
358
+ // when increased by the multiplier below.
359
+ // here, we round to the token decimal places to match the internal logic
360
+ inputQuantity: memeSaleQtyDefaultSupply.decimalPlaces(10).toString()
361
+ })
362
+ )
363
+ );
364
+ galaPurchaseQtyDefaultSupply = new BigNumber(sellRes.Data?.outputQuantity ?? 0);
365
+ });
366
+
367
+ test("Adjustable supply: Single transaction yields expected quantity for 100x scaled 1 Billion supply", async () => {
368
+ // Given
369
+ const multiplier = 100;
370
+
371
+ // Same Gala purchase amount should buy 100x meme token output
372
+ const inputQty = new BigNumber(galaPurchaseQtyDefaultSupply);
373
+
374
+ sale = new LaunchpadSale(
375
+ vaultAddress,
376
+ currencyInstance.instanceKeyObj(),
377
+ undefined,
378
+ users.testUser1.identityKey,
379
+ undefined,
380
+ multiplier
381
+ );
382
+
383
+ const { ctx, contract } = fixture(LaunchpadContract)
384
+ .registeredUsers(users.testUser1)
385
+ .savedState(
386
+ currencyClass,
387
+ currencyInstance,
388
+ launchpadGalaClass,
389
+ launchpadGalaInstance,
390
+ sale,
391
+ salelaunchpadGalaBalance,
392
+ saleCurrencyBalance,
393
+ userlaunchpadGalaBalance,
394
+ userCurrencyBalance
395
+ );
396
+
397
+ const buyDto = new ExactTokenQuantityDto(vaultAddress, new BigNumber("500").times(multiplier));
398
+
399
+ buyDto.uniqueKey = randomUniqueKey();
400
+ buyDto.sign(users.testUser1.privateKey);
401
+
402
+ const expectedBuyResponse = plainToInstance(TradeResDto, {
403
+ inputQuantity: "0.00825575",
404
+ totalFees: "0",
405
+ totalTokenSold: new BigNumber("500").times(multiplier).toString(),
406
+ outputQuantity: new BigNumber("500").times(multiplier).toString(),
407
+ tokenName: "AUTOMATEDTESTCOIN",
408
+ tradeType: "Buy",
409
+ uniqueKey: buyDto.uniqueKey,
410
+ vaultAddress: "service|GALA$Unit$none$none$launchpad",
411
+ userAddress: "client|testUser1",
412
+ isFinalized: false,
413
+ functionName: "BuyExactToken"
414
+ });
415
+
416
+ const sellDto = new NativeTokenQuantityDto(vaultAddress, inputQty);
417
+ sellDto.uniqueKey = randomUniqueKey();
418
+ const signedDto = sellDto.signed(users.testUser1.privateKey);
419
+
420
+ // When
421
+ const buyRes = await contract.BuyExactToken(ctx, buyDto);
422
+
423
+ const sellRes = await contract.SellWithNative(ctx, signedDto);
424
+
425
+ // Then
426
+ expect(buyRes).toEqual(transactionSuccess(expectedBuyResponse));
427
+
428
+ expect(sellRes).toEqual(
429
+ transactionSuccess(
430
+ expect.objectContaining({
431
+ inputQuantity: memeSaleQtyDefaultSupply.times(multiplier).toString(),
432
+ outputQuantity: galaPurchaseQtyDefaultSupply.toString()
433
+ })
434
+ )
435
+ );
436
+
437
+ const galaPurchaseQty100xSupply = new BigNumber(sellRes.Data?.outputQuantity ?? -1);
438
+ const memeSaleQty100xSupply = new BigNumber(sellRes.Data?.inputQuantity ?? -1);
439
+
440
+ // Compared to the previous test where the Launchpad has the default 10 Million supply,
441
+ // We expect the Meme token Qty to scale 100x and the Gala Qty to remain the same
442
+ expect(galaPurchaseQtyDefaultSupply).toEqual(galaPurchaseQty100xSupply);
443
+ expect(memeSaleQtyDefaultSupply).toEqual(memeSaleQty100xSupply.dividedBy(multiplier));
444
+ });
289
445
  });
@@ -54,9 +54,13 @@ export async function fetchAndValidateSale(
54
54
  return sale;
55
55
  }
56
56
 
57
- export function getBondingConstants() {
57
+ export function getBondingConstants(multiplier?: number) {
58
+ const exponentFactor =
59
+ multiplier && multiplier > 0
60
+ ? new Decimal("1166069000000").times(1 / multiplier)
61
+ : new Decimal("1166069000000");
58
62
  return {
59
- exponentFactor: new Decimal("1166069000000"), // exponent factor / b
63
+ exponentFactor: exponentFactor, // exponent factor / b
60
64
  euler: new Decimal("2.7182818284590452353602874713527"), // e
61
65
  decimals: new Decimal("1e+18") // scaling factor for decimals
62
66
  };