@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
|
@@ -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
|
});
|
|
@@ -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
|
});
|
|
@@ -16,10 +16,53 @@ import { GalaChainContext } from "@gala-chain/chaincode";
|
|
|
16
16
|
import BigNumber from "bignumber.js";
|
|
17
17
|
import Decimal from "decimal.js";
|
|
18
18
|
|
|
19
|
-
import { LaunchpadSale, NativeTokenQuantityDto } from "../../api/types";
|
|
20
|
-
import {
|
|
19
|
+
import { LaunchpadSale, NativeTokenQuantityDto, TradeCalculationResDto } from "../../api/types";
|
|
20
|
+
import {
|
|
21
|
+
fetchAndValidateSale,
|
|
22
|
+
fetchLaunchpadFeeAddress,
|
|
23
|
+
fetchTokenDecimals,
|
|
24
|
+
getBondingConstants
|
|
25
|
+
} from "../utils";
|
|
21
26
|
import { calculateTransactionFee } from "./fees";
|
|
22
27
|
|
|
28
|
+
function calculateTokensPurchasable(
|
|
29
|
+
nativeTokens: Decimal,
|
|
30
|
+
totalTokensSold: Decimal,
|
|
31
|
+
nativeTokenDecimals: number,
|
|
32
|
+
sellingTokenDecimals: number
|
|
33
|
+
): [string, string] {
|
|
34
|
+
const basePrice = new Decimal(LaunchpadSale.BASE_PRICE);
|
|
35
|
+
const { exponentFactor, euler, decimals } = getBondingConstants();
|
|
36
|
+
|
|
37
|
+
// Round native tokens, then calculate tokens based on that rounded amount
|
|
38
|
+
const roundedNativeTokens = nativeTokens.toDecimalPlaces(nativeTokenDecimals, Decimal.ROUND_UP);
|
|
39
|
+
|
|
40
|
+
// Calculate tokens purchasable: newTokens = (decimals / exponentFactor) * ln((nativeTokens * exponentFactor / basePrice) + e^(exponentFactor * totalTokensSold / decimals)) - totalTokensSold
|
|
41
|
+
// Where:
|
|
42
|
+
// constant = nativeTokens * exponentFactor / basePrice
|
|
43
|
+
// exponent1 = exponentFactor * totalTokensSold / decimals
|
|
44
|
+
// eResult1 = e^(exponent1) = e^(exponentFactor * totalTokensSold / decimals)
|
|
45
|
+
// ethScaled = constant + eResult1 = (nativeTokens * exponentFactor / basePrice) + e^(exponentFactor * totalTokensSold / decimals)
|
|
46
|
+
// lnEthScaled = ln(ethScaled) * decimals
|
|
47
|
+
// lnEthScaledBase = lnEthScaled / exponentFactor = (decimals / exponentFactor) * ln(ethScaled)
|
|
48
|
+
// result = lnEthScaledBase - totalTokensSold
|
|
49
|
+
const constant = roundedNativeTokens.mul(exponentFactor).div(basePrice);
|
|
50
|
+
const exponent1 = exponentFactor.mul(totalTokensSold).div(decimals);
|
|
51
|
+
const eResult1 = euler.pow(exponent1);
|
|
52
|
+
const ethScaled = constant.add(eResult1);
|
|
53
|
+
const lnEthScaled = ethScaled.ln().mul(decimals);
|
|
54
|
+
const lnEthScaledBase = lnEthScaled.div(exponentFactor);
|
|
55
|
+
const result = lnEthScaledBase.minus(totalTokensSold);
|
|
56
|
+
let roundedResult = result.toDecimalPlaces(sellingTokenDecimals, Decimal.ROUND_DOWN);
|
|
57
|
+
|
|
58
|
+
// Cap total supply to 10 million
|
|
59
|
+
if (roundedResult.add(totalTokensSold).greaterThan(new Decimal("1e+7"))) {
|
|
60
|
+
roundedResult = new Decimal("1e+7").minus(new Decimal(totalTokensSold));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return [roundedNativeTokens.toFixed(), roundedResult.toFixed()];
|
|
64
|
+
}
|
|
65
|
+
|
|
23
66
|
/**
|
|
24
67
|
* Calculates the number of tokens that can be purchased using a specified amount
|
|
25
68
|
* of native tokens based on a bonding curve mechanism.
|
|
@@ -31,12 +74,15 @@ import { calculateTransactionFee } from "./fees";
|
|
|
31
74
|
* @param buyTokenDTO - The data transfer object containing the sale address
|
|
32
75
|
* and the amount of native tokens to spend for the purchase.
|
|
33
76
|
*
|
|
34
|
-
* @returns A promise that resolves to a
|
|
35
|
-
*
|
|
77
|
+
* @returns A promise that resolves to a TradeCalculationResDto object containing the calculated
|
|
78
|
+
* quantity of tokens to be received, the original quantity of native tokens used, and extra fees.
|
|
36
79
|
*
|
|
37
80
|
* @throws Error if the calculation results in an invalid state.
|
|
38
81
|
*/
|
|
39
|
-
export async function callMemeTokenOut(
|
|
82
|
+
export async function callMemeTokenOut(
|
|
83
|
+
ctx: GalaChainContext,
|
|
84
|
+
buyTokenDTO: NativeTokenQuantityDto
|
|
85
|
+
): Promise<TradeCalculationResDto> {
|
|
40
86
|
// Convert input amount to Decimal
|
|
41
87
|
let nativeTokens = new Decimal(buyTokenDTO.nativeTokenQuantity.toString());
|
|
42
88
|
|
|
@@ -44,11 +90,13 @@ export async function callMemeTokenOut(ctx: GalaChainContext, buyTokenDTO: Nativ
|
|
|
44
90
|
let totalTokensSold = new Decimal(0);
|
|
45
91
|
|
|
46
92
|
// Fetch sale details and update parameters if this is not a sale premint calculation
|
|
93
|
+
let sale: LaunchpadSale | undefined;
|
|
47
94
|
if (!buyTokenDTO.IsPreMint) {
|
|
48
|
-
|
|
95
|
+
sale = await fetchAndValidateSale(ctx, buyTokenDTO.vaultAddress);
|
|
49
96
|
totalTokensSold = new Decimal(sale.fetchTokensSold());
|
|
50
97
|
|
|
51
|
-
// Enforce market cap limit
|
|
98
|
+
// Enforce market cap limit, adjust number of native tokens that will be used in the transaction
|
|
99
|
+
// if total GALA tokens will exceed the market cap
|
|
52
100
|
if (
|
|
53
101
|
nativeTokens
|
|
54
102
|
.add(new Decimal(sale.nativeTokenQuantity))
|
|
@@ -58,34 +106,32 @@ export async function callMemeTokenOut(ctx: GalaChainContext, buyTokenDTO: Nativ
|
|
|
58
106
|
}
|
|
59
107
|
}
|
|
60
108
|
|
|
61
|
-
//
|
|
62
|
-
const
|
|
63
|
-
|
|
109
|
+
// Get token decimals for rounding
|
|
110
|
+
const { nativeTokenDecimals, sellingTokenDecimals } = sale
|
|
111
|
+
? await fetchTokenDecimals(ctx, sale)
|
|
112
|
+
: {
|
|
113
|
+
nativeTokenDecimals: LaunchpadSale.NATIVE_TOKEN_DECIMALS,
|
|
114
|
+
sellingTokenDecimals: LaunchpadSale.SELLING_TOKEN_DECIMALS
|
|
115
|
+
};
|
|
64
116
|
|
|
65
|
-
//
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const result = lnEthScaledBase.minus(totalTokensSold);
|
|
73
|
-
let roundedResult = result.toDecimalPlaces(18, Decimal.ROUND_DOWN);
|
|
74
|
-
|
|
75
|
-
// Cap total supply to 10 million
|
|
76
|
-
if (roundedResult.add(totalTokensSold).greaterThan(new Decimal("1e+7"))) {
|
|
77
|
-
roundedResult = new Decimal("1e+7").minus(new Decimal(totalTokensSold));
|
|
78
|
-
}
|
|
117
|
+
// Calculate tokens purchasable using bonding curve math
|
|
118
|
+
const [originalQuantity, calculatedQuantity] = calculateTokensPurchasable(
|
|
119
|
+
nativeTokens,
|
|
120
|
+
totalTokensSold,
|
|
121
|
+
nativeTokenDecimals,
|
|
122
|
+
sellingTokenDecimals
|
|
123
|
+
);
|
|
79
124
|
|
|
80
125
|
// Fetch fee configuration and return result
|
|
81
126
|
const launchpadFeeAddressConfiguration = await fetchLaunchpadFeeAddress(ctx);
|
|
82
127
|
|
|
83
128
|
return {
|
|
84
|
-
|
|
129
|
+
originalQuantity: originalQuantity,
|
|
130
|
+
calculatedQuantity: calculatedQuantity,
|
|
85
131
|
extraFees: {
|
|
86
132
|
reverseBondingCurve: "0",
|
|
87
133
|
transactionFees: calculateTransactionFee(
|
|
88
|
-
BigNumber(
|
|
134
|
+
BigNumber(originalQuantity),
|
|
89
135
|
launchpadFeeAddressConfiguration?.feeAmount
|
|
90
136
|
)
|
|
91
137
|
}
|
|
@@ -16,10 +16,51 @@ import { GalaChainContext } from "@gala-chain/chaincode";
|
|
|
16
16
|
import BigNumber from "bignumber.js";
|
|
17
17
|
import Decimal from "decimal.js";
|
|
18
18
|
|
|
19
|
-
import { ExactTokenQuantityDto } from "../../api/types";
|
|
20
|
-
import {
|
|
19
|
+
import { ExactTokenQuantityDto, LaunchpadSale, TradeCalculationResDto } from "../../api/types";
|
|
20
|
+
import {
|
|
21
|
+
fetchAndValidateSale,
|
|
22
|
+
fetchLaunchpadFeeAddress,
|
|
23
|
+
fetchTokenDecimals,
|
|
24
|
+
getBondingConstants
|
|
25
|
+
} from "../utils";
|
|
21
26
|
import { calculateTransactionFee } from "./fees";
|
|
22
27
|
|
|
28
|
+
function calculateNativeTokensRequired(
|
|
29
|
+
tokensToBuy: Decimal,
|
|
30
|
+
totalTokensSold: Decimal,
|
|
31
|
+
sellingTokenDecimals: number,
|
|
32
|
+
nativeTokenDecimals: number
|
|
33
|
+
): [string, string] {
|
|
34
|
+
const basePrice = new Decimal(LaunchpadSale.BASE_PRICE);
|
|
35
|
+
const { exponentFactor, euler, decimals } = getBondingConstants();
|
|
36
|
+
|
|
37
|
+
// Round tokens first, then calculate native tokens based on that rounded amount
|
|
38
|
+
const roundedTokensToBuy = tokensToBuy.toDecimalPlaces(sellingTokenDecimals, Decimal.ROUND_DOWN);
|
|
39
|
+
|
|
40
|
+
// Calculate native tokens required: price = (basePrice / exponentFactor) * (e^(exponentFactor * (totalTokensSold + tokensToBuy) / decimals) - e^(exponentFactor * totalTokensSold / decimals))
|
|
41
|
+
// Where:
|
|
42
|
+
// exponent1 = exponentFactor * (totalTokensSold + tokensToBuy) / decimals
|
|
43
|
+
// exponent2 = exponentFactor * totalTokensSold / decimals
|
|
44
|
+
// eResult1 = e^(exponent1) = e^(exponentFactor * (totalTokensSold + tokensToBuy) / decimals)
|
|
45
|
+
// eResult2 = e^(exponent2) = e^(exponentFactor * totalTokensSold / decimals)
|
|
46
|
+
// constantFactor = basePrice / exponentFactor
|
|
47
|
+
// differenceOfExponentials = eResult1 - eResult2
|
|
48
|
+
// price = constantFactor * differenceOfExponentials
|
|
49
|
+
const exponent1 = exponentFactor.mul(totalTokensSold.add(roundedTokensToBuy)).div(decimals);
|
|
50
|
+
const exponent2 = exponentFactor.mul(totalTokensSold).div(decimals);
|
|
51
|
+
|
|
52
|
+
const eResult1 = euler.pow(exponent1);
|
|
53
|
+
const eResult2 = euler.pow(exponent2);
|
|
54
|
+
|
|
55
|
+
const constantFactor = basePrice.div(exponentFactor);
|
|
56
|
+
const differenceOfExponentials = eResult1.minus(eResult2);
|
|
57
|
+
|
|
58
|
+
const price = constantFactor.mul(differenceOfExponentials);
|
|
59
|
+
const roundedPrice = price.toDecimalPlaces(nativeTokenDecimals, Decimal.ROUND_UP);
|
|
60
|
+
|
|
61
|
+
return [roundedTokensToBuy.toFixed(), roundedPrice.toFixed()];
|
|
62
|
+
}
|
|
63
|
+
|
|
23
64
|
/**
|
|
24
65
|
* Calculates the amount of native tokens required to purchase a specified amount
|
|
25
66
|
* of tokens using a bonding curve mechanism.
|
|
@@ -31,43 +72,45 @@ import { calculateTransactionFee } from "./fees";
|
|
|
31
72
|
* @param buyTokenDTO - The data transfer object containing the sale address
|
|
32
73
|
* and the exact amount of tokens to be purchased.
|
|
33
74
|
*
|
|
34
|
-
* @returns A promise that resolves to a
|
|
35
|
-
*
|
|
75
|
+
* @returns A promise that resolves to a TradeCalculationResDto object containing the calculated
|
|
76
|
+
* quantity of native tokens required for the purchase, the original quantity of tokens used, and extra fees.
|
|
36
77
|
*
|
|
37
78
|
* @throws Error if the calculation encounters an invalid state or data.
|
|
38
79
|
*/
|
|
39
|
-
export async function callNativeTokenIn(
|
|
80
|
+
export async function callNativeTokenIn(
|
|
81
|
+
ctx: GalaChainContext,
|
|
82
|
+
buyTokenDTO: ExactTokenQuantityDto
|
|
83
|
+
): Promise<TradeCalculationResDto> {
|
|
40
84
|
const sale = await fetchAndValidateSale(ctx, buyTokenDTO.vaultAddress);
|
|
41
85
|
const totalTokensSold = new Decimal(sale.fetchTokensSold());
|
|
42
86
|
|
|
43
87
|
let tokensToBuy = new Decimal(buyTokenDTO.tokenQuantity.toString());
|
|
44
|
-
const basePrice = new Decimal(sale.fetchBasePrice());
|
|
45
|
-
const { exponentFactor, euler, decimals } = getBondingConstants();
|
|
46
88
|
|
|
89
|
+
// Adjust tokensToBuy if user is trying to buy more tokens than the total supply
|
|
47
90
|
if (tokensToBuy.add(totalTokensSold).greaterThan(new Decimal("1e+7"))) {
|
|
48
91
|
tokensToBuy = new Decimal(sale.sellingTokenQuantity);
|
|
49
92
|
}
|
|
50
93
|
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
const eResult1 = euler.pow(exponent1);
|
|
55
|
-
const eResult2 = euler.pow(exponent2);
|
|
56
|
-
|
|
57
|
-
const constantFactor = basePrice.div(exponentFactor);
|
|
58
|
-
const differenceOfExponentials = eResult1.minus(eResult2);
|
|
94
|
+
// Get token decimals for rounding
|
|
95
|
+
const { nativeTokenDecimals, sellingTokenDecimals } = await fetchTokenDecimals(ctx, sale);
|
|
59
96
|
|
|
60
|
-
|
|
97
|
+
// Calculate native tokens required using bonding curve math
|
|
98
|
+
const [originalQuantity, calculatedQuantity] = calculateNativeTokensRequired(
|
|
99
|
+
tokensToBuy,
|
|
100
|
+
totalTokensSold,
|
|
101
|
+
sellingTokenDecimals,
|
|
102
|
+
nativeTokenDecimals
|
|
103
|
+
);
|
|
61
104
|
|
|
62
105
|
const launchpadFeeAddressConfiguration = await fetchLaunchpadFeeAddress(ctx);
|
|
63
106
|
|
|
64
|
-
const roundedPrice = price.toDecimalPlaces(8, Decimal.ROUND_UP).toFixed();
|
|
65
107
|
return {
|
|
66
|
-
|
|
108
|
+
originalQuantity: originalQuantity,
|
|
109
|
+
calculatedQuantity: calculatedQuantity,
|
|
67
110
|
extraFees: {
|
|
68
111
|
reverseBondingCurve: "0",
|
|
69
112
|
transactionFees: calculateTransactionFee(
|
|
70
|
-
BigNumber(
|
|
113
|
+
BigNumber(calculatedQuantity),
|
|
71
114
|
launchpadFeeAddressConfiguration?.feeAmount
|
|
72
115
|
)
|
|
73
116
|
}
|
|
@@ -16,25 +16,38 @@ import { GalaChainContext } from "@gala-chain/chaincode";
|
|
|
16
16
|
import BigNumber from "bignumber.js";
|
|
17
17
|
import Decimal from "decimal.js";
|
|
18
18
|
|
|
19
|
-
import { ExactTokenQuantityDto, LaunchpadSale } from "../../api/types";
|
|
20
|
-
import {
|
|
19
|
+
import { ExactTokenQuantityDto, LaunchpadSale, TradeCalculationResDto } from "../../api/types";
|
|
20
|
+
import {
|
|
21
|
+
fetchAndValidateSale,
|
|
22
|
+
fetchLaunchpadFeeAddress,
|
|
23
|
+
fetchTokenDecimals,
|
|
24
|
+
getBondingConstants
|
|
25
|
+
} from "../utils";
|
|
21
26
|
import { calculateReverseBondingCurveFee, calculateTransactionFee } from "./fees";
|
|
22
27
|
|
|
23
|
-
function calculateNativeTokensReceived(
|
|
28
|
+
function calculateNativeTokensReceived(
|
|
29
|
+
sale: LaunchpadSale,
|
|
30
|
+
tokensToSellBn: BigNumber,
|
|
31
|
+
sellingTokenDecimals: number,
|
|
32
|
+
nativeTokenDecimals: number
|
|
33
|
+
): [string, string] {
|
|
24
34
|
const totalTokensSold = new Decimal(sale.fetchTokensSold());
|
|
25
35
|
|
|
26
36
|
let tokensToSell = new Decimal(tokensToSellBn.toString());
|
|
27
|
-
const basePrice = new Decimal(
|
|
37
|
+
const basePrice = new Decimal(LaunchpadSale.BASE_PRICE);
|
|
28
38
|
const { exponentFactor, euler, decimals } = getBondingConstants();
|
|
29
39
|
|
|
30
40
|
let newTotalTokensSold = totalTokensSold.minus(tokensToSell);
|
|
31
41
|
|
|
32
|
-
if (newTotalTokensSold.
|
|
42
|
+
if (newTotalTokensSold.lessThan(0)) {
|
|
33
43
|
tokensToSell = totalTokensSold;
|
|
34
44
|
newTotalTokensSold = new Decimal(0);
|
|
35
45
|
}
|
|
36
46
|
|
|
37
|
-
|
|
47
|
+
// Round tokens first, then calculate native tokens based on that rounded amount
|
|
48
|
+
const roundedTokensToSell = tokensToSell.toDecimalPlaces(sellingTokenDecimals, Decimal.ROUND_UP);
|
|
49
|
+
|
|
50
|
+
const exponent1 = exponentFactor.mul(newTotalTokensSold.add(roundedTokensToSell)).div(decimals);
|
|
38
51
|
const exponent2 = exponentFactor.mul(newTotalTokensSold).div(decimals);
|
|
39
52
|
|
|
40
53
|
const eResult1 = euler.pow(exponent1);
|
|
@@ -43,9 +56,9 @@ function calculateNativeTokensReceived(sale: LaunchpadSale, tokensToSellBn: BigN
|
|
|
43
56
|
const constantFactor = basePrice.div(exponentFactor);
|
|
44
57
|
const differenceOfExponentials = eResult1.minus(eResult2);
|
|
45
58
|
const price = constantFactor.mul(differenceOfExponentials);
|
|
46
|
-
const roundedPrice = price.toDecimalPlaces(
|
|
59
|
+
const roundedPrice = price.toDecimalPlaces(nativeTokenDecimals, Decimal.ROUND_DOWN);
|
|
47
60
|
|
|
48
|
-
return roundedPrice.toFixed();
|
|
61
|
+
return [roundedTokensToSell.toFixed(), roundedPrice.toFixed()];
|
|
49
62
|
}
|
|
50
63
|
|
|
51
64
|
/**
|
|
@@ -59,23 +72,33 @@ function calculateNativeTokensReceived(sale: LaunchpadSale, tokensToSellBn: BigN
|
|
|
59
72
|
* @param sellTokenDTO - The data transfer object containing the sale address
|
|
60
73
|
* and the exact amount of tokens to be sold.
|
|
61
74
|
*
|
|
62
|
-
* @returns A promise that resolves to a
|
|
63
|
-
*
|
|
75
|
+
* @returns A promise that resolves to a TradeCalculationResDto object containing the calculated
|
|
76
|
+
* quantity of native tokens received, the quantity of tokens required, and extra fees.
|
|
64
77
|
*
|
|
65
78
|
* @throws DefaultError if the calculated new total tokens sold is less than zero
|
|
66
79
|
* or if the input amount is invalid.
|
|
67
80
|
*/
|
|
68
|
-
export async function callNativeTokenOut(
|
|
81
|
+
export async function callNativeTokenOut(
|
|
82
|
+
ctx: GalaChainContext,
|
|
83
|
+
sellTokenDTO: ExactTokenQuantityDto
|
|
84
|
+
): Promise<TradeCalculationResDto> {
|
|
69
85
|
const sale = await fetchAndValidateSale(ctx, sellTokenDTO.vaultAddress);
|
|
70
|
-
const
|
|
86
|
+
const { nativeTokenDecimals, sellingTokenDecimals } = await fetchTokenDecimals(ctx, sale);
|
|
87
|
+
const [originalQuantity, calculatedQuantity] = calculateNativeTokensReceived(
|
|
88
|
+
sale,
|
|
89
|
+
sellTokenDTO.tokenQuantity,
|
|
90
|
+
sellingTokenDecimals,
|
|
91
|
+
nativeTokenDecimals
|
|
92
|
+
);
|
|
71
93
|
const launchpadFeeAddressConfiguration = await fetchLaunchpadFeeAddress(ctx);
|
|
72
94
|
|
|
73
95
|
return {
|
|
74
|
-
|
|
96
|
+
originalQuantity: originalQuantity,
|
|
97
|
+
calculatedQuantity: calculatedQuantity,
|
|
75
98
|
extraFees: {
|
|
76
|
-
reverseBondingCurve: calculateReverseBondingCurveFee(sale, BigNumber(
|
|
99
|
+
reverseBondingCurve: calculateReverseBondingCurveFee(sale, BigNumber(calculatedQuantity)).toString(),
|
|
77
100
|
transactionFees: calculateTransactionFee(
|
|
78
|
-
BigNumber(
|
|
101
|
+
BigNumber(calculatedQuantity),
|
|
79
102
|
launchpadFeeAddressConfiguration?.feeAmount
|
|
80
103
|
)
|
|
81
104
|
}
|
|
@@ -155,7 +155,6 @@ describe("configureLaunchpadFeeAddress", () => {
|
|
|
155
155
|
|
|
156
156
|
// Update with zero fee amount
|
|
157
157
|
const configDto = new ConfigureLaunchpadFeeAddressDto();
|
|
158
|
-
configDto.newPlatformFeeAddress = asValidUserAlias("client|feeAddress"); // Keep same address
|
|
159
158
|
configDto.newFeeAmount = 0;
|
|
160
159
|
configDto.uniqueKey = randomUniqueKey();
|
|
161
160
|
const signedDto = configDto.signed(users.admin.privateKey);
|
|
@@ -35,7 +35,7 @@ export async function configureLaunchpadFeeAddress(
|
|
|
35
35
|
dto: ConfigureLaunchpadFeeAddressDto
|
|
36
36
|
): Promise<LaunchpadFeeConfig> {
|
|
37
37
|
// Validate input: at least one field must be present
|
|
38
|
-
if (!dto.newPlatformFeeAddress && !dto.newAuthorities?.length &&
|
|
38
|
+
if (!dto.newPlatformFeeAddress && !dto.newAuthorities?.length && dto.newFeeAmount === undefined) {
|
|
39
39
|
throw new ValidationFailedError("None of the input fields are present.");
|
|
40
40
|
}
|
|
41
41
|
|