@gala-chain/launchpad 1.0.14 → 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.
- package/lib/package.json +1 -1
- package/lib/src/api/types/LaunchpadDtos.d.ts +2 -1
- package/lib/src/api/types/LaunchpadDtos.d.ts.map +1 -1
- package/lib/src/api/types/LaunchpadDtos.js +9 -1
- package/lib/src/api/types/LaunchpadDtos.js.map +1 -1
- package/lib/src/api/types/LaunchpadSale.d.ts +3 -1
- package/lib/src/api/types/LaunchpadSale.d.ts.map +1 -1
- package/lib/src/api/types/LaunchpadSale.js +18 -4
- package/lib/src/api/types/LaunchpadSale.js.map +1 -1
- package/lib/src/chaincode/launchpad/createSale.d.ts.map +1 -1
- package/lib/src/chaincode/launchpad/createSale.js +14 -0
- package/lib/src/chaincode/launchpad/createSale.js.map +1 -1
- package/lib/src/chaincode/launchpad/sellExactToken.js +1 -1
- package/lib/src/chaincode/launchpad/sellExactToken.js.map +1 -1
- package/lib/src/chaincode/utils/launchpadSaleUtils.d.ts.map +1 -1
- package/lib/src/chaincode/utils/launchpadSaleUtils.js +9 -0
- package/lib/src/chaincode/utils/launchpadSaleUtils.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/api/types/LaunchpadDtos.ts +11 -1
- package/src/api/types/LaunchpadSale.ts +19 -4
- package/src/chaincode/launchpad/buyExactToken.spec.ts +1 -1
- package/src/chaincode/launchpad/buyWithNative.spec.ts +103 -8
- package/src/chaincode/launchpad/callMemeTokenOut.spec.ts +1 -1
- package/src/chaincode/launchpad/createSale.ts +25 -2
- package/src/chaincode/launchpad/sellExactToken.ts +1 -1
- package/src/chaincode/utils/launchpadSaleUtils.ts +12 -0
package/package.json
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
ArrayNotEmpty,
|
|
28
28
|
IsArray,
|
|
29
29
|
IsBoolean,
|
|
30
|
+
IsInt,
|
|
30
31
|
IsNotEmpty,
|
|
31
32
|
IsNumber,
|
|
32
33
|
IsOptional,
|
|
@@ -115,6 +116,10 @@ export class CreateTokenSaleDTO extends SubmitCallDTO {
|
|
|
115
116
|
@IsOptional()
|
|
116
117
|
public twitterUrl?: string;
|
|
117
118
|
|
|
119
|
+
@IsOptional()
|
|
120
|
+
@IsInt()
|
|
121
|
+
public saleStartTime?: number;
|
|
122
|
+
|
|
118
123
|
@IsOptional()
|
|
119
124
|
@ValidateNested()
|
|
120
125
|
@Type(() => ReverseBondingCurveConfigurationDto)
|
|
@@ -128,7 +133,8 @@ export class CreateTokenSaleDTO extends SubmitCallDTO {
|
|
|
128
133
|
preBuyQuantity: BigNumber,
|
|
129
134
|
tokenCollection: string,
|
|
130
135
|
tokenCategory: string,
|
|
131
|
-
reverseBondingCurveConfiguration?: ReverseBondingCurveConfigurationDto
|
|
136
|
+
reverseBondingCurveConfiguration?: ReverseBondingCurveConfigurationDto,
|
|
137
|
+
saleStartTime?: number
|
|
132
138
|
) {
|
|
133
139
|
super();
|
|
134
140
|
this.tokenName = tokenName;
|
|
@@ -139,6 +145,10 @@ export class CreateTokenSaleDTO extends SubmitCallDTO {
|
|
|
139
145
|
this.tokenCollection = tokenCollection;
|
|
140
146
|
this.tokenCategory = tokenCategory;
|
|
141
147
|
this.reverseBondingCurveConfiguration = reverseBondingCurveConfiguration;
|
|
148
|
+
|
|
149
|
+
if (saleStartTime !== undefined) {
|
|
150
|
+
this.saleStartTime = saleStartTime;
|
|
151
|
+
}
|
|
142
152
|
}
|
|
143
153
|
}
|
|
144
154
|
|
|
@@ -23,12 +23,13 @@ import {
|
|
|
23
23
|
} from "@gala-chain/api";
|
|
24
24
|
import BigNumber from "bignumber.js";
|
|
25
25
|
import { Exclude, Type } from "class-transformer";
|
|
26
|
-
import { IsNotEmpty, IsOptional, IsString, ValidateNested } from "class-validator";
|
|
26
|
+
import { IsInt, IsNotEmpty, IsOptional, IsPositive, IsString, ValidateNested } from "class-validator";
|
|
27
27
|
import { JSONSchema } from "class-validator-jsonschema";
|
|
28
28
|
|
|
29
29
|
import { ReverseBondingCurveConfigurationChainObject } from "./LaunchpadDtos";
|
|
30
30
|
|
|
31
31
|
export enum SaleStatus {
|
|
32
|
+
UPCOMING = "Upcoming",
|
|
32
33
|
ONGOING = "Ongoing",
|
|
33
34
|
END = "Finished"
|
|
34
35
|
}
|
|
@@ -49,6 +50,10 @@ export class LaunchpadSale extends ChainObject {
|
|
|
49
50
|
@IsNotEmpty()
|
|
50
51
|
public saleStatus: SaleStatus;
|
|
51
52
|
|
|
53
|
+
@IsOptional()
|
|
54
|
+
@IsInt()
|
|
55
|
+
public saleStartTime?: number;
|
|
56
|
+
|
|
52
57
|
@IsNotEmpty()
|
|
53
58
|
@ValidateNested()
|
|
54
59
|
@Type(() => TokenInstanceKey)
|
|
@@ -104,7 +109,7 @@ export class LaunchpadSale extends ChainObject {
|
|
|
104
109
|
@JSONSchema({
|
|
105
110
|
description: "The decimals of the selling token."
|
|
106
111
|
})
|
|
107
|
-
public static SELLING_TOKEN_DECIMALS =
|
|
112
|
+
public static SELLING_TOKEN_DECIMALS = 9;
|
|
108
113
|
|
|
109
114
|
@JSONSchema({
|
|
110
115
|
description: "The decimals of the native token."
|
|
@@ -115,7 +120,8 @@ export class LaunchpadSale extends ChainObject {
|
|
|
115
120
|
vaultAddress: UserAlias,
|
|
116
121
|
sellingToken: TokenInstanceKey,
|
|
117
122
|
reverseBondingCurveConfiguration: ReverseBondingCurveConfigurationChainObject | undefined,
|
|
118
|
-
saleOwner: UserAlias
|
|
123
|
+
saleOwner: UserAlias,
|
|
124
|
+
saleStartTime?: number | undefined
|
|
119
125
|
) {
|
|
120
126
|
super();
|
|
121
127
|
|
|
@@ -125,11 +131,20 @@ export class LaunchpadSale extends ChainObject {
|
|
|
125
131
|
this.sellingToken = sellingToken;
|
|
126
132
|
this.sellingTokenQuantity = "1e+7";
|
|
127
133
|
|
|
134
|
+
if (saleStartTime) {
|
|
135
|
+
this.saleStartTime = saleStartTime;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (this.saleStartTime !== undefined && this.saleStartTime > 0) {
|
|
139
|
+
this.saleStatus = SaleStatus.UPCOMING;
|
|
140
|
+
} else {
|
|
141
|
+
this.saleStatus = SaleStatus.ONGOING;
|
|
142
|
+
}
|
|
143
|
+
|
|
128
144
|
this.basePrice = new BigNumber(LaunchpadSale.BASE_PRICE);
|
|
129
145
|
this.exponentFactor = new BigNumber("1166069000000");
|
|
130
146
|
this.maxSupply = new BigNumber("1e+7");
|
|
131
147
|
this.euler = new BigNumber("2.7182818284590452353602874713527");
|
|
132
|
-
this.saleStatus = SaleStatus.ONGOING;
|
|
133
148
|
|
|
134
149
|
const nativeTokenInstance = new TokenInstanceKey();
|
|
135
150
|
nativeTokenInstance.collection = "GALA";
|
|
@@ -60,7 +60,7 @@ describe("buyWithNative", () => {
|
|
|
60
60
|
|
|
61
61
|
launchpadGalaClass = plainToInstance(TokenClass, {
|
|
62
62
|
...launchpadgala.tokenClassPlain(),
|
|
63
|
-
decimals:
|
|
63
|
+
decimals: LaunchpadSale.NATIVE_TOKEN_DECIMALS
|
|
64
64
|
});
|
|
65
65
|
|
|
66
66
|
vaultAddress = asValidUserAlias(`service|${launchpadGalaClassKey.toStringKey()}$launchpad`);
|
|
@@ -27,7 +27,13 @@ import { currency, fixture, transactionError, transactionSuccess, users } from "
|
|
|
27
27
|
import BigNumber from "bignumber.js";
|
|
28
28
|
import { plainToInstance } from "class-transformer";
|
|
29
29
|
|
|
30
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
ExactTokenQuantityDto,
|
|
32
|
+
LaunchpadFeeConfig,
|
|
33
|
+
LaunchpadSale,
|
|
34
|
+
NativeTokenQuantityDto,
|
|
35
|
+
TradeResDto
|
|
36
|
+
} from "../../api/types";
|
|
31
37
|
import { LaunchpadContract } from "../LaunchpadContract";
|
|
32
38
|
import launchpadgala from "../test/launchpadgala";
|
|
33
39
|
|
|
@@ -46,13 +52,15 @@ describe("buyWithNative", () => {
|
|
|
46
52
|
|
|
47
53
|
beforeEach(() => {
|
|
48
54
|
//Given
|
|
49
|
-
currencyClass =
|
|
55
|
+
currencyClass = plainToInstance(TokenClass, {
|
|
56
|
+
...currency.tokenClassPlain(),
|
|
57
|
+
decimals: LaunchpadSale.SELLING_TOKEN_DECIMALS
|
|
58
|
+
});
|
|
50
59
|
currencyInstance = currency.tokenInstance();
|
|
51
|
-
launchpadGalaClass = launchpadgala.tokenClass();
|
|
52
60
|
|
|
53
61
|
launchpadGalaClass = plainToInstance(TokenClass, {
|
|
54
62
|
...launchpadgala.tokenClassPlain(),
|
|
55
|
-
decimals:
|
|
63
|
+
decimals: LaunchpadSale.NATIVE_TOKEN_DECIMALS
|
|
56
64
|
});
|
|
57
65
|
|
|
58
66
|
launchpadGalaInstance = launchpadgala.tokenInstance();
|
|
@@ -154,8 +162,8 @@ describe("buyWithNative", () => {
|
|
|
154
162
|
const expectedResponse = plainToInstance(TradeResDto, {
|
|
155
163
|
inputQuantity: "150",
|
|
156
164
|
totalFees: "0",
|
|
157
|
-
totalTokenSold: "2101667.
|
|
158
|
-
outputQuantity: "2101667.
|
|
165
|
+
totalTokenSold: "2101667.889065163",
|
|
166
|
+
outputQuantity: "2101667.889065163",
|
|
159
167
|
tokenName: "AUTOMATEDTESTCOIN",
|
|
160
168
|
tradeType: "Buy",
|
|
161
169
|
vaultAddress: "service|GALA$Unit$none$none$launchpad",
|
|
@@ -200,8 +208,8 @@ describe("buyWithNative", () => {
|
|
|
200
208
|
const expectedResponse = plainToInstance(TradeResDto, {
|
|
201
209
|
inputQuantity: "1000",
|
|
202
210
|
totalFees: "320",
|
|
203
|
-
totalTokenSold: "3663321.
|
|
204
|
-
outputQuantity: "3663321.
|
|
211
|
+
totalTokenSold: "3663321.362813055",
|
|
212
|
+
outputQuantity: "3663321.362813055",
|
|
205
213
|
tokenName: "AUTOMATEDTESTCOIN",
|
|
206
214
|
tradeType: "Buy",
|
|
207
215
|
vaultAddress: "service|GALA$Unit$none$none$launchpad",
|
|
@@ -256,4 +264,91 @@ describe("buyWithNative", () => {
|
|
|
256
264
|
)
|
|
257
265
|
);
|
|
258
266
|
});
|
|
267
|
+
|
|
268
|
+
it("should return inverse native tokens when buying and selling tokens", async () => {
|
|
269
|
+
//Given
|
|
270
|
+
salelaunchpadGalaBalance.subtractQuantity(new BigNumber("1e+7"), 0);
|
|
271
|
+
saleCurrencyBalance.addQuantity(new BigNumber("1e+7"));
|
|
272
|
+
userlaunchpadGalaBalance.addQuantity(new BigNumber("1e+7"));
|
|
273
|
+
const { ctx, contract } = fixture(LaunchpadContract)
|
|
274
|
+
.registeredUsers(users.testUser1)
|
|
275
|
+
.savedState(
|
|
276
|
+
currencyClass,
|
|
277
|
+
currencyInstance,
|
|
278
|
+
launchpadGalaClass,
|
|
279
|
+
launchpadGalaInstance,
|
|
280
|
+
sale,
|
|
281
|
+
salelaunchpadGalaBalance,
|
|
282
|
+
saleCurrencyBalance,
|
|
283
|
+
userlaunchpadGalaBalance,
|
|
284
|
+
userCurrencyBalance
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
const arr: string[] = [
|
|
288
|
+
"31.27520343",
|
|
289
|
+
"100.3731319",
|
|
290
|
+
"322.1326962",
|
|
291
|
+
"1033.837164",
|
|
292
|
+
"3317.947211",
|
|
293
|
+
"10648.46001",
|
|
294
|
+
"34174.65481",
|
|
295
|
+
"109678.4915",
|
|
296
|
+
"351996.8694",
|
|
297
|
+
"1129679.88999"
|
|
298
|
+
];
|
|
299
|
+
|
|
300
|
+
const sellingArr: string[] = [];
|
|
301
|
+
const sellArr: string[] = [
|
|
302
|
+
"1000000.000060721",
|
|
303
|
+
"1000000.000017497",
|
|
304
|
+
"999999.999868935",
|
|
305
|
+
"1000000.000187964",
|
|
306
|
+
"999999.999879254",
|
|
307
|
+
"999999.999929098",
|
|
308
|
+
"1000000.000096442",
|
|
309
|
+
"999999.999800915",
|
|
310
|
+
"1000000.000112742",
|
|
311
|
+
"999999.000293124"
|
|
312
|
+
];
|
|
313
|
+
|
|
314
|
+
// When - Buy tokens using native token amounts from arr
|
|
315
|
+
for (let i = 0; i < arr.length; i++) {
|
|
316
|
+
let nativeCoins = Number(arr[i]);
|
|
317
|
+
nativeCoins = roundToDecimal(nativeCoins, 8);
|
|
318
|
+
|
|
319
|
+
const dto = new NativeTokenQuantityDto(vaultAddress, new BigNumber(nativeCoins));
|
|
320
|
+
dto.uniqueKey = randomUniqueKey();
|
|
321
|
+
dto.sign(users.testUser1.privateKey);
|
|
322
|
+
|
|
323
|
+
const buyTokenRes = await contract.BuyWithNative(ctx, dto);
|
|
324
|
+
expect(buyTokenRes).toEqual(transactionSuccess());
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// When - Sell tokens back in reverse order
|
|
328
|
+
for (let i = sellArr.length - 1; i >= 0; i--) {
|
|
329
|
+
const sellDto = new ExactTokenQuantityDto(vaultAddress, new BigNumber(sellArr[i]));
|
|
330
|
+
sellDto.uniqueKey = randomUniqueKey();
|
|
331
|
+
sellDto.sign(users.testUser1.privateKey);
|
|
332
|
+
|
|
333
|
+
const sellTokenRes = await contract.SellExactToken(ctx, sellDto);
|
|
334
|
+
expect(sellTokenRes).toEqual(transactionSuccess());
|
|
335
|
+
sellingArr.push(sellTokenRes.Data?.outputQuantity || "0");
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Then - Verify sellingArr is the inverse of arr with the extra ""
|
|
339
|
+
const expectedSellingArr = [...arr].reverse();
|
|
340
|
+
|
|
341
|
+
// Compare each element, checking that values match up to 4 decimal places
|
|
342
|
+
const tolerance = new BigNumber("0.0001"); // 4 decimal places tolerance
|
|
343
|
+
for (let i = 1; i < sellingArr.length; i++) {
|
|
344
|
+
const sellingValue = new BigNumber(sellingArr[i]);
|
|
345
|
+
const expectedValue = new BigNumber(expectedSellingArr[i]);
|
|
346
|
+
const difference = sellingValue.minus(expectedValue).abs();
|
|
347
|
+
expect(difference.isLessThanOrEqualTo(tolerance)).toBe(true);
|
|
348
|
+
}
|
|
349
|
+
});
|
|
259
350
|
});
|
|
351
|
+
function roundToDecimal(value, decimals) {
|
|
352
|
+
const factor = Math.pow(10, decimals);
|
|
353
|
+
return Math.round(value * factor) / factor;
|
|
354
|
+
}
|
|
@@ -143,7 +143,7 @@ describe("callMemeTokenOut", () => {
|
|
|
143
143
|
|
|
144
144
|
// Then
|
|
145
145
|
expect(response.Data).toMatchObject({
|
|
146
|
-
calculatedQuantity: "458291.
|
|
146
|
+
calculatedQuantity: "458291.302953644",
|
|
147
147
|
extraFees: { reverseBondingCurve: "0", transactionFees: "0.00000000" }
|
|
148
148
|
});
|
|
149
149
|
});
|
|
@@ -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 {
|
|
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
|
|
|
@@ -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,
|
|
@@ -58,7 +58,7 @@ export async function sellExactToken(
|
|
|
58
58
|
const memeToken = sale.fetchSellingTokenInstanceKey();
|
|
59
59
|
|
|
60
60
|
// Abort if the vault doesn't have enough native tokens to pay the user
|
|
61
|
-
if (
|
|
61
|
+
if (nativeTokensPayout.isGreaterThan(nativeTokensLeftInVault)) {
|
|
62
62
|
throw new ValidationFailedError("Not enough GALA in sale contract to carry out this operation.");
|
|
63
63
|
}
|
|
64
64
|
|
|
@@ -35,6 +35,18 @@ export async function fetchAndValidateSale(
|
|
|
35
35
|
if (sale === undefined) {
|
|
36
36
|
throw new NotFoundError("Sale record not found.");
|
|
37
37
|
}
|
|
38
|
+
|
|
39
|
+
if (sale.saleStatus === SaleStatus.UPCOMING) {
|
|
40
|
+
if (sale.saleStartTime !== undefined && sale.saleStartTime > 0 && sale.saleStartTime < ctx.txUnixTime) {
|
|
41
|
+
sale.saleStatus = SaleStatus.ONGOING;
|
|
42
|
+
} else {
|
|
43
|
+
throw new DefaultError(
|
|
44
|
+
`Upcoming: This sale is coming soon. ` +
|
|
45
|
+
`${sale.saleStartTime ? "Sale starts at: " + sale.saleStartTime + " Unix time." : "Start time TBD."}`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
38
50
|
if (sale.saleStatus === SaleStatus.END) {
|
|
39
51
|
throw new DefaultError("This sale has already ended.");
|
|
40
52
|
}
|