@gala-chain/launchpad 1.0.1 → 1.0.3

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 (110) hide show
  1. package/lib/package.json +2 -2
  2. package/lib/src/api/types/LaunchpadBatchSubmitAuthorities.d.ts +12 -0
  3. package/lib/src/api/types/LaunchpadBatchSubmitAuthorities.d.ts.map +1 -0
  4. package/lib/src/api/types/LaunchpadBatchSubmitAuthorities.js +56 -0
  5. package/lib/src/api/types/LaunchpadBatchSubmitAuthorities.js.map +1 -0
  6. package/lib/src/api/types/LaunchpadDtos.d.ts +14 -0
  7. package/lib/src/api/types/LaunchpadDtos.d.ts.map +1 -1
  8. package/lib/src/api/types/LaunchpadDtos.js +45 -1
  9. package/lib/src/api/types/LaunchpadDtos.js.map +1 -1
  10. package/lib/src/api/types/LaunchpadFeeConfig.d.ts +3 -2
  11. package/lib/src/api/types/LaunchpadFeeConfig.d.ts.map +1 -1
  12. package/lib/src/api/types/LaunchpadFeeConfig.js +15 -6
  13. package/lib/src/api/types/LaunchpadFeeConfig.js.map +1 -1
  14. package/lib/src/api/types/LaunchpadFinalizeAllocation.js +1 -2
  15. package/lib/src/api/types/LaunchpadFinalizeAllocation.js.map +1 -1
  16. package/lib/src/api/types/LaunchpadSale.js +1 -2
  17. package/lib/src/api/types/LaunchpadSale.js.map +1 -1
  18. package/lib/src/api/types/index.d.ts +1 -0
  19. package/lib/src/api/types/index.d.ts.map +1 -1
  20. package/lib/src/api/types/index.js +1 -0
  21. package/lib/src/api/types/index.js.map +1 -1
  22. package/lib/src/api/validators/decorators.js.map +1 -1
  23. package/lib/src/chaincode/LaunchpadContract.d.ts +6 -1
  24. package/lib/src/chaincode/LaunchpadContract.d.ts.map +1 -1
  25. package/lib/src/chaincode/LaunchpadContract.js +97 -0
  26. package/lib/src/chaincode/LaunchpadContract.js.map +1 -1
  27. package/lib/src/chaincode/launchpad/buyExactToken.d.ts.map +1 -1
  28. package/lib/src/chaincode/launchpad/buyExactToken.js +22 -9
  29. package/lib/src/chaincode/launchpad/buyExactToken.js.map +1 -1
  30. package/lib/src/chaincode/launchpad/buyWithNative.d.ts.map +1 -1
  31. package/lib/src/chaincode/launchpad/buyWithNative.js +31 -9
  32. package/lib/src/chaincode/launchpad/buyWithNative.js.map +1 -1
  33. package/lib/src/chaincode/launchpad/callMemeTokenIn.d.ts +1 -0
  34. package/lib/src/chaincode/launchpad/callMemeTokenIn.d.ts.map +1 -1
  35. package/lib/src/chaincode/launchpad/callMemeTokenIn.js +3 -5
  36. package/lib/src/chaincode/launchpad/callMemeTokenIn.js.map +1 -1
  37. package/lib/src/chaincode/launchpad/callMemeTokenOut.d.ts +1 -0
  38. package/lib/src/chaincode/launchpad/callMemeTokenOut.d.ts.map +1 -1
  39. package/lib/src/chaincode/launchpad/callMemeTokenOut.js +5 -5
  40. package/lib/src/chaincode/launchpad/callMemeTokenOut.js.map +1 -1
  41. package/lib/src/chaincode/launchpad/callNativeTokenIn.d.ts +1 -0
  42. package/lib/src/chaincode/launchpad/callNativeTokenIn.d.ts.map +1 -1
  43. package/lib/src/chaincode/launchpad/callNativeTokenIn.js +7 -6
  44. package/lib/src/chaincode/launchpad/callNativeTokenIn.js.map +1 -1
  45. package/lib/src/chaincode/launchpad/callNativeTokenOut.d.ts +1 -0
  46. package/lib/src/chaincode/launchpad/callNativeTokenOut.d.ts.map +1 -1
  47. package/lib/src/chaincode/launchpad/callNativeTokenOut.js +4 -5
  48. package/lib/src/chaincode/launchpad/callNativeTokenOut.js.map +1 -1
  49. package/lib/src/chaincode/launchpad/configureLaunchpadFeeConfig.d.ts +12 -0
  50. package/lib/src/chaincode/launchpad/configureLaunchpadFeeConfig.d.ts.map +1 -1
  51. package/lib/src/chaincode/launchpad/configureLaunchpadFeeConfig.js +26 -6
  52. package/lib/src/chaincode/launchpad/configureLaunchpadFeeConfig.js.map +1 -1
  53. package/lib/src/chaincode/launchpad/createSale.d.ts.map +1 -1
  54. package/lib/src/chaincode/launchpad/createSale.js +10 -12
  55. package/lib/src/chaincode/launchpad/createSale.js.map +1 -1
  56. package/lib/src/chaincode/launchpad/fees.d.ts +1 -0
  57. package/lib/src/chaincode/launchpad/fees.d.ts.map +1 -1
  58. package/lib/src/chaincode/launchpad/fees.js +6 -2
  59. package/lib/src/chaincode/launchpad/fees.js.map +1 -1
  60. package/lib/src/chaincode/launchpad/fetchLaunchpadAdressConfig.js.map +1 -1
  61. package/lib/src/chaincode/launchpad/fetchSaleDetails.d.ts.map +1 -1
  62. package/lib/src/chaincode/launchpad/fetchSaleDetails.js +0 -4
  63. package/lib/src/chaincode/launchpad/fetchSaleDetails.js.map +1 -1
  64. package/lib/src/chaincode/launchpad/finaliseSale.js +3 -3
  65. package/lib/src/chaincode/launchpad/finaliseSale.js.map +1 -1
  66. package/lib/src/chaincode/launchpad/finalizeTokenAllocation.js.map +1 -1
  67. package/lib/src/chaincode/launchpad/index.d.ts +1 -0
  68. package/lib/src/chaincode/launchpad/index.d.ts.map +1 -1
  69. package/lib/src/chaincode/launchpad/index.js +1 -0
  70. package/lib/src/chaincode/launchpad/index.js.map +1 -1
  71. package/lib/src/chaincode/launchpad/launchpadBatchSubmitAuthorizations.d.ts +24 -0
  72. package/lib/src/chaincode/launchpad/launchpadBatchSubmitAuthorizations.d.ts.map +1 -0
  73. package/lib/src/chaincode/launchpad/launchpadBatchSubmitAuthorizations.js +84 -0
  74. package/lib/src/chaincode/launchpad/launchpadBatchSubmitAuthorizations.js.map +1 -0
  75. package/lib/src/chaincode/launchpad/preMintCalculation.d.ts.map +1 -1
  76. package/lib/src/chaincode/launchpad/preMintCalculation.js +0 -4
  77. package/lib/src/chaincode/launchpad/preMintCalculation.js.map +1 -1
  78. package/lib/src/chaincode/launchpad/sellExactToken.d.ts.map +1 -1
  79. package/lib/src/chaincode/launchpad/sellExactToken.js +27 -11
  80. package/lib/src/chaincode/launchpad/sellExactToken.js.map +1 -1
  81. package/lib/src/chaincode/launchpad/sellWithNative.d.ts.map +1 -1
  82. package/lib/src/chaincode/launchpad/sellWithNative.js +30 -8
  83. package/lib/src/chaincode/launchpad/sellWithNative.js.map +1 -1
  84. package/lib/src/chaincode/utils/launchpadSaleUtils.js +2 -2
  85. package/lib/src/chaincode/utils/launchpadSaleUtils.js.map +1 -1
  86. package/lib/src/cli.js.map +1 -1
  87. package/lib/tsconfig.tsbuildinfo +1 -1
  88. package/package.json +3 -3
  89. package/src/api/types/LaunchpadBatchSubmitAuthorities.ts +53 -0
  90. package/src/api/types/LaunchpadDtos.ts +38 -0
  91. package/src/api/types/LaunchpadFeeConfig.ts +13 -4
  92. package/src/api/types/index.ts +1 -0
  93. package/src/chaincode/LaunchpadContract.ts +104 -1
  94. package/src/chaincode/launchpad/buyExactToken.ts +18 -6
  95. package/src/chaincode/launchpad/buyWithNative.ts +29 -6
  96. package/src/chaincode/launchpad/callMemeTokenIn.ts +9 -8
  97. package/src/chaincode/launchpad/callMemeTokenOut.ts +9 -7
  98. package/src/chaincode/launchpad/callNativeTokenIn.ts +11 -8
  99. package/src/chaincode/launchpad/callNativeTokenOut.ts +9 -8
  100. package/src/chaincode/launchpad/configureLaunchpadFeeConfig.ts +27 -4
  101. package/src/chaincode/launchpad/createSale.ts +1 -5
  102. package/src/chaincode/launchpad/fees.ts +5 -1
  103. package/src/chaincode/launchpad/fetchSaleDetails.ts +0 -5
  104. package/src/chaincode/launchpad/finaliseSale.ts +3 -3
  105. package/src/chaincode/launchpad/index.ts +1 -0
  106. package/src/chaincode/launchpad/launchpadBatchSubmitAuthorizations.spec.ts +185 -0
  107. package/src/chaincode/launchpad/launchpadBatchSubmitAuthorizations.ts +105 -0
  108. package/src/chaincode/launchpad/preMintCalculation.ts +0 -5
  109. package/src/chaincode/launchpad/sellExactToken.ts +30 -15
  110. package/src/chaincode/launchpad/sellWithNative.ts +31 -7
@@ -18,19 +18,35 @@ import { getObjectByKey, putChainObject } from "@gala-chain/chaincode";
18
18
 
19
19
  import { ConfigureLaunchpadFeeAddressDto, LaunchpadFeeConfig } from "../../api/types";
20
20
 
21
+ /**
22
+ * Configures or updates the Launchpad platform fee settings.
23
+ *
24
+ * This function initializes or modifies the fee address, fee amount, and list of authorities
25
+ * responsible for managing Launchpad platform fees. Only users from the designated curator
26
+ * organization or authorized addresses are permitted to make these changes.
27
+ *
28
+ * @param ctx - The context object providing access to the GalaChain environment.
29
+ * @param dto - The data transfer object containing the new fee address, fee amount,
30
+ * and optional list of new authorities.
31
+ * @returns A promise that resolves to the updated or newly created `LaunchpadFeeConfig` object.
32
+ */
21
33
  export async function configureLaunchpadFeeAddress(
22
34
  ctx: GalaChainContext,
23
35
  dto: ConfigureLaunchpadFeeAddressDto
24
36
  ): Promise<LaunchpadFeeConfig> {
25
- if (!dto.newPlatformFeeAddress && !dto.newAuthorities?.length) {
37
+ // Validate input: at least one field must be present
38
+ if (!dto.newPlatformFeeAddress && !dto.newAuthorities?.length && !dto.newFeeAmount) {
26
39
  throw new ValidationFailedError("None of the input fields are present.");
27
40
  }
28
41
 
29
42
  const curatorOrgMsp = process.env.CURATOR_ORG_MSP ?? "CuratorOrg";
30
43
 
31
44
  const key = ctx.stub.createCompositeKey(LaunchpadFeeConfig.INDEX_KEY, []);
45
+
46
+ // Attempt to fetch existing config from the chain
32
47
  let platformFeeAddress = await getObjectByKey(ctx, LaunchpadFeeConfig, key).catch((e) => {
33
48
  const chainError = ChainError.from(e);
49
+ // Handle case where config doesn't exist yet
34
50
  if (chainError.matches(ErrorCode.NOT_FOUND)) {
35
51
  return undefined;
36
52
  } else {
@@ -38,25 +54,32 @@ export async function configureLaunchpadFeeAddress(
38
54
  }
39
55
  });
40
56
 
57
+ // Authorization check
41
58
  if (ctx.clientIdentity.getMSPID() !== curatorOrgMsp) {
42
59
  if (!platformFeeAddress || !platformFeeAddress.authorities.includes(ctx.callingUser)) {
43
60
  throw new UnauthorizedError(`CallingUser ${ctx.callingUser} is not authorized to create or update`);
44
61
  }
45
62
  }
46
63
 
64
+ // If no existing config, this is the initial setup
47
65
  if (!platformFeeAddress) {
48
- if (!dto.newPlatformFeeAddress) {
66
+ // Require both address and fee amount on initial setup
67
+ if (!dto.newPlatformFeeAddress || !dto.newFeeAmount) {
49
68
  throw new ValidationFailedError(
50
- "Must provide a platform fee address in the initial setup of the configuration."
69
+ "Must provide a launchpad platform fee address and fee amount in the initial setup of the configuration."
51
70
  );
52
71
  }
72
+ // Create new configuration
53
73
  platformFeeAddress = new LaunchpadFeeConfig(
54
74
  dto.newPlatformFeeAddress,
75
+ dto.newFeeAmount,
55
76
  dto.newAuthorities ?? [ctx.callingUser]
56
77
  );
57
78
  } else if (platformFeeAddress && platformFeeAddress.authorities.includes(ctx.callingUser)) {
58
- platformFeeAddress.setNewFeeAddress(
79
+ // Caller is authorized and updating the existing config
80
+ platformFeeAddress.updateFeeConfig(
59
81
  dto.newPlatformFeeAddress ?? platformFeeAddress.feeAddress,
82
+ dto.newFeeAmount ?? platformFeeAddress.feeAmount,
60
83
  dto.newAuthorities ?? platformFeeAddress.authorities
61
84
  );
62
85
  } else {
@@ -21,16 +21,12 @@ import {
21
21
  putChainObject,
22
22
  updateTokenClass
23
23
  } from "@gala-chain/chaincode";
24
- import { BigNumber } from "bignumber.js";
24
+ import BigNumber from "bignumber.js";
25
25
 
26
26
  import { CreateSaleResDto, CreateTokenSaleDTO, LaunchpadSale, NativeTokenQuantityDto } from "../../api/types";
27
27
  import { PreConditionFailedError } from "../../api/utils/error";
28
28
  import { buyWithNative } from "./buyWithNative";
29
29
 
30
- BigNumber.config({
31
- ROUNDING_MODE: BigNumber.ROUND_UP
32
- });
33
-
34
30
  /**
35
31
  * Creates a new token sale (Launchpad) in the GalaChain environment.
36
32
  *
@@ -56,7 +56,7 @@ export async function payReverseBondingCurveFee(
56
56
  nativeTokensToReceive: BigNumber,
57
57
  maxAcceptableFee?: BigNumber
58
58
  ) {
59
- const feeAmount = await calculateReverseBondingCurveFee(sale, nativeTokensToReceive);
59
+ const feeAmount = calculateReverseBondingCurveFee(sale, nativeTokensToReceive);
60
60
 
61
61
  if (feeAmount.isZero()) {
62
62
  return; // No fee
@@ -103,3 +103,7 @@ export async function payReverseBondingCurveFee(
103
103
  authorizedOnBehalf: undefined
104
104
  });
105
105
  }
106
+
107
+ export function calculateTransactionFee(tokensBeingTraded: BigNumber, feeAmount?: number) {
108
+ return tokensBeingTraded.multipliedBy(feeAmount ?? 0).toFixed(8, BigNumber.ROUND_UP);
109
+ }
@@ -14,14 +14,9 @@
14
14
  */
15
15
  import { NotFoundError } from "@gala-chain/api";
16
16
  import { GalaChainContext, getObjectByKey } from "@gala-chain/chaincode";
17
- import { BigNumber } from "bignumber.js";
18
17
 
19
18
  import { FetchSaleDto, LaunchpadSale } from "../../api/types";
20
19
 
21
- BigNumber.config({
22
- ROUNDING_MODE: BigNumber.ROUND_UP
23
- });
24
-
25
20
  /**
26
21
  * Fetches the details of a specific token sale (LaunchpadSale) using its sale address.
27
22
  *
@@ -46,8 +46,8 @@ export async function finalizeSale(ctx: GalaChainContext, sale: LaunchpadSale):
46
46
  const key = ctx.stub.createCompositeKey(LaunchpadFinalizeFeeAllocation.INDEX_KEY, []);
47
47
  const feeAllocation = await getObjectByKey(ctx, LaunchpadFinalizeFeeAllocation, key).catch(() => undefined);
48
48
 
49
- const platformFeeAddressConfiguration = await fetchLaunchpadFeeAddress(ctx);
50
- if (!platformFeeAddressConfiguration) {
49
+ const launchpadFeeAddressConfiguration = await fetchLaunchpadFeeAddress(ctx);
50
+ if (!launchpadFeeAddressConfiguration) {
51
51
  throw new PreConditionFailedError("Platform fee configuration is yet to be defined.");
52
52
  }
53
53
 
@@ -75,7 +75,7 @@ export async function finalizeSale(ctx: GalaChainContext, sale: LaunchpadSale):
75
75
 
76
76
  await transferToken(ctx, {
77
77
  from: vaultAddressAlias,
78
- to: platformFeeAddressConfiguration.feeAddress,
78
+ to: launchpadFeeAddressConfiguration.feeAddress,
79
79
  tokenInstanceKey: nativeToken,
80
80
  quantity: new BigNumber(sale.nativeTokenQuantity)
81
81
  .times(platformFeePercentage)
@@ -26,3 +26,4 @@ export * from "./finalizeTokenAllocation";
26
26
  export * from "./sellWithNative";
27
27
  export * from "./configureLaunchpadFeeConfig";
28
28
  export * from "./fetchLaunchpadAdressConfig";
29
+ export * from "./launchpadBatchSubmitAuthorizations";
@@ -0,0 +1,185 @@
1
+ /*
2
+ * Copyright (c) Gala Games Inc. All rights reserved.
3
+ * Licensed under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License.
5
+ * You may obtain a copy of the License at
6
+ *
7
+ * http://www.apache.org/licenses/LICENSE-2.0
8
+ *
9
+ * Unless required by applicable law or agreed to in writing, software
10
+ * distributed under the License is distributed on an "AS IS" BASIS,
11
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ * See the License for the specific language governing permissions and
13
+ * limitations under the License.
14
+ */
15
+ import { NotFoundError } from "@gala-chain/api";
16
+ import { GalaChainContext, getObjectByKey, putChainObject } from "@gala-chain/chaincode";
17
+
18
+ import {
19
+ AuthorizeBatchSubmitterDto,
20
+ BatchSubmitAuthoritiesResDto,
21
+ DeauthorizeBatchSubmitterDto,
22
+ FetchBatchSubmitAuthoritiesDto,
23
+ LaunchpadBatchSubmitAuthorities
24
+ } from "../../api/types";
25
+ import {
26
+ authorizeLaunchpadBatchSubmitter,
27
+ deauthorizeLaunchpadBatchSubmitter,
28
+ fetchLaunchpadBatchSubmitAuthorities,
29
+ getLaunchpadBatchSubmitAuthorities
30
+ } from "./launchpadBatchSubmitAuthorizations";
31
+
32
+ jest.mock("@gala-chain/chaincode", () => ({
33
+ ...jest.requireActual("@gala-chain/chaincode"),
34
+ getObjectByKey: jest.fn(),
35
+ putChainObject: jest.fn()
36
+ }));
37
+
38
+ // Mock context for testing
39
+ const createMockContext = (callingUser: string): GalaChainContext => {
40
+ const mockStub = {
41
+ createCompositeKey: (indexKey: string, attributes: string[]) => {
42
+ return `${indexKey}${attributes.join("|")}`;
43
+ },
44
+ getState: jest.fn(),
45
+ putState: jest.fn()
46
+ };
47
+
48
+ return {
49
+ callingUser,
50
+ stub: mockStub as any,
51
+ clientIdentity: {
52
+ getMSPID: () => "CuratorOrg"
53
+ } as any
54
+ } as GalaChainContext;
55
+ };
56
+
57
+ describe("BatchSubmitAuthorizations", () => {
58
+ beforeEach(() => {
59
+ jest.clearAllMocks();
60
+ });
61
+
62
+ describe("BatchSubmitAuthorizations chain object", () => {
63
+ it("should create with initial authorities", () => {
64
+ const auth = new LaunchpadBatchSubmitAuthorities(["user1", "user2"]);
65
+ expect(auth.authorities).toEqual(["user1", "user2"]);
66
+ });
67
+
68
+ it("should add authority", () => {
69
+ const auth = new LaunchpadBatchSubmitAuthorities(["user1"]);
70
+ auth.addAuthority("user2");
71
+ expect(auth.authorities).toEqual(["user1", "user2"]);
72
+ });
73
+
74
+ it("should not add duplicate authority", () => {
75
+ const auth = new LaunchpadBatchSubmitAuthorities(["user1"]);
76
+ auth.addAuthority("user1");
77
+ expect(auth.authorities).toEqual(["user1"]);
78
+ });
79
+
80
+ it("should remove authority", () => {
81
+ const auth = new LaunchpadBatchSubmitAuthorities(["user1", "user2"]);
82
+ auth.removeAuthority("user1");
83
+ expect(auth.authorities).toEqual(["user2"]);
84
+ });
85
+
86
+ it("should check if user is authorized", () => {
87
+ const auth = new LaunchpadBatchSubmitAuthorities(["user1", "user2"]);
88
+ expect(auth.isAuthorized("user1")).toBe(true);
89
+ expect(auth.isAuthorized("user3")).toBe(false);
90
+ });
91
+
92
+ it("should get authorized authorities", () => {
93
+ const auth = new LaunchpadBatchSubmitAuthorities(["user1", "user2"]);
94
+ const authorities = auth.getAuthorities();
95
+ expect(authorities).toEqual(["user1", "user2"]);
96
+ // Should return a copy, not the original array
97
+ authorities.push("user3");
98
+ expect(auth.authorities).toEqual(["user1", "user2"]);
99
+ });
100
+ });
101
+
102
+ describe("fetchBatchSubmitAuthorizations", () => {
103
+ it("should return existing authorizations", async () => {
104
+ const ctx = createMockContext("user1");
105
+ const existingAuth = new LaunchpadBatchSubmitAuthorities(["user1", "user2"]);
106
+
107
+ (getObjectByKey as jest.Mock).mockResolvedValue(existingAuth);
108
+ (putChainObject as jest.Mock).mockResolvedValue(undefined);
109
+
110
+ const result = await fetchLaunchpadBatchSubmitAuthorities(ctx);
111
+
112
+ expect(result).toBe(existingAuth);
113
+ });
114
+ });
115
+
116
+ describe("authorizeBatchSubmitter", () => {
117
+ it("should authorize new users when caller is authorized", async () => {
118
+ const ctx = createMockContext("user1");
119
+ const existingAuth = new LaunchpadBatchSubmitAuthorities(["user1"]);
120
+ (putChainObject as jest.Mock).mockResolvedValue(existingAuth);
121
+ (putChainObject as jest.Mock).mockResolvedValue(undefined);
122
+
123
+ const dto = new AuthorizeBatchSubmitterDto();
124
+ dto.authorities = ["user2", "user3"];
125
+
126
+ const result = await authorizeLaunchpadBatchSubmitter(ctx, dto);
127
+
128
+ expect(result).toBeInstanceOf(BatchSubmitAuthoritiesResDto);
129
+ expect(result.authorities).toContain("user1");
130
+ expect(result.authorities).toContain("user2");
131
+ expect(result.authorities).toContain("user3");
132
+ });
133
+
134
+ it("should create new authorities object when none exists", async () => {
135
+ const ctx = createMockContext("user1");
136
+
137
+ (getObjectByKey as jest.Mock).mockRejectedValue(new NotFoundError("Not found"));
138
+ (putChainObject as jest.Mock).mockResolvedValue(undefined);
139
+
140
+ const dto = new AuthorizeBatchSubmitterDto();
141
+ dto.authorities = ["user1", "user2"];
142
+
143
+ const result = await authorizeLaunchpadBatchSubmitter(ctx, dto);
144
+
145
+ expect(result).toBeInstanceOf(BatchSubmitAuthoritiesResDto);
146
+ expect(result.authorities).toContain("user1");
147
+ expect(result.authorities).toContain("user2");
148
+ expect(putChainObject).toHaveBeenCalled();
149
+ });
150
+ });
151
+
152
+ describe("deauthorizeBatchSubmitter", () => {
153
+ it("should deauthorize user when caller is authorized", async () => {
154
+ const ctx = createMockContext("user1");
155
+ const existingAuth = new LaunchpadBatchSubmitAuthorities(["user1", "user2"]);
156
+
157
+ (getObjectByKey as jest.Mock).mockResolvedValue(existingAuth);
158
+ (putChainObject as jest.Mock).mockResolvedValue(undefined);
159
+
160
+ const dto = new DeauthorizeBatchSubmitterDto();
161
+ dto.authority = "user2";
162
+
163
+ const result = await deauthorizeLaunchpadBatchSubmitter(ctx, dto);
164
+
165
+ expect(result).toBeInstanceOf(BatchSubmitAuthoritiesResDto);
166
+ expect(result.authorities).toContain("user1");
167
+ expect(result.authorities).not.toContain("user2");
168
+ });
169
+ });
170
+
171
+ describe("getBatchSubmitAuthorizations", () => {
172
+ it("should return current authorizations", async () => {
173
+ const ctx = createMockContext("user1");
174
+ const existingAuth = new LaunchpadBatchSubmitAuthorities(["user1", "user2"]);
175
+
176
+ (getObjectByKey as jest.Mock).mockResolvedValue(existingAuth);
177
+
178
+ const dto = new FetchBatchSubmitAuthoritiesDto();
179
+ const result = await getLaunchpadBatchSubmitAuthorities(ctx, dto);
180
+
181
+ expect(result).toBeInstanceOf(BatchSubmitAuthoritiesResDto);
182
+ expect(result.authorities).toEqual(["user1", "user2"]);
183
+ });
184
+ });
185
+ });
@@ -0,0 +1,105 @@
1
+ /*
2
+ * Copyright (c) Gala Games Inc. All rights reserved.
3
+ * Licensed under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License.
5
+ * You may obtain a copy of the License at
6
+ *
7
+ * http://www.apache.org/licenses/LICENSE-2.0
8
+ *
9
+ * Unless required by applicable law or agreed to in writing, software
10
+ * distributed under the License is distributed on an "AS IS" BASIS,
11
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ * See the License for the specific language governing permissions and
13
+ * limitations under the License.
14
+ */
15
+ import { ChainError } from "@gala-chain/api";
16
+ import { GalaChainContext, getObjectByKey, putChainObject } from "@gala-chain/chaincode";
17
+
18
+ import {
19
+ AuthorizeBatchSubmitterDto,
20
+ BatchSubmitAuthoritiesResDto,
21
+ DeauthorizeBatchSubmitterDto,
22
+ ErrorCode,
23
+ FetchBatchSubmitAuthoritiesDto,
24
+ LaunchpadBatchSubmitAuthorities
25
+ } from "../../api";
26
+
27
+ /**
28
+ * Fetches the batch submit authorities from the chain.
29
+ */
30
+ export async function fetchLaunchpadBatchSubmitAuthorities(
31
+ ctx: GalaChainContext
32
+ ): Promise<LaunchpadBatchSubmitAuthorities> {
33
+ const key = ctx.stub.createCompositeKey(LaunchpadBatchSubmitAuthorities.INDEX_KEY, []);
34
+
35
+ return await getObjectByKey(ctx, LaunchpadBatchSubmitAuthorities, key);
36
+ }
37
+
38
+ /**
39
+ * Authorizes users to call BatchSubmit operations.
40
+ * Only existing authorized users can add new authorizations.
41
+ */
42
+ export async function authorizeLaunchpadBatchSubmitter(
43
+ ctx: GalaChainContext,
44
+ dto: AuthorizeBatchSubmitterDto
45
+ ): Promise<BatchSubmitAuthoritiesResDto> {
46
+ const key = ctx.stub.createCompositeKey(LaunchpadBatchSubmitAuthorities.INDEX_KEY, []);
47
+ const authorities = await getObjectByKey(ctx, LaunchpadBatchSubmitAuthorities, key).catch((e) => {
48
+ const chainError = ChainError.from(e);
49
+ if (chainError.matches(ErrorCode.NOT_FOUND)) {
50
+ return new LaunchpadBatchSubmitAuthorities([]);
51
+ } else {
52
+ throw chainError;
53
+ }
54
+ });
55
+
56
+ // Add new authorities
57
+ for (const authority of dto.authorities) {
58
+ authorities.addAuthority(authority);
59
+ }
60
+
61
+ await putChainObject(ctx, authorities);
62
+
63
+ const result = new BatchSubmitAuthoritiesResDto();
64
+ result.authorities = authorities.getAuthorities();
65
+ return result;
66
+ }
67
+
68
+ /**
69
+ * Deauthorizes a user from calling BatchSubmit operations.
70
+ */
71
+ export async function deauthorizeLaunchpadBatchSubmitter(
72
+ ctx: GalaChainContext,
73
+ dto: DeauthorizeBatchSubmitterDto
74
+ ): Promise<BatchSubmitAuthoritiesResDto> {
75
+ const authorities = await fetchLaunchpadBatchSubmitAuthorities(ctx);
76
+
77
+ authorities.removeAuthority(dto.authority);
78
+ await putChainObject(ctx, authorities);
79
+
80
+ const result = new BatchSubmitAuthoritiesResDto();
81
+ result.authorities = authorities.getAuthorities();
82
+ return result;
83
+ }
84
+
85
+ /**
86
+ * Fetches the current batch submit authorizations.
87
+ */
88
+ export async function getLaunchpadBatchSubmitAuthorities(
89
+ ctx: GalaChainContext,
90
+ dto: FetchBatchSubmitAuthoritiesDto
91
+ ): Promise<BatchSubmitAuthoritiesResDto> {
92
+ const authorities = await fetchLaunchpadBatchSubmitAuthorities(ctx);
93
+ const result = new BatchSubmitAuthoritiesResDto();
94
+
95
+ result.authorities = authorities.getAuthorities();
96
+ return result;
97
+ }
98
+
99
+ /**
100
+ * Checks if the calling user is authorized to perform batch submit operations.
101
+ */
102
+ export async function isAuthorizedForLaunchpadBatchSubmit(ctx: GalaChainContext): Promise<boolean> {
103
+ const authorities = await fetchLaunchpadBatchSubmitAuthorities(ctx);
104
+ return authorities.isAuthorized(ctx.callingUser);
105
+ }
@@ -12,16 +12,11 @@
12
12
  * See the License for the specific language governing permissions and
13
13
  * limitations under the License.
14
14
  */
15
- import { BigNumber } from "bignumber.js";
16
15
  import Decimal from "decimal.js";
17
16
 
18
17
  import { LaunchpadSale, PreMintCalculationDto } from "../../api/types";
19
18
  import { getBondingConstants } from "../utils";
20
19
 
21
- BigNumber.config({
22
- ROUNDING_MODE: BigNumber.ROUND_UP
23
- });
24
-
25
20
  /**
26
21
  * Calculates the number of tokens that can be purchased using a specified amount
27
22
  * of native tokens based on a bonding curve mechanism.
@@ -12,20 +12,16 @@
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";
15
16
  import { GalaChainContext, fetchTokenClass, putChainObject, transferToken } from "@gala-chain/chaincode";
16
17
  import { BigNumber } from "bignumber.js";
17
18
 
18
- import { ExactTokenQuantityDto, NativeTokenQuantityDto, TradeResDto } from "../../api/types";
19
+ import { ExactTokenQuantityDto, TradeResDto } from "../../api/types";
19
20
  import { SlippageToleranceExceededError } from "../../api/utils/error";
20
- import { fetchAndValidateSale } from "../utils";
21
- import { callMemeTokenIn } from "./callMemeTokenIn";
21
+ import { fetchAndValidateSale, fetchLaunchpadFeeAddress } from "../utils";
22
22
  import { callNativeTokenOut } from "./callNativeTokenOut";
23
23
  import { payReverseBondingCurveFee } from "./fees";
24
24
 
25
- BigNumber.config({
26
- ROUNDING_MODE: BigNumber.ROUND_UP
27
- });
28
-
29
25
  /**
30
26
  * Executes the sale of an exact amount of tokens for native tokens (e.g., GALA).
31
27
  *
@@ -48,24 +44,24 @@ export async function sellExactToken(
48
44
  ctx: GalaChainContext,
49
45
  sellTokenDTO: ExactTokenQuantityDto
50
46
  ): Promise<TradeResDto> {
47
+ // Fetch and validate the current sale object
51
48
  const sale = await fetchAndValidateSale(ctx, sellTokenDTO.vaultAddress);
52
49
 
50
+ // Determine how much native token (e.g., GALA) the user will receive for the exact token quantity
53
51
  const callNativeTokenOutResult = await callNativeTokenOut(ctx, sellTokenDTO);
54
- let nativeTokensToProvide = new BigNumber(callNativeTokenOutResult.calculatedQuantity);
52
+ const nativeTokensToProvide = new BigNumber(callNativeTokenOutResult.calculatedQuantity);
53
+ const transactionFees = callNativeTokenOutResult.extraFees.transactionFees;
54
+
55
55
  const nativeTokensLeftInVault = new BigNumber(sale.nativeTokenQuantity);
56
56
  const nativeToken = sale.fetchNativeTokenInstanceKey();
57
57
  const memeToken = sale.fetchSellingTokenInstanceKey();
58
58
 
59
+ // Abort if the vault doesn't have enough native tokens to pay the user
59
60
  if (nativeTokensLeftInVault.comparedTo(nativeTokensToProvide) < 0) {
60
- nativeTokensToProvide = nativeTokensLeftInVault;
61
- const nativeTokensBeingSoldDto = new NativeTokenQuantityDto(
62
- sellTokenDTO.vaultAddress,
63
- nativeTokensToProvide
64
- );
65
- const callMemeTokenInResult = await callMemeTokenIn(ctx, nativeTokensBeingSoldDto);
66
- sellTokenDTO.tokenQuantity = new BigNumber(callMemeTokenInResult.calculatedQuantity);
61
+ throw new ValidationFailedError("Not enough GALA in sale contract to carry out this operation.");
67
62
  }
68
63
 
64
+ // Enforce slippage tolerance: expected amount must not be greater than what will actually be received
69
65
  if (
70
66
  sellTokenDTO.expectedNativeToken &&
71
67
  sellTokenDTO.expectedNativeToken.comparedTo(nativeTokensToProvide) > 0
@@ -84,6 +80,20 @@ export async function sellExactToken(
84
80
  sellTokenDTO.extraFees?.maxAcceptableReverseBondingCurveFee
85
81
  );
86
82
 
83
+ // Transfer launchpad transaction fee if applicable
84
+ const launchpadFeeAddressConfiguration = await fetchLaunchpadFeeAddress(ctx);
85
+ if (launchpadFeeAddressConfiguration && transactionFees) {
86
+ await transferToken(ctx, {
87
+ from: ctx.callingUser,
88
+ to: launchpadFeeAddressConfiguration.feeAddress,
89
+ tokenInstanceKey: nativeToken,
90
+ quantity: new BigNumber(transactionFees),
91
+ allowancesToUse: [],
92
+ authorizedOnBehalf: undefined
93
+ });
94
+ }
95
+
96
+ // Transfer meme tokens from user to vault
87
97
  await transferToken(ctx, {
88
98
  from: ctx.callingUser,
89
99
  to: sellTokenDTO.vaultAddress,
@@ -93,6 +103,7 @@ export async function sellExactToken(
93
103
  authorizedOnBehalf: undefined
94
104
  });
95
105
 
106
+ // Transfer native tokens from vault to user
96
107
  await transferToken(ctx, {
97
108
  from: sellTokenDTO.vaultAddress,
98
109
  to: ctx.callingUser,
@@ -105,12 +116,16 @@ export async function sellExactToken(
105
116
  }
106
117
  });
107
118
 
119
+ // Update sale state with this transaction
108
120
  sale.sellToken(sellTokenDTO.tokenQuantity, nativeTokensToProvide);
109
121
  await putChainObject(ctx, sale);
110
122
 
111
123
  const token = await fetchTokenClass(ctx, sale.sellingToken);
112
124
  return {
113
125
  inputQuantity: sellTokenDTO.tokenQuantity.toFixed(),
126
+ totalFees: new BigNumber(transactionFees)
127
+ .plus(sellTokenDTO.extraFees?.maxAcceptableReverseBondingCurveFee ?? 0)
128
+ .toFixed(),
114
129
  outputQuantity: nativeTokensToProvide.toFixed(),
115
130
  tokenName: token.name,
116
131
  tradeType: "Sell",
@@ -12,19 +12,16 @@
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";
15
16
  import { GalaChainContext, fetchTokenClass, putChainObject, transferToken } from "@gala-chain/chaincode";
16
- import { BigNumber } from "bignumber.js";
17
+ import BigNumber from "bignumber.js";
17
18
 
18
19
  import { NativeTokenQuantityDto, TradeResDto } from "../../api/types";
19
20
  import { SlippageToleranceExceededError } from "../../api/utils/error";
20
- import { fetchAndValidateSale } from "../utils";
21
+ import { fetchAndValidateSale, fetchLaunchpadFeeAddress } from "../utils";
21
22
  import { callMemeTokenIn } from "./callMemeTokenIn";
22
23
  import { payReverseBondingCurveFee } from "./fees";
23
24
 
24
- BigNumber.config({
25
- ROUNDING_MODE: BigNumber.ROUND_UP
26
- });
27
-
28
25
  /**
29
26
  * Executes a sale of tokens using native tokens (e.g., GALA) in exchange for the specified token amount.
30
27
  *
@@ -48,18 +45,25 @@ export async function sellWithNative(
48
45
  ctx: GalaChainContext,
49
46
  sellTokenDTO: NativeTokenQuantityDto
50
47
  ): Promise<TradeResDto> {
48
+ // Fetch and validate the sale object
51
49
  const sale = await fetchAndValidateSale(ctx, sellTokenDTO.vaultAddress);
52
50
 
53
51
  const nativeTokensLeftInVault = new BigNumber(sale.nativeTokenQuantity);
52
+
53
+ // Cap nativeTokenQuantity to the vault balance if the requested amount exceeds it
54
54
  if (nativeTokensLeftInVault.comparedTo(sellTokenDTO.nativeTokenQuantity) < 0) {
55
- sellTokenDTO.nativeTokenQuantity = nativeTokensLeftInVault;
55
+ throw new ValidationFailedError("Not enough GALA in sale contract to carry out this operation.");
56
56
  }
57
57
 
58
+ // Calculate how many tokens need to be sold to get the requested native amount
58
59
  const callMemeTokenInResult = await callMemeTokenIn(ctx, sellTokenDTO);
60
+ const transactionFees = callMemeTokenInResult.extraFees.transactionFees;
59
61
  const tokensToSell = new BigNumber(callMemeTokenInResult.calculatedQuantity);
62
+
60
63
  const nativeToken = sale.fetchNativeTokenInstanceKey();
61
64
  const memeToken = sale.fetchSellingTokenInstanceKey();
62
65
 
66
+ // Enforce slippage tolerance
63
67
  if (sellTokenDTO.expectedToken && sellTokenDTO.expectedToken.comparedTo(tokensToSell) < 0) {
64
68
  throw new SlippageToleranceExceededError(
65
69
  "Token amount expected to cost for this operation is less than the the actual amount required."
@@ -75,6 +79,20 @@ export async function sellWithNative(
75
79
  sellTokenDTO.extraFees?.maxAcceptableReverseBondingCurveFee
76
80
  );
77
81
 
82
+ // Transfer launchpad transaction fees if applicable
83
+ const launchpadFeeAddressConfiguration = await fetchLaunchpadFeeAddress(ctx);
84
+ if (launchpadFeeAddressConfiguration && transactionFees) {
85
+ await transferToken(ctx, {
86
+ from: ctx.callingUser,
87
+ to: launchpadFeeAddressConfiguration.feeAddress,
88
+ tokenInstanceKey: nativeToken,
89
+ quantity: new BigNumber(transactionFees),
90
+ allowancesToUse: [],
91
+ authorizedOnBehalf: undefined
92
+ });
93
+ }
94
+
95
+ // Send meme tokens from user to vault
78
96
  await transferToken(ctx, {
79
97
  from: ctx.callingUser,
80
98
  to: sellTokenDTO.vaultAddress,
@@ -83,6 +101,8 @@ export async function sellWithNative(
83
101
  allowancesToUse: [],
84
102
  authorizedOnBehalf: undefined
85
103
  });
104
+
105
+ // Send native tokens from vault to user
86
106
  await transferToken(ctx, {
87
107
  from: sellTokenDTO.vaultAddress,
88
108
  to: ctx.callingUser,
@@ -95,12 +115,16 @@ export async function sellWithNative(
95
115
  }
96
116
  });
97
117
 
118
+ // Update internal sale tracking
98
119
  sale.sellToken(tokensToSell, sellTokenDTO.nativeTokenQuantity);
99
120
  await putChainObject(ctx, sale);
100
121
 
101
122
  const token = await fetchTokenClass(ctx, sale.sellingToken);
102
123
  return {
103
124
  inputQuantity: tokensToSell.toFixed(),
125
+ totalFees: new BigNumber(transactionFees)
126
+ .plus(sellTokenDTO.extraFees?.maxAcceptableReverseBondingCurveFee ?? 0)
127
+ .toFixed(),
104
128
  outputQuantity: sellTokenDTO.nativeTokenQuantity.toFixed(),
105
129
  tokenName: token.name,
106
130
  tradeType: "Sell",