@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.
- package/lib/package.json +1 -1
- package/lib/src/api/types/LaunchpadDtos.d.ts +3 -1
- package/lib/src/api/types/LaunchpadDtos.d.ts.map +1 -1
- package/lib/src/api/types/LaunchpadDtos.js +14 -1
- package/lib/src/api/types/LaunchpadDtos.js.map +1 -1
- package/lib/src/api/types/LaunchpadSale.d.ts +5 -2
- package/lib/src/api/types/LaunchpadSale.d.ts.map +1 -1
- package/lib/src/api/types/LaunchpadSale.js +35 -9
- package/lib/src/api/types/LaunchpadSale.js.map +1 -1
- package/lib/src/chaincode/LaunchpadContract.js +6 -6
- 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 +18 -42
- 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 +12 -43
- package/lib/src/chaincode/launchpad/buyWithNative.js.map +1 -1
- package/lib/src/chaincode/launchpad/callMemeTokenIn.d.ts +4 -10
- package/lib/src/chaincode/launchpad/callMemeTokenIn.d.ts.map +1 -1
- package/lib/src/chaincode/launchpad/callMemeTokenIn.js +13 -9
- package/lib/src/chaincode/launchpad/callMemeTokenIn.js.map +1 -1
- package/lib/src/chaincode/launchpad/callMemeTokenOut.d.ts +4 -10
- package/lib/src/chaincode/launchpad/callMemeTokenOut.d.ts.map +1 -1
- package/lib/src/chaincode/launchpad/callMemeTokenOut.js +46 -22
- package/lib/src/chaincode/launchpad/callMemeTokenOut.js.map +1 -1
- package/lib/src/chaincode/launchpad/callNativeTokenIn.d.ts +4 -10
- package/lib/src/chaincode/launchpad/callNativeTokenIn.d.ts.map +1 -1
- package/lib/src/chaincode/launchpad/callNativeTokenIn.js +35 -14
- package/lib/src/chaincode/launchpad/callNativeTokenIn.js.map +1 -1
- package/lib/src/chaincode/launchpad/callNativeTokenOut.d.ts +4 -10
- package/lib/src/chaincode/launchpad/callNativeTokenOut.d.ts.map +1 -1
- package/lib/src/chaincode/launchpad/callNativeTokenOut.js +17 -12
- package/lib/src/chaincode/launchpad/callNativeTokenOut.js.map +1 -1
- package/lib/src/chaincode/launchpad/configureLaunchpadFeeConfig.js +1 -1
- package/lib/src/chaincode/launchpad/configureLaunchpadFeeConfig.js.map +1 -1
- package/lib/src/chaincode/launchpad/createSale.d.ts.map +1 -1
- package/lib/src/chaincode/launchpad/createSale.js +15 -1
- package/lib/src/chaincode/launchpad/createSale.js.map +1 -1
- package/lib/src/chaincode/launchpad/fees.d.ts +11 -0
- package/lib/src/chaincode/launchpad/fees.d.ts.map +1 -1
- package/lib/src/chaincode/launchpad/fees.js +42 -3
- package/lib/src/chaincode/launchpad/fees.js.map +1 -1
- package/lib/src/chaincode/launchpad/finaliseSale.d.ts.map +1 -1
- package/lib/src/chaincode/launchpad/finaliseSale.js +7 -4
- 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 +14 -23
- 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 +10 -19
- package/lib/src/chaincode/launchpad/sellWithNative.js.map +1 -1
- package/lib/src/chaincode/test/launchpadgala.d.ts.map +1 -1
- package/lib/src/chaincode/test/launchpadgala.js +2 -1
- package/lib/src/chaincode/test/launchpadgala.js.map +1 -1
- package/lib/src/chaincode/utils/launchpadSaleUtils.d.ts +4 -0
- package/lib/src/chaincode/utils/launchpadSaleUtils.d.ts.map +1 -1
- package/lib/src/chaincode/utils/launchpadSaleUtils.js +19 -1
- package/lib/src/chaincode/utils/launchpadSaleUtils.js.map +1 -1
- package/lib/src/cli.js +3 -1
- package/lib/src/cli.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/api/types/LaunchpadDtos.ts +15 -1
- package/src/api/types/LaunchpadSale.ts +32 -10
- package/src/chaincode/LaunchpadContract.ts +3 -3
- package/src/chaincode/launchpad/buyExactToken.spec.ts +3 -38
- package/src/chaincode/launchpad/buyExactToken.ts +22 -56
- package/src/chaincode/launchpad/buyWithNative.spec.ts +105 -45
- package/src/chaincode/launchpad/buyWithNative.ts +15 -65
- package/src/chaincode/launchpad/callMemeTokenIn.ts +36 -12
- package/src/chaincode/launchpad/callMemeTokenOut.spec.ts +2 -2
- package/src/chaincode/launchpad/callMemeTokenOut.ts +72 -26
- package/src/chaincode/launchpad/callNativeTokenIn.ts +62 -19
- package/src/chaincode/launchpad/callNativeTokenOut.ts +38 -15
- package/src/chaincode/launchpad/configureLaunchpadFeeConfig.spec.ts +0 -1
- package/src/chaincode/launchpad/configureLaunchpadFeeConfig.ts +1 -1
- package/src/chaincode/launchpad/createSale.ts +26 -3
- package/src/chaincode/launchpad/fees.ts +55 -3
- package/src/chaincode/launchpad/finaliseSale.ts +7 -4
- package/src/chaincode/launchpad/launchpadBatchSubmitAuthorizations.spec.ts +150 -68
- package/src/chaincode/launchpad/sellExactToken.spec.ts +4 -81
- package/src/chaincode/launchpad/sellExactToken.ts +16 -25
- package/src/chaincode/launchpad/sellWithNative.spec.ts +2 -42
- package/src/chaincode/launchpad/sellWithNative.ts +14 -23
- package/src/chaincode/test/launchpadgala.ts +3 -1
- package/src/chaincode/utils/launchpadSaleUtils.ts +25 -1
- package/src/cli.ts +3 -1
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
|
|
|
@@ -369,6 +379,10 @@ export class TradeCalculationResFeesDto {
|
|
|
369
379
|
}
|
|
370
380
|
|
|
371
381
|
export class TradeCalculationResDto {
|
|
382
|
+
@IsNotEmpty()
|
|
383
|
+
@IsString()
|
|
384
|
+
public originalQuantity: string;
|
|
385
|
+
|
|
372
386
|
@IsNotEmpty()
|
|
373
387
|
@IsString()
|
|
374
388
|
public calculatedQuantity: string;
|
|
@@ -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,12 +50,18 @@ export class LaunchpadSale extends ChainObject {
|
|
|
49
50
|
@IsNotEmpty()
|
|
50
51
|
public saleStatus: SaleStatus;
|
|
51
52
|
|
|
52
|
-
@
|
|
53
|
+
@IsOptional()
|
|
54
|
+
@IsInt()
|
|
55
|
+
public saleStartTime?: number;
|
|
56
|
+
|
|
53
57
|
@IsNotEmpty()
|
|
58
|
+
@ValidateNested()
|
|
59
|
+
@Type(() => TokenInstanceKey)
|
|
54
60
|
public sellingToken: TokenInstanceKey;
|
|
55
61
|
|
|
56
|
-
@Type(() => TokenInstanceKey)
|
|
57
62
|
@IsNotEmpty()
|
|
63
|
+
@ValidateNested()
|
|
64
|
+
@Type(() => TokenInstanceKey)
|
|
58
65
|
public nativeToken: TokenInstanceKey;
|
|
59
66
|
|
|
60
67
|
@IsString()
|
|
@@ -99,11 +106,22 @@ export class LaunchpadSale extends ChainObject {
|
|
|
99
106
|
})
|
|
100
107
|
public static BASE_PRICE = "16506671506650";
|
|
101
108
|
|
|
109
|
+
@JSONSchema({
|
|
110
|
+
description: "The decimals of the selling token."
|
|
111
|
+
})
|
|
112
|
+
public static SELLING_TOKEN_DECIMALS = 9;
|
|
113
|
+
|
|
114
|
+
@JSONSchema({
|
|
115
|
+
description: "The decimals of the native token."
|
|
116
|
+
})
|
|
117
|
+
public static NATIVE_TOKEN_DECIMALS = 8;
|
|
118
|
+
|
|
102
119
|
constructor(
|
|
103
120
|
vaultAddress: UserAlias,
|
|
104
121
|
sellingToken: TokenInstanceKey,
|
|
105
122
|
reverseBondingCurveConfiguration: ReverseBondingCurveConfigurationChainObject | undefined,
|
|
106
|
-
saleOwner: UserAlias
|
|
123
|
+
saleOwner: UserAlias,
|
|
124
|
+
saleStartTime?: number | undefined
|
|
107
125
|
) {
|
|
108
126
|
super();
|
|
109
127
|
|
|
@@ -113,11 +131,20 @@ export class LaunchpadSale extends ChainObject {
|
|
|
113
131
|
this.sellingToken = sellingToken;
|
|
114
132
|
this.sellingTokenQuantity = "1e+7";
|
|
115
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
|
+
|
|
116
144
|
this.basePrice = new BigNumber(LaunchpadSale.BASE_PRICE);
|
|
117
145
|
this.exponentFactor = new BigNumber("1166069000000");
|
|
118
146
|
this.maxSupply = new BigNumber("1e+7");
|
|
119
147
|
this.euler = new BigNumber("2.7182818284590452353602874713527");
|
|
120
|
-
this.saleStatus = SaleStatus.ONGOING;
|
|
121
148
|
|
|
122
149
|
const nativeTokenInstance = new TokenInstanceKey();
|
|
123
150
|
nativeTokenInstance.collection = "GALA";
|
|
@@ -165,11 +192,6 @@ export class LaunchpadSale extends ChainObject {
|
|
|
165
192
|
return nativeTokenInVault.toString();
|
|
166
193
|
}
|
|
167
194
|
|
|
168
|
-
public fetchBasePrice() {
|
|
169
|
-
const basePriceBigNumber = new BigNumber(this.basePrice);
|
|
170
|
-
return basePriceBigNumber.toString();
|
|
171
|
-
}
|
|
172
|
-
|
|
173
195
|
public finalizeSale() {
|
|
174
196
|
this.saleStatus = SaleStatus.END;
|
|
175
197
|
this.nativeTokenQuantity = "0";
|
|
@@ -137,7 +137,7 @@ export class LaunchpadContract extends GalaContract {
|
|
|
137
137
|
@Submit({
|
|
138
138
|
in: ConfigureLaunchpadFeeAddressDto,
|
|
139
139
|
out: LaunchpadFeeConfig,
|
|
140
|
-
allowedOrgs: ["CuratorOrg"]
|
|
140
|
+
allowedOrgs: [process.env.CURATOR_ORG_MSP ?? "CuratorOrg"]
|
|
141
141
|
})
|
|
142
142
|
public async ConfigureLaunchpadFeeAddress(
|
|
143
143
|
ctx: GalaChainContext,
|
|
@@ -149,7 +149,7 @@ export class LaunchpadContract extends GalaContract {
|
|
|
149
149
|
@Submit({
|
|
150
150
|
in: FinalizeTokenAllocationDto,
|
|
151
151
|
out: LaunchpadFinalizeFeeAllocation,
|
|
152
|
-
allowedOrgs: ["CuratorOrg"]
|
|
152
|
+
allowedOrgs: [process.env.CURATOR_ORG_MSP ?? "CuratorOrg"]
|
|
153
153
|
})
|
|
154
154
|
public async FinalizeTokenAllocation(
|
|
155
155
|
ctx: GalaChainContext,
|
|
@@ -209,7 +209,7 @@ export class LaunchpadContract extends GalaContract {
|
|
|
209
209
|
@Evaluate({
|
|
210
210
|
in: ChainCallDTO,
|
|
211
211
|
out: LaunchpadFeeConfig,
|
|
212
|
-
allowedOrgs: ["CuratorOrg"]
|
|
212
|
+
allowedOrgs: [process.env.CURATOR_ORG_MSP ?? "CuratorOrg"]
|
|
213
213
|
})
|
|
214
214
|
public async FetchLaunchpadFeeConfig(
|
|
215
215
|
ctx: GalaChainContext,
|
|
@@ -23,8 +23,7 @@ import {
|
|
|
23
23
|
asValidUserAlias,
|
|
24
24
|
randomUniqueKey
|
|
25
25
|
} from "@gala-chain/api";
|
|
26
|
-
import {
|
|
27
|
-
import { currency, fixture, transactionError, transactionSuccess, users } from "@gala-chain/test";
|
|
26
|
+
import { currency, fixture, transactionSuccess, users } from "@gala-chain/test";
|
|
28
27
|
import BigNumber from "bignumber.js";
|
|
29
28
|
import { plainToInstance } from "class-transformer";
|
|
30
29
|
|
|
@@ -61,7 +60,7 @@ describe("buyWithNative", () => {
|
|
|
61
60
|
|
|
62
61
|
launchpadGalaClass = plainToInstance(TokenClass, {
|
|
63
62
|
...launchpadgala.tokenClassPlain(),
|
|
64
|
-
decimals:
|
|
63
|
+
decimals: LaunchpadSale.NATIVE_TOKEN_DECIMALS
|
|
65
64
|
});
|
|
66
65
|
|
|
67
66
|
vaultAddress = asValidUserAlias(`service|${launchpadGalaClassKey.toStringKey()}$launchpad`);
|
|
@@ -128,40 +127,6 @@ describe("buyWithNative", () => {
|
|
|
128
127
|
expect(buyTokenRes).toEqual(transactionSuccess());
|
|
129
128
|
});
|
|
130
129
|
|
|
131
|
-
it("should reject buy when meme token has 0 decimals and input dto contains fractional quantity", async () => {
|
|
132
|
-
// Given - Setup token with 0 decimals to force decimal precision error
|
|
133
|
-
const zeroDecimalCurrencyClass = plainToInstance(TokenClass, {
|
|
134
|
-
...currency.tokenClassPlain(),
|
|
135
|
-
decimals: 0 // Integer-only token
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
const { ctx, contract } = fixture(LaunchpadContract)
|
|
139
|
-
.registeredUsers(users.testUser1)
|
|
140
|
-
.savedState(
|
|
141
|
-
zeroDecimalCurrencyClass,
|
|
142
|
-
currencyInstance,
|
|
143
|
-
launchpadGalaClass,
|
|
144
|
-
launchpadGalaInstance,
|
|
145
|
-
sale,
|
|
146
|
-
salelaunchpadGalaBalance,
|
|
147
|
-
saleCurrencyBalance,
|
|
148
|
-
userlaunchpadGalaBalance,
|
|
149
|
-
userCurrencyBalance
|
|
150
|
-
);
|
|
151
|
-
|
|
152
|
-
const dto = new ExactTokenQuantityDto(vaultAddress, new BigNumber("500.555"));
|
|
153
|
-
dto.uniqueKey = randomUniqueKey();
|
|
154
|
-
dto.sign(users.testUser1.privateKey);
|
|
155
|
-
|
|
156
|
-
// When
|
|
157
|
-
const buyTokenRes = await contract.BuyExactToken(ctx, dto);
|
|
158
|
-
|
|
159
|
-
// Then
|
|
160
|
-
expect(buyTokenRes).toEqual(
|
|
161
|
-
transactionError(new InvalidDecimalError(dto.tokenQuantity, zeroDecimalCurrencyClass.decimals))
|
|
162
|
-
);
|
|
163
|
-
});
|
|
164
|
-
|
|
165
130
|
test("User should be able to buy exact tokens, without fee configured", async () => {
|
|
166
131
|
// Given
|
|
167
132
|
const { ctx, contract } = fixture(LaunchpadContract)
|
|
@@ -185,7 +150,7 @@ describe("buyWithNative", () => {
|
|
|
185
150
|
|
|
186
151
|
const expectedResponse = plainToInstance(TradeResDto, {
|
|
187
152
|
inputQuantity: "0.00825575",
|
|
188
|
-
totalFees: "0
|
|
153
|
+
totalFees: "0",
|
|
189
154
|
totalTokenSold: "500",
|
|
190
155
|
outputQuantity: "500",
|
|
191
156
|
tokenName: "AUTOMATEDTESTCOIN",
|
|
@@ -12,20 +12,14 @@
|
|
|
12
12
|
* See the License for the specific language governing permissions and
|
|
13
13
|
* limitations under the License.
|
|
14
14
|
*/
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
GalaChainContext,
|
|
18
|
-
fetchOrCreateBalance,
|
|
19
|
-
fetchTokenClass,
|
|
20
|
-
putChainObject,
|
|
21
|
-
transferToken
|
|
22
|
-
} from "@gala-chain/chaincode";
|
|
15
|
+
import { GalaChainContext, fetchTokenClass, putChainObject, transferToken } from "@gala-chain/chaincode";
|
|
23
16
|
import BigNumber from "bignumber.js";
|
|
24
17
|
|
|
25
|
-
import { ExactTokenQuantityDto,
|
|
18
|
+
import { ExactTokenQuantityDto, TradeResDto } from "../../api/types";
|
|
26
19
|
import { SlippageToleranceExceededError } from "../../api/utils/error";
|
|
27
|
-
import { fetchAndValidateSale
|
|
20
|
+
import { fetchAndValidateSale } from "../utils";
|
|
28
21
|
import { callNativeTokenIn } from "./callNativeTokenIn";
|
|
22
|
+
import { transferTransactionFees } from "./fees";
|
|
29
23
|
import { finalizeSale } from "./finaliseSale";
|
|
30
24
|
|
|
31
25
|
/**
|
|
@@ -52,67 +46,39 @@ export async function buyExactToken(
|
|
|
52
46
|
|
|
53
47
|
// Fetch and validate the sale based on the provided vault address
|
|
54
48
|
const sale = await fetchAndValidateSale(ctx, buyTokenDTO.vaultAddress);
|
|
55
|
-
const
|
|
49
|
+
const tokensLeftInVault = new BigNumber(sale.sellingTokenQuantity);
|
|
56
50
|
|
|
57
51
|
// Calculate the required amount of native tokens to buy the specified token amount
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
52
|
+
const callNativeTokenInResult = await callNativeTokenIn(ctx, buyTokenDTO);
|
|
53
|
+
const tokensToBuy = new BigNumber(callNativeTokenInResult.originalQuantity); // number of tokens user wants to buy
|
|
54
|
+
const nativeTokensRequired = new BigNumber(callNativeTokenInResult.calculatedQuantity); // number of native tokens required to buy the tokens
|
|
55
|
+
const transactionFees = new BigNumber(callNativeTokenInResult.extraFees.transactionFees); // transaction fees
|
|
56
|
+
|
|
61
57
|
const nativeToken = sale.fetchNativeTokenInstanceKey();
|
|
62
58
|
const memeToken = sale.fetchSellingTokenInstanceKey();
|
|
63
59
|
|
|
64
|
-
// If the requested token amount exceeds what's available,
|
|
65
|
-
if
|
|
66
|
-
|
|
67
|
-
const callNativeTokenInResult2 = await callNativeTokenIn(ctx, buyTokenDTO);
|
|
68
|
-
nativeTokensToBuy = new BigNumber(callNativeTokenInResult2.calculatedQuantity);
|
|
69
|
-
transactionFees = callNativeTokenInResult2.extraFees.transactionFees;
|
|
70
|
-
isSaleFinalized = true;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Check if the native tokens used exceed the market cap, finalizing the sale if true
|
|
74
|
-
if (
|
|
75
|
-
nativeTokensToBuy
|
|
76
|
-
.plus(new BigNumber(sale.nativeTokenQuantity))
|
|
77
|
-
.gte(new BigNumber(LaunchpadSale.MARKET_CAP))
|
|
78
|
-
) {
|
|
60
|
+
// If the requested token amount exceeds what's available, finalise the sale
|
|
61
|
+
// Token amounts have been adjusted in the callNativeTokenIn function if they exceed the total supply
|
|
62
|
+
if (tokensLeftInVault.isLessThanOrEqualTo(buyTokenDTO.tokenQuantity)) {
|
|
79
63
|
isSaleFinalized = true;
|
|
80
64
|
}
|
|
81
65
|
|
|
82
66
|
// Ensure the expected native token amount is not less than the actual amount required
|
|
83
|
-
if (buyTokenDTO.expectedNativeToken && buyTokenDTO.expectedNativeToken.
|
|
67
|
+
if (buyTokenDTO.expectedNativeToken && buyTokenDTO.expectedNativeToken.isLessThan(nativeTokensRequired)) {
|
|
84
68
|
throw new SlippageToleranceExceededError(
|
|
85
|
-
`expected ${buyTokenDTO.expectedNativeToken.toString()}, but at least ${
|
|
69
|
+
`expected ${buyTokenDTO.expectedNativeToken.toString()}, but at least ${nativeTokensRequired.toString()} are required to complete this operation. Increase the expected amount or adjust your slippage tolerance.`
|
|
86
70
|
);
|
|
87
71
|
}
|
|
88
72
|
|
|
89
73
|
// Transfer transaction fees
|
|
90
|
-
|
|
91
|
-
if (launchpadFeeAddressConfiguration && transactionFees) {
|
|
92
|
-
const totalRequired = nativeTokensToBuy.plus(new BigNumber(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
|
-
}
|
|
100
|
-
await transferToken(ctx, {
|
|
101
|
-
from: ctx.callingUser,
|
|
102
|
-
to: launchpadFeeAddressConfiguration.feeAddress,
|
|
103
|
-
tokenInstanceKey: nativeToken,
|
|
104
|
-
quantity: new BigNumber(transactionFees),
|
|
105
|
-
allowancesToUse: [],
|
|
106
|
-
authorizedOnBehalf: undefined
|
|
107
|
-
});
|
|
108
|
-
}
|
|
74
|
+
await transferTransactionFees(ctx, sale, transactionFees, nativeToken, nativeTokensRequired);
|
|
109
75
|
|
|
110
76
|
// Transfer native tokens from the buyer to the vault
|
|
111
77
|
await transferToken(ctx, {
|
|
112
78
|
from: ctx.callingUser,
|
|
113
79
|
to: buyTokenDTO.vaultAddress,
|
|
114
80
|
tokenInstanceKey: nativeToken,
|
|
115
|
-
quantity:
|
|
81
|
+
quantity: nativeTokensRequired,
|
|
116
82
|
allowancesToUse: [],
|
|
117
83
|
authorizedOnBehalf: undefined
|
|
118
84
|
});
|
|
@@ -122,7 +88,7 @@ export async function buyExactToken(
|
|
|
122
88
|
from: buyTokenDTO.vaultAddress,
|
|
123
89
|
to: ctx.callingUser,
|
|
124
90
|
tokenInstanceKey: memeToken,
|
|
125
|
-
quantity:
|
|
91
|
+
quantity: tokensToBuy,
|
|
126
92
|
allowancesToUse: [],
|
|
127
93
|
authorizedOnBehalf: {
|
|
128
94
|
callingOnBehalf: buyTokenDTO.vaultAddress,
|
|
@@ -131,7 +97,7 @@ export async function buyExactToken(
|
|
|
131
97
|
});
|
|
132
98
|
|
|
133
99
|
// Update the sale record with the purchased token details
|
|
134
|
-
sale.buyToken(
|
|
100
|
+
sale.buyToken(tokensToBuy, nativeTokensRequired);
|
|
135
101
|
await putChainObject(ctx, sale);
|
|
136
102
|
|
|
137
103
|
// If the sale is finalized, create a V3 pool and add liquidity
|
|
@@ -142,9 +108,9 @@ export async function buyExactToken(
|
|
|
142
108
|
// Return the updated balance response
|
|
143
109
|
const token = await fetchTokenClass(ctx, sale.sellingToken);
|
|
144
110
|
return {
|
|
145
|
-
inputQuantity:
|
|
146
|
-
totalFees: transactionFees,
|
|
147
|
-
outputQuantity:
|
|
111
|
+
inputQuantity: nativeTokensRequired.toFixed(),
|
|
112
|
+
totalFees: transactionFees.toFixed(),
|
|
113
|
+
outputQuantity: tokensToBuy.toFixed(),
|
|
148
114
|
tokenName: token.name,
|
|
149
115
|
tradeType: "Buy",
|
|
150
116
|
vaultAddress: buyTokenDTO.vaultAddress,
|
|
@@ -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();
|
|
@@ -130,41 +138,6 @@ describe("buyWithNative", () => {
|
|
|
130
138
|
);
|
|
131
139
|
});
|
|
132
140
|
|
|
133
|
-
it("should reject buy when input dto has higher fractional precision than GALA TokenClass", async () => {
|
|
134
|
-
// Given
|
|
135
|
-
const zeroDecimalLaunchpadClass = plainToInstance(TokenClass, {
|
|
136
|
-
...launchpadgala.tokenClassPlain(),
|
|
137
|
-
decimals: 0 // Integer-only meme token
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
const { ctx, contract } = fixture(LaunchpadContract)
|
|
141
|
-
.registeredUsers(users.testUser1)
|
|
142
|
-
.savedState(
|
|
143
|
-
currencyInstance,
|
|
144
|
-
currencyClass,
|
|
145
|
-
zeroDecimalLaunchpadClass,
|
|
146
|
-
launchpadGalaInstance,
|
|
147
|
-
sale,
|
|
148
|
-
salelaunchpadGalaBalance,
|
|
149
|
-
saleCurrencyBalance,
|
|
150
|
-
userlaunchpadGalaBalance,
|
|
151
|
-
userCurrencyBalance
|
|
152
|
-
);
|
|
153
|
-
|
|
154
|
-
// Use a native token amount that will produce fractional meme tokens from bonding curve
|
|
155
|
-
const dto = new NativeTokenQuantityDto(vaultAddress, new BigNumber("0.01"));
|
|
156
|
-
dto.uniqueKey = randomUniqueKey();
|
|
157
|
-
dto.sign(users.testUser1.privateKey);
|
|
158
|
-
|
|
159
|
-
// When
|
|
160
|
-
const buyTokenRes = await contract.BuyWithNative(ctx, dto);
|
|
161
|
-
|
|
162
|
-
// Then - Expect error due to decimal precision mismatch
|
|
163
|
-
expect(buyTokenRes).toEqual(
|
|
164
|
-
transactionError(new InvalidDecimalError(dto.nativeTokenQuantity, zeroDecimalLaunchpadClass.decimals))
|
|
165
|
-
);
|
|
166
|
-
});
|
|
167
|
-
|
|
168
141
|
test("User buys tokens by providing native gala, without fee needing to be configured", async () => {
|
|
169
142
|
//Given
|
|
170
143
|
const { ctx, contract } = fixture(LaunchpadContract)
|
|
@@ -188,9 +161,9 @@ describe("buyWithNative", () => {
|
|
|
188
161
|
|
|
189
162
|
const expectedResponse = plainToInstance(TradeResDto, {
|
|
190
163
|
inputQuantity: "150",
|
|
191
|
-
totalFees: "0
|
|
192
|
-
totalTokenSold: "2101667.
|
|
193
|
-
outputQuantity: "2101667.
|
|
164
|
+
totalFees: "0",
|
|
165
|
+
totalTokenSold: "2101667.889065163",
|
|
166
|
+
outputQuantity: "2101667.889065163",
|
|
194
167
|
tokenName: "AUTOMATEDTESTCOIN",
|
|
195
168
|
tradeType: "Buy",
|
|
196
169
|
vaultAddress: "service|GALA$Unit$none$none$launchpad",
|
|
@@ -234,9 +207,9 @@ describe("buyWithNative", () => {
|
|
|
234
207
|
|
|
235
208
|
const expectedResponse = plainToInstance(TradeResDto, {
|
|
236
209
|
inputQuantity: "1000",
|
|
237
|
-
totalFees: "320
|
|
238
|
-
totalTokenSold: "3663321.
|
|
239
|
-
outputQuantity: "3663321.
|
|
210
|
+
totalFees: "320",
|
|
211
|
+
totalTokenSold: "3663321.362813055",
|
|
212
|
+
outputQuantity: "3663321.362813055",
|
|
240
213
|
tokenName: "AUTOMATEDTESTCOIN",
|
|
241
214
|
tradeType: "Buy",
|
|
242
215
|
vaultAddress: "service|GALA$Unit$none$none$launchpad",
|
|
@@ -291,4 +264,91 @@ describe("buyWithNative", () => {
|
|
|
291
264
|
)
|
|
292
265
|
);
|
|
293
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
|
+
});
|
|
294
350
|
});
|
|
351
|
+
function roundToDecimal(value, decimals) {
|
|
352
|
+
const factor = Math.pow(10, decimals);
|
|
353
|
+
return Math.round(value * factor) / factor;
|
|
354
|
+
}
|