@gala-chain/launchpad 1.0.12 → 1.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/package.json +1 -1
- package/lib/src/api/types/LaunchpadDtos.d.ts +1 -0
- package/lib/src/api/types/LaunchpadDtos.d.ts.map +1 -1
- package/lib/src/api/types/LaunchpadDtos.js +5 -0
- package/lib/src/api/types/LaunchpadDtos.js.map +1 -1
- package/lib/src/api/types/LaunchpadSale.d.ts +2 -1
- package/lib/src/api/types/LaunchpadSale.d.ts.map +1 -1
- package/lib/src/api/types/LaunchpadSale.js +18 -6
- 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.js +1 -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 +9 -6
- 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 +10 -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 +4 -0
- package/src/api/types/LaunchpadSale.ts +14 -7
- package/src/chaincode/LaunchpadContract.ts +3 -3
- package/src/chaincode/launchpad/buyExactToken.spec.ts +2 -37
- package/src/chaincode/launchpad/buyExactToken.ts +22 -56
- package/src/chaincode/launchpad/buyWithNative.spec.ts +2 -37
- package/src/chaincode/launchpad/buyWithNative.ts +15 -65
- package/src/chaincode/launchpad/callMemeTokenIn.ts +36 -12
- package/src/chaincode/launchpad/callMemeTokenOut.spec.ts +1 -1
- 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 +1 -1
- package/src/chaincode/launchpad/fees.ts +55 -3
- package/src/chaincode/launchpad/finaliseSale.ts +11 -8
- 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 +13 -1
- package/src/cli.ts +3 -1
package/package.json
CHANGED
|
@@ -49,12 +49,14 @@ export class LaunchpadSale extends ChainObject {
|
|
|
49
49
|
@IsNotEmpty()
|
|
50
50
|
public saleStatus: SaleStatus;
|
|
51
51
|
|
|
52
|
-
@Type(() => TokenInstanceKey)
|
|
53
52
|
@IsNotEmpty()
|
|
53
|
+
@ValidateNested()
|
|
54
|
+
@Type(() => TokenInstanceKey)
|
|
54
55
|
public sellingToken: TokenInstanceKey;
|
|
55
56
|
|
|
56
|
-
@Type(() => TokenInstanceKey)
|
|
57
57
|
@IsNotEmpty()
|
|
58
|
+
@ValidateNested()
|
|
59
|
+
@Type(() => TokenInstanceKey)
|
|
58
60
|
public nativeToken: TokenInstanceKey;
|
|
59
61
|
|
|
60
62
|
@IsString()
|
|
@@ -99,6 +101,16 @@ export class LaunchpadSale extends ChainObject {
|
|
|
99
101
|
})
|
|
100
102
|
public static BASE_PRICE = "16506671506650";
|
|
101
103
|
|
|
104
|
+
@JSONSchema({
|
|
105
|
+
description: "The decimals of the selling token."
|
|
106
|
+
})
|
|
107
|
+
public static SELLING_TOKEN_DECIMALS = 18;
|
|
108
|
+
|
|
109
|
+
@JSONSchema({
|
|
110
|
+
description: "The decimals of the native token."
|
|
111
|
+
})
|
|
112
|
+
public static NATIVE_TOKEN_DECIMALS = 8;
|
|
113
|
+
|
|
102
114
|
constructor(
|
|
103
115
|
vaultAddress: UserAlias,
|
|
104
116
|
sellingToken: TokenInstanceKey,
|
|
@@ -165,11 +177,6 @@ export class LaunchpadSale extends ChainObject {
|
|
|
165
177
|
return nativeTokenInVault.toString();
|
|
166
178
|
}
|
|
167
179
|
|
|
168
|
-
public fetchBasePrice() {
|
|
169
|
-
const basePriceBigNumber = new BigNumber(this.basePrice);
|
|
170
|
-
return basePriceBigNumber.toString();
|
|
171
|
-
}
|
|
172
|
-
|
|
173
180
|
public finalizeSale() {
|
|
174
181
|
this.saleStatus = SaleStatus.END;
|
|
175
182
|
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
|
|
|
@@ -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,
|
|
@@ -130,41 +130,6 @@ describe("buyWithNative", () => {
|
|
|
130
130
|
);
|
|
131
131
|
});
|
|
132
132
|
|
|
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
133
|
test("User buys tokens by providing native gala, without fee needing to be configured", async () => {
|
|
169
134
|
//Given
|
|
170
135
|
const { ctx, contract } = fixture(LaunchpadContract)
|
|
@@ -188,7 +153,7 @@ describe("buyWithNative", () => {
|
|
|
188
153
|
|
|
189
154
|
const expectedResponse = plainToInstance(TradeResDto, {
|
|
190
155
|
inputQuantity: "150",
|
|
191
|
-
totalFees: "0
|
|
156
|
+
totalFees: "0",
|
|
192
157
|
totalTokenSold: "2101667.8890651635",
|
|
193
158
|
outputQuantity: "2101667.8890651635",
|
|
194
159
|
tokenName: "AUTOMATEDTESTCOIN",
|
|
@@ -234,7 +199,7 @@ describe("buyWithNative", () => {
|
|
|
234
199
|
|
|
235
200
|
const expectedResponse = plainToInstance(TradeResDto, {
|
|
236
201
|
inputQuantity: "1000",
|
|
237
|
-
totalFees: "320
|
|
202
|
+
totalFees: "320",
|
|
238
203
|
totalTokenSold: "3663321.3628130557",
|
|
239
204
|
outputQuantity: "3663321.3628130557",
|
|
240
205
|
tokenName: "AUTOMATEDTESTCOIN",
|
|
@@ -12,22 +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
|
-
getObjectByKey,
|
|
21
|
-
putChainObject,
|
|
22
|
-
transferToken
|
|
23
|
-
} from "@gala-chain/chaincode";
|
|
15
|
+
import { GalaChainContext, fetchTokenClass, putChainObject, transferToken } from "@gala-chain/chaincode";
|
|
24
16
|
import BigNumber from "bignumber.js";
|
|
25
17
|
|
|
26
|
-
import {
|
|
18
|
+
import { LaunchpadSale, NativeTokenQuantityDto, TradeResDto } from "../../api/types";
|
|
27
19
|
import { SlippageToleranceExceededError } from "../../api/utils/error";
|
|
28
|
-
import { fetchAndValidateSale
|
|
20
|
+
import { fetchAndValidateSale } from "../utils";
|
|
29
21
|
import { callMemeTokenOut } from "./callMemeTokenOut";
|
|
30
|
-
import {
|
|
22
|
+
import { transferTransactionFees } from "./fees";
|
|
31
23
|
import { finalizeSale } from "./finaliseSale";
|
|
32
24
|
|
|
33
25
|
/**
|
|
@@ -56,83 +48,41 @@ export async function buyWithNative(
|
|
|
56
48
|
|
|
57
49
|
// Fetch and validate sale state
|
|
58
50
|
const sale = await fetchAndValidateSale(ctx, buyTokenDTO.vaultAddress);
|
|
59
|
-
const tokensLeftInVault = new BigNumber(sale.sellingTokenQuantity);
|
|
60
51
|
|
|
61
52
|
// Calculate how many tokens the user can buy and fee info
|
|
62
53
|
const callMemeTokenOutResult = await callMemeTokenOut(ctx, buyTokenDTO);
|
|
63
|
-
|
|
64
|
-
|
|
54
|
+
const transactionFees = new BigNumber(callMemeTokenOutResult.extraFees.transactionFees); // transaction fees
|
|
55
|
+
const nativeTokensRequired = new BigNumber(callMemeTokenOutResult.originalQuantity); // number of native tokens user wants to spend
|
|
56
|
+
const tokensToBuy = new BigNumber(callMemeTokenOutResult.calculatedQuantity); // number of tokens user will be buying
|
|
65
57
|
|
|
66
58
|
const nativeToken = sale.fetchNativeTokenInstanceKey();
|
|
67
59
|
const memeToken = sale.fetchSellingTokenInstanceKey();
|
|
68
60
|
|
|
69
|
-
//
|
|
70
|
-
// because otherwise `transferToken()` call below will fail with
|
|
71
|
-
// an INVALID_DECIMALS error.
|
|
72
|
-
const { collection, category, type, additionalKey } = sale.sellingToken;
|
|
73
|
-
|
|
74
|
-
const memeTokenClass = await getObjectByKey(
|
|
75
|
-
ctx,
|
|
76
|
-
TokenClass,
|
|
77
|
-
TokenClass.getCompositeKeyFromParts(TokenClass.INDEX_KEY, [collection, category, type, additionalKey])
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
tokensToBuy = tokensToBuy.decimalPlaces(memeTokenClass.decimals);
|
|
81
|
-
|
|
82
|
-
// If vault has fewer tokens than what user wants to buy, cap the purchase
|
|
83
|
-
if (tokensLeftInVault.comparedTo(tokensToBuy) <= 0) {
|
|
84
|
-
tokensToBuy = tokensLeftInVault.decimalPlaces(memeTokenClass.decimals);
|
|
85
|
-
const nativeTokensRequiredToBuyDto = new ExactTokenQuantityDto(buyTokenDTO.vaultAddress, tokensToBuy);
|
|
86
|
-
const callNativeTokenInResult = await callNativeTokenIn(ctx, nativeTokensRequiredToBuyDto);
|
|
87
|
-
transactionFees = callMemeTokenOutResult.extraFees.transactionFees;
|
|
88
|
-
buyTokenDTO.nativeTokenQuantity = new BigNumber(callNativeTokenInResult.calculatedQuantity);
|
|
89
|
-
isSaleFinalized = true;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Finalize sale if market cap is reached
|
|
61
|
+
// If native tokens required exceeds the market cap, the sale can be finalized
|
|
93
62
|
if (
|
|
94
63
|
buyTokenDTO.nativeTokenQuantity
|
|
95
64
|
.plus(new BigNumber(sale.nativeTokenQuantity))
|
|
96
|
-
.
|
|
65
|
+
.isGreaterThanOrEqualTo(new BigNumber(LaunchpadSale.MARKET_CAP))
|
|
97
66
|
) {
|
|
98
67
|
isSaleFinalized = true;
|
|
99
68
|
}
|
|
100
69
|
|
|
101
70
|
// Check for slippage condition
|
|
102
|
-
if (buyTokenDTO.expectedToken && buyTokenDTO.expectedToken.
|
|
71
|
+
if (buyTokenDTO.expectedToken && buyTokenDTO.expectedToken.isGreaterThan(tokensToBuy)) {
|
|
103
72
|
throw new SlippageToleranceExceededError(
|
|
104
73
|
`expected ${buyTokenDTO.expectedToken.toString()}, but only ${tokensToBuy.toString()} tokens can be provided. Reduce the expected amount or adjust your slippage tolerance.`
|
|
105
74
|
);
|
|
106
75
|
}
|
|
107
76
|
|
|
108
77
|
// Transfer transaction fees to launchpad fee address
|
|
109
|
-
|
|
110
|
-
if (launchpadFeeAddressConfiguration && transactionFees) {
|
|
111
|
-
const totalRequired = new BigNumber(buyTokenDTO.nativeTokenQuantity).plus(transactionFees);
|
|
112
|
-
|
|
113
|
-
const buyerBalance = await fetchOrCreateBalance(ctx, ctx.callingUser, sale.nativeToken);
|
|
114
|
-
if (buyerBalance.getQuantityTotal().lt(totalRequired)) {
|
|
115
|
-
throw new ValidationFailedError(
|
|
116
|
-
`Insufficient balance: Total amount required including fee is ${totalRequired}`
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
await transferToken(ctx, {
|
|
121
|
-
from: ctx.callingUser,
|
|
122
|
-
to: launchpadFeeAddressConfiguration.feeAddress,
|
|
123
|
-
tokenInstanceKey: nativeToken,
|
|
124
|
-
quantity: new BigNumber(transactionFees),
|
|
125
|
-
allowancesToUse: [],
|
|
126
|
-
authorizedOnBehalf: undefined
|
|
127
|
-
});
|
|
128
|
-
}
|
|
78
|
+
await transferTransactionFees(ctx, sale, transactionFees, nativeToken, nativeTokensRequired);
|
|
129
79
|
|
|
130
80
|
// Transfer native tokens from buyer to vault
|
|
131
81
|
await transferToken(ctx, {
|
|
132
82
|
from: ctx.callingUser,
|
|
133
83
|
to: buyTokenDTO.vaultAddress,
|
|
134
84
|
tokenInstanceKey: nativeToken,
|
|
135
|
-
quantity:
|
|
85
|
+
quantity: nativeTokensRequired,
|
|
136
86
|
allowancesToUse: [],
|
|
137
87
|
authorizedOnBehalf: undefined
|
|
138
88
|
});
|
|
@@ -151,7 +101,7 @@ export async function buyWithNative(
|
|
|
151
101
|
});
|
|
152
102
|
|
|
153
103
|
// Update sale object with purchase data
|
|
154
|
-
sale.buyToken(tokensToBuy,
|
|
104
|
+
sale.buyToken(tokensToBuy, nativeTokensRequired);
|
|
155
105
|
await putChainObject(ctx, sale);
|
|
156
106
|
|
|
157
107
|
// Finalize sale if it's complete
|
|
@@ -161,8 +111,8 @@ export async function buyWithNative(
|
|
|
161
111
|
|
|
162
112
|
const token = await fetchTokenClass(ctx, sale.sellingToken);
|
|
163
113
|
return {
|
|
164
|
-
inputQuantity:
|
|
165
|
-
totalFees: transactionFees,
|
|
114
|
+
inputQuantity: nativeTokensRequired.toFixed(),
|
|
115
|
+
totalFees: transactionFees.toFixed(),
|
|
166
116
|
outputQuantity: tokensToBuy.toFixed(),
|
|
167
117
|
tokenName: token.name,
|
|
168
118
|
tradeType: "Buy",
|
|
@@ -17,14 +17,27 @@ import { GalaChainContext } from "@gala-chain/chaincode";
|
|
|
17
17
|
import BigNumber from "bignumber.js";
|
|
18
18
|
import Decimal from "decimal.js";
|
|
19
19
|
|
|
20
|
-
import { LaunchpadSale, NativeTokenQuantityDto } from "../../api/types";
|
|
21
|
-
import {
|
|
20
|
+
import { LaunchpadSale, NativeTokenQuantityDto, TradeCalculationResDto } from "../../api/types";
|
|
21
|
+
import {
|
|
22
|
+
fetchAndValidateSale,
|
|
23
|
+
fetchLaunchpadFeeAddress,
|
|
24
|
+
fetchTokenDecimals,
|
|
25
|
+
getBondingConstants
|
|
26
|
+
} from "../utils";
|
|
22
27
|
import { calculateReverseBondingCurveFee, calculateTransactionFee } from "./fees";
|
|
23
28
|
|
|
24
|
-
function calculateMemeTokensRequired(
|
|
29
|
+
function calculateMemeTokensRequired(
|
|
30
|
+
sale: LaunchpadSale,
|
|
31
|
+
requestedNativeTokenQuantity: BigNumber,
|
|
32
|
+
nativeTokenDecimals: number,
|
|
33
|
+
sellingTokenDecimals: number
|
|
34
|
+
): [string, string] {
|
|
25
35
|
const totalTokensSold = new Decimal(sale.fetchTokensSold()); // current tokens sold / x
|
|
26
|
-
let nativeTokens = new Decimal(requestedNativeTokenQuantity.toString())
|
|
27
|
-
|
|
36
|
+
let nativeTokens = new Decimal(requestedNativeTokenQuantity.toString()).toDecimalPlaces(
|
|
37
|
+
nativeTokenDecimals,
|
|
38
|
+
Decimal.ROUND_DOWN
|
|
39
|
+
);
|
|
40
|
+
const basePrice = new Decimal(LaunchpadSale.BASE_PRICE); // base price / a
|
|
28
41
|
const { exponentFactor, euler, decimals } = getBondingConstants();
|
|
29
42
|
|
|
30
43
|
const nativeTokenInVault = new Decimal(sale.nativeTokenQuantity);
|
|
@@ -36,16 +49,16 @@ function calculateMemeTokensRequired(sale: LaunchpadSale, requestedNativeTokenQu
|
|
|
36
49
|
const exp1 = euler.pow(exponent);
|
|
37
50
|
const constantFactor = nativeTokens.mul(exponentFactor).div(basePrice);
|
|
38
51
|
|
|
39
|
-
if (exp1.
|
|
52
|
+
if (exp1.lessThanOrEqualTo(constantFactor)) {
|
|
40
53
|
throw new ValidationFailedError("Cannot sell more tokens than have been bought in this sale.");
|
|
41
54
|
}
|
|
42
55
|
|
|
43
56
|
const adjustedExp = exp1.minus(constantFactor);
|
|
44
57
|
const lnAdjustedExp = adjustedExp.ln();
|
|
45
58
|
const tokensSent = totalTokensSold.minus(lnAdjustedExp.mul(decimals).div(exponentFactor));
|
|
46
|
-
const roundedTokenSent = tokensSent.toDecimalPlaces(
|
|
59
|
+
const roundedTokenSent = tokensSent.toDecimalPlaces(sellingTokenDecimals, Decimal.ROUND_UP);
|
|
47
60
|
|
|
48
|
-
return roundedTokenSent.toFixed();
|
|
61
|
+
return [nativeTokens.toFixed(), roundedTokenSent.toFixed()];
|
|
49
62
|
}
|
|
50
63
|
|
|
51
64
|
/**
|
|
@@ -61,17 +74,28 @@ function calculateMemeTokensRequired(sale: LaunchpadSale, requestedNativeTokenQu
|
|
|
61
74
|
* - `nativeTokenAmount`: The amount of native tokens to be recieved from sale.
|
|
62
75
|
* - `expectedToken` (optional): The expected amount of tokens to be sold.
|
|
63
76
|
*
|
|
64
|
-
* @returns A promise that resolves to a
|
|
65
|
-
*
|
|
77
|
+
* @returns A promise that resolves to a TradeCalculationResDto object containing the calculated
|
|
78
|
+
* quantity of tokens to be sent, the original quantity of native tokens used, and extra fees.
|
|
66
79
|
*
|
|
67
80
|
* @throws Error if the calculation results in an invalid amount (e.g., `InvalidAmountError`).
|
|
68
81
|
*/
|
|
69
|
-
export async function callMemeTokenIn(
|
|
82
|
+
export async function callMemeTokenIn(
|
|
83
|
+
ctx: GalaChainContext,
|
|
84
|
+
sellTokenDTO: NativeTokenQuantityDto
|
|
85
|
+
): Promise<TradeCalculationResDto> {
|
|
70
86
|
const sale = await fetchAndValidateSale(ctx, sellTokenDTO.vaultAddress);
|
|
71
87
|
const launchpadFeeAddressConfiguration = await fetchLaunchpadFeeAddress(ctx);
|
|
88
|
+
const { nativeTokenDecimals, sellingTokenDecimals } = await fetchTokenDecimals(ctx, sale);
|
|
89
|
+
const [originalQuantity, calculatedQuantity] = calculateMemeTokensRequired(
|
|
90
|
+
sale,
|
|
91
|
+
sellTokenDTO.nativeTokenQuantity,
|
|
92
|
+
nativeTokenDecimals,
|
|
93
|
+
sellingTokenDecimals
|
|
94
|
+
);
|
|
72
95
|
|
|
73
96
|
return {
|
|
74
|
-
|
|
97
|
+
originalQuantity: originalQuantity,
|
|
98
|
+
calculatedQuantity: calculatedQuantity,
|
|
75
99
|
extraFees: {
|
|
76
100
|
reverseBondingCurve: calculateReverseBondingCurveFee(sale, sellTokenDTO.nativeTokenQuantity).toString(),
|
|
77
101
|
transactionFees: calculateTransactionFee(
|
|
@@ -111,7 +111,7 @@ describe("callMemeTokenOut", () => {
|
|
|
111
111
|
|
|
112
112
|
// // Then
|
|
113
113
|
expect(response.Data).toMatchObject({
|
|
114
|
-
calculatedQuantity: "58497.
|
|
114
|
+
calculatedQuantity: "58497.5300046064",
|
|
115
115
|
extraFees: { reverseBondingCurve: "0", transactionFees: "0.00000000" }
|
|
116
116
|
});
|
|
117
117
|
});
|