@gala-chain/launchpad 1.0.13 → 1.0.14

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 (86) hide show
  1. package/lib/package.json +1 -1
  2. package/lib/src/api/types/LaunchpadDtos.d.ts +1 -0
  3. package/lib/src/api/types/LaunchpadDtos.d.ts.map +1 -1
  4. package/lib/src/api/types/LaunchpadDtos.js +5 -0
  5. package/lib/src/api/types/LaunchpadDtos.js.map +1 -1
  6. package/lib/src/api/types/LaunchpadSale.d.ts +2 -1
  7. package/lib/src/api/types/LaunchpadSale.d.ts.map +1 -1
  8. package/lib/src/api/types/LaunchpadSale.js +18 -6
  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.js +1 -1
  37. package/lib/src/chaincode/launchpad/createSale.js.map +1 -1
  38. package/lib/src/chaincode/launchpad/fees.d.ts +11 -0
  39. package/lib/src/chaincode/launchpad/fees.d.ts.map +1 -1
  40. package/lib/src/chaincode/launchpad/fees.js +42 -3
  41. package/lib/src/chaincode/launchpad/fees.js.map +1 -1
  42. package/lib/src/chaincode/launchpad/finaliseSale.d.ts.map +1 -1
  43. package/lib/src/chaincode/launchpad/finaliseSale.js +7 -4
  44. package/lib/src/chaincode/launchpad/finaliseSale.js.map +1 -1
  45. package/lib/src/chaincode/launchpad/sellExactToken.d.ts.map +1 -1
  46. package/lib/src/chaincode/launchpad/sellExactToken.js +14 -23
  47. package/lib/src/chaincode/launchpad/sellExactToken.js.map +1 -1
  48. package/lib/src/chaincode/launchpad/sellWithNative.d.ts.map +1 -1
  49. package/lib/src/chaincode/launchpad/sellWithNative.js +10 -19
  50. package/lib/src/chaincode/launchpad/sellWithNative.js.map +1 -1
  51. package/lib/src/chaincode/test/launchpadgala.d.ts.map +1 -1
  52. package/lib/src/chaincode/test/launchpadgala.js +2 -1
  53. package/lib/src/chaincode/test/launchpadgala.js.map +1 -1
  54. package/lib/src/chaincode/utils/launchpadSaleUtils.d.ts +4 -0
  55. package/lib/src/chaincode/utils/launchpadSaleUtils.d.ts.map +1 -1
  56. package/lib/src/chaincode/utils/launchpadSaleUtils.js +10 -1
  57. package/lib/src/chaincode/utils/launchpadSaleUtils.js.map +1 -1
  58. package/lib/src/cli.js +3 -1
  59. package/lib/src/cli.js.map +1 -1
  60. package/lib/tsconfig.tsbuildinfo +1 -1
  61. package/package.json +1 -1
  62. package/src/api/types/LaunchpadDtos.ts +4 -0
  63. package/src/api/types/LaunchpadSale.ts +14 -7
  64. package/src/chaincode/LaunchpadContract.ts +3 -3
  65. package/src/chaincode/launchpad/buyExactToken.spec.ts +2 -37
  66. package/src/chaincode/launchpad/buyExactToken.ts +22 -56
  67. package/src/chaincode/launchpad/buyWithNative.spec.ts +2 -37
  68. package/src/chaincode/launchpad/buyWithNative.ts +15 -65
  69. package/src/chaincode/launchpad/callMemeTokenIn.ts +36 -12
  70. package/src/chaincode/launchpad/callMemeTokenOut.spec.ts +1 -1
  71. package/src/chaincode/launchpad/callMemeTokenOut.ts +72 -26
  72. package/src/chaincode/launchpad/callNativeTokenIn.ts +62 -19
  73. package/src/chaincode/launchpad/callNativeTokenOut.ts +38 -15
  74. package/src/chaincode/launchpad/configureLaunchpadFeeConfig.spec.ts +0 -1
  75. package/src/chaincode/launchpad/configureLaunchpadFeeConfig.ts +1 -1
  76. package/src/chaincode/launchpad/createSale.ts +1 -1
  77. package/src/chaincode/launchpad/fees.ts +55 -3
  78. package/src/chaincode/launchpad/finaliseSale.ts +7 -4
  79. package/src/chaincode/launchpad/launchpadBatchSubmitAuthorizations.spec.ts +150 -68
  80. package/src/chaincode/launchpad/sellExactToken.spec.ts +4 -81
  81. package/src/chaincode/launchpad/sellExactToken.ts +16 -25
  82. package/src/chaincode/launchpad/sellWithNative.spec.ts +2 -42
  83. package/src/chaincode/launchpad/sellWithNative.ts +14 -23
  84. package/src/chaincode/test/launchpadgala.ts +3 -1
  85. package/src/chaincode/utils/launchpadSaleUtils.ts +13 -1
  86. 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.14",
4
4
  "description": "GalaChain Launchpad Chaincode",
5
5
  "main": "lib/src/index.js",
6
6
  "types": "lib/src/index.d.ts",
@@ -369,6 +369,10 @@ export class TradeCalculationResFeesDto {
369
369
  }
370
370
 
371
371
  export class TradeCalculationResDto {
372
+ @IsNotEmpty()
373
+ @IsString()
374
+ public originalQuantity: string;
375
+
372
376
  @IsNotEmpty()
373
377
  @IsString()
374
378
  public calculatedQuantity: string;
@@ -49,12 +49,14 @@ export class LaunchpadSale extends ChainObject {
49
49
  @IsNotEmpty()
50
50
  public saleStatus: SaleStatus;
51
51
 
52
- @Type(() => TokenInstanceKey)
53
52
  @IsNotEmpty()
53
+ @ValidateNested()
54
+ @Type(() => TokenInstanceKey)
54
55
  public sellingToken: TokenInstanceKey;
55
56
 
56
- @Type(() => TokenInstanceKey)
57
57
  @IsNotEmpty()
58
+ @ValidateNested()
59
+ @Type(() => TokenInstanceKey)
58
60
  public nativeToken: TokenInstanceKey;
59
61
 
60
62
  @IsString()
@@ -99,6 +101,16 @@ export class LaunchpadSale extends ChainObject {
99
101
  })
100
102
  public static BASE_PRICE = "16506671506650";
101
103
 
104
+ @JSONSchema({
105
+ description: "The decimals of the selling token."
106
+ })
107
+ public static SELLING_TOKEN_DECIMALS = 18;
108
+
109
+ @JSONSchema({
110
+ description: "The decimals of the native token."
111
+ })
112
+ public static NATIVE_TOKEN_DECIMALS = 8;
113
+
102
114
  constructor(
103
115
  vaultAddress: UserAlias,
104
116
  sellingToken: TokenInstanceKey,
@@ -165,11 +177,6 @@ export class LaunchpadSale extends ChainObject {
165
177
  return nativeTokenInVault.toString();
166
178
  }
167
179
 
168
- public fetchBasePrice() {
169
- const basePriceBigNumber = new BigNumber(this.basePrice);
170
- return basePriceBigNumber.toString();
171
- }
172
-
173
180
  public finalizeSale() {
174
181
  this.saleStatus = SaleStatus.END;
175
182
  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
 
@@ -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,
@@ -130,41 +130,6 @@ describe("buyWithNative", () => {
130
130
  );
131
131
  });
132
132
 
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
133
  test("User buys tokens by providing native gala, without fee needing to be configured", async () => {
169
134
  //Given
170
135
  const { ctx, contract } = fixture(LaunchpadContract)
@@ -188,7 +153,7 @@ describe("buyWithNative", () => {
188
153
 
189
154
  const expectedResponse = plainToInstance(TradeResDto, {
190
155
  inputQuantity: "150",
191
- totalFees: "0.00000000",
156
+ totalFees: "0",
192
157
  totalTokenSold: "2101667.8890651635",
193
158
  outputQuantity: "2101667.8890651635",
194
159
  tokenName: "AUTOMATEDTESTCOIN",
@@ -234,7 +199,7 @@ describe("buyWithNative", () => {
234
199
 
235
200
  const expectedResponse = plainToInstance(TradeResDto, {
236
201
  inputQuantity: "1000",
237
- totalFees: "320.00000000",
202
+ totalFees: "320",
238
203
  totalTokenSold: "3663321.3628130557",
239
204
  outputQuantity: "3663321.3628130557",
240
205
  tokenName: "AUTOMATEDTESTCOIN",
@@ -12,22 +12,14 @@
12
12
  * See the License for the specific language governing permissions and
13
13
  * limitations under the License.
14
14
  */
15
- import { TokenClass, ValidationFailedError } from "@gala-chain/api";
16
- import {
17
- GalaChainContext,
18
- fetchOrCreateBalance,
19
- fetchTokenClass,
20
- getObjectByKey,
21
- putChainObject,
22
- transferToken
23
- } from "@gala-chain/chaincode";
15
+ import { GalaChainContext, fetchTokenClass, putChainObject, transferToken } from "@gala-chain/chaincode";
24
16
  import BigNumber from "bignumber.js";
25
17
 
26
- import { ExactTokenQuantityDto, LaunchpadSale, NativeTokenQuantityDto, TradeResDto } from "../../api/types";
18
+ import { LaunchpadSale, NativeTokenQuantityDto, TradeResDto } from "../../api/types";
27
19
  import { SlippageToleranceExceededError } from "../../api/utils/error";
28
- import { fetchAndValidateSale, fetchLaunchpadFeeAddress } from "../utils";
20
+ import { fetchAndValidateSale } from "../utils";
29
21
  import { callMemeTokenOut } from "./callMemeTokenOut";
30
- import { callNativeTokenIn } from "./callNativeTokenIn";
22
+ import { transferTransactionFees } from "./fees";
31
23
  import { finalizeSale } from "./finaliseSale";
32
24
 
33
25
  /**
@@ -56,83 +48,41 @@ export async function buyWithNative(
56
48
 
57
49
  // Fetch and validate sale state
58
50
  const sale = await fetchAndValidateSale(ctx, buyTokenDTO.vaultAddress);
59
- const tokensLeftInVault = new BigNumber(sale.sellingTokenQuantity);
60
51
 
61
52
  // Calculate how many tokens the user can buy and fee info
62
53
  const callMemeTokenOutResult = await callMemeTokenOut(ctx, buyTokenDTO);
63
- let transactionFees = callMemeTokenOutResult.extraFees.transactionFees;
64
- let tokensToBuy = new BigNumber(callMemeTokenOutResult.calculatedQuantity);
54
+ const transactionFees = new BigNumber(callMemeTokenOutResult.extraFees.transactionFees); // transaction fees
55
+ const nativeTokensRequired = new BigNumber(callMemeTokenOutResult.originalQuantity); // number of native tokens user wants to spend
56
+ const tokensToBuy = new BigNumber(callMemeTokenOutResult.calculatedQuantity); // number of tokens user will be buying
65
57
 
66
58
  const nativeToken = sale.fetchNativeTokenInstanceKey();
67
59
  const memeToken = sale.fetchSellingTokenInstanceKey();
68
60
 
69
- // Round tokensToBuy based on decimals property of sellToken TokenClass entry,
70
- // because otherwise `transferToken()` call below will fail with
71
- // an INVALID_DECIMALS error.
72
- const { collection, category, type, additionalKey } = sale.sellingToken;
73
-
74
- const memeTokenClass = await getObjectByKey(
75
- ctx,
76
- TokenClass,
77
- TokenClass.getCompositeKeyFromParts(TokenClass.INDEX_KEY, [collection, category, type, additionalKey])
78
- );
79
-
80
- tokensToBuy = tokensToBuy.decimalPlaces(memeTokenClass.decimals);
81
-
82
- // If vault has fewer tokens than what user wants to buy, cap the purchase
83
- if (tokensLeftInVault.comparedTo(tokensToBuy) <= 0) {
84
- tokensToBuy = tokensLeftInVault.decimalPlaces(memeTokenClass.decimals);
85
- const nativeTokensRequiredToBuyDto = new ExactTokenQuantityDto(buyTokenDTO.vaultAddress, tokensToBuy);
86
- const callNativeTokenInResult = await callNativeTokenIn(ctx, nativeTokensRequiredToBuyDto);
87
- transactionFees = callMemeTokenOutResult.extraFees.transactionFees;
88
- buyTokenDTO.nativeTokenQuantity = new BigNumber(callNativeTokenInResult.calculatedQuantity);
89
- isSaleFinalized = true;
90
- }
91
-
92
- // Finalize sale if market cap is reached
61
+ // If native tokens required exceeds the market cap, the sale can be finalized
93
62
  if (
94
63
  buyTokenDTO.nativeTokenQuantity
95
64
  .plus(new BigNumber(sale.nativeTokenQuantity))
96
- .gte(new BigNumber(LaunchpadSale.MARKET_CAP))
65
+ .isGreaterThanOrEqualTo(new BigNumber(LaunchpadSale.MARKET_CAP))
97
66
  ) {
98
67
  isSaleFinalized = true;
99
68
  }
100
69
 
101
70
  // Check for slippage condition
102
- if (buyTokenDTO.expectedToken && buyTokenDTO.expectedToken.comparedTo(tokensToBuy) > 0) {
71
+ if (buyTokenDTO.expectedToken && buyTokenDTO.expectedToken.isGreaterThan(tokensToBuy)) {
103
72
  throw new SlippageToleranceExceededError(
104
73
  `expected ${buyTokenDTO.expectedToken.toString()}, but only ${tokensToBuy.toString()} tokens can be provided. Reduce the expected amount or adjust your slippage tolerance.`
105
74
  );
106
75
  }
107
76
 
108
77
  // Transfer transaction fees to launchpad fee address
109
- const launchpadFeeAddressConfiguration = await fetchLaunchpadFeeAddress(ctx);
110
- if (launchpadFeeAddressConfiguration && transactionFees) {
111
- const totalRequired = new BigNumber(buyTokenDTO.nativeTokenQuantity).plus(transactionFees);
112
-
113
- const buyerBalance = await fetchOrCreateBalance(ctx, ctx.callingUser, sale.nativeToken);
114
- if (buyerBalance.getQuantityTotal().lt(totalRequired)) {
115
- throw new ValidationFailedError(
116
- `Insufficient balance: Total amount required including fee is ${totalRequired}`
117
- );
118
- }
119
-
120
- await transferToken(ctx, {
121
- from: ctx.callingUser,
122
- to: launchpadFeeAddressConfiguration.feeAddress,
123
- tokenInstanceKey: nativeToken,
124
- quantity: new BigNumber(transactionFees),
125
- allowancesToUse: [],
126
- authorizedOnBehalf: undefined
127
- });
128
- }
78
+ await transferTransactionFees(ctx, sale, transactionFees, nativeToken, nativeTokensRequired);
129
79
 
130
80
  // Transfer native tokens from buyer to vault
131
81
  await transferToken(ctx, {
132
82
  from: ctx.callingUser,
133
83
  to: buyTokenDTO.vaultAddress,
134
84
  tokenInstanceKey: nativeToken,
135
- quantity: buyTokenDTO.nativeTokenQuantity,
85
+ quantity: nativeTokensRequired,
136
86
  allowancesToUse: [],
137
87
  authorizedOnBehalf: undefined
138
88
  });
@@ -151,7 +101,7 @@ export async function buyWithNative(
151
101
  });
152
102
 
153
103
  // Update sale object with purchase data
154
- sale.buyToken(tokensToBuy, buyTokenDTO.nativeTokenQuantity);
104
+ sale.buyToken(tokensToBuy, nativeTokensRequired);
155
105
  await putChainObject(ctx, sale);
156
106
 
157
107
  // Finalize sale if it's complete
@@ -161,8 +111,8 @@ export async function buyWithNative(
161
111
 
162
112
  const token = await fetchTokenClass(ctx, sale.sellingToken);
163
113
  return {
164
- inputQuantity: buyTokenDTO.nativeTokenQuantity.toFixed(),
165
- totalFees: transactionFees,
114
+ inputQuantity: nativeTokensRequired.toFixed(),
115
+ totalFees: transactionFees.toFixed(),
166
116
  outputQuantity: tokensToBuy.toFixed(),
167
117
  tokenName: token.name,
168
118
  tradeType: "Buy",
@@ -17,14 +17,27 @@ import { GalaChainContext } from "@gala-chain/chaincode";
17
17
  import BigNumber from "bignumber.js";
18
18
  import Decimal from "decimal.js";
19
19
 
20
- import { LaunchpadSale, NativeTokenQuantityDto } from "../../api/types";
21
- import { fetchAndValidateSale, fetchLaunchpadFeeAddress, getBondingConstants } from "../utils";
20
+ import { LaunchpadSale, NativeTokenQuantityDto, TradeCalculationResDto } from "../../api/types";
21
+ import {
22
+ fetchAndValidateSale,
23
+ fetchLaunchpadFeeAddress,
24
+ fetchTokenDecimals,
25
+ getBondingConstants
26
+ } from "../utils";
22
27
  import { calculateReverseBondingCurveFee, calculateTransactionFee } from "./fees";
23
28
 
24
- function calculateMemeTokensRequired(sale: LaunchpadSale, requestedNativeTokenQuantity: BigNumber) {
29
+ function calculateMemeTokensRequired(
30
+ sale: LaunchpadSale,
31
+ requestedNativeTokenQuantity: BigNumber,
32
+ nativeTokenDecimals: number,
33
+ sellingTokenDecimals: number
34
+ ): [string, string] {
25
35
  const totalTokensSold = new Decimal(sale.fetchTokensSold()); // current tokens sold / x
26
- let nativeTokens = new Decimal(requestedNativeTokenQuantity.toString()); // native tokens used to buy / y
27
- const basePrice = new Decimal(sale.fetchBasePrice()); // base price / a
36
+ let nativeTokens = new Decimal(requestedNativeTokenQuantity.toString()).toDecimalPlaces(
37
+ nativeTokenDecimals,
38
+ Decimal.ROUND_DOWN
39
+ );
40
+ const basePrice = new Decimal(LaunchpadSale.BASE_PRICE); // base price / a
28
41
  const { exponentFactor, euler, decimals } = getBondingConstants();
29
42
 
30
43
  const nativeTokenInVault = new Decimal(sale.nativeTokenQuantity);
@@ -36,16 +49,16 @@ function calculateMemeTokensRequired(sale: LaunchpadSale, requestedNativeTokenQu
36
49
  const exp1 = euler.pow(exponent);
37
50
  const constantFactor = nativeTokens.mul(exponentFactor).div(basePrice);
38
51
 
39
- if (exp1.lte(constantFactor)) {
52
+ if (exp1.lessThanOrEqualTo(constantFactor)) {
40
53
  throw new ValidationFailedError("Cannot sell more tokens than have been bought in this sale.");
41
54
  }
42
55
 
43
56
  const adjustedExp = exp1.minus(constantFactor);
44
57
  const lnAdjustedExp = adjustedExp.ln();
45
58
  const tokensSent = totalTokensSold.minus(lnAdjustedExp.mul(decimals).div(exponentFactor));
46
- const roundedTokenSent = tokensSent.toDecimalPlaces(18, Decimal.ROUND_UP);
59
+ const roundedTokenSent = tokensSent.toDecimalPlaces(sellingTokenDecimals, Decimal.ROUND_UP);
47
60
 
48
- return roundedTokenSent.toFixed();
61
+ return [nativeTokens.toFixed(), roundedTokenSent.toFixed()];
49
62
  }
50
63
 
51
64
  /**
@@ -61,17 +74,28 @@ function calculateMemeTokensRequired(sale: LaunchpadSale, requestedNativeTokenQu
61
74
  * - `nativeTokenAmount`: The amount of native tokens to be recieved from sale.
62
75
  * - `expectedToken` (optional): The expected amount of tokens to be sold.
63
76
  *
64
- * @returns A promise that resolves to a string representing the calculated amount of
65
- * tokens to be sent, rounded up to 18 decimal places.
77
+ * @returns A promise that resolves to a TradeCalculationResDto object containing the calculated
78
+ * quantity of tokens to be sent, the original quantity of native tokens used, and extra fees.
66
79
  *
67
80
  * @throws Error if the calculation results in an invalid amount (e.g., `InvalidAmountError`).
68
81
  */
69
- export async function callMemeTokenIn(ctx: GalaChainContext, sellTokenDTO: NativeTokenQuantityDto) {
82
+ export async function callMemeTokenIn(
83
+ ctx: GalaChainContext,
84
+ sellTokenDTO: NativeTokenQuantityDto
85
+ ): Promise<TradeCalculationResDto> {
70
86
  const sale = await fetchAndValidateSale(ctx, sellTokenDTO.vaultAddress);
71
87
  const launchpadFeeAddressConfiguration = await fetchLaunchpadFeeAddress(ctx);
88
+ const { nativeTokenDecimals, sellingTokenDecimals } = await fetchTokenDecimals(ctx, sale);
89
+ const [originalQuantity, calculatedQuantity] = calculateMemeTokensRequired(
90
+ sale,
91
+ sellTokenDTO.nativeTokenQuantity,
92
+ nativeTokenDecimals,
93
+ sellingTokenDecimals
94
+ );
72
95
 
73
96
  return {
74
- calculatedQuantity: calculateMemeTokensRequired(sale, sellTokenDTO.nativeTokenQuantity),
97
+ originalQuantity: originalQuantity,
98
+ calculatedQuantity: calculatedQuantity,
75
99
  extraFees: {
76
100
  reverseBondingCurve: calculateReverseBondingCurveFee(sale, sellTokenDTO.nativeTokenQuantity).toString(),
77
101
  transactionFees: calculateTransactionFee(
@@ -111,7 +111,7 @@ describe("callMemeTokenOut", () => {
111
111
 
112
112
  // // Then
113
113
  expect(response.Data).toMatchObject({
114
- calculatedQuantity: "58497.530004606452819",
114
+ calculatedQuantity: "58497.5300046064",
115
115
  extraFees: { reverseBondingCurve: "0", transactionFees: "0.00000000" }
116
116
  });
117
117
  });