@gala-chain/launchpad 1.0.6 → 1.0.8
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.
- package/lib/package.json +1 -1
- package/lib/src/api/types/LaunchpadDtos.d.ts +5 -0
- package/lib/src/api/types/LaunchpadDtos.d.ts.map +1 -1
- package/lib/src/api/types/LaunchpadDtos.js +16 -1
- package/lib/src/api/types/LaunchpadDtos.js.map +1 -1
- package/lib/src/chaincode/LaunchpadContract.d.ts +4 -3
- package/lib/src/chaincode/LaunchpadContract.d.ts.map +1 -1
- package/lib/src/chaincode/LaunchpadContract.js +23 -4
- package/lib/src/chaincode/LaunchpadContract.js.map +1 -1
- package/lib/src/chaincode/launchpad/buyExactToken.d.ts.map +1 -1
- package/lib/src/chaincode/launchpad/buyExactToken.js +9 -1
- package/lib/src/chaincode/launchpad/buyExactToken.js.map +1 -1
- package/lib/src/chaincode/launchpad/buyWithNative.d.ts.map +1 -1
- package/lib/src/chaincode/launchpad/buyWithNative.js +10 -2
- package/lib/src/chaincode/launchpad/buyWithNative.js.map +1 -1
- package/lib/src/chaincode/launchpad/callMemeTokenOut.d.ts.map +1 -1
- package/lib/src/chaincode/launchpad/callMemeTokenOut.js.map +1 -1
- package/lib/src/chaincode/launchpad/callNativeTokenIn.d.ts.map +1 -1
- package/lib/src/chaincode/launchpad/callNativeTokenIn.js.map +1 -1
- package/lib/src/chaincode/launchpad/callNativeTokenOut.d.ts.map +1 -1
- package/lib/src/chaincode/launchpad/callNativeTokenOut.js.map +1 -1
- package/lib/src/chaincode/launchpad/fetchLaunchpadAdressConfig.d.ts +2 -1
- package/lib/src/chaincode/launchpad/fetchLaunchpadAdressConfig.d.ts.map +1 -1
- package/lib/src/chaincode/launchpad/fetchLaunchpadAdressConfig.js +1 -1
- package/lib/src/chaincode/launchpad/fetchLaunchpadAdressConfig.js.map +1 -1
- package/lib/src/chaincode/launchpad/fetchLaunchpadFeeAmount.d.ts +12 -0
- package/lib/src/chaincode/launchpad/fetchLaunchpadFeeAmount.d.ts.map +1 -0
- package/lib/src/chaincode/launchpad/fetchLaunchpadFeeAmount.js +37 -0
- package/lib/src/chaincode/launchpad/fetchLaunchpadFeeAmount.js.map +1 -0
- package/lib/src/chaincode/launchpad/finaliseSale.js +3 -3
- package/lib/src/chaincode/launchpad/finaliseSale.js.map +1 -1
- package/lib/src/chaincode/launchpad/sellExactToken.d.ts.map +1 -1
- package/lib/src/chaincode/launchpad/sellExactToken.js +3 -1
- package/lib/src/chaincode/launchpad/sellExactToken.js.map +1 -1
- package/lib/src/chaincode/launchpad/sellWithNative.d.ts.map +1 -1
- package/lib/src/chaincode/launchpad/sellWithNative.js +3 -1
- package/lib/src/chaincode/launchpad/sellWithNative.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/api/types/LaunchpadDtos.ts +11 -0
- package/src/chaincode/LaunchpadContract.ts +25 -5
- package/src/chaincode/launchpad/buyExactToken.spec.ts +222 -0
- package/src/chaincode/launchpad/buyExactToken.ts +19 -2
- package/src/chaincode/launchpad/buyWithNative.spec.ts +221 -0
- package/src/chaincode/launchpad/buyWithNative.ts +21 -3
- package/src/chaincode/launchpad/callMemeTokenOut.spec.ts +1 -1
- package/src/chaincode/launchpad/callMemeTokenOut.ts +1 -0
- package/src/chaincode/launchpad/callNativeTokenIn.ts +1 -0
- package/src/chaincode/launchpad/callNativeTokenOut.ts +1 -0
- package/src/chaincode/launchpad/fetchLaunchpadAdressConfig.ts +5 -2
- package/src/chaincode/launchpad/fetchLaunchpadFeeAmount.spec.ts +67 -0
- package/src/chaincode/launchpad/fetchLaunchpadFeeAmount.ts +40 -0
- package/src/chaincode/launchpad/finaliseSale.ts +3 -3
- package/src/chaincode/launchpad/sellExactToken.ts +3 -1
- package/src/chaincode/launchpad/sellWithNative.ts +3 -1
package/package.json
CHANGED
|
@@ -306,6 +306,12 @@ export class TradeResDto {
|
|
|
306
306
|
|
|
307
307
|
@IsNotEmpty()
|
|
308
308
|
public functionName: string;
|
|
309
|
+
|
|
310
|
+
@IsString()
|
|
311
|
+
public uniqueKey: string;
|
|
312
|
+
|
|
313
|
+
@IsString()
|
|
314
|
+
public totalTokenSold: string;
|
|
309
315
|
}
|
|
310
316
|
|
|
311
317
|
export class FetchSaleDto extends ChainCallDTO {
|
|
@@ -394,3 +400,8 @@ export class BatchSubmitAuthoritiesResDto extends ChainCallDTO {
|
|
|
394
400
|
@IsString({ each: true })
|
|
395
401
|
authorities: string[];
|
|
396
402
|
}
|
|
403
|
+
|
|
404
|
+
export class TransactionFeeResDto {
|
|
405
|
+
@IsNumber()
|
|
406
|
+
feeAmount: number;
|
|
407
|
+
}
|
|
@@ -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 { BatchDto, GalaChainResponse, UnauthorizedError } from "@gala-chain/api";
|
|
15
|
+
import { BatchDto, ChainCallDTO, GalaChainResponse, UnauthorizedError } from "@gala-chain/api";
|
|
16
16
|
import {
|
|
17
17
|
BatchWriteLimitExceededError,
|
|
18
18
|
EVALUATE,
|
|
@@ -42,7 +42,8 @@ import {
|
|
|
42
42
|
LaunchpadSale,
|
|
43
43
|
NativeTokenQuantityDto,
|
|
44
44
|
TradeCalculationResDto,
|
|
45
|
-
TradeResDto
|
|
45
|
+
TradeResDto,
|
|
46
|
+
TransactionFeeResDto
|
|
46
47
|
} from "../api/types";
|
|
47
48
|
import {
|
|
48
49
|
buyExactTokenFeeGate,
|
|
@@ -70,10 +71,13 @@ import {
|
|
|
70
71
|
sellExactToken,
|
|
71
72
|
sellWithNative
|
|
72
73
|
} from "./launchpad";
|
|
74
|
+
import { fetchLaunchpadFeeAmount } from "./launchpad/fetchLaunchpadFeeAmount";
|
|
73
75
|
|
|
74
76
|
export class LaunchpadContract extends GalaContract {
|
|
75
77
|
constructor() {
|
|
76
|
-
super("Launchpad", version
|
|
78
|
+
super("Launchpad", version, {
|
|
79
|
+
allowNonRegisteredUsers: true
|
|
80
|
+
});
|
|
77
81
|
}
|
|
78
82
|
|
|
79
83
|
@Submit({
|
|
@@ -203,11 +207,27 @@ export class LaunchpadContract extends GalaContract {
|
|
|
203
207
|
}
|
|
204
208
|
|
|
205
209
|
@Evaluate({
|
|
210
|
+
in: ChainCallDTO,
|
|
206
211
|
out: LaunchpadFeeConfig,
|
|
207
212
|
allowedOrgs: ["CuratorOrg"]
|
|
208
213
|
})
|
|
209
|
-
public async FetchLaunchpadFeeConfig(
|
|
210
|
-
|
|
214
|
+
public async FetchLaunchpadFeeConfig(
|
|
215
|
+
ctx: GalaChainContext,
|
|
216
|
+
dto: ChainCallDTO
|
|
217
|
+
): Promise<LaunchpadFeeConfig> {
|
|
218
|
+
return fetchLaunchpadFeeConfig(ctx, dto);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
@GalaTransaction({
|
|
222
|
+
type: EVALUATE,
|
|
223
|
+
in: ChainCallDTO,
|
|
224
|
+
out: TransactionFeeResDto
|
|
225
|
+
})
|
|
226
|
+
public async FetchLaunchpadFeeAmount(
|
|
227
|
+
ctx: GalaChainContext,
|
|
228
|
+
dto: ChainCallDTO
|
|
229
|
+
): Promise<TransactionFeeResDto> {
|
|
230
|
+
return fetchLaunchpadFeeAmount(ctx, dto);
|
|
211
231
|
}
|
|
212
232
|
|
|
213
233
|
@GalaTransaction({
|
|
@@ -0,0 +1,222 @@
|
|
|
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 {
|
|
16
|
+
GalaChainResponse,
|
|
17
|
+
TokenBalance,
|
|
18
|
+
TokenClass,
|
|
19
|
+
TokenClassKey,
|
|
20
|
+
TokenInstance,
|
|
21
|
+
UserAlias,
|
|
22
|
+
ValidationFailedError,
|
|
23
|
+
asValidUserAlias,
|
|
24
|
+
randomUniqueKey
|
|
25
|
+
} from "@gala-chain/api";
|
|
26
|
+
import { currency, fixture, users } from "@gala-chain/test";
|
|
27
|
+
import BigNumber from "bignumber.js";
|
|
28
|
+
import { plainToInstance } from "class-transformer";
|
|
29
|
+
|
|
30
|
+
import {
|
|
31
|
+
ExactTokenQuantityDto,
|
|
32
|
+
LaunchpadFeeConfig,
|
|
33
|
+
LaunchpadSale,
|
|
34
|
+
NativeTokenQuantityDto
|
|
35
|
+
} from "../../api/types";
|
|
36
|
+
import { LaunchpadContract } from "../LaunchpadContract";
|
|
37
|
+
import launchpadgala from "../test/launchpadgala";
|
|
38
|
+
|
|
39
|
+
describe("buyWithNative", () => {
|
|
40
|
+
let currencyClass: TokenClass;
|
|
41
|
+
let currencyInstance: TokenInstance;
|
|
42
|
+
let launchpadGalaClass: TokenClass;
|
|
43
|
+
let launchpadGalaInstance: TokenInstance;
|
|
44
|
+
let launchpadGalaClassKey: TokenClassKey;
|
|
45
|
+
let vaultAddress: UserAlias;
|
|
46
|
+
let sale: LaunchpadSale;
|
|
47
|
+
let salelaunchpadGalaBalance: TokenBalance;
|
|
48
|
+
let saleCurrencyBalance: TokenBalance;
|
|
49
|
+
let userlaunchpadGalaBalance: TokenBalance;
|
|
50
|
+
let userCurrencyBalance: TokenBalance;
|
|
51
|
+
|
|
52
|
+
beforeEach(() => {
|
|
53
|
+
//Given
|
|
54
|
+
currencyClass = currency.tokenClass();
|
|
55
|
+
currencyInstance = currency.tokenInstance();
|
|
56
|
+
launchpadGalaClass = launchpadgala.tokenClass();
|
|
57
|
+
launchpadGalaInstance = launchpadgala.tokenInstance();
|
|
58
|
+
launchpadGalaClassKey = launchpadgala.tokenClassKey();
|
|
59
|
+
|
|
60
|
+
launchpadGalaClass = plainToInstance(TokenClass, {
|
|
61
|
+
...launchpadgala.tokenClassPlain(),
|
|
62
|
+
decimals: 18
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
vaultAddress = asValidUserAlias(`service|${launchpadGalaClassKey.toStringKey()}$launchpad`);
|
|
66
|
+
|
|
67
|
+
// Initialize sale with manual values
|
|
68
|
+
sale = new LaunchpadSale(
|
|
69
|
+
vaultAddress,
|
|
70
|
+
launchpadGalaInstance.instanceKeyObj(),
|
|
71
|
+
undefined,
|
|
72
|
+
users.testUser1.identityKey
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
// Create sale balances - sale needs tokens to pay out
|
|
76
|
+
salelaunchpadGalaBalance = plainToInstance(TokenBalance, {
|
|
77
|
+
...launchpadgala.tokenBalance(),
|
|
78
|
+
owner: vaultAddress,
|
|
79
|
+
quantity: new BigNumber("1e+7")
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
saleCurrencyBalance = plainToInstance(TokenBalance, {
|
|
83
|
+
...currency.tokenBalance(),
|
|
84
|
+
owner: vaultAddress,
|
|
85
|
+
quantity: new BigNumber("1e+7")
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Create user balances - user needs tokens to swap
|
|
89
|
+
userlaunchpadGalaBalance = plainToInstance(TokenBalance, {
|
|
90
|
+
...launchpadgala.tokenBalance(),
|
|
91
|
+
owner: users.testUser1.identityKey,
|
|
92
|
+
quantity: new BigNumber("1000000") // User has 10k launchpadGala tokens
|
|
93
|
+
});
|
|
94
|
+
userCurrencyBalance = plainToInstance(TokenBalance, {
|
|
95
|
+
...currency.tokenBalance(),
|
|
96
|
+
owner: users.testUser1.identityKey
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("User should be able to buy exact tokens, without fee configured", async () => {
|
|
101
|
+
//Given
|
|
102
|
+
const { ctx, contract } = fixture(LaunchpadContract)
|
|
103
|
+
.registeredUsers(users.testUser1)
|
|
104
|
+
.savedState(
|
|
105
|
+
currencyClass,
|
|
106
|
+
currencyInstance,
|
|
107
|
+
launchpadGalaClass,
|
|
108
|
+
launchpadGalaInstance,
|
|
109
|
+
sale,
|
|
110
|
+
salelaunchpadGalaBalance,
|
|
111
|
+
saleCurrencyBalance,
|
|
112
|
+
userlaunchpadGalaBalance,
|
|
113
|
+
userCurrencyBalance
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
const dto = new ExactTokenQuantityDto(vaultAddress, new BigNumber("500"));
|
|
117
|
+
|
|
118
|
+
dto.uniqueKey = randomUniqueKey();
|
|
119
|
+
dto.sign(users.testUser1.privateKey);
|
|
120
|
+
|
|
121
|
+
//When
|
|
122
|
+
const buyTokenRes = await contract.BuyExactToken(ctx, dto);
|
|
123
|
+
|
|
124
|
+
//Then
|
|
125
|
+
expect(buyTokenRes.Data).toMatchObject({
|
|
126
|
+
inputQuantity: "0.00825575",
|
|
127
|
+
totalFees: "0.00000000",
|
|
128
|
+
outputQuantity: "500",
|
|
129
|
+
tokenName: "AUTOMATEDTESTCOIN",
|
|
130
|
+
tradeType: "Buy",
|
|
131
|
+
vaultAddress: "service|GALA$Unit$none$none$launchpad",
|
|
132
|
+
userAddress: "client|testUser1",
|
|
133
|
+
isFinalized: false,
|
|
134
|
+
functionName: "BuyExactToken"
|
|
135
|
+
});
|
|
136
|
+
expect(buyTokenRes.Data?.inputQuantity).toEqual("0.00825575");
|
|
137
|
+
expect(buyTokenRes.Data?.outputQuantity).toEqual("500");
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test("User should be able to buy tokens , fee configured check", async () => {
|
|
141
|
+
//Given
|
|
142
|
+
const launchpadConfig = new LaunchpadFeeConfig(users.testUser2.identityKey, Number("0.32"), [
|
|
143
|
+
users.testUser2.identityKey
|
|
144
|
+
]);
|
|
145
|
+
|
|
146
|
+
const { ctx, contract } = fixture(LaunchpadContract)
|
|
147
|
+
.registeredUsers(users.testUser1)
|
|
148
|
+
.savedState(
|
|
149
|
+
currencyClass,
|
|
150
|
+
currencyInstance,
|
|
151
|
+
launchpadGalaClass,
|
|
152
|
+
launchpadGalaInstance,
|
|
153
|
+
sale,
|
|
154
|
+
salelaunchpadGalaBalance,
|
|
155
|
+
saleCurrencyBalance,
|
|
156
|
+
userlaunchpadGalaBalance,
|
|
157
|
+
userCurrencyBalance,
|
|
158
|
+
launchpadConfig
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
const dto = new ExactTokenQuantityDto(vaultAddress, new BigNumber("5430"));
|
|
162
|
+
|
|
163
|
+
dto.uniqueKey = randomUniqueKey();
|
|
164
|
+
dto.sign(users.testUser1.privateKey);
|
|
165
|
+
|
|
166
|
+
//When
|
|
167
|
+
const buyTokenRes = await contract.BuyExactToken(ctx, dto);
|
|
168
|
+
|
|
169
|
+
//Then
|
|
170
|
+
expect(buyTokenRes.Data).toMatchObject({
|
|
171
|
+
inputQuantity: "0.08991559",
|
|
172
|
+
totalFees: "0.02877299",
|
|
173
|
+
outputQuantity: "5430",
|
|
174
|
+
tokenName: "AUTOMATEDTESTCOIN",
|
|
175
|
+
tradeType: "Buy",
|
|
176
|
+
vaultAddress: "service|GALA$Unit$none$none$launchpad",
|
|
177
|
+
userAddress: "client|testUser1",
|
|
178
|
+
isFinalized: false,
|
|
179
|
+
functionName: "BuyExactToken"
|
|
180
|
+
});
|
|
181
|
+
expect(buyTokenRes.Data?.inputQuantity).toEqual("0.08991559");
|
|
182
|
+
expect(buyTokenRes.Data?.outputQuantity).toEqual("5430");
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("should throw error if user has insufficient funds incuding the transaction fees", async () => {
|
|
186
|
+
//Given
|
|
187
|
+
const launchpadConfig = new LaunchpadFeeConfig(users.testUser2.identityKey, Number("0.32"), [
|
|
188
|
+
users.testUser2.identityKey
|
|
189
|
+
]);
|
|
190
|
+
const { ctx, contract } = fixture(LaunchpadContract)
|
|
191
|
+
.registeredUsers(users.testUser1)
|
|
192
|
+
.savedState(
|
|
193
|
+
currencyClass,
|
|
194
|
+
currencyInstance,
|
|
195
|
+
launchpadGalaClass,
|
|
196
|
+
launchpadGalaInstance,
|
|
197
|
+
sale,
|
|
198
|
+
salelaunchpadGalaBalance,
|
|
199
|
+
saleCurrencyBalance,
|
|
200
|
+
userlaunchpadGalaBalance,
|
|
201
|
+
userCurrencyBalance,
|
|
202
|
+
launchpadConfig
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
const dto = new NativeTokenQuantityDto(vaultAddress, new BigNumber("10000000"));
|
|
206
|
+
|
|
207
|
+
dto.uniqueKey = randomUniqueKey();
|
|
208
|
+
dto.sign(users.testUser1.privateKey);
|
|
209
|
+
|
|
210
|
+
//When
|
|
211
|
+
const buyTokenRes = await contract.BuyWithNative(ctx, dto);
|
|
212
|
+
|
|
213
|
+
//Then
|
|
214
|
+
expect(buyTokenRes).toEqual(
|
|
215
|
+
GalaChainResponse.Error(
|
|
216
|
+
new ValidationFailedError(
|
|
217
|
+
"Insufficient balance: Total amount required including fee is 2166101.31430784"
|
|
218
|
+
)
|
|
219
|
+
)
|
|
220
|
+
);
|
|
221
|
+
});
|
|
222
|
+
});
|
|
@@ -12,7 +12,14 @@
|
|
|
12
12
|
* See the License for the specific language governing permissions and
|
|
13
13
|
* limitations under the License.
|
|
14
14
|
*/
|
|
15
|
-
import {
|
|
15
|
+
import { ValidationFailedError } from "@gala-chain/api";
|
|
16
|
+
import {
|
|
17
|
+
GalaChainContext,
|
|
18
|
+
fetchOrCreateBalance,
|
|
19
|
+
fetchTokenClass,
|
|
20
|
+
putChainObject,
|
|
21
|
+
transferToken
|
|
22
|
+
} from "@gala-chain/chaincode";
|
|
16
23
|
import BigNumber from "bignumber.js";
|
|
17
24
|
|
|
18
25
|
import { ExactTokenQuantityDto, LaunchpadSale, TradeResDto } from "../../api/types";
|
|
@@ -82,6 +89,14 @@ export async function buyExactToken(
|
|
|
82
89
|
// Transfer transaction fees
|
|
83
90
|
const launchpadFeeAddressConfiguration = await fetchLaunchpadFeeAddress(ctx);
|
|
84
91
|
if (launchpadFeeAddressConfiguration && transactionFees) {
|
|
92
|
+
const totalRequired = new BigNumber(buyTokenDTO.tokenQuantity).plus(transactionFees);
|
|
93
|
+
|
|
94
|
+
const buyerBalance = await fetchOrCreateBalance(ctx, ctx.callingUser, sale.nativeToken);
|
|
95
|
+
if (buyerBalance.getQuantityTotal().lt(totalRequired)) {
|
|
96
|
+
throw new ValidationFailedError(
|
|
97
|
+
`Insufficient balance: Total amount required including fee is ${totalRequired}`
|
|
98
|
+
);
|
|
99
|
+
}
|
|
85
100
|
await transferToken(ctx, {
|
|
86
101
|
from: ctx.callingUser,
|
|
87
102
|
to: launchpadFeeAddressConfiguration.feeAddress,
|
|
@@ -135,6 +150,8 @@ export async function buyExactToken(
|
|
|
135
150
|
vaultAddress: buyTokenDTO.vaultAddress,
|
|
136
151
|
userAddress: ctx.callingUser,
|
|
137
152
|
isFinalized: isSaleFinalized,
|
|
138
|
-
functionName: "BuyExactToken"
|
|
153
|
+
functionName: "BuyExactToken",
|
|
154
|
+
uniqueKey: buyTokenDTO.uniqueKey,
|
|
155
|
+
totalTokenSold: sale.fetchTokensSold()
|
|
139
156
|
};
|
|
140
157
|
}
|
|
@@ -0,0 +1,221 @@
|
|
|
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 {
|
|
16
|
+
GalaChainResponse,
|
|
17
|
+
TokenBalance,
|
|
18
|
+
TokenClass,
|
|
19
|
+
TokenClassKey,
|
|
20
|
+
TokenInstance,
|
|
21
|
+
UserAlias,
|
|
22
|
+
ValidationFailedError,
|
|
23
|
+
asValidUserAlias,
|
|
24
|
+
randomUniqueKey
|
|
25
|
+
} from "@gala-chain/api";
|
|
26
|
+
import { currency, fixture, users } from "@gala-chain/test";
|
|
27
|
+
import BigNumber from "bignumber.js";
|
|
28
|
+
import { plainToInstance } from "class-transformer";
|
|
29
|
+
|
|
30
|
+
import { LaunchpadFeeConfig, LaunchpadSale, NativeTokenQuantityDto } from "../../api/types";
|
|
31
|
+
import { LaunchpadContract } from "../LaunchpadContract";
|
|
32
|
+
import launchpadgala from "../test/launchpadgala";
|
|
33
|
+
|
|
34
|
+
describe("buyWithNative", () => {
|
|
35
|
+
let currencyClass: TokenClass;
|
|
36
|
+
let currencyInstance: TokenInstance;
|
|
37
|
+
let launchpadGalaClass: TokenClass;
|
|
38
|
+
let launchpadGalaInstance: TokenInstance;
|
|
39
|
+
let launchpadGalaClassKey: TokenClassKey;
|
|
40
|
+
let vaultAddress: UserAlias;
|
|
41
|
+
let sale: LaunchpadSale;
|
|
42
|
+
let salelaunchpadGalaBalance: TokenBalance;
|
|
43
|
+
let saleCurrencyBalance: TokenBalance;
|
|
44
|
+
let userlaunchpadGalaBalance: TokenBalance;
|
|
45
|
+
let userCurrencyBalance: TokenBalance;
|
|
46
|
+
|
|
47
|
+
beforeEach(() => {
|
|
48
|
+
//Given
|
|
49
|
+
currencyClass = currency.tokenClass();
|
|
50
|
+
currencyInstance = currency.tokenInstance();
|
|
51
|
+
launchpadGalaClass = launchpadgala.tokenClass();
|
|
52
|
+
|
|
53
|
+
launchpadGalaClass = plainToInstance(TokenClass, {
|
|
54
|
+
...launchpadgala.tokenClassPlain(),
|
|
55
|
+
decimals: 18
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
launchpadGalaInstance = launchpadgala.tokenInstance();
|
|
59
|
+
launchpadGalaClassKey = launchpadgala.tokenClassKey();
|
|
60
|
+
|
|
61
|
+
vaultAddress = asValidUserAlias(`service|${launchpadGalaClassKey.toStringKey()}$launchpad`);
|
|
62
|
+
|
|
63
|
+
// Initialize sale with manual values
|
|
64
|
+
sale = new LaunchpadSale(
|
|
65
|
+
vaultAddress,
|
|
66
|
+
launchpadGalaInstance.instanceKeyObj(),
|
|
67
|
+
undefined,
|
|
68
|
+
users.testUser1.identityKey
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
// Create sale balances - sale needs tokens to pay out
|
|
72
|
+
salelaunchpadGalaBalance = plainToInstance(TokenBalance, {
|
|
73
|
+
...launchpadgala.tokenBalance(),
|
|
74
|
+
owner: vaultAddress,
|
|
75
|
+
quantity: new BigNumber("1e+7")
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
saleCurrencyBalance = plainToInstance(TokenBalance, {
|
|
79
|
+
...currency.tokenBalance(),
|
|
80
|
+
owner: vaultAddress,
|
|
81
|
+
quantity: new BigNumber("1e+7")
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Create user balances - user needs tokens to swap
|
|
85
|
+
userlaunchpadGalaBalance = plainToInstance(TokenBalance, {
|
|
86
|
+
...launchpadgala.tokenBalance(),
|
|
87
|
+
owner: users.testUser1.identityKey,
|
|
88
|
+
quantity: new BigNumber("1000000") // User has 10k launchpadGala tokens
|
|
89
|
+
});
|
|
90
|
+
userCurrencyBalance = plainToInstance(TokenBalance, {
|
|
91
|
+
...currency.tokenBalance(),
|
|
92
|
+
owner: users.testUser1.identityKey
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("User should be able to buy tokens with providing native gala , without fee configured", async () => {
|
|
97
|
+
//Given
|
|
98
|
+
const { ctx, contract } = fixture(LaunchpadContract)
|
|
99
|
+
.registeredUsers(users.testUser1)
|
|
100
|
+
.savedState(
|
|
101
|
+
currencyClass,
|
|
102
|
+
currencyInstance,
|
|
103
|
+
launchpadGalaClass,
|
|
104
|
+
launchpadGalaInstance,
|
|
105
|
+
sale,
|
|
106
|
+
salelaunchpadGalaBalance,
|
|
107
|
+
saleCurrencyBalance,
|
|
108
|
+
userlaunchpadGalaBalance,
|
|
109
|
+
userCurrencyBalance
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const dto = new NativeTokenQuantityDto(vaultAddress, new BigNumber("150"));
|
|
113
|
+
|
|
114
|
+
dto.uniqueKey = randomUniqueKey();
|
|
115
|
+
dto.sign(users.testUser1.privateKey);
|
|
116
|
+
|
|
117
|
+
//When
|
|
118
|
+
const buyTokenRes = await contract.BuyWithNative(ctx, dto);
|
|
119
|
+
|
|
120
|
+
//Then
|
|
121
|
+
expect(buyTokenRes.Data).toMatchObject({
|
|
122
|
+
inputQuantity: "150",
|
|
123
|
+
totalFees: "0.00000000",
|
|
124
|
+
outputQuantity: "2101667.8890651635002",
|
|
125
|
+
tokenName: "AUTOMATEDTESTCOIN",
|
|
126
|
+
tradeType: "Buy",
|
|
127
|
+
vaultAddress: "service|GALA$Unit$none$none$launchpad",
|
|
128
|
+
userAddress: "client|testUser1",
|
|
129
|
+
isFinalized: false,
|
|
130
|
+
functionName: "BuyWithNative"
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
expect(buyTokenRes.Data?.inputQuantity).toEqual("150");
|
|
134
|
+
expect(buyTokenRes.Data?.outputQuantity).toEqual("2101667.8890651635002");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("User should be able to buy tokens , fee configured check", async () => {
|
|
138
|
+
//Given
|
|
139
|
+
const launchpadConfig = new LaunchpadFeeConfig(users.testUser2.identityKey, Number("0.32"), [
|
|
140
|
+
users.testUser2.identityKey
|
|
141
|
+
]);
|
|
142
|
+
|
|
143
|
+
const { ctx, contract } = fixture(LaunchpadContract)
|
|
144
|
+
.registeredUsers(users.testUser1)
|
|
145
|
+
.savedState(
|
|
146
|
+
currencyClass,
|
|
147
|
+
currencyInstance,
|
|
148
|
+
launchpadGalaClass,
|
|
149
|
+
launchpadGalaInstance,
|
|
150
|
+
sale,
|
|
151
|
+
salelaunchpadGalaBalance,
|
|
152
|
+
saleCurrencyBalance,
|
|
153
|
+
userlaunchpadGalaBalance,
|
|
154
|
+
userCurrencyBalance,
|
|
155
|
+
launchpadConfig
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
const dto = new NativeTokenQuantityDto(vaultAddress, new BigNumber("1000"));
|
|
159
|
+
dto.uniqueKey = randomUniqueKey();
|
|
160
|
+
dto.sign(users.testUser1.privateKey);
|
|
161
|
+
|
|
162
|
+
//When
|
|
163
|
+
const buyTokenRes = await contract.BuyWithNative(ctx, dto);
|
|
164
|
+
|
|
165
|
+
//Then
|
|
166
|
+
expect(buyTokenRes.Data).toMatchObject({
|
|
167
|
+
inputQuantity: "1000",
|
|
168
|
+
totalFees: "320.00000000",
|
|
169
|
+
outputQuantity: "3663321.3628130557168",
|
|
170
|
+
tokenName: "AUTOMATEDTESTCOIN",
|
|
171
|
+
tradeType: "Buy",
|
|
172
|
+
vaultAddress: "service|GALA$Unit$none$none$launchpad",
|
|
173
|
+
userAddress: "client|testUser1",
|
|
174
|
+
isFinalized: false,
|
|
175
|
+
functionName: "BuyWithNative"
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
expect(buyTokenRes.Data?.totalFees).toEqual("320.00000000");
|
|
179
|
+
expect(buyTokenRes.Data?.inputQuantity).toEqual("1000");
|
|
180
|
+
expect(buyTokenRes.Data?.outputQuantity).toEqual("3663321.3628130557168");
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("should throw error if user has insufficient funds incuding the transaction fees", async () => {
|
|
184
|
+
//Given
|
|
185
|
+
const launchpadConfig = new LaunchpadFeeConfig(users.testUser2.identityKey, Number("0.32"), [
|
|
186
|
+
users.testUser2.identityKey
|
|
187
|
+
]);
|
|
188
|
+
|
|
189
|
+
const { ctx, contract } = fixture(LaunchpadContract)
|
|
190
|
+
.registeredUsers(users.testUser1)
|
|
191
|
+
.savedState(
|
|
192
|
+
currencyClass,
|
|
193
|
+
currencyInstance,
|
|
194
|
+
launchpadGalaClass,
|
|
195
|
+
launchpadGalaInstance,
|
|
196
|
+
sale,
|
|
197
|
+
salelaunchpadGalaBalance,
|
|
198
|
+
saleCurrencyBalance,
|
|
199
|
+
userlaunchpadGalaBalance,
|
|
200
|
+
userCurrencyBalance,
|
|
201
|
+
launchpadConfig
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
const dto = new NativeTokenQuantityDto(vaultAddress, new BigNumber("10000000"));
|
|
205
|
+
|
|
206
|
+
dto.uniqueKey = randomUniqueKey();
|
|
207
|
+
dto.sign(users.testUser1.privateKey);
|
|
208
|
+
|
|
209
|
+
//When
|
|
210
|
+
const buyTokenRes = await contract.BuyWithNative(ctx, dto);
|
|
211
|
+
|
|
212
|
+
//Then
|
|
213
|
+
expect(buyTokenRes).toEqual(
|
|
214
|
+
GalaChainResponse.Error(
|
|
215
|
+
new ValidationFailedError(
|
|
216
|
+
"Insufficient balance: Total amount required including fee is 2166101.31430784"
|
|
217
|
+
)
|
|
218
|
+
)
|
|
219
|
+
);
|
|
220
|
+
});
|
|
221
|
+
});
|
|
@@ -12,7 +12,14 @@
|
|
|
12
12
|
* See the License for the specific language governing permissions and
|
|
13
13
|
* limitations under the License.
|
|
14
14
|
*/
|
|
15
|
-
import {
|
|
15
|
+
import { ValidationFailedError } from "@gala-chain/api";
|
|
16
|
+
import {
|
|
17
|
+
GalaChainContext,
|
|
18
|
+
fetchOrCreateBalance,
|
|
19
|
+
fetchTokenClass,
|
|
20
|
+
putChainObject,
|
|
21
|
+
transferToken
|
|
22
|
+
} from "@gala-chain/chaincode";
|
|
16
23
|
import BigNumber from "bignumber.js";
|
|
17
24
|
|
|
18
25
|
import { ExactTokenQuantityDto, LaunchpadSale, NativeTokenQuantityDto, TradeResDto } from "../../api/types";
|
|
@@ -79,13 +86,22 @@ export async function buyWithNative(
|
|
|
79
86
|
// Check for slippage condition
|
|
80
87
|
if (buyTokenDTO.expectedToken && buyTokenDTO.expectedToken.comparedTo(tokensToBuy) > 0) {
|
|
81
88
|
throw new SlippageToleranceExceededError(
|
|
82
|
-
"Tokens expected from this operation are more than the
|
|
89
|
+
"Tokens expected from this operation are more than the actual amount that will be provided."
|
|
83
90
|
);
|
|
84
91
|
}
|
|
85
92
|
|
|
86
93
|
// Transfer transaction fees to launchpad fee address
|
|
87
94
|
const launchpadFeeAddressConfiguration = await fetchLaunchpadFeeAddress(ctx);
|
|
88
95
|
if (launchpadFeeAddressConfiguration && transactionFees) {
|
|
96
|
+
const totalRequired = new BigNumber(buyTokenDTO.nativeTokenQuantity).plus(transactionFees);
|
|
97
|
+
|
|
98
|
+
const buyerBalance = await fetchOrCreateBalance(ctx, ctx.callingUser, sale.nativeToken);
|
|
99
|
+
if (buyerBalance.getQuantityTotal().lt(totalRequired)) {
|
|
100
|
+
throw new ValidationFailedError(
|
|
101
|
+
`Insufficient balance: Total amount required including fee is ${totalRequired}`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
89
105
|
await transferToken(ctx, {
|
|
90
106
|
from: ctx.callingUser,
|
|
91
107
|
to: launchpadFeeAddressConfiguration.feeAddress,
|
|
@@ -138,6 +154,8 @@ export async function buyWithNative(
|
|
|
138
154
|
vaultAddress: buyTokenDTO.vaultAddress,
|
|
139
155
|
userAddress: ctx.callingUser,
|
|
140
156
|
isFinalized: isSaleFinalized,
|
|
141
|
-
functionName: "BuyWithNative"
|
|
157
|
+
functionName: "BuyWithNative",
|
|
158
|
+
uniqueKey: buyTokenDTO.uniqueKey,
|
|
159
|
+
totalTokenSold: sale.fetchTokensSold()
|
|
142
160
|
};
|
|
143
161
|
}
|
|
@@ -141,7 +141,7 @@ describe("callMemeTokenOut", () => {
|
|
|
141
141
|
// When
|
|
142
142
|
const response = await contract.CallMemeTokenOut(ctx, signedDto);
|
|
143
143
|
|
|
144
|
-
//
|
|
144
|
+
// Then
|
|
145
145
|
expect(response.Data).toMatchObject({
|
|
146
146
|
calculatedQuantity: "458291.30295364487969",
|
|
147
147
|
extraFees: { reverseBondingCurve: "0", transactionFees: "0.00000000" }
|
|
@@ -79,6 +79,7 @@ export async function callMemeTokenOut(ctx: GalaChainContext, buyTokenDTO: Nativ
|
|
|
79
79
|
|
|
80
80
|
// Fetch fee configuration and return result
|
|
81
81
|
const launchpadFeeAddressConfiguration = await fetchLaunchpadFeeAddress(ctx);
|
|
82
|
+
|
|
82
83
|
return {
|
|
83
84
|
calculatedQuantity: roundedResult.toFixed(),
|
|
84
85
|
extraFees: {
|
|
@@ -60,6 +60,7 @@ export async function callNativeTokenIn(ctx: GalaChainContext, buyTokenDTO: Exac
|
|
|
60
60
|
const price = constantFactor.mul(differenceOfExponentials);
|
|
61
61
|
|
|
62
62
|
const launchpadFeeAddressConfiguration = await fetchLaunchpadFeeAddress(ctx);
|
|
63
|
+
|
|
63
64
|
const roundedPrice = price.toDecimalPlaces(8, Decimal.ROUND_UP).toFixed();
|
|
64
65
|
return {
|
|
65
66
|
calculatedQuantity: roundedPrice,
|
|
@@ -69,6 +69,7 @@ export async function callNativeTokenOut(ctx: GalaChainContext, sellTokenDTO: Ex
|
|
|
69
69
|
const sale = await fetchAndValidateSale(ctx, sellTokenDTO.vaultAddress);
|
|
70
70
|
const nativeTokensReceived = calculateNativeTokensReceived(sale, sellTokenDTO.tokenQuantity);
|
|
71
71
|
const launchpadFeeAddressConfiguration = await fetchLaunchpadFeeAddress(ctx);
|
|
72
|
+
|
|
72
73
|
return {
|
|
73
74
|
calculatedQuantity: nativeTokensReceived,
|
|
74
75
|
extraFees: {
|
|
@@ -12,13 +12,16 @@
|
|
|
12
12
|
* See the License for the specific language governing permissions and
|
|
13
13
|
* limitations under the License.
|
|
14
14
|
*/
|
|
15
|
-
import { NotFoundError, UnauthorizedError } from "@gala-chain/api";
|
|
15
|
+
import { ChainCallDTO, NotFoundError, UnauthorizedError } from "@gala-chain/api";
|
|
16
16
|
import { GalaChainContext } from "@gala-chain/chaincode";
|
|
17
17
|
|
|
18
18
|
import { LaunchpadFeeConfig } from "../../api/types";
|
|
19
19
|
import { fetchLaunchpadFeeAddress } from "../utils";
|
|
20
20
|
|
|
21
|
-
export async function fetchLaunchpadFeeConfig(
|
|
21
|
+
export async function fetchLaunchpadFeeConfig(
|
|
22
|
+
ctx: GalaChainContext,
|
|
23
|
+
dto: ChainCallDTO
|
|
24
|
+
): Promise<LaunchpadFeeConfig> {
|
|
22
25
|
const curatorOrgMsp = process.env.CURATOR_ORG_MSP ?? "CuratorOrg";
|
|
23
26
|
|
|
24
27
|
const platformFeeAddress = await fetchLaunchpadFeeAddress(ctx);
|