@gala-chain/launchpad 1.0.1 → 1.0.2

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 (88) hide show
  1. package/lib/package.json +1 -1
  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 +14 -4
  13. package/lib/src/api/types/LaunchpadFeeConfig.js.map +1 -1
  14. package/lib/src/api/types/index.d.ts +1 -0
  15. package/lib/src/api/types/index.d.ts.map +1 -1
  16. package/lib/src/api/types/index.js +1 -0
  17. package/lib/src/api/types/index.js.map +1 -1
  18. package/lib/src/chaincode/LaunchpadContract.d.ts +6 -1
  19. package/lib/src/chaincode/LaunchpadContract.d.ts.map +1 -1
  20. package/lib/src/chaincode/LaunchpadContract.js +85 -0
  21. package/lib/src/chaincode/LaunchpadContract.js.map +1 -1
  22. package/lib/src/chaincode/launchpad/buyExactToken.d.ts.map +1 -1
  23. package/lib/src/chaincode/launchpad/buyExactToken.js +15 -0
  24. package/lib/src/chaincode/launchpad/buyExactToken.js.map +1 -1
  25. package/lib/src/chaincode/launchpad/buyWithNative.d.ts.map +1 -1
  26. package/lib/src/chaincode/launchpad/buyWithNative.js +24 -0
  27. package/lib/src/chaincode/launchpad/buyWithNative.js.map +1 -1
  28. package/lib/src/chaincode/launchpad/callMemeTokenIn.d.ts +1 -0
  29. package/lib/src/chaincode/launchpad/callMemeTokenIn.d.ts.map +1 -1
  30. package/lib/src/chaincode/launchpad/callMemeTokenIn.js +3 -1
  31. package/lib/src/chaincode/launchpad/callMemeTokenIn.js.map +1 -1
  32. package/lib/src/chaincode/launchpad/callMemeTokenOut.d.ts +1 -0
  33. package/lib/src/chaincode/launchpad/callMemeTokenOut.d.ts.map +1 -1
  34. package/lib/src/chaincode/launchpad/callMemeTokenOut.js +4 -1
  35. package/lib/src/chaincode/launchpad/callMemeTokenOut.js.map +1 -1
  36. package/lib/src/chaincode/launchpad/callNativeTokenIn.d.ts +1 -0
  37. package/lib/src/chaincode/launchpad/callNativeTokenIn.d.ts.map +1 -1
  38. package/lib/src/chaincode/launchpad/callNativeTokenIn.js +6 -2
  39. package/lib/src/chaincode/launchpad/callNativeTokenIn.js.map +1 -1
  40. package/lib/src/chaincode/launchpad/callNativeTokenOut.d.ts +1 -0
  41. package/lib/src/chaincode/launchpad/callNativeTokenOut.d.ts.map +1 -1
  42. package/lib/src/chaincode/launchpad/callNativeTokenOut.js +3 -1
  43. package/lib/src/chaincode/launchpad/callNativeTokenOut.js.map +1 -1
  44. package/lib/src/chaincode/launchpad/configureLaunchpadFeeConfig.d.ts +12 -0
  45. package/lib/src/chaincode/launchpad/configureLaunchpadFeeConfig.d.ts.map +1 -1
  46. package/lib/src/chaincode/launchpad/configureLaunchpadFeeConfig.js +26 -6
  47. package/lib/src/chaincode/launchpad/configureLaunchpadFeeConfig.js.map +1 -1
  48. package/lib/src/chaincode/launchpad/fees.d.ts +1 -0
  49. package/lib/src/chaincode/launchpad/fees.d.ts.map +1 -1
  50. package/lib/src/chaincode/launchpad/fees.js +6 -2
  51. package/lib/src/chaincode/launchpad/fees.js.map +1 -1
  52. package/lib/src/chaincode/launchpad/finaliseSale.js +3 -3
  53. package/lib/src/chaincode/launchpad/finaliseSale.js.map +1 -1
  54. package/lib/src/chaincode/launchpad/index.d.ts +1 -0
  55. package/lib/src/chaincode/launchpad/index.d.ts.map +1 -1
  56. package/lib/src/chaincode/launchpad/index.js +1 -0
  57. package/lib/src/chaincode/launchpad/index.js.map +1 -1
  58. package/lib/src/chaincode/launchpad/launchpadBatchSubmitAuthorizations.d.ts +24 -0
  59. package/lib/src/chaincode/launchpad/launchpadBatchSubmitAuthorizations.d.ts.map +1 -0
  60. package/lib/src/chaincode/launchpad/launchpadBatchSubmitAuthorizations.js +84 -0
  61. package/lib/src/chaincode/launchpad/launchpadBatchSubmitAuthorizations.js.map +1 -0
  62. package/lib/src/chaincode/launchpad/sellExactToken.d.ts.map +1 -1
  63. package/lib/src/chaincode/launchpad/sellExactToken.js +27 -8
  64. package/lib/src/chaincode/launchpad/sellExactToken.js.map +1 -1
  65. package/lib/src/chaincode/launchpad/sellWithNative.d.ts.map +1 -1
  66. package/lib/src/chaincode/launchpad/sellWithNative.js +26 -2
  67. package/lib/src/chaincode/launchpad/sellWithNative.js.map +1 -1
  68. package/lib/tsconfig.tsbuildinfo +1 -1
  69. package/package.json +2 -2
  70. package/src/api/types/LaunchpadBatchSubmitAuthorities.ts +53 -0
  71. package/src/api/types/LaunchpadDtos.ts +38 -0
  72. package/src/api/types/LaunchpadFeeConfig.ts +13 -4
  73. package/src/api/types/index.ts +1 -0
  74. package/src/chaincode/LaunchpadContract.ts +96 -1
  75. package/src/chaincode/launchpad/buyExactToken.ts +17 -1
  76. package/src/chaincode/launchpad/buyWithNative.ts +28 -1
  77. package/src/chaincode/launchpad/callMemeTokenIn.ts +8 -3
  78. package/src/chaincode/launchpad/callMemeTokenOut.ts +8 -2
  79. package/src/chaincode/launchpad/callNativeTokenIn.ts +10 -3
  80. package/src/chaincode/launchpad/callNativeTokenOut.ts +8 -3
  81. package/src/chaincode/launchpad/configureLaunchpadFeeConfig.ts +27 -4
  82. package/src/chaincode/launchpad/fees.ts +5 -1
  83. package/src/chaincode/launchpad/finaliseSale.ts +3 -3
  84. package/src/chaincode/launchpad/index.ts +1 -0
  85. package/src/chaincode/launchpad/launchpadBatchSubmitAuthorizations.spec.ts +185 -0
  86. package/src/chaincode/launchpad/launchpadBatchSubmitAuthorizations.ts +105 -0
  87. package/src/chaincode/launchpad/sellExactToken.ts +30 -11
  88. package/src/chaincode/launchpad/sellWithNative.ts +30 -2
@@ -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,13 +12,13 @@
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
 
@@ -48,24 +48,24 @@ export async function sellExactToken(
48
48
  ctx: GalaChainContext,
49
49
  sellTokenDTO: ExactTokenQuantityDto
50
50
  ): Promise<TradeResDto> {
51
+ // Fetch and validate the current sale object
51
52
  const sale = await fetchAndValidateSale(ctx, sellTokenDTO.vaultAddress);
52
53
 
54
+ // Determine how much native token (e.g., GALA) the user will receive for the exact token quantity
53
55
  const callNativeTokenOutResult = await callNativeTokenOut(ctx, sellTokenDTO);
54
- let nativeTokensToProvide = new BigNumber(callNativeTokenOutResult.calculatedQuantity);
56
+ const nativeTokensToProvide = new BigNumber(callNativeTokenOutResult.calculatedQuantity);
57
+ const transactionFees = callNativeTokenOutResult.extraFees.transactionFees;
58
+
55
59
  const nativeTokensLeftInVault = new BigNumber(sale.nativeTokenQuantity);
56
60
  const nativeToken = sale.fetchNativeTokenInstanceKey();
57
61
  const memeToken = sale.fetchSellingTokenInstanceKey();
58
62
 
63
+ // Abort if the vault doesn't have enough native tokens to pay the user
59
64
  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);
65
+ throw new ValidationFailedError("Not enough GALA in sale contract to carry out this operation.");
67
66
  }
68
67
 
68
+ // Enforce slippage tolerance: expected amount must not be greater than what will actually be received
69
69
  if (
70
70
  sellTokenDTO.expectedNativeToken &&
71
71
  sellTokenDTO.expectedNativeToken.comparedTo(nativeTokensToProvide) > 0
@@ -84,6 +84,20 @@ export async function sellExactToken(
84
84
  sellTokenDTO.extraFees?.maxAcceptableReverseBondingCurveFee
85
85
  );
86
86
 
87
+ // Transfer launchpad transaction fee if applicable
88
+ const launchpadFeeAddressConfiguration = await fetchLaunchpadFeeAddress(ctx);
89
+ if (launchpadFeeAddressConfiguration && transactionFees) {
90
+ await transferToken(ctx, {
91
+ from: ctx.callingUser,
92
+ to: launchpadFeeAddressConfiguration.feeAddress,
93
+ tokenInstanceKey: nativeToken,
94
+ quantity: new BigNumber(transactionFees),
95
+ allowancesToUse: [],
96
+ authorizedOnBehalf: undefined
97
+ });
98
+ }
99
+
100
+ // Transfer meme tokens from user to vault
87
101
  await transferToken(ctx, {
88
102
  from: ctx.callingUser,
89
103
  to: sellTokenDTO.vaultAddress,
@@ -93,6 +107,7 @@ export async function sellExactToken(
93
107
  authorizedOnBehalf: undefined
94
108
  });
95
109
 
110
+ // Transfer native tokens from vault to user
96
111
  await transferToken(ctx, {
97
112
  from: sellTokenDTO.vaultAddress,
98
113
  to: ctx.callingUser,
@@ -105,12 +120,16 @@ export async function sellExactToken(
105
120
  }
106
121
  });
107
122
 
123
+ // Update sale state with this transaction
108
124
  sale.sellToken(sellTokenDTO.tokenQuantity, nativeTokensToProvide);
109
125
  await putChainObject(ctx, sale);
110
126
 
111
127
  const token = await fetchTokenClass(ctx, sale.sellingToken);
112
128
  return {
113
129
  inputQuantity: sellTokenDTO.tokenQuantity.toFixed(),
130
+ totalFees: new BigNumber(transactionFees)
131
+ .plus(sellTokenDTO.extraFees?.maxAcceptableReverseBondingCurveFee ?? 0)
132
+ .toFixed(),
114
133
  outputQuantity: nativeTokensToProvide.toFixed(),
115
134
  tokenName: token.name,
116
135
  tradeType: "Sell",
@@ -12,12 +12,13 @@
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
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
 
@@ -48,18 +49,25 @@ export async function sellWithNative(
48
49
  ctx: GalaChainContext,
49
50
  sellTokenDTO: NativeTokenQuantityDto
50
51
  ): Promise<TradeResDto> {
52
+ // Fetch and validate the sale object
51
53
  const sale = await fetchAndValidateSale(ctx, sellTokenDTO.vaultAddress);
52
54
 
53
55
  const nativeTokensLeftInVault = new BigNumber(sale.nativeTokenQuantity);
56
+
57
+ // Cap nativeTokenQuantity to the vault balance if the requested amount exceeds it
54
58
  if (nativeTokensLeftInVault.comparedTo(sellTokenDTO.nativeTokenQuantity) < 0) {
55
- sellTokenDTO.nativeTokenQuantity = nativeTokensLeftInVault;
59
+ throw new ValidationFailedError("Not enough GALA in sale contract to carry out this operation.");
56
60
  }
57
61
 
62
+ // Calculate how many tokens need to be sold to get the requested native amount
58
63
  const callMemeTokenInResult = await callMemeTokenIn(ctx, sellTokenDTO);
64
+ const transactionFees = callMemeTokenInResult.extraFees.transactionFees;
59
65
  const tokensToSell = new BigNumber(callMemeTokenInResult.calculatedQuantity);
66
+
60
67
  const nativeToken = sale.fetchNativeTokenInstanceKey();
61
68
  const memeToken = sale.fetchSellingTokenInstanceKey();
62
69
 
70
+ // Enforce slippage tolerance
63
71
  if (sellTokenDTO.expectedToken && sellTokenDTO.expectedToken.comparedTo(tokensToSell) < 0) {
64
72
  throw new SlippageToleranceExceededError(
65
73
  "Token amount expected to cost for this operation is less than the the actual amount required."
@@ -75,6 +83,20 @@ export async function sellWithNative(
75
83
  sellTokenDTO.extraFees?.maxAcceptableReverseBondingCurveFee
76
84
  );
77
85
 
86
+ // Transfer launchpad transaction fees if applicable
87
+ const launchpadFeeAddressConfiguration = await fetchLaunchpadFeeAddress(ctx);
88
+ if (launchpadFeeAddressConfiguration && transactionFees) {
89
+ await transferToken(ctx, {
90
+ from: ctx.callingUser,
91
+ to: launchpadFeeAddressConfiguration.feeAddress,
92
+ tokenInstanceKey: nativeToken,
93
+ quantity: new BigNumber(transactionFees),
94
+ allowancesToUse: [],
95
+ authorizedOnBehalf: undefined
96
+ });
97
+ }
98
+
99
+ // Send meme tokens from user to vault
78
100
  await transferToken(ctx, {
79
101
  from: ctx.callingUser,
80
102
  to: sellTokenDTO.vaultAddress,
@@ -83,6 +105,8 @@ export async function sellWithNative(
83
105
  allowancesToUse: [],
84
106
  authorizedOnBehalf: undefined
85
107
  });
108
+
109
+ // Send native tokens from vault to user
86
110
  await transferToken(ctx, {
87
111
  from: sellTokenDTO.vaultAddress,
88
112
  to: ctx.callingUser,
@@ -95,12 +119,16 @@ export async function sellWithNative(
95
119
  }
96
120
  });
97
121
 
122
+ // Update internal sale tracking
98
123
  sale.sellToken(tokensToSell, sellTokenDTO.nativeTokenQuantity);
99
124
  await putChainObject(ctx, sale);
100
125
 
101
126
  const token = await fetchTokenClass(ctx, sale.sellingToken);
102
127
  return {
103
128
  inputQuantity: tokensToSell.toFixed(),
129
+ totalFees: new BigNumber(transactionFees)
130
+ .plus(sellTokenDTO.extraFees?.maxAcceptableReverseBondingCurveFee ?? 0)
131
+ .toFixed(),
104
132
  outputQuantity: sellTokenDTO.nativeTokenQuantity.toFixed(),
105
133
  tokenName: token.name,
106
134
  tradeType: "Sell",