@gala-chain/launchpad 1.0.13 → 1.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/lib/package.json +1 -1
  2. package/lib/src/api/types/LaunchpadDtos.d.ts +3 -1
  3. package/lib/src/api/types/LaunchpadDtos.d.ts.map +1 -1
  4. package/lib/src/api/types/LaunchpadDtos.js +14 -1
  5. package/lib/src/api/types/LaunchpadDtos.js.map +1 -1
  6. package/lib/src/api/types/LaunchpadSale.d.ts +5 -2
  7. package/lib/src/api/types/LaunchpadSale.d.ts.map +1 -1
  8. package/lib/src/api/types/LaunchpadSale.js +35 -9
  9. package/lib/src/api/types/LaunchpadSale.js.map +1 -1
  10. package/lib/src/chaincode/LaunchpadContract.js +6 -6
  11. package/lib/src/chaincode/LaunchpadContract.js.map +1 -1
  12. package/lib/src/chaincode/launchpad/buyExactToken.d.ts.map +1 -1
  13. package/lib/src/chaincode/launchpad/buyExactToken.js +18 -42
  14. package/lib/src/chaincode/launchpad/buyExactToken.js.map +1 -1
  15. package/lib/src/chaincode/launchpad/buyWithNative.d.ts.map +1 -1
  16. package/lib/src/chaincode/launchpad/buyWithNative.js +12 -43
  17. package/lib/src/chaincode/launchpad/buyWithNative.js.map +1 -1
  18. package/lib/src/chaincode/launchpad/callMemeTokenIn.d.ts +4 -10
  19. package/lib/src/chaincode/launchpad/callMemeTokenIn.d.ts.map +1 -1
  20. package/lib/src/chaincode/launchpad/callMemeTokenIn.js +13 -9
  21. package/lib/src/chaincode/launchpad/callMemeTokenIn.js.map +1 -1
  22. package/lib/src/chaincode/launchpad/callMemeTokenOut.d.ts +4 -10
  23. package/lib/src/chaincode/launchpad/callMemeTokenOut.d.ts.map +1 -1
  24. package/lib/src/chaincode/launchpad/callMemeTokenOut.js +46 -22
  25. package/lib/src/chaincode/launchpad/callMemeTokenOut.js.map +1 -1
  26. package/lib/src/chaincode/launchpad/callNativeTokenIn.d.ts +4 -10
  27. package/lib/src/chaincode/launchpad/callNativeTokenIn.d.ts.map +1 -1
  28. package/lib/src/chaincode/launchpad/callNativeTokenIn.js +35 -14
  29. package/lib/src/chaincode/launchpad/callNativeTokenIn.js.map +1 -1
  30. package/lib/src/chaincode/launchpad/callNativeTokenOut.d.ts +4 -10
  31. package/lib/src/chaincode/launchpad/callNativeTokenOut.d.ts.map +1 -1
  32. package/lib/src/chaincode/launchpad/callNativeTokenOut.js +17 -12
  33. package/lib/src/chaincode/launchpad/callNativeTokenOut.js.map +1 -1
  34. package/lib/src/chaincode/launchpad/configureLaunchpadFeeConfig.js +1 -1
  35. package/lib/src/chaincode/launchpad/configureLaunchpadFeeConfig.js.map +1 -1
  36. package/lib/src/chaincode/launchpad/createSale.d.ts.map +1 -1
  37. package/lib/src/chaincode/launchpad/createSale.js +15 -1
  38. package/lib/src/chaincode/launchpad/createSale.js.map +1 -1
  39. package/lib/src/chaincode/launchpad/fees.d.ts +11 -0
  40. package/lib/src/chaincode/launchpad/fees.d.ts.map +1 -1
  41. package/lib/src/chaincode/launchpad/fees.js +42 -3
  42. package/lib/src/chaincode/launchpad/fees.js.map +1 -1
  43. package/lib/src/chaincode/launchpad/finaliseSale.d.ts.map +1 -1
  44. package/lib/src/chaincode/launchpad/finaliseSale.js +7 -4
  45. package/lib/src/chaincode/launchpad/finaliseSale.js.map +1 -1
  46. package/lib/src/chaincode/launchpad/sellExactToken.d.ts.map +1 -1
  47. package/lib/src/chaincode/launchpad/sellExactToken.js +14 -23
  48. package/lib/src/chaincode/launchpad/sellExactToken.js.map +1 -1
  49. package/lib/src/chaincode/launchpad/sellWithNative.d.ts.map +1 -1
  50. package/lib/src/chaincode/launchpad/sellWithNative.js +10 -19
  51. package/lib/src/chaincode/launchpad/sellWithNative.js.map +1 -1
  52. package/lib/src/chaincode/test/launchpadgala.d.ts.map +1 -1
  53. package/lib/src/chaincode/test/launchpadgala.js +2 -1
  54. package/lib/src/chaincode/test/launchpadgala.js.map +1 -1
  55. package/lib/src/chaincode/utils/launchpadSaleUtils.d.ts +4 -0
  56. package/lib/src/chaincode/utils/launchpadSaleUtils.d.ts.map +1 -1
  57. package/lib/src/chaincode/utils/launchpadSaleUtils.js +19 -1
  58. package/lib/src/chaincode/utils/launchpadSaleUtils.js.map +1 -1
  59. package/lib/src/cli.js +3 -1
  60. package/lib/src/cli.js.map +1 -1
  61. package/lib/tsconfig.tsbuildinfo +1 -1
  62. package/package.json +1 -1
  63. package/src/api/types/LaunchpadDtos.ts +15 -1
  64. package/src/api/types/LaunchpadSale.ts +32 -10
  65. package/src/chaincode/LaunchpadContract.ts +3 -3
  66. package/src/chaincode/launchpad/buyExactToken.spec.ts +3 -38
  67. package/src/chaincode/launchpad/buyExactToken.ts +22 -56
  68. package/src/chaincode/launchpad/buyWithNative.spec.ts +105 -45
  69. package/src/chaincode/launchpad/buyWithNative.ts +15 -65
  70. package/src/chaincode/launchpad/callMemeTokenIn.ts +36 -12
  71. package/src/chaincode/launchpad/callMemeTokenOut.spec.ts +2 -2
  72. package/src/chaincode/launchpad/callMemeTokenOut.ts +72 -26
  73. package/src/chaincode/launchpad/callNativeTokenIn.ts +62 -19
  74. package/src/chaincode/launchpad/callNativeTokenOut.ts +38 -15
  75. package/src/chaincode/launchpad/configureLaunchpadFeeConfig.spec.ts +0 -1
  76. package/src/chaincode/launchpad/configureLaunchpadFeeConfig.ts +1 -1
  77. package/src/chaincode/launchpad/createSale.ts +26 -3
  78. package/src/chaincode/launchpad/fees.ts +55 -3
  79. package/src/chaincode/launchpad/finaliseSale.ts +7 -4
  80. package/src/chaincode/launchpad/launchpadBatchSubmitAuthorizations.spec.ts +150 -68
  81. package/src/chaincode/launchpad/sellExactToken.spec.ts +4 -81
  82. package/src/chaincode/launchpad/sellExactToken.ts +16 -25
  83. package/src/chaincode/launchpad/sellWithNative.spec.ts +2 -42
  84. package/src/chaincode/launchpad/sellWithNative.ts +14 -23
  85. package/src/chaincode/test/launchpadgala.ts +3 -1
  86. package/src/chaincode/utils/launchpadSaleUtils.ts +25 -1
  87. package/src/cli.ts +3 -1
@@ -12,7 +12,7 @@
12
12
  * See the License for the specific language governing permissions and
13
13
  * limitations under the License.
14
14
  */
15
- import { ConflictError, TokenInstanceKey, asValidUserAlias } from "@gala-chain/api";
15
+ import { ConflictError, DefaultError, TokenInstanceKey, asValidUserAlias } from "@gala-chain/api";
16
16
  import {
17
17
  GalaChainContext,
18
18
  createTokenClass,
@@ -23,7 +23,13 @@ import {
23
23
  } from "@gala-chain/chaincode";
24
24
  import BigNumber from "bignumber.js";
25
25
 
26
- import { CreateSaleResDto, CreateTokenSaleDTO, LaunchpadSale, NativeTokenQuantityDto } from "../../api/types";
26
+ import {
27
+ CreateSaleResDto,
28
+ CreateTokenSaleDTO,
29
+ LaunchpadSale,
30
+ NativeTokenQuantityDto,
31
+ SaleStatus
32
+ } from "../../api/types";
27
33
  import { PreConditionFailedError } from "../../api/utils/error";
28
34
  import { buyWithNative } from "./buyWithNative";
29
35
 
@@ -85,7 +91,7 @@ export async function createSale(
85
91
  network: "GC",
86
92
  tokenClass: tokenInstanceKey.getTokenClassKey(),
87
93
  isNonFungible: false,
88
- decimals: 18,
94
+ decimals: LaunchpadSale.SELLING_TOKEN_DECIMALS,
89
95
  name: launchpadDetails.tokenName,
90
96
  symbol: launchpadDetails.tokenSymbol,
91
97
  description: launchpadDetails.tokenDescription,
@@ -131,6 +137,23 @@ export async function createSale(
131
137
  isSaleFinalized = tradeStatus.isFinalized;
132
138
  }
133
139
 
140
+ // handling the optional saleStartTime after the preBuyQuantity allows
141
+ // creators to still optionally specify a pre-buy even when a
142
+ // sale is marked Upcoming / Coming Soon.
143
+ // Otherwise `buyWithNative` would throw an error when validating the sale is active.
144
+ if (launchpadDetails.saleStartTime !== undefined) {
145
+ launchpad.saleStartTime = launchpadDetails.saleStartTime;
146
+
147
+ // handle edge case
148
+ // if a sale was immediately sold out with a pre-buy on creation,
149
+ // do not set the ended sale back to UPCOMING
150
+ if (launchpad.saleStatus !== SaleStatus.END) {
151
+ launchpad.saleStatus = SaleStatus.UPCOMING;
152
+ }
153
+
154
+ await putChainObject(ctx, launchpad);
155
+ }
156
+
134
157
  // Return the response object
135
158
  return {
136
159
  image: launchpadDetails.tokenImage,
@@ -12,9 +12,10 @@
12
12
  * See the License for the specific language governing permissions and
13
13
  * limitations under the License.
14
14
  */
15
- import { FeeReceiptStatus } from "@gala-chain/api";
15
+ import { FeeReceiptStatus, ValidationFailedError } from "@gala-chain/api";
16
16
  import {
17
17
  GalaChainContext,
18
+ fetchOrCreateBalance,
18
19
  transferToken,
19
20
  txUnixTimeToDateIndexKeys,
20
21
  writeChannelPaymentReceipt,
@@ -27,7 +28,7 @@ import { SlippageToleranceExceededError } from "../../api/utils/error";
27
28
  import { fetchLaunchpadFeeAddress } from "../utils";
28
29
 
29
30
  const REVERSE_BONDING_CURVE_FEE_CODE = "LaunchpadReverseBondingCurveFee";
30
- const NATIVE_TOKEN_DECIMALS = 8;
31
+ const NATIVE_TOKEN_DECIMALS = LaunchpadSale.NATIVE_TOKEN_DECIMALS;
31
32
 
32
33
  export function calculateReverseBondingCurveFee(sale: LaunchpadSale, nativeTokensToReceive: BigNumber) {
33
34
  if (
@@ -104,5 +105,56 @@ export async function payReverseBondingCurveFee(
104
105
  }
105
106
 
106
107
  export function calculateTransactionFee(tokensBeingTraded: BigNumber, feeAmount?: number) {
107
- return tokensBeingTraded.multipliedBy(feeAmount ?? 0).toFixed(8, BigNumber.ROUND_UP);
108
+ return tokensBeingTraded.multipliedBy(feeAmount ?? 0).toFixed(NATIVE_TOKEN_DECIMALS, BigNumber.ROUND_UP);
109
+ }
110
+
111
+ /**
112
+ * Transfers transaction fees to the launchpad fee address if applicable.
113
+ * Optionally validates that the user has sufficient balance when nativeTokensRequired is provided.
114
+ *
115
+ * @param ctx - The context object providing access to the GalaChain environment.
116
+ * @param sale - The launchpad sale object.
117
+ * @param transactionFees - The transaction fees amount (as BigNumber or string).
118
+ * @param nativeToken - The native token instance key (returned from sale.fetchNativeTokenInstanceKey()).
119
+ * @param nativeTokensRequired - Optional. If provided, validates user has sufficient balance for fees + required tokens.
120
+ */
121
+ export async function transferTransactionFees(
122
+ ctx: GalaChainContext,
123
+ sale: LaunchpadSale,
124
+ transactionFees: BigNumber | string,
125
+ nativeToken: ReturnType<LaunchpadSale["fetchNativeTokenInstanceKey"]>,
126
+ nativeTokensRequired?: BigNumber
127
+ ): Promise<void> {
128
+ const launchpadFeeAddressConfiguration = await fetchLaunchpadFeeAddress(ctx);
129
+ const transactionFeesBn =
130
+ typeof transactionFees === "string" ? new BigNumber(transactionFees) : transactionFees;
131
+
132
+ // Check if transaction fees is greater than 0 and if the launchpad fee address configuration where
133
+ // the fees are sent to is defined
134
+ if (!launchpadFeeAddressConfiguration || !transactionFeesBn.isGreaterThan(0)) {
135
+ return;
136
+ }
137
+
138
+ // If nativeTokensRequired is provided, validate user has sufficient balance
139
+ if (nativeTokensRequired) {
140
+ const totalRequired = nativeTokensRequired.plus(transactionFeesBn);
141
+ const buyerBalance = await fetchOrCreateBalance(ctx, ctx.callingUser, sale.nativeToken);
142
+
143
+ // Check if the buyer has sufficient balance to pay the transaction fees
144
+ if (buyerBalance.getQuantityTotal().isLessThan(totalRequired)) {
145
+ throw new ValidationFailedError(
146
+ `Insufficient balance: Total amount required including fee is ${totalRequired}`
147
+ );
148
+ }
149
+ }
150
+
151
+ // Transfer transaction fees to the launchpad fee address
152
+ await transferToken(ctx, {
153
+ from: ctx.callingUser,
154
+ to: launchpadFeeAddressConfiguration.feeAddress,
155
+ tokenInstanceKey: nativeToken,
156
+ quantity: transactionFeesBn,
157
+ allowancesToUse: [],
158
+ authorizedOnBehalf: undefined
159
+ });
108
160
  }
@@ -65,7 +65,7 @@ export async function finalizeSale(ctx: GalaChainContext, sale: LaunchpadSale):
65
65
  tokenInstanceKey: nativeToken,
66
66
  quantity: new BigNumber(sale.nativeTokenQuantity)
67
67
  .times(ownerAllocationPercentage)
68
- .decimalPlaces(8, BigNumber.ROUND_DOWN),
68
+ .decimalPlaces(LaunchpadSale.NATIVE_TOKEN_DECIMALS, BigNumber.ROUND_DOWN),
69
69
  allowancesToUse: [],
70
70
  authorizedOnBehalf: {
71
71
  callingOnBehalf: vaultAddressAlias,
@@ -79,7 +79,7 @@ export async function finalizeSale(ctx: GalaChainContext, sale: LaunchpadSale):
79
79
  tokenInstanceKey: nativeToken,
80
80
  quantity: new BigNumber(sale.nativeTokenQuantity)
81
81
  .times(platformFeePercentage)
82
- .decimalPlaces(8, BigNumber.ROUND_DOWN),
82
+ .decimalPlaces(LaunchpadSale.NATIVE_TOKEN_DECIMALS, BigNumber.ROUND_DOWN),
83
83
  allowancesToUse: [],
84
84
  authorizedOnBehalf: {
85
85
  callingOnBehalf: vaultAddressAlias,
@@ -110,7 +110,10 @@ export async function finalizeSale(ctx: GalaChainContext, sale: LaunchpadSale):
110
110
  const poolInfo = await getSlot0(ctx, poolDTO);
111
111
 
112
112
  // Proceed normally if price in the pool is within an acceptable range
113
- const priceCloseEnough = sqrtPrice.minus(poolInfo.sqrtPrice).abs().lte(sqrtPrice.multipliedBy(0.05));
113
+ const priceCloseEnough = sqrtPrice
114
+ .minus(poolInfo.sqrtPrice)
115
+ .abs()
116
+ .isLessThanOrEqualTo(sqrtPrice.multipliedBy(0.05));
114
117
  const expectedNativeTokenRequired = new BigNumber(sale.nativeTokenQuantity).times(
115
118
  liquidityAllocationPercentage
116
119
  );
@@ -200,7 +203,7 @@ function calculateFinalLaunchpadPrice(
200
203
  areTokensSorted: boolean
201
204
  ): { sqrtPrice: BigNumber; finalPrice: BigNumber } {
202
205
  const totalTokensSold = new Decimal(sale.fetchTokensSold());
203
- const basePrice = new Decimal(sale.fetchBasePrice());
206
+ const basePrice = new Decimal(LaunchpadSale.BASE_PRICE);
204
207
  const { exponentFactor, euler, decimals } = getBondingConstants();
205
208
 
206
209
  const exponent = exponentFactor.mul(totalTokensSold).div(decimals);
@@ -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(