@gala-chain/launchpad 1.0.12 → 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 +9 -6
  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 +11 -8
  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
@@ -12,8 +12,9 @@
12
12
  * See the License for the specific language governing permissions and
13
13
  * limitations under the License.
14
14
  */
15
- import { NotFoundError } from "@gala-chain/api";
16
- import { GalaChainContext, getObjectByKey, putChainObject } from "@gala-chain/chaincode";
15
+ import { randomUniqueKey } from "@gala-chain/api";
16
+ import { fixture, transactionSuccess, users } from "@gala-chain/test";
17
+ import { plainToInstance } from "class-transformer";
17
18
 
18
19
  import {
19
20
  AuthorizeBatchSubmitterDto,
@@ -22,6 +23,7 @@ import {
22
23
  FetchBatchSubmitAuthoritiesDto,
23
24
  LaunchpadBatchSubmitAuthorities
24
25
  } from "../../api/types";
26
+ import { LaunchpadContract } from "../LaunchpadContract";
25
27
  import {
26
28
  authorizeLaunchpadBatchSubmitter,
27
29
  deauthorizeLaunchpadBatchSubmitter,
@@ -29,37 +31,8 @@ import {
29
31
  getLaunchpadBatchSubmitAuthorities
30
32
  } from "./launchpadBatchSubmitAuthorizations";
31
33
 
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
34
  describe("BatchSubmitAuthorizations", () => {
58
- beforeEach(() => {
59
- jest.clearAllMocks();
60
- });
61
-
62
- describe("BatchSubmitAuthorizations chain object", () => {
35
+ describe("BatchSubmitAuthorities chain object", () => {
63
36
  it("should create with initial authorities", () => {
64
37
  const auth = new LaunchpadBatchSubmitAuthorities(["user1", "user2"]);
65
38
  expect(auth.authorities).toEqual(["user1", "user2"]);
@@ -99,87 +72,196 @@ describe("BatchSubmitAuthorizations", () => {
99
72
  });
100
73
  });
101
74
 
102
- describe("fetchBatchSubmitAuthorizations", () => {
75
+ describe("fetchLaunchpadBatchSubmitAuthorities", () => {
103
76
  it("should return existing authorizations", async () => {
104
- const ctx = createMockContext("user1");
105
- const existingAuth = new LaunchpadBatchSubmitAuthorities(["user1", "user2"]);
77
+ // Given
78
+ const existingAuth = new LaunchpadBatchSubmitAuthorities([
79
+ users.testUser1.identityKey,
80
+ users.testUser2.identityKey
81
+ ]);
106
82
 
107
- (getObjectByKey as jest.Mock).mockResolvedValue(existingAuth);
108
- (putChainObject as jest.Mock).mockResolvedValue(undefined);
83
+ const { ctx } = fixture(LaunchpadContract).registeredUsers(users.testUser1).savedState(existingAuth);
109
84
 
85
+ // When
110
86
  const result = await fetchLaunchpadBatchSubmitAuthorities(ctx);
111
87
 
112
- expect(result).toBe(existingAuth);
88
+ // Then
89
+ expect(result).toBeInstanceOf(LaunchpadBatchSubmitAuthorities);
90
+ expect(result.authorities).toContain(users.testUser1.identityKey);
91
+ expect(result.authorities).toContain(users.testUser2.identityKey);
113
92
  });
114
93
  });
115
94
 
116
- describe("authorizeBatchSubmitter", () => {
95
+ describe("authorizeLaunchpadBatchSubmitter", () => {
117
96
  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);
97
+ // Given
98
+ const existingAuth = new LaunchpadBatchSubmitAuthorities([users.testUser1.identityKey]);
99
+
100
+ const { ctx } = fixture(LaunchpadContract).registeredUsers(users.testUser1).savedState(existingAuth);
122
101
 
123
102
  const dto = new AuthorizeBatchSubmitterDto();
124
- dto.authorities = ["user2", "user3"];
103
+ dto.authorities = [users.testUser2.identityKey];
104
+ dto.uniqueKey = randomUniqueKey();
105
+ dto.sign(users.testUser1.privateKey);
125
106
 
107
+ // When
126
108
  const result = await authorizeLaunchpadBatchSubmitter(ctx, dto);
127
109
 
110
+ // Then
128
111
  expect(result).toBeInstanceOf(BatchSubmitAuthoritiesResDto);
129
- expect(result.authorities).toContain("user1");
130
- expect(result.authorities).toContain("user2");
131
- expect(result.authorities).toContain("user3");
112
+ expect(result.authorities).toContain(users.testUser1.identityKey);
113
+ expect(result.authorities).toContain(users.testUser2.identityKey);
132
114
  });
133
115
 
134
116
  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);
117
+ // Given
118
+ const { ctx } = fixture(LaunchpadContract).registeredUsers(users.testUser1);
139
119
 
140
120
  const dto = new AuthorizeBatchSubmitterDto();
141
- dto.authorities = ["user1", "user2"];
121
+ dto.authorities = [users.testUser1.identityKey, users.testUser2.identityKey];
122
+ dto.uniqueKey = randomUniqueKey();
123
+ dto.sign(users.testUser1.privateKey);
142
124
 
125
+ // When
143
126
  const result = await authorizeLaunchpadBatchSubmitter(ctx, dto);
144
127
 
128
+ // Then
145
129
  expect(result).toBeInstanceOf(BatchSubmitAuthoritiesResDto);
146
- expect(result.authorities).toContain("user1");
147
- expect(result.authorities).toContain("user2");
148
- expect(putChainObject).toHaveBeenCalled();
130
+ expect(result.authorities).toContain(users.testUser1.identityKey);
131
+ expect(result.authorities).toContain(users.testUser2.identityKey);
149
132
  });
150
133
  });
151
134
 
152
- describe("deauthorizeBatchSubmitter", () => {
135
+ describe("deauthorizeLaunchpadBatchSubmitter", () => {
153
136
  it("should deauthorize user when caller is authorized", async () => {
154
- const ctx = createMockContext("user1");
155
- const existingAuth = new LaunchpadBatchSubmitAuthorities(["user1", "user2"]);
137
+ // Given
138
+ const existingAuth = new LaunchpadBatchSubmitAuthorities([
139
+ users.testUser1.identityKey,
140
+ users.testUser2.identityKey
141
+ ]);
156
142
 
157
- (getObjectByKey as jest.Mock).mockResolvedValue(existingAuth);
158
- (putChainObject as jest.Mock).mockResolvedValue(undefined);
143
+ const { ctx } = fixture(LaunchpadContract).registeredUsers(users.testUser1).savedState(existingAuth);
159
144
 
160
145
  const dto = new DeauthorizeBatchSubmitterDto();
161
- dto.authority = "user2";
146
+ dto.authority = users.testUser2.identityKey;
147
+ dto.uniqueKey = randomUniqueKey();
148
+ dto.sign(users.testUser1.privateKey);
162
149
 
150
+ // When
163
151
  const result = await deauthorizeLaunchpadBatchSubmitter(ctx, dto);
164
152
 
153
+ // Then
165
154
  expect(result).toBeInstanceOf(BatchSubmitAuthoritiesResDto);
166
- expect(result.authorities).toContain("user1");
167
- expect(result.authorities).not.toContain("user2");
155
+ expect(result.authorities).toContain(users.testUser1.identityKey);
156
+ expect(result.authorities).not.toContain(users.testUser2.identityKey);
168
157
  });
169
158
  });
170
159
 
171
- describe("getBatchSubmitAuthorizations", () => {
160
+ describe("getLaunchpadBatchSubmitAuthorities", () => {
172
161
  it("should return current authorizations", async () => {
173
- const ctx = createMockContext("user1");
174
- const existingAuth = new LaunchpadBatchSubmitAuthorities(["user1", "user2"]);
162
+ // Given
163
+ const existingAuth = new LaunchpadBatchSubmitAuthorities([
164
+ users.testUser1.identityKey,
165
+ users.testUser2.identityKey
166
+ ]);
175
167
 
176
- (getObjectByKey as jest.Mock).mockResolvedValue(existingAuth);
168
+ const { ctx } = fixture(LaunchpadContract).registeredUsers(users.testUser1).savedState(existingAuth);
177
169
 
178
170
  const dto = new FetchBatchSubmitAuthoritiesDto();
171
+ dto.uniqueKey = randomUniqueKey();
172
+ dto.sign(users.testUser1.privateKey);
173
+
174
+ // When
179
175
  const result = await getLaunchpadBatchSubmitAuthorities(ctx, dto);
180
176
 
177
+ // Then
181
178
  expect(result).toBeInstanceOf(BatchSubmitAuthoritiesResDto);
182
- expect(result.authorities).toEqual(["user1", "user2"]);
179
+ expect(result.authorities).toEqual([users.testUser1.identityKey, users.testUser2.identityKey]);
180
+ });
181
+ });
182
+
183
+ describe("AuthorizeBatchSubmitter contract method", () => {
184
+ it("should authorize new users through contract", async () => {
185
+ // Given
186
+ const existingAuth = new LaunchpadBatchSubmitAuthorities([users.admin.identityKey]);
187
+
188
+ const { ctx, contract } = fixture(LaunchpadContract)
189
+ .caClientIdentity("test-admin", "CuratorOrg")
190
+ .registeredUsers(users.admin)
191
+ .savedState(existingAuth);
192
+
193
+ const dto = new AuthorizeBatchSubmitterDto();
194
+ dto.authorities = [users.testUser2.identityKey];
195
+ dto.uniqueKey = randomUniqueKey();
196
+ dto.sign(users.admin.privateKey);
197
+
198
+ const expectedResponse = plainToInstance(BatchSubmitAuthoritiesResDto, {
199
+ authorities: [users.admin.identityKey, users.testUser2.identityKey]
200
+ });
201
+
202
+ // When
203
+ const result = await contract.AuthorizeBatchSubmitter(ctx, dto);
204
+
205
+ // Then
206
+ expect(result).toEqual(transactionSuccess(expectedResponse));
207
+ });
208
+ });
209
+
210
+ describe("DeauthorizeBatchSubmitter contract method", () => {
211
+ it("should deauthorize user through contract", async () => {
212
+ // Given
213
+ const existingAuth = new LaunchpadBatchSubmitAuthorities([
214
+ users.admin.identityKey,
215
+ users.testUser2.identityKey
216
+ ]);
217
+
218
+ const { ctx, contract } = fixture(LaunchpadContract)
219
+ .caClientIdentity("test-admin", "CuratorOrg")
220
+ .registeredUsers(users.admin)
221
+ .savedState(existingAuth);
222
+
223
+ const dto = new DeauthorizeBatchSubmitterDto();
224
+ dto.authority = users.testUser2.identityKey;
225
+ dto.uniqueKey = randomUniqueKey();
226
+ dto.sign(users.admin.privateKey);
227
+
228
+ const expectedResponse = plainToInstance(BatchSubmitAuthoritiesResDto, {
229
+ authorities: [users.admin.identityKey]
230
+ });
231
+
232
+ // When
233
+ const result = await contract.DeauthorizeBatchSubmitter(ctx, dto);
234
+
235
+ // Then
236
+ expect(result).toEqual(transactionSuccess(expectedResponse));
237
+ });
238
+ });
239
+
240
+ describe("GetBatchSubmitAuthorities contract method", () => {
241
+ it("should return current authorizations through contract", async () => {
242
+ // Given
243
+ const existingAuth = new LaunchpadBatchSubmitAuthorities([
244
+ users.testUser1.identityKey,
245
+ users.testUser2.identityKey
246
+ ]);
247
+
248
+ const { ctx, contract } = fixture(LaunchpadContract)
249
+ .registeredUsers(users.testUser1)
250
+ .savedState(existingAuth);
251
+
252
+ const dto = new FetchBatchSubmitAuthoritiesDto();
253
+ dto.uniqueKey = randomUniqueKey();
254
+ dto.sign(users.testUser1.privateKey);
255
+
256
+ const expectedResponse = plainToInstance(BatchSubmitAuthoritiesResDto, {
257
+ authorities: [users.testUser1.identityKey, users.testUser2.identityKey]
258
+ });
259
+
260
+ // When
261
+ const result = await contract.GetBatchSubmitAuthorities(ctx, dto);
262
+
263
+ // Then
264
+ expect(result).toEqual(transactionSuccess(expectedResponse));
183
265
  });
184
266
  });
185
267
  });
@@ -21,8 +21,7 @@ import {
21
21
  asValidUserAlias,
22
22
  randomUniqueKey
23
23
  } from "@gala-chain/api";
24
- import { InvalidDecimalError } from "@gala-chain/chaincode";
25
- import { currency, fixture, transactionError, users } from "@gala-chain/test";
24
+ import { currency, fixture, users } from "@gala-chain/test";
26
25
  import BigNumber from "bignumber.js";
27
26
  import { plainToInstance } from "class-transformer";
28
27
 
@@ -87,7 +86,7 @@ describe("sellExactToken", () => {
87
86
 
88
87
  it("should sell exact token amount successfully", async () => {
89
88
  // Given
90
- sale.buyToken(new BigNumber("1000"), new BigNumber("50")); // Users bought tokens, sale now has GALA
89
+ sale.buyToken(new BigNumber("1000"), new BigNumber("100")); // Users bought tokens, sale now has GALA
91
90
  const { ctx, contract } = fixture(LaunchpadContract)
92
91
  .registeredUsers(users.testUser1)
93
92
  .savedState(
@@ -116,82 +115,6 @@ describe("sellExactToken", () => {
116
115
  expect(response.Data).toHaveProperty("isFinalized");
117
116
  });
118
117
 
119
- it("should reject sell when native token has 0 decimals and bonding curve produces fractional quantity", async () => {
120
- // Given
121
- const zeroDecimalNativeClass = plainToInstance(TokenClass, {
122
- ...launchpadgala.tokenClassPlain(),
123
- decimals: 0 // Integer-only native token
124
- });
125
-
126
- // Simulate prior buys to establish sale state
127
- sale.buyToken(new BigNumber("5000"), new BigNumber("100"));
128
-
129
- const { ctx, contract } = fixture(LaunchpadContract)
130
- .registeredUsers(users.testUser1)
131
- .savedState(
132
- currencyClass,
133
- currencyInstance,
134
- zeroDecimalNativeClass,
135
- launchpadGalaInstance,
136
- sale,
137
- salelaunchpadGalaBalance,
138
- saleCurrencyBalance,
139
- userlaunchpadGalaBalance,
140
- userCurrencyBalance
141
- );
142
-
143
- // Choose a token quantity that will produce fractional native tokens from bonding curve
144
- const sellDto = new ExactTokenQuantityDto(vaultAddress, new BigNumber("100"));
145
- sellDto.uniqueKey = randomUniqueKey();
146
- const signedDto = sellDto.signed(users.testUser1.privateKey);
147
-
148
- // When
149
- const response = await contract.SellExactToken(ctx, signedDto);
150
-
151
- // Then - Expect error due to decimal precision mismatch
152
- expect(response).toEqual(
153
- transactionError(new InvalidDecimalError(new BigNumber("0.00166022"), zeroDecimalNativeClass.decimals))
154
- );
155
- });
156
-
157
- it("should reject sell when meme token has 0 decimals and input dto contains greater fractional precision", async () => {
158
- // Given
159
- const zeroDecimalMemeClass = plainToInstance(TokenClass, {
160
- ...currency.tokenClassPlain(),
161
- decimals: 0 // Integer-only native token
162
- });
163
-
164
- // Simulate prior buys to establish sale state
165
- sale.buyToken(new BigNumber("5000"), new BigNumber("100"));
166
-
167
- const { ctx, contract } = fixture(LaunchpadContract)
168
- .registeredUsers(users.testUser1)
169
- .savedState(
170
- zeroDecimalMemeClass,
171
- currencyInstance,
172
- launchpadGalaClass,
173
- launchpadGalaInstance,
174
- sale,
175
- salelaunchpadGalaBalance,
176
- saleCurrencyBalance,
177
- userlaunchpadGalaBalance,
178
- userCurrencyBalance
179
- );
180
-
181
- // Choose a token quantity that will produce fractional native tokens from bonding curve
182
- const sellDto = new ExactTokenQuantityDto(vaultAddress, new BigNumber("100.555"));
183
- sellDto.uniqueKey = randomUniqueKey();
184
- const signedDto = sellDto.signed(users.testUser1.privateKey);
185
-
186
- // When
187
- const response = await contract.SellExactToken(ctx, signedDto);
188
-
189
- // Then - Expect error due to decimal precision mismatch
190
- expect(response).toEqual(
191
- transactionError(new InvalidDecimalError(sellDto.tokenQuantity, zeroDecimalMemeClass.decimals))
192
- );
193
- });
194
-
195
118
  it("should handle small token sell amount", async () => {
196
119
  // Given
197
120
  sale.buyToken(new BigNumber("500"), new BigNumber("25")); // Users bought tokens, sale now has GALA
@@ -224,7 +147,7 @@ describe("sellExactToken", () => {
224
147
 
225
148
  it("should handle sell with expected native token parameter", async () => {
226
149
  // Given
227
- sale.buyToken(new BigNumber("800"), new BigNumber("40")); // Users bought tokens, sale now has GALA
150
+ sale.buyToken(new BigNumber("800"), new BigNumber("50")); // Users bought tokens, sale now has GALA
228
151
  const { ctx, contract } = fixture(LaunchpadContract)
229
152
  .registeredUsers(users.testUser1)
230
153
  .savedState(
@@ -254,7 +177,7 @@ describe("sellExactToken", () => {
254
177
 
255
178
  it("should handle large token sell amount", async () => {
256
179
  // Given
257
- sale.buyToken(new BigNumber("2000"), new BigNumber("100")); // Users bought tokens, sale now has GALA
180
+ sale.buyToken(new BigNumber("2000"), new BigNumber("500")); // Users bought tokens, sale now has GALA
258
181
  const { ctx, contract } = fixture(LaunchpadContract)
259
182
  .registeredUsers(users.testUser1)
260
183
  .savedState(
@@ -18,9 +18,9 @@ import { BigNumber } from "bignumber.js";
18
18
 
19
19
  import { ExactTokenQuantityDto, TradeResDto } from "../../api/types";
20
20
  import { SlippageToleranceExceededError } from "../../api/utils/error";
21
- import { fetchAndValidateSale, fetchLaunchpadFeeAddress } from "../utils";
21
+ import { fetchAndValidateSale } from "../utils";
22
22
  import { callNativeTokenOut } from "./callNativeTokenOut";
23
- import { payReverseBondingCurveFee } from "./fees";
23
+ import { payReverseBondingCurveFee, transferTransactionFees } from "./fees";
24
24
 
25
25
  /**
26
26
  * Executes the sale of an exact amount of tokens for native tokens (e.g., GALA).
@@ -49,25 +49,26 @@ export async function sellExactToken(
49
49
 
50
50
  // Determine how much native token (e.g., GALA) the user will receive for the exact token quantity
51
51
  const callNativeTokenOutResult = await callNativeTokenOut(ctx, sellTokenDTO);
52
- const nativeTokensToProvide = new BigNumber(callNativeTokenOutResult.calculatedQuantity);
53
- const transactionFees = callNativeTokenOutResult.extraFees.transactionFees;
52
+ const tokensBeingSold = new BigNumber(callNativeTokenOutResult.originalQuantity); // number of tokens user wants to sell
53
+ const nativeTokensPayout = new BigNumber(callNativeTokenOutResult.calculatedQuantity); // number of native tokens user will receive
54
+ const transactionFees = new BigNumber(callNativeTokenOutResult.extraFees.transactionFees); // transaction fees
54
55
 
55
56
  const nativeTokensLeftInVault = new BigNumber(sale.nativeTokenQuantity);
56
57
  const nativeToken = sale.fetchNativeTokenInstanceKey();
57
58
  const memeToken = sale.fetchSellingTokenInstanceKey();
58
59
 
59
60
  // Abort if the vault doesn't have enough native tokens to pay the user
60
- if (nativeTokensLeftInVault.comparedTo(nativeTokensToProvide) < 0) {
61
+ if (new BigNumber(sellTokenDTO.tokenQuantity).isGreaterThan(nativeTokensLeftInVault)) {
61
62
  throw new ValidationFailedError("Not enough GALA in sale contract to carry out this operation.");
62
63
  }
63
64
 
64
65
  // Enforce slippage tolerance: expected amount must not be greater than what will actually be received
65
66
  if (
66
67
  sellTokenDTO.expectedNativeToken &&
67
- sellTokenDTO.expectedNativeToken.comparedTo(nativeTokensToProvide) > 0
68
+ sellTokenDTO.expectedNativeToken.isGreaterThan(nativeTokensPayout)
68
69
  ) {
69
70
  throw new SlippageToleranceExceededError(
70
- `expected ${sellTokenDTO.expectedNativeToken.toString()}, but only ${nativeTokensToProvide.toString()} tokens can be provided. Reduce the expected amount or adjust your slippage tolerance.`
71
+ `expected ${sellTokenDTO.expectedNativeToken.toString()}, but only ${nativeTokensPayout.toString()} tokens can be provided. Reduce the expected amount or adjust your slippage tolerance.`
71
72
  );
72
73
  }
73
74
 
@@ -76,29 +77,19 @@ export async function sellExactToken(
76
77
  await payReverseBondingCurveFee(
77
78
  ctx,
78
79
  sale,
79
- nativeTokensToProvide,
80
+ nativeTokensPayout,
80
81
  sellTokenDTO.extraFees?.maxAcceptableReverseBondingCurveFee
81
82
  );
82
83
 
83
84
  // 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
- }
85
+ await transferTransactionFees(ctx, sale, transactionFees, nativeToken);
95
86
 
96
87
  // Transfer meme tokens from user to vault
97
88
  await transferToken(ctx, {
98
89
  from: ctx.callingUser,
99
90
  to: sellTokenDTO.vaultAddress,
100
91
  tokenInstanceKey: memeToken,
101
- quantity: sellTokenDTO.tokenQuantity,
92
+ quantity: tokensBeingSold,
102
93
  allowancesToUse: [],
103
94
  authorizedOnBehalf: undefined
104
95
  });
@@ -108,7 +99,7 @@ export async function sellExactToken(
108
99
  from: sellTokenDTO.vaultAddress,
109
100
  to: ctx.callingUser,
110
101
  tokenInstanceKey: nativeToken,
111
- quantity: nativeTokensToProvide,
102
+ quantity: nativeTokensPayout,
112
103
  allowancesToUse: [],
113
104
  authorizedOnBehalf: {
114
105
  callingOnBehalf: sellTokenDTO.vaultAddress,
@@ -117,16 +108,16 @@ export async function sellExactToken(
117
108
  });
118
109
 
119
110
  // Update sale state with this transaction
120
- sale.sellToken(sellTokenDTO.tokenQuantity, nativeTokensToProvide);
111
+ sale.sellToken(tokensBeingSold, nativeTokensPayout);
121
112
  await putChainObject(ctx, sale);
122
113
 
123
114
  const token = await fetchTokenClass(ctx, sale.sellingToken);
124
115
  return {
125
- inputQuantity: sellTokenDTO.tokenQuantity.toFixed(),
126
- totalFees: new BigNumber(transactionFees)
116
+ inputQuantity: tokensBeingSold.toFixed(),
117
+ totalFees: transactionFees
127
118
  .plus(sellTokenDTO.extraFees?.maxAcceptableReverseBondingCurveFee ?? 0)
128
119
  .toFixed(),
129
- outputQuantity: nativeTokensToProvide.toFixed(),
120
+ outputQuantity: nativeTokensPayout.toFixed(),
130
121
  tokenName: token.name,
131
122
  tradeType: "Sell",
132
123
  vaultAddress: sellTokenDTO.vaultAddress,
@@ -125,46 +125,6 @@ describe("sellWithNative", () => {
125
125
  );
126
126
  });
127
127
 
128
- it("should reject sell when dto contains fractional precision greater than native TokenClass.decimals", async () => {
129
- // Given - Setup meme token with 0 decimals to force decimal precision error
130
- const zeroDecimalLaunchpadGalaClass = plainToInstance(TokenClass, {
131
- ...launchpadgala.tokenClassPlain(),
132
- decimals: 8 // This codebase currently hard-codes 8 as NATIVE_TOKEN_DECIMALS...
133
- });
134
-
135
- // Simulate prior buys to establish sale state with native tokens
136
- sale.buyToken(new BigNumber("100"), new BigNumber("100"));
137
-
138
- const { ctx, contract } = fixture(LaunchpadContract)
139
- .registeredUsers(users.testUser1)
140
- .savedState(
141
- currencyClass,
142
- currencyInstance,
143
- zeroDecimalLaunchpadGalaClass,
144
- launchpadGalaInstance,
145
- sale,
146
- salelaunchpadGalaBalance,
147
- saleCurrencyBalance,
148
- userlaunchpadGalaBalance,
149
- userCurrencyBalance
150
- );
151
-
152
- // Request native tokens that will require fractional meme tokens from bonding curve
153
- const sellDto = new NativeTokenQuantityDto(vaultAddress, new BigNumber("0.123456789"));
154
- sellDto.uniqueKey = randomUniqueKey();
155
- sellDto.sign(users.testUser1.privateKey);
156
-
157
- // When
158
- const response = await contract.SellWithNative(ctx, sellDto);
159
-
160
- // Then - Expect error due to decimal precision mismatch
161
- expect(response).toEqual(
162
- transactionError(
163
- new InvalidDecimalError(sellDto.nativeTokenQuantity, zeroDecimalLaunchpadGalaClass.decimals)
164
- )
165
- );
166
- });
167
-
168
128
  it("should sell tokens for native currency successfully", async () => {
169
129
  // Given
170
130
  sale.buyToken(new BigNumber("10000"), new BigNumber("10")); // Users bought tokens, sale now has GALA
@@ -188,12 +148,12 @@ describe("sellWithNative", () => {
188
148
 
189
149
  const expectedResponse = plainToInstance(TradeResDto, {
190
150
  functionName: "SellWithNative",
191
- inputQuantity: "6008.9271949682",
151
+ inputQuantity: "6008.9271949683",
192
152
  isFinalized: false,
193
153
  outputQuantity: "0.1",
194
154
  tokenName: "AUTOMATEDTESTCOIN",
195
155
  totalFees: "0",
196
- totalTokenSold: "3991.0728050318",
156
+ totalTokenSold: "3991.0728050317",
197
157
  tradeType: "Sell",
198
158
  uniqueKey: sellDto.uniqueKey,
199
159
  userAddress: "client|testUser1",