@gala-chain/launchpad 1.0.13 → 1.0.15

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 (87) 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 +14 -1
  5. package/lib/src/api/types/LaunchpadDtos.js.map +1 -1
  6. package/lib/src/api/types/LaunchpadSale.d.ts +5 -2
  7. package/lib/src/api/types/LaunchpadSale.d.ts.map +1 -1
  8. package/lib/src/api/types/LaunchpadSale.js +35 -9
  9. package/lib/src/api/types/LaunchpadSale.js.map +1 -1
  10. package/lib/src/chaincode/LaunchpadContract.js +6 -6
  11. package/lib/src/chaincode/LaunchpadContract.js.map +1 -1
  12. package/lib/src/chaincode/launchpad/buyExactToken.d.ts.map +1 -1
  13. package/lib/src/chaincode/launchpad/buyExactToken.js +18 -42
  14. package/lib/src/chaincode/launchpad/buyExactToken.js.map +1 -1
  15. package/lib/src/chaincode/launchpad/buyWithNative.d.ts.map +1 -1
  16. package/lib/src/chaincode/launchpad/buyWithNative.js +12 -43
  17. package/lib/src/chaincode/launchpad/buyWithNative.js.map +1 -1
  18. package/lib/src/chaincode/launchpad/callMemeTokenIn.d.ts +4 -10
  19. package/lib/src/chaincode/launchpad/callMemeTokenIn.d.ts.map +1 -1
  20. package/lib/src/chaincode/launchpad/callMemeTokenIn.js +13 -9
  21. package/lib/src/chaincode/launchpad/callMemeTokenIn.js.map +1 -1
  22. package/lib/src/chaincode/launchpad/callMemeTokenOut.d.ts +4 -10
  23. package/lib/src/chaincode/launchpad/callMemeTokenOut.d.ts.map +1 -1
  24. package/lib/src/chaincode/launchpad/callMemeTokenOut.js +46 -22
  25. package/lib/src/chaincode/launchpad/callMemeTokenOut.js.map +1 -1
  26. package/lib/src/chaincode/launchpad/callNativeTokenIn.d.ts +4 -10
  27. package/lib/src/chaincode/launchpad/callNativeTokenIn.d.ts.map +1 -1
  28. package/lib/src/chaincode/launchpad/callNativeTokenIn.js +35 -14
  29. package/lib/src/chaincode/launchpad/callNativeTokenIn.js.map +1 -1
  30. package/lib/src/chaincode/launchpad/callNativeTokenOut.d.ts +4 -10
  31. package/lib/src/chaincode/launchpad/callNativeTokenOut.d.ts.map +1 -1
  32. package/lib/src/chaincode/launchpad/callNativeTokenOut.js +17 -12
  33. package/lib/src/chaincode/launchpad/callNativeTokenOut.js.map +1 -1
  34. package/lib/src/chaincode/launchpad/configureLaunchpadFeeConfig.js +1 -1
  35. package/lib/src/chaincode/launchpad/configureLaunchpadFeeConfig.js.map +1 -1
  36. package/lib/src/chaincode/launchpad/createSale.d.ts.map +1 -1
  37. package/lib/src/chaincode/launchpad/createSale.js +15 -1
  38. package/lib/src/chaincode/launchpad/createSale.js.map +1 -1
  39. package/lib/src/chaincode/launchpad/fees.d.ts +11 -0
  40. package/lib/src/chaincode/launchpad/fees.d.ts.map +1 -1
  41. package/lib/src/chaincode/launchpad/fees.js +42 -3
  42. package/lib/src/chaincode/launchpad/fees.js.map +1 -1
  43. package/lib/src/chaincode/launchpad/finaliseSale.d.ts.map +1 -1
  44. package/lib/src/chaincode/launchpad/finaliseSale.js +7 -4
  45. package/lib/src/chaincode/launchpad/finaliseSale.js.map +1 -1
  46. package/lib/src/chaincode/launchpad/sellExactToken.d.ts.map +1 -1
  47. package/lib/src/chaincode/launchpad/sellExactToken.js +14 -23
  48. package/lib/src/chaincode/launchpad/sellExactToken.js.map +1 -1
  49. package/lib/src/chaincode/launchpad/sellWithNative.d.ts.map +1 -1
  50. package/lib/src/chaincode/launchpad/sellWithNative.js +10 -19
  51. package/lib/src/chaincode/launchpad/sellWithNative.js.map +1 -1
  52. package/lib/src/chaincode/test/launchpadgala.d.ts.map +1 -1
  53. package/lib/src/chaincode/test/launchpadgala.js +2 -1
  54. package/lib/src/chaincode/test/launchpadgala.js.map +1 -1
  55. package/lib/src/chaincode/utils/launchpadSaleUtils.d.ts +4 -0
  56. package/lib/src/chaincode/utils/launchpadSaleUtils.d.ts.map +1 -1
  57. package/lib/src/chaincode/utils/launchpadSaleUtils.js +19 -1
  58. package/lib/src/chaincode/utils/launchpadSaleUtils.js.map +1 -1
  59. package/lib/src/cli.js +3 -1
  60. package/lib/src/cli.js.map +1 -1
  61. package/lib/tsconfig.tsbuildinfo +1 -1
  62. package/package.json +1 -1
  63. package/src/api/types/LaunchpadDtos.ts +15 -1
  64. package/src/api/types/LaunchpadSale.ts +32 -10
  65. package/src/chaincode/LaunchpadContract.ts +3 -3
  66. package/src/chaincode/launchpad/buyExactToken.spec.ts +3 -38
  67. package/src/chaincode/launchpad/buyExactToken.ts +22 -56
  68. package/src/chaincode/launchpad/buyWithNative.spec.ts +105 -45
  69. package/src/chaincode/launchpad/buyWithNative.ts +15 -65
  70. package/src/chaincode/launchpad/callMemeTokenIn.ts +36 -12
  71. package/src/chaincode/launchpad/callMemeTokenOut.spec.ts +2 -2
  72. package/src/chaincode/launchpad/callMemeTokenOut.ts +72 -26
  73. package/src/chaincode/launchpad/callNativeTokenIn.ts +62 -19
  74. package/src/chaincode/launchpad/callNativeTokenOut.ts +38 -15
  75. package/src/chaincode/launchpad/configureLaunchpadFeeConfig.spec.ts +0 -1
  76. package/src/chaincode/launchpad/configureLaunchpadFeeConfig.ts +1 -1
  77. package/src/chaincode/launchpad/createSale.ts +26 -3
  78. package/src/chaincode/launchpad/fees.ts +55 -3
  79. package/src/chaincode/launchpad/finaliseSale.ts +7 -4
  80. package/src/chaincode/launchpad/launchpadBatchSubmitAuthorizations.spec.ts +150 -68
  81. package/src/chaincode/launchpad/sellExactToken.spec.ts +4 -81
  82. package/src/chaincode/launchpad/sellExactToken.ts +16 -25
  83. package/src/chaincode/launchpad/sellWithNative.spec.ts +2 -42
  84. package/src/chaincode/launchpad/sellWithNative.ts +14 -23
  85. package/src/chaincode/test/launchpadgala.ts +3 -1
  86. package/src/chaincode/utils/launchpadSaleUtils.ts +25 -1
  87. package/src/cli.ts +3 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gala-chain/launchpad",
3
- "version": "1.0.13",
3
+ "version": "1.0.15",
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,6 +116,10 @@ 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)
@@ -128,7 +133,8 @@ export class CreateTokenSaleDTO extends SubmitCallDTO {
128
133
  preBuyQuantity: BigNumber,
129
134
  tokenCollection: string,
130
135
  tokenCategory: string,
131
- reverseBondingCurveConfiguration?: ReverseBondingCurveConfigurationDto
136
+ reverseBondingCurveConfiguration?: ReverseBondingCurveConfigurationDto,
137
+ saleStartTime?: number
132
138
  ) {
133
139
  super();
134
140
  this.tokenName = tokenName;
@@ -139,6 +145,10 @@ export class CreateTokenSaleDTO extends SubmitCallDTO {
139
145
  this.tokenCollection = tokenCollection;
140
146
  this.tokenCategory = tokenCategory;
141
147
  this.reverseBondingCurveConfiguration = reverseBondingCurveConfiguration;
148
+
149
+ if (saleStartTime !== undefined) {
150
+ this.saleStartTime = saleStartTime;
151
+ }
142
152
  }
143
153
  }
144
154
 
@@ -369,6 +379,10 @@ export class TradeCalculationResFeesDto {
369
379
  }
370
380
 
371
381
  export class TradeCalculationResDto {
382
+ @IsNotEmpty()
383
+ @IsString()
384
+ public originalQuantity: string;
385
+
372
386
  @IsNotEmpty()
373
387
  @IsString()
374
388
  public calculatedQuantity: string;
@@ -23,12 +23,13 @@ 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 { IsInt, IsNotEmpty, IsOptional, IsPositive, IsString, ValidateNested } from "class-validator";
27
27
  import { JSONSchema } from "class-validator-jsonschema";
28
28
 
29
29
  import { ReverseBondingCurveConfigurationChainObject } from "./LaunchpadDtos";
30
30
 
31
31
  export enum SaleStatus {
32
+ UPCOMING = "Upcoming",
32
33
  ONGOING = "Ongoing",
33
34
  END = "Finished"
34
35
  }
@@ -49,12 +50,18 @@ export class LaunchpadSale extends ChainObject {
49
50
  @IsNotEmpty()
50
51
  public saleStatus: SaleStatus;
51
52
 
52
- @Type(() => TokenInstanceKey)
53
+ @IsOptional()
54
+ @IsInt()
55
+ public saleStartTime?: number;
56
+
53
57
  @IsNotEmpty()
58
+ @ValidateNested()
59
+ @Type(() => TokenInstanceKey)
54
60
  public sellingToken: TokenInstanceKey;
55
61
 
56
- @Type(() => TokenInstanceKey)
57
62
  @IsNotEmpty()
63
+ @ValidateNested()
64
+ @Type(() => TokenInstanceKey)
58
65
  public nativeToken: TokenInstanceKey;
59
66
 
60
67
  @IsString()
@@ -99,11 +106,22 @@ export class LaunchpadSale extends ChainObject {
99
106
  })
100
107
  public static BASE_PRICE = "16506671506650";
101
108
 
109
+ @JSONSchema({
110
+ description: "The decimals of the selling token."
111
+ })
112
+ public static SELLING_TOKEN_DECIMALS = 9;
113
+
114
+ @JSONSchema({
115
+ description: "The decimals of the native token."
116
+ })
117
+ public static NATIVE_TOKEN_DECIMALS = 8;
118
+
102
119
  constructor(
103
120
  vaultAddress: UserAlias,
104
121
  sellingToken: TokenInstanceKey,
105
122
  reverseBondingCurveConfiguration: ReverseBondingCurveConfigurationChainObject | undefined,
106
- saleOwner: UserAlias
123
+ saleOwner: UserAlias,
124
+ saleStartTime?: number | undefined
107
125
  ) {
108
126
  super();
109
127
 
@@ -113,11 +131,20 @@ export class LaunchpadSale extends ChainObject {
113
131
  this.sellingToken = sellingToken;
114
132
  this.sellingTokenQuantity = "1e+7";
115
133
 
134
+ if (saleStartTime) {
135
+ this.saleStartTime = saleStartTime;
136
+ }
137
+
138
+ if (this.saleStartTime !== undefined && this.saleStartTime > 0) {
139
+ this.saleStatus = SaleStatus.UPCOMING;
140
+ } else {
141
+ this.saleStatus = SaleStatus.ONGOING;
142
+ }
143
+
116
144
  this.basePrice = new BigNumber(LaunchpadSale.BASE_PRICE);
117
145
  this.exponentFactor = new BigNumber("1166069000000");
118
146
  this.maxSupply = new BigNumber("1e+7");
119
147
  this.euler = new BigNumber("2.7182818284590452353602874713527");
120
- this.saleStatus = SaleStatus.ONGOING;
121
148
 
122
149
  const nativeTokenInstance = new TokenInstanceKey();
123
150
  nativeTokenInstance.collection = "GALA";
@@ -165,11 +192,6 @@ export class LaunchpadSale extends ChainObject {
165
192
  return nativeTokenInVault.toString();
166
193
  }
167
194
 
168
- public fetchBasePrice() {
169
- const basePriceBigNumber = new BigNumber(this.basePrice);
170
- return basePriceBigNumber.toString();
171
- }
172
-
173
195
  public finalizeSale() {
174
196
  this.saleStatus = SaleStatus.END;
175
197
  this.nativeTokenQuantity = "0";
@@ -137,7 +137,7 @@ export class LaunchpadContract extends GalaContract {
137
137
  @Submit({
138
138
  in: ConfigureLaunchpadFeeAddressDto,
139
139
  out: LaunchpadFeeConfig,
140
- allowedOrgs: ["CuratorOrg"]
140
+ allowedOrgs: [process.env.CURATOR_ORG_MSP ?? "CuratorOrg"]
141
141
  })
142
142
  public async ConfigureLaunchpadFeeAddress(
143
143
  ctx: GalaChainContext,
@@ -149,7 +149,7 @@ export class LaunchpadContract extends GalaContract {
149
149
  @Submit({
150
150
  in: FinalizeTokenAllocationDto,
151
151
  out: LaunchpadFinalizeFeeAllocation,
152
- allowedOrgs: ["CuratorOrg"]
152
+ allowedOrgs: [process.env.CURATOR_ORG_MSP ?? "CuratorOrg"]
153
153
  })
154
154
  public async FinalizeTokenAllocation(
155
155
  ctx: GalaChainContext,
@@ -209,7 +209,7 @@ export class LaunchpadContract extends GalaContract {
209
209
  @Evaluate({
210
210
  in: ChainCallDTO,
211
211
  out: LaunchpadFeeConfig,
212
- allowedOrgs: ["CuratorOrg"]
212
+ allowedOrgs: [process.env.CURATOR_ORG_MSP ?? "CuratorOrg"]
213
213
  })
214
214
  public async FetchLaunchpadFeeConfig(
215
215
  ctx: GalaChainContext,
@@ -23,8 +23,7 @@ import {
23
23
  asValidUserAlias,
24
24
  randomUniqueKey
25
25
  } from "@gala-chain/api";
26
- import { InvalidDecimalError } from "@gala-chain/chaincode";
27
- import { currency, fixture, transactionError, transactionSuccess, users } from "@gala-chain/test";
26
+ import { currency, fixture, transactionSuccess, users } from "@gala-chain/test";
28
27
  import BigNumber from "bignumber.js";
29
28
  import { plainToInstance } from "class-transformer";
30
29
 
@@ -61,7 +60,7 @@ describe("buyWithNative", () => {
61
60
 
62
61
  launchpadGalaClass = plainToInstance(TokenClass, {
63
62
  ...launchpadgala.tokenClassPlain(),
64
- decimals: 8
63
+ decimals: LaunchpadSale.NATIVE_TOKEN_DECIMALS
65
64
  });
66
65
 
67
66
  vaultAddress = asValidUserAlias(`service|${launchpadGalaClassKey.toStringKey()}$launchpad`);
@@ -128,40 +127,6 @@ describe("buyWithNative", () => {
128
127
  expect(buyTokenRes).toEqual(transactionSuccess());
129
128
  });
130
129
 
131
- it("should reject buy when meme token has 0 decimals and input dto contains fractional quantity", async () => {
132
- // Given - Setup token with 0 decimals to force decimal precision error
133
- const zeroDecimalCurrencyClass = plainToInstance(TokenClass, {
134
- ...currency.tokenClassPlain(),
135
- decimals: 0 // Integer-only token
136
- });
137
-
138
- const { ctx, contract } = fixture(LaunchpadContract)
139
- .registeredUsers(users.testUser1)
140
- .savedState(
141
- zeroDecimalCurrencyClass,
142
- currencyInstance,
143
- launchpadGalaClass,
144
- launchpadGalaInstance,
145
- sale,
146
- salelaunchpadGalaBalance,
147
- saleCurrencyBalance,
148
- userlaunchpadGalaBalance,
149
- userCurrencyBalance
150
- );
151
-
152
- const dto = new ExactTokenQuantityDto(vaultAddress, new BigNumber("500.555"));
153
- dto.uniqueKey = randomUniqueKey();
154
- dto.sign(users.testUser1.privateKey);
155
-
156
- // When
157
- const buyTokenRes = await contract.BuyExactToken(ctx, dto);
158
-
159
- // Then
160
- expect(buyTokenRes).toEqual(
161
- transactionError(new InvalidDecimalError(dto.tokenQuantity, zeroDecimalCurrencyClass.decimals))
162
- );
163
- });
164
-
165
130
  test("User should be able to buy exact tokens, without fee configured", async () => {
166
131
  // Given
167
132
  const { ctx, contract } = fixture(LaunchpadContract)
@@ -185,7 +150,7 @@ describe("buyWithNative", () => {
185
150
 
186
151
  const expectedResponse = plainToInstance(TradeResDto, {
187
152
  inputQuantity: "0.00825575",
188
- totalFees: "0.00000000",
153
+ totalFees: "0",
189
154
  totalTokenSold: "500",
190
155
  outputQuantity: "500",
191
156
  tokenName: "AUTOMATEDTESTCOIN",
@@ -12,20 +12,14 @@
12
12
  * See the License for the specific language governing permissions and
13
13
  * limitations under the License.
14
14
  */
15
- import { ValidationFailedError } from "@gala-chain/api";
16
- import {
17
- GalaChainContext,
18
- fetchOrCreateBalance,
19
- fetchTokenClass,
20
- putChainObject,
21
- transferToken
22
- } from "@gala-chain/chaincode";
15
+ import { GalaChainContext, fetchTokenClass, putChainObject, transferToken } from "@gala-chain/chaincode";
23
16
  import BigNumber from "bignumber.js";
24
17
 
25
- import { ExactTokenQuantityDto, LaunchpadSale, TradeResDto } from "../../api/types";
18
+ import { ExactTokenQuantityDto, TradeResDto } from "../../api/types";
26
19
  import { SlippageToleranceExceededError } from "../../api/utils/error";
27
- import { fetchAndValidateSale, fetchLaunchpadFeeAddress } from "../utils";
20
+ import { fetchAndValidateSale } from "../utils";
28
21
  import { callNativeTokenIn } from "./callNativeTokenIn";
22
+ import { transferTransactionFees } from "./fees";
29
23
  import { finalizeSale } from "./finaliseSale";
30
24
 
31
25
  /**
@@ -52,67 +46,39 @@ export async function buyExactToken(
52
46
 
53
47
  // Fetch and validate the sale based on the provided vault address
54
48
  const sale = await fetchAndValidateSale(ctx, buyTokenDTO.vaultAddress);
55
- const tokenLeftInVault = new BigNumber(sale.sellingTokenQuantity);
49
+ const tokensLeftInVault = new BigNumber(sale.sellingTokenQuantity);
56
50
 
57
51
  // Calculate the required amount of native tokens to buy the specified token amount
58
- const callNativeTokenInResult1 = await callNativeTokenIn(ctx, buyTokenDTO);
59
- let transactionFees = callNativeTokenInResult1.extraFees.transactionFees;
60
- let nativeTokensToBuy = new BigNumber(callNativeTokenInResult1.calculatedQuantity);
52
+ const callNativeTokenInResult = await callNativeTokenIn(ctx, buyTokenDTO);
53
+ const tokensToBuy = new BigNumber(callNativeTokenInResult.originalQuantity); // number of tokens user wants to buy
54
+ const nativeTokensRequired = new BigNumber(callNativeTokenInResult.calculatedQuantity); // number of native tokens required to buy the tokens
55
+ const transactionFees = new BigNumber(callNativeTokenInResult.extraFees.transactionFees); // transaction fees
56
+
61
57
  const nativeToken = sale.fetchNativeTokenInstanceKey();
62
58
  const memeToken = sale.fetchSellingTokenInstanceKey();
63
59
 
64
- // If the requested token amount exceeds what's available, adjust it and recalculate native tokens needed
65
- if (tokenLeftInVault.lte(buyTokenDTO.tokenQuantity)) {
66
- buyTokenDTO.tokenQuantity = tokenLeftInVault;
67
- const callNativeTokenInResult2 = await callNativeTokenIn(ctx, buyTokenDTO);
68
- nativeTokensToBuy = new BigNumber(callNativeTokenInResult2.calculatedQuantity);
69
- transactionFees = callNativeTokenInResult2.extraFees.transactionFees;
70
- isSaleFinalized = true;
71
- }
72
-
73
- // Check if the native tokens used exceed the market cap, finalizing the sale if true
74
- if (
75
- nativeTokensToBuy
76
- .plus(new BigNumber(sale.nativeTokenQuantity))
77
- .gte(new BigNumber(LaunchpadSale.MARKET_CAP))
78
- ) {
60
+ // If the requested token amount exceeds what's available, finalise the sale
61
+ // Token amounts have been adjusted in the callNativeTokenIn function if they exceed the total supply
62
+ if (tokensLeftInVault.isLessThanOrEqualTo(buyTokenDTO.tokenQuantity)) {
79
63
  isSaleFinalized = true;
80
64
  }
81
65
 
82
66
  // Ensure the expected native token amount is not less than the actual amount required
83
- if (buyTokenDTO.expectedNativeToken && buyTokenDTO.expectedNativeToken.comparedTo(nativeTokensToBuy) < 0) {
67
+ if (buyTokenDTO.expectedNativeToken && buyTokenDTO.expectedNativeToken.isLessThan(nativeTokensRequired)) {
84
68
  throw new SlippageToleranceExceededError(
85
- `expected ${buyTokenDTO.expectedNativeToken.toString()}, but at least ${nativeTokensToBuy.toString()} are required to complete this operation. Increase the expected amount or adjust your slippage tolerance.`
69
+ `expected ${buyTokenDTO.expectedNativeToken.toString()}, but at least ${nativeTokensRequired.toString()} are required to complete this operation. Increase the expected amount or adjust your slippage tolerance.`
86
70
  );
87
71
  }
88
72
 
89
73
  // Transfer transaction fees
90
- const launchpadFeeAddressConfiguration = await fetchLaunchpadFeeAddress(ctx);
91
- if (launchpadFeeAddressConfiguration && transactionFees) {
92
- const totalRequired = nativeTokensToBuy.plus(new BigNumber(transactionFees));
93
-
94
- const buyerBalance = await fetchOrCreateBalance(ctx, ctx.callingUser, sale.nativeToken);
95
- if (buyerBalance.getQuantityTotal().lt(totalRequired)) {
96
- throw new ValidationFailedError(
97
- `Insufficient balance: Total amount required including fee is ${totalRequired}`
98
- );
99
- }
100
- await transferToken(ctx, {
101
- from: ctx.callingUser,
102
- to: launchpadFeeAddressConfiguration.feeAddress,
103
- tokenInstanceKey: nativeToken,
104
- quantity: new BigNumber(transactionFees),
105
- allowancesToUse: [],
106
- authorizedOnBehalf: undefined
107
- });
108
- }
74
+ await transferTransactionFees(ctx, sale, transactionFees, nativeToken, nativeTokensRequired);
109
75
 
110
76
  // Transfer native tokens from the buyer to the vault
111
77
  await transferToken(ctx, {
112
78
  from: ctx.callingUser,
113
79
  to: buyTokenDTO.vaultAddress,
114
80
  tokenInstanceKey: nativeToken,
115
- quantity: nativeTokensToBuy,
81
+ quantity: nativeTokensRequired,
116
82
  allowancesToUse: [],
117
83
  authorizedOnBehalf: undefined
118
84
  });
@@ -122,7 +88,7 @@ export async function buyExactToken(
122
88
  from: buyTokenDTO.vaultAddress,
123
89
  to: ctx.callingUser,
124
90
  tokenInstanceKey: memeToken,
125
- quantity: buyTokenDTO.tokenQuantity,
91
+ quantity: tokensToBuy,
126
92
  allowancesToUse: [],
127
93
  authorizedOnBehalf: {
128
94
  callingOnBehalf: buyTokenDTO.vaultAddress,
@@ -131,7 +97,7 @@ export async function buyExactToken(
131
97
  });
132
98
 
133
99
  // Update the sale record with the purchased token details
134
- sale.buyToken(buyTokenDTO.tokenQuantity, nativeTokensToBuy);
100
+ sale.buyToken(tokensToBuy, nativeTokensRequired);
135
101
  await putChainObject(ctx, sale);
136
102
 
137
103
  // If the sale is finalized, create a V3 pool and add liquidity
@@ -142,9 +108,9 @@ export async function buyExactToken(
142
108
  // Return the updated balance response
143
109
  const token = await fetchTokenClass(ctx, sale.sellingToken);
144
110
  return {
145
- inputQuantity: nativeTokensToBuy.toFixed(),
146
- totalFees: transactionFees,
147
- outputQuantity: buyTokenDTO.tokenQuantity.toFixed(),
111
+ inputQuantity: nativeTokensRequired.toFixed(),
112
+ totalFees: transactionFees.toFixed(),
113
+ outputQuantity: tokensToBuy.toFixed(),
148
114
  tokenName: token.name,
149
115
  tradeType: "Buy",
150
116
  vaultAddress: buyTokenDTO.vaultAddress,
@@ -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();
@@ -130,41 +138,6 @@ describe("buyWithNative", () => {
130
138
  );
131
139
  });
132
140
 
133
- it("should reject buy when input dto has higher fractional precision than GALA TokenClass", async () => {
134
- // Given
135
- const zeroDecimalLaunchpadClass = plainToInstance(TokenClass, {
136
- ...launchpadgala.tokenClassPlain(),
137
- decimals: 0 // Integer-only meme token
138
- });
139
-
140
- const { ctx, contract } = fixture(LaunchpadContract)
141
- .registeredUsers(users.testUser1)
142
- .savedState(
143
- currencyInstance,
144
- currencyClass,
145
- zeroDecimalLaunchpadClass,
146
- launchpadGalaInstance,
147
- sale,
148
- salelaunchpadGalaBalance,
149
- saleCurrencyBalance,
150
- userlaunchpadGalaBalance,
151
- userCurrencyBalance
152
- );
153
-
154
- // Use a native token amount that will produce fractional meme tokens from bonding curve
155
- const dto = new NativeTokenQuantityDto(vaultAddress, new BigNumber("0.01"));
156
- dto.uniqueKey = randomUniqueKey();
157
- dto.sign(users.testUser1.privateKey);
158
-
159
- // When
160
- const buyTokenRes = await contract.BuyWithNative(ctx, dto);
161
-
162
- // Then - Expect error due to decimal precision mismatch
163
- expect(buyTokenRes).toEqual(
164
- transactionError(new InvalidDecimalError(dto.nativeTokenQuantity, zeroDecimalLaunchpadClass.decimals))
165
- );
166
- });
167
-
168
141
  test("User buys tokens by providing native gala, without fee needing to be configured", async () => {
169
142
  //Given
170
143
  const { ctx, contract } = fixture(LaunchpadContract)
@@ -188,9 +161,9 @@ describe("buyWithNative", () => {
188
161
 
189
162
  const expectedResponse = plainToInstance(TradeResDto, {
190
163
  inputQuantity: "150",
191
- totalFees: "0.00000000",
192
- totalTokenSold: "2101667.8890651635",
193
- outputQuantity: "2101667.8890651635",
164
+ totalFees: "0",
165
+ totalTokenSold: "2101667.889065163",
166
+ outputQuantity: "2101667.889065163",
194
167
  tokenName: "AUTOMATEDTESTCOIN",
195
168
  tradeType: "Buy",
196
169
  vaultAddress: "service|GALA$Unit$none$none$launchpad",
@@ -234,9 +207,9 @@ describe("buyWithNative", () => {
234
207
 
235
208
  const expectedResponse = plainToInstance(TradeResDto, {
236
209
  inputQuantity: "1000",
237
- totalFees: "320.00000000",
238
- totalTokenSold: "3663321.3628130557",
239
- outputQuantity: "3663321.3628130557",
210
+ totalFees: "320",
211
+ totalTokenSold: "3663321.362813055",
212
+ outputQuantity: "3663321.362813055",
240
213
  tokenName: "AUTOMATEDTESTCOIN",
241
214
  tradeType: "Buy",
242
215
  vaultAddress: "service|GALA$Unit$none$none$launchpad",
@@ -291,4 +264,91 @@ describe("buyWithNative", () => {
291
264
  )
292
265
  );
293
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
+ });
294
350
  });
351
+ function roundToDecimal(value, decimals) {
352
+ const factor = Math.pow(10, decimals);
353
+ return Math.round(value * factor) / factor;
354
+ }