@symmetry-hq/sdk 1.0.1
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/dist/src/constants.d.ts +23 -0
- package/dist/src/constants.js +38 -0
- package/dist/src/index.d.ts +804 -0
- package/dist/src/index.js +2097 -0
- package/dist/src/instructions/automation/auction.d.ts +6 -0
- package/dist/src/instructions/automation/auction.js +40 -0
- package/dist/src/instructions/automation/claimBounty.d.ts +12 -0
- package/dist/src/instructions/automation/claimBounty.js +44 -0
- package/dist/src/instructions/automation/flashSwap.d.ts +21 -0
- package/dist/src/instructions/automation/flashSwap.js +74 -0
- package/dist/src/instructions/automation/priceUpdate.d.ts +19 -0
- package/dist/src/instructions/automation/priceUpdate.js +89 -0
- package/dist/src/instructions/automation/rebalanceIntent.d.ts +32 -0
- package/dist/src/instructions/automation/rebalanceIntent.js +117 -0
- package/dist/src/instructions/automation/rebalanceSwap.d.ts +11 -0
- package/dist/src/instructions/automation/rebalanceSwap.js +42 -0
- package/dist/src/instructions/management/addBounty.d.ts +7 -0
- package/dist/src/instructions/management/addBounty.js +41 -0
- package/dist/src/instructions/management/admin.d.ts +9 -0
- package/dist/src/instructions/management/admin.js +53 -0
- package/dist/src/instructions/management/claimFees.d.ts +15 -0
- package/dist/src/instructions/management/claimFees.js +95 -0
- package/dist/src/instructions/management/createBasket.d.ts +21 -0
- package/dist/src/instructions/management/createBasket.js +98 -0
- package/dist/src/instructions/management/edit.d.ts +51 -0
- package/dist/src/instructions/management/edit.js +477 -0
- package/dist/src/instructions/management/luts.d.ts +30 -0
- package/dist/src/instructions/management/luts.js +99 -0
- package/dist/src/instructions/pda.d.ts +25 -0
- package/dist/src/instructions/pda.js +128 -0
- package/dist/src/instructions/user/deposit.d.ts +20 -0
- package/dist/src/instructions/user/deposit.js +100 -0
- package/dist/src/instructions/user/withdraw.d.ts +8 -0
- package/dist/src/instructions/user/withdraw.js +36 -0
- package/dist/src/jup.d.ts +49 -0
- package/dist/src/jup.js +80 -0
- package/dist/src/keeperMonitor.d.ts +52 -0
- package/dist/src/keeperMonitor.js +624 -0
- package/dist/src/layouts/basket.d.ts +191 -0
- package/dist/src/layouts/basket.js +51 -0
- package/dist/src/layouts/config.d.ts +281 -0
- package/dist/src/layouts/config.js +237 -0
- package/dist/src/layouts/fraction.d.ts +20 -0
- package/dist/src/layouts/fraction.js +164 -0
- package/dist/src/layouts/intents/bounty.d.ts +18 -0
- package/dist/src/layouts/intents/bounty.js +19 -0
- package/dist/src/layouts/intents/intent.d.ts +209 -0
- package/dist/src/layouts/intents/intent.js +97 -0
- package/dist/src/layouts/intents/rebalanceIntent.d.ts +212 -0
- package/dist/src/layouts/intents/rebalanceIntent.js +94 -0
- package/dist/src/layouts/lookupTable.d.ts +7 -0
- package/dist/src/layouts/lookupTable.js +10 -0
- package/dist/src/layouts/oracle.d.ts +63 -0
- package/dist/src/layouts/oracle.js +96 -0
- package/dist/src/states/basket.d.ts +14 -0
- package/dist/src/states/basket.js +479 -0
- package/dist/src/states/config.d.ts +3 -0
- package/dist/src/states/config.js +71 -0
- package/dist/src/states/intents/intent.d.ts +10 -0
- package/dist/src/states/intents/intent.js +316 -0
- package/dist/src/states/intents/rebalanceIntent.d.ts +42 -0
- package/dist/src/states/intents/rebalanceIntent.js +680 -0
- package/dist/src/states/oracles/constants.d.ts +9 -0
- package/dist/src/states/oracles/constants.js +15 -0
- package/dist/src/states/oracles/oracle.d.ts +24 -0
- package/dist/src/states/oracles/oracle.js +168 -0
- package/dist/src/states/oracles/pythOracle.d.ts +132 -0
- package/dist/src/states/oracles/pythOracle.js +609 -0
- package/dist/src/states/oracles/raydiumClmmOracle.d.ts +184 -0
- package/dist/src/states/oracles/raydiumClmmOracle.js +843 -0
- package/dist/src/states/oracles/raydiumCpmmOracle.d.ts +120 -0
- package/dist/src/states/oracles/raydiumCpmmOracle.js +540 -0
- package/dist/src/states/oracles/switchboardOracle.d.ts +0 -0
- package/dist/src/states/oracles/switchboardOracle.js +1 -0
- package/dist/src/states/withdrawBasketFees.d.ts +10 -0
- package/dist/src/states/withdrawBasketFees.js +154 -0
- package/dist/src/txUtils.d.ts +65 -0
- package/dist/src/txUtils.js +306 -0
- package/dist/test.d.ts +1 -0
- package/dist/test.js +561 -0
- package/package.json +31 -0
- package/src/constants.ts +40 -0
- package/src/index.ts +2431 -0
- package/src/instructions/automation/auction.ts +55 -0
- package/src/instructions/automation/claimBounty.ts +69 -0
- package/src/instructions/automation/flashSwap.ts +104 -0
- package/src/instructions/automation/priceUpdate.ts +117 -0
- package/src/instructions/automation/rebalanceIntent.ts +181 -0
- package/src/instructions/management/addBounty.ts +55 -0
- package/src/instructions/management/admin.ts +72 -0
- package/src/instructions/management/claimFees.ts +129 -0
- package/src/instructions/management/createBasket.ts +138 -0
- package/src/instructions/management/edit.ts +602 -0
- package/src/instructions/management/luts.ts +157 -0
- package/src/instructions/pda.ts +151 -0
- package/src/instructions/user/deposit.ts +143 -0
- package/src/instructions/user/withdraw.ts +53 -0
- package/src/jup.ts +113 -0
- package/src/keeperMonitor.ts +585 -0
- package/src/layouts/basket.ts +233 -0
- package/src/layouts/config.ts +576 -0
- package/src/layouts/fraction.ts +164 -0
- package/src/layouts/intents/bounty.ts +35 -0
- package/src/layouts/intents/intent.ts +324 -0
- package/src/layouts/intents/rebalanceIntent.ts +306 -0
- package/src/layouts/lookupTable.ts +14 -0
- package/src/layouts/oracle.ts +157 -0
- package/src/states/basket.ts +527 -0
- package/src/states/config.ts +62 -0
- package/src/states/intents/intent.ts +311 -0
- package/src/states/intents/rebalanceIntent.ts +751 -0
- package/src/states/oracles/constants.ts +13 -0
- package/src/states/oracles/oracle.ts +212 -0
- package/src/states/oracles/pythOracle.ts +874 -0
- package/src/states/oracles/raydiumClmmOracle.ts +1193 -0
- package/src/states/oracles/raydiumCpmmOracle.ts +784 -0
- package/src/states/oracles/switchboardOracle.ts +0 -0
- package/src/states/withdrawBasketFees.ts +160 -0
- package/src/txUtils.ts +424 -0
- package/test.ts +609 -0
- package/tsconfig.json +101 -0
|
@@ -0,0 +1,751 @@
|
|
|
1
|
+
import { Connection, GetProgramAccountsResponse, PublicKey } from "@solana/web3.js";
|
|
2
|
+
import { AuctionData, AuctionTimestamps, ClaimBountyData, DepositData, FormattedRebalanceIntent, MintData, PriceUpdatesData, REBALANCE_ACTION_STRINGS, REBALANCE_TYPE_STRINGS, RebalanceAction, RebalanceIntent, RebalanceIntentLayout, RebalanceType, RedeemData, TokenAuction, UIRebalanceIntent } from "../../layouts/intents/rebalanceIntent";
|
|
3
|
+
import { GetProgramAccountsFilter } from "@solana/web3.js";
|
|
4
|
+
import { BASKETS_V3_PROGRAM_ID, HUNDRED_PERCENT_BPS, MAX_SUPPORTED_TOKENS_PER_BASKET, MAX_TRANSFER_TOKENS } from "../../constants";
|
|
5
|
+
import { Fraction, fractionAdd, fractionDiv, fractionLt, fractionLte, fractionMul, fractionRoundDown, fractionRoundUp, fractionSub, fractionToDecimal } from "../../layouts/fraction";
|
|
6
|
+
import { getMultipleAccountsInfoBatched } from "../../txUtils";
|
|
7
|
+
import BN from "bn.js";
|
|
8
|
+
import { Basket, FormattedBasket } from "../../layouts/basket";
|
|
9
|
+
import { OraclePriceOnChain } from "../../layouts/oracle";
|
|
10
|
+
import { fetchBasket } from "../basket";
|
|
11
|
+
|
|
12
|
+
export function computeRebalanceIntentBountyAmount(
|
|
13
|
+
rebalance_type: RebalanceType,
|
|
14
|
+
num_tokens: number,
|
|
15
|
+
bounty_bond: number,
|
|
16
|
+
bounty_per_task: number,
|
|
17
|
+
bounty_per_price_update_task_max: number,
|
|
18
|
+
): number {
|
|
19
|
+
let num_tasks = 0;
|
|
20
|
+
num_tasks += 1; // FinishPriceUpdates;
|
|
21
|
+
num_tasks += 1; // CancelRebalance;
|
|
22
|
+
if (rebalance_type == RebalanceType.Deposit) {
|
|
23
|
+
num_tasks += 1; // MintBasket;
|
|
24
|
+
}
|
|
25
|
+
if (rebalance_type == RebalanceType.Deposit || rebalance_type == RebalanceType.Withdraw) {
|
|
26
|
+
num_tasks += num_tokens; // TokenSettlement;
|
|
27
|
+
} else {
|
|
28
|
+
num_tasks += 1; // AuctionCreation;
|
|
29
|
+
}
|
|
30
|
+
let claim_bounty_tasks = (
|
|
31
|
+
num_tasks + MAX_TRANSFER_TOKENS - 1
|
|
32
|
+
) / MAX_TRANSFER_TOKENS;
|
|
33
|
+
|
|
34
|
+
let price_update_tasks = num_tokens;
|
|
35
|
+
let tasks = num_tasks + claim_bounty_tasks;
|
|
36
|
+
|
|
37
|
+
let bounty_total =
|
|
38
|
+
bounty_per_price_update_task_max * price_update_tasks +
|
|
39
|
+
bounty_per_task * tasks +
|
|
40
|
+
bounty_bond;
|
|
41
|
+
|
|
42
|
+
return bounty_total;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function formatRebalanceIntent(rebalanceIntent: RebalanceIntent, basket?: FormattedBasket): UIRebalanceIntent {
|
|
46
|
+
let numTokens = MAX_SUPPORTED_TOKENS_PER_BASKET;
|
|
47
|
+
while (numTokens > 0 && rebalanceIntent.tokens[numTokens - 1].mint.equals(PublicKey.default))
|
|
48
|
+
numTokens--;
|
|
49
|
+
rebalanceIntent.tokens = rebalanceIntent.tokens.slice(0, numTokens);
|
|
50
|
+
rebalanceIntent.priceUpdateTasks = rebalanceIntent.priceUpdateTasks.slice(0, numTokens);
|
|
51
|
+
rebalanceIntent.tokenSettlementTasks = rebalanceIntent.tokenSettlementTasks.slice(0, numTokens);
|
|
52
|
+
let priceUpdateTasks = [];
|
|
53
|
+
for (let i = 0; i < numTokens; i++) {
|
|
54
|
+
priceUpdateTasks.push({
|
|
55
|
+
completed_by: rebalanceIntent.priceUpdateTasks[i].completedBy.toBase58(),
|
|
56
|
+
completed_bounty: parseInt(rebalanceIntent.priceUpdateTasks[i].completedBounty.toString()),
|
|
57
|
+
completed_time: parseInt(rebalanceIntent.priceUpdateTasks[i].completedTime.toString()),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
let tokenSettlementTasks = [];
|
|
61
|
+
for (let i = 0; i < numTokens; i++) {
|
|
62
|
+
tokenSettlementTasks.push({
|
|
63
|
+
completed_by: rebalanceIntent.tokenSettlementTasks[i].completedBy.toBase58(),
|
|
64
|
+
completed_bounty: parseInt(rebalanceIntent.tokenSettlementTasks[i].completedBounty.toString()),
|
|
65
|
+
completed_time: parseInt(rebalanceIntent.tokenSettlementTasks[i].completedTime.toString()),
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
let placeholderTask = {
|
|
69
|
+
completed_by: rebalanceIntent.placeholderTask.completedBy.toBase58(),
|
|
70
|
+
completed_bounty: parseInt(rebalanceIntent.placeholderTask.completedBounty.toString()),
|
|
71
|
+
completed_time: parseInt(rebalanceIntent.placeholderTask.completedTime.toString()),
|
|
72
|
+
};
|
|
73
|
+
let finishPriceUpdateTask = {
|
|
74
|
+
completed_by: rebalanceIntent.finishPriceUpdateTask.completedBy.toBase58(),
|
|
75
|
+
completed_bounty: parseInt(rebalanceIntent.finishPriceUpdateTask.completedBounty.toString()),
|
|
76
|
+
completed_time: parseInt(rebalanceIntent.finishPriceUpdateTask.completedTime.toString()),
|
|
77
|
+
};
|
|
78
|
+
let auctionCreationTask = {
|
|
79
|
+
completed_by: rebalanceIntent.auctionCreationTask.completedBy.toBase58(),
|
|
80
|
+
completed_bounty: parseInt(rebalanceIntent.auctionCreationTask.completedBounty.toString()),
|
|
81
|
+
completed_time: parseInt(rebalanceIntent.auctionCreationTask.completedTime.toString()),
|
|
82
|
+
};
|
|
83
|
+
let mintBasketTask = {
|
|
84
|
+
completed_by: rebalanceIntent.mintBasketTask.completedBy.toBase58(),
|
|
85
|
+
completed_bounty: parseInt(rebalanceIntent.mintBasketTask.completedBounty.toString()),
|
|
86
|
+
completed_time: parseInt(rebalanceIntent.mintBasketTask.completedTime.toString()),
|
|
87
|
+
};
|
|
88
|
+
let cancelRebalanceTask = {
|
|
89
|
+
completed_by: rebalanceIntent.cancelRebalanceTask.completedBy.toBase58(),
|
|
90
|
+
completed_bounty: parseInt(rebalanceIntent.cancelRebalanceTask.completedBounty.toString()),
|
|
91
|
+
completed_time: parseInt(rebalanceIntent.cancelRebalanceTask.completedTime.toString()),
|
|
92
|
+
};
|
|
93
|
+
let tasks = [
|
|
94
|
+
placeholderTask,
|
|
95
|
+
finishPriceUpdateTask,
|
|
96
|
+
auctionCreationTask,
|
|
97
|
+
mintBasketTask,
|
|
98
|
+
cancelRebalanceTask,
|
|
99
|
+
...tokenSettlementTasks,
|
|
100
|
+
...priceUpdateTasks,
|
|
101
|
+
]
|
|
102
|
+
let depositData: DepositData = {
|
|
103
|
+
tokens: rebalanceIntent.tokens.map(token => ({
|
|
104
|
+
mint: token.mint.toBase58(),
|
|
105
|
+
amount: parseInt(token.amount.toString()),
|
|
106
|
+
})),
|
|
107
|
+
}
|
|
108
|
+
let priceUpdatesData: PriceUpdatesData = {
|
|
109
|
+
tokens: rebalanceIntent.priceUpdateTasks.map((task, index) => ({
|
|
110
|
+
mint: rebalanceIntent.tokens[index].mint.toBase58(),
|
|
111
|
+
updated: parseInt(task.completedTime.toString()) > 0 ? true : false,
|
|
112
|
+
price: fractionToDecimal(rebalanceIntent.tokens[index].price.price).toNumber(),
|
|
113
|
+
conf: fractionToDecimal(rebalanceIntent.tokens[index].price.conf).toNumber(),
|
|
114
|
+
expiration: parseInt(task.completedTime.toString()) + 60,
|
|
115
|
+
update_time: parseInt(task.completedTime.toString()),
|
|
116
|
+
})),
|
|
117
|
+
}
|
|
118
|
+
let auctionData: AuctionData = {
|
|
119
|
+
auction_stages: rebalanceIntent.auctions.map(auction => ({
|
|
120
|
+
start_time: parseInt(auction.startTime.toString()),
|
|
121
|
+
end_time: parseInt(auction.endTime.toString()),
|
|
122
|
+
})),
|
|
123
|
+
tokens: rebalanceIntent.tokens.map(token => ({
|
|
124
|
+
mint: token.mint.toBase58(),
|
|
125
|
+
amount: parseInt(token.amount.toString()),
|
|
126
|
+
target_amount: parseInt(token.targetAmount.toString()),
|
|
127
|
+
price: fractionToDecimal(token.price.price).toNumber(),
|
|
128
|
+
conf: fractionToDecimal(token.price.conf).toNumber(),
|
|
129
|
+
})),
|
|
130
|
+
}
|
|
131
|
+
let mintData: MintData = {
|
|
132
|
+
mintAmount: 0,
|
|
133
|
+
mintValue: 0,
|
|
134
|
+
fees: {
|
|
135
|
+
host: 0,
|
|
136
|
+
creator: 0,
|
|
137
|
+
managers: 0,
|
|
138
|
+
symmetry: 0,
|
|
139
|
+
basket: 0,
|
|
140
|
+
},
|
|
141
|
+
tokens: auctionData.tokens.map(token => ({
|
|
142
|
+
mint: token.mint,
|
|
143
|
+
contribution_amount: 0,
|
|
144
|
+
remaining_amount: 0,
|
|
145
|
+
})),
|
|
146
|
+
}
|
|
147
|
+
if (basket) {
|
|
148
|
+
let minRatio = 1;
|
|
149
|
+
for (let i = 0; i < basket.composition.length; i++) {
|
|
150
|
+
if (basket.composition[i].amount == 0)
|
|
151
|
+
continue;
|
|
152
|
+
let ratio = auctionData.tokens[i].amount / basket.composition[i].amount;
|
|
153
|
+
minRatio = Math.min(minRatio, ratio);
|
|
154
|
+
}
|
|
155
|
+
let mintValue = 0;
|
|
156
|
+
let remainingValue = 0;
|
|
157
|
+
let mintAmount = Math.floor(basket.supply_outstanding * minRatio);
|
|
158
|
+
for (let i = 0; i < basket.composition.length; i++) {
|
|
159
|
+
let contributionAmount = Math.floor(basket.composition[i].amount * minRatio);
|
|
160
|
+
contributionAmount = Math.min(contributionAmount, auctionData.tokens[i].amount);
|
|
161
|
+
mintData.tokens[i].contribution_amount = contributionAmount;
|
|
162
|
+
mintData.tokens[i].remaining_amount = auctionData.tokens[i].amount - contributionAmount;
|
|
163
|
+
mintValue += contributionAmount * auctionData.tokens[i].price;
|
|
164
|
+
remainingValue += (auctionData.tokens[i].amount - contributionAmount) * auctionData.tokens[i].price;
|
|
165
|
+
}
|
|
166
|
+
mintData.mintValue = mintValue;
|
|
167
|
+
mintData.mintAmount = mintAmount;
|
|
168
|
+
mintData.fees.host = Math.floor(mintValue * rebalanceIntent.basketFeeSettings.hostDepositFeeBps / HUNDRED_PERCENT_BPS);
|
|
169
|
+
mintData.fees.creator = Math.floor(mintValue * rebalanceIntent.basketFeeSettings.creatorDepositFeeBps / HUNDRED_PERCENT_BPS);
|
|
170
|
+
mintData.fees.managers = Math.floor(mintValue * rebalanceIntent.basketFeeSettings.managersDepositFeeBps / HUNDRED_PERCENT_BPS);
|
|
171
|
+
mintData.fees.basket = Math.floor(mintValue * rebalanceIntent.basketFeeSettings.basketDepositFeeBps / HUNDRED_PERCENT_BPS);
|
|
172
|
+
mintData.fees.symmetry = Math.floor(mintValue * 10 / HUNDRED_PERCENT_BPS);
|
|
173
|
+
mintData.mintAmount -= mintData.fees.host + mintData.fees.creator + mintData.fees.managers + mintData.fees.symmetry + mintData.fees.basket;
|
|
174
|
+
}
|
|
175
|
+
let redeemData: RedeemData = {
|
|
176
|
+
tokens: rebalanceIntent.tokens.map(token => ({
|
|
177
|
+
mint: token.mint.toBase58(),
|
|
178
|
+
amount: parseInt(token.amount.toString()),
|
|
179
|
+
})).filter(token => token.amount > 0),
|
|
180
|
+
}
|
|
181
|
+
let bountyData: ClaimBountyData = {
|
|
182
|
+
bounty_mint: rebalanceIntent.bounty.bountyMint.toBase58(),
|
|
183
|
+
keepers: [],
|
|
184
|
+
unused_bounty: {
|
|
185
|
+
pubkey: rebalanceIntent.bounty.bountyDepositor.toBase58(),
|
|
186
|
+
bounty_amount: parseInt(rebalanceIntent.bounty.bountyLeft.toString()),
|
|
187
|
+
},
|
|
188
|
+
rent: {
|
|
189
|
+
pubkey: rebalanceIntent.rentPayer.toBase58(),
|
|
190
|
+
sol_amount: rebalanceIntent.solBalance ?? 0,
|
|
191
|
+
},
|
|
192
|
+
}
|
|
193
|
+
for (let i = 0; i < tasks.length; i++) {
|
|
194
|
+
if (tasks[i].completed_bounty > 0) {
|
|
195
|
+
let indexOf = bountyData.keepers.findIndex(keeper => keeper.pubkey == tasks[i].completed_by);
|
|
196
|
+
if (indexOf == -1) {
|
|
197
|
+
bountyData.keepers.push({
|
|
198
|
+
pubkey: tasks[i].completed_by,
|
|
199
|
+
bounty_amount: tasks[i].completed_bounty,
|
|
200
|
+
});
|
|
201
|
+
bountyData.unused_bounty.bounty_amount -= tasks[i].completed_bounty;
|
|
202
|
+
} else {
|
|
203
|
+
bountyData.keepers[indexOf].bounty_amount += tasks[i].completed_bounty;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
let formatted: FormattedRebalanceIntent = {
|
|
208
|
+
pubkey: rebalanceIntent.ownAddress!.toBase58(),
|
|
209
|
+
sol_balance: rebalanceIntent.solBalance ?? 0,
|
|
210
|
+
basket: rebalanceIntent.basket.toBase58(),
|
|
211
|
+
owner: rebalanceIntent.owner.toBase58(),
|
|
212
|
+
rent_payer: rebalanceIntent.rentPayer.toBase58(),
|
|
213
|
+
rebalance_type: REBALANCE_TYPE_STRINGS.get(rebalanceIntent.rebalanceType) ?? "basket_custom",
|
|
214
|
+
current_action: REBALANCE_ACTION_STRINGS.get(rebalanceIntent.currentAction) ?? "not_active",
|
|
215
|
+
withdraw_params_burn_amount: parseInt(rebalanceIntent.withdrawParamsBurnAmount.toString()),
|
|
216
|
+
withdraw_params_amount_wo_fees: parseInt(rebalanceIntent.withdrawParamsAmountWoFees.toString()),
|
|
217
|
+
withdraw_params_keep_tokens_bitmask: rebalanceIntent.withdrawParamsKeepTokensBitmask,
|
|
218
|
+
withdraw_params_keep_all_tokens: rebalanceIntent.withdrawParamsKeepAllTokens == 1 ? true : false,
|
|
219
|
+
basket_fee_settings: {
|
|
220
|
+
host_deposit_fee_bps: rebalanceIntent.basketFeeSettings.hostDepositFeeBps,
|
|
221
|
+
host_withdraw_fee_bps: rebalanceIntent.basketFeeSettings.hostWithdrawFeeBps,
|
|
222
|
+
host_management_fee_bps: rebalanceIntent.basketFeeSettings.hostManagementFeeBps,
|
|
223
|
+
host_performance_fee_bps: rebalanceIntent.basketFeeSettings.hostPerformanceFeeBps,
|
|
224
|
+
creator_deposit_fee_bps: rebalanceIntent.basketFeeSettings.creatorDepositFeeBps,
|
|
225
|
+
creator_withdraw_fee_bps: rebalanceIntent.basketFeeSettings.creatorWithdrawFeeBps,
|
|
226
|
+
creator_management_fee_bps: rebalanceIntent.basketFeeSettings.creatorManagementFeeBps,
|
|
227
|
+
creator_performance_fee_bps: rebalanceIntent.basketFeeSettings.creatorPerformanceFeeBps,
|
|
228
|
+
managers_deposit_fee_bps: rebalanceIntent.basketFeeSettings.managersDepositFeeBps,
|
|
229
|
+
managers_withdraw_fee_bps: rebalanceIntent.basketFeeSettings.managersWithdrawFeeBps,
|
|
230
|
+
managers_management_fee_bps: rebalanceIntent.basketFeeSettings.managersManagementFeeBps,
|
|
231
|
+
managers_performance_fee_bps: rebalanceIntent.basketFeeSettings.managersPerformanceFeeBps,
|
|
232
|
+
basket_deposit_fee_bps: rebalanceIntent.basketFeeSettings.basketDepositFeeBps,
|
|
233
|
+
basket_withdraw_fee_bps: rebalanceIntent.basketFeeSettings.basketWithdrawFeeBps,
|
|
234
|
+
modification_delay: parseInt(rebalanceIntent.basketFeeSettings.modificationDelay.toString()),
|
|
235
|
+
updated_at: 0,
|
|
236
|
+
},
|
|
237
|
+
execution_start_time: parseInt(rebalanceIntent.executionStartTime.toString()),
|
|
238
|
+
rebalance_threshold_slippage_bps: rebalanceIntent.rebalanceThresholdSlippageBps,
|
|
239
|
+
per_trade_rebalance_threshold_slippage_bps: rebalanceIntent.perTradeRebalanceThresholdSlippageBps,
|
|
240
|
+
initial_tvl: fractionToDecimal(rebalanceIntent.initialTvl).toNumber(),
|
|
241
|
+
auction_update_timestamp: parseInt(rebalanceIntent.auctionUpdateTimestamp.toString()),
|
|
242
|
+
auctions: rebalanceIntent.auctions.map(auction => ({
|
|
243
|
+
start_time: parseInt(auction.startTime.toString()),
|
|
244
|
+
end_time: parseInt(auction.endTime.toString()),
|
|
245
|
+
})),
|
|
246
|
+
tokens: rebalanceIntent.tokens.map(token => ({
|
|
247
|
+
mint: token.mint.toBase58(),
|
|
248
|
+
amount: parseInt(token.amount.toString()),
|
|
249
|
+
target_amount: parseInt(token.targetAmount.toString()),
|
|
250
|
+
price: {
|
|
251
|
+
price: fractionToDecimal(token.price.price).toNumber(),
|
|
252
|
+
conf: fractionToDecimal(token.price.conf).toNumber(),
|
|
253
|
+
update_time: parseInt(token.price.updateTime.toString()),
|
|
254
|
+
},
|
|
255
|
+
keep_token: token.keepToken == 1 ? true : false,
|
|
256
|
+
})),
|
|
257
|
+
last_action_timestamp: parseInt(rebalanceIntent.lastActionTimestamp.toString()),
|
|
258
|
+
bounty: {
|
|
259
|
+
bounty_depositor: rebalanceIntent.bounty.bountyDepositor.toBase58(),
|
|
260
|
+
bounty_mint: rebalanceIntent.bounty.bountyMint.toBase58(),
|
|
261
|
+
bounty_per_price_update_task: {
|
|
262
|
+
min_bounty: parseInt(rebalanceIntent.bounty.bountyPerPriceUpdateTask.minBounty.toString()),
|
|
263
|
+
max_bounty: parseInt(rebalanceIntent.bounty.bountyPerPriceUpdateTask.maxBounty.toString()),
|
|
264
|
+
min_bounty_until: parseInt(rebalanceIntent.bounty.bountyPerPriceUpdateTask.minBountyUntil.toString()),
|
|
265
|
+
max_bounty_after: parseInt(rebalanceIntent.bounty.bountyPerPriceUpdateTask.maxBountyAfter.toString()),
|
|
266
|
+
},
|
|
267
|
+
bounty_per_task: {
|
|
268
|
+
min_bounty: parseInt(rebalanceIntent.bounty.bountyPerTask.minBounty.toString()),
|
|
269
|
+
max_bounty: parseInt(rebalanceIntent.bounty.bountyPerTask.maxBounty.toString()),
|
|
270
|
+
min_bounty_until: parseInt(rebalanceIntent.bounty.bountyPerTask.minBountyUntil.toString()),
|
|
271
|
+
max_bounty_after: parseInt(rebalanceIntent.bounty.bountyPerTask.maxBountyAfter.toString()),
|
|
272
|
+
},
|
|
273
|
+
bounty_total: parseInt(rebalanceIntent.bounty.bountyTotal.toString()),
|
|
274
|
+
bounty_left: parseInt(rebalanceIntent.bounty.bountyLeft.toString()),
|
|
275
|
+
},
|
|
276
|
+
bounty_adjustment_amount: parseInt(rebalanceIntent.bountyAdjustmentAmount.toString()),
|
|
277
|
+
placeholder_task: placeholderTask,
|
|
278
|
+
price_update_tasks: priceUpdateTasks,
|
|
279
|
+
finish_price_update_task: finishPriceUpdateTask,
|
|
280
|
+
auction_creation_task: auctionCreationTask,
|
|
281
|
+
mint_basket_task: mintBasketTask,
|
|
282
|
+
cancel_rebalance_task: cancelRebalanceTask,
|
|
283
|
+
token_settlement_tasks: tokenSettlementTasks,
|
|
284
|
+
};
|
|
285
|
+
let uiRebalanceIntent: UIRebalanceIntent = {
|
|
286
|
+
rebalance_type: formatted.mint_basket_task.completed_time > 0 ? "deposit" : formatted.rebalance_type,
|
|
287
|
+
|
|
288
|
+
formatted_data: formatted,
|
|
289
|
+
chain_data: rebalanceIntent,
|
|
290
|
+
deposit_data: null,
|
|
291
|
+
price_updates_data: null,
|
|
292
|
+
auction_data: null,
|
|
293
|
+
mint_data: null,
|
|
294
|
+
redeem_data: null,
|
|
295
|
+
claim_bounty_data: null,
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (formatted.current_action == "deposit_tokens") {
|
|
299
|
+
uiRebalanceIntent.deposit_data = depositData;
|
|
300
|
+
}
|
|
301
|
+
if (formatted.current_action == "update_prices") {
|
|
302
|
+
uiRebalanceIntent.price_updates_data = priceUpdatesData;
|
|
303
|
+
}
|
|
304
|
+
if (formatted.current_action == "auction") {
|
|
305
|
+
let now = Math.floor(Date.now() / 1000);
|
|
306
|
+
if (now <= auctionData.auction_stages[2].end_time) {
|
|
307
|
+
uiRebalanceIntent.auction_data = auctionData;
|
|
308
|
+
} else {
|
|
309
|
+
if (formatted.rebalance_type == "deposit") {
|
|
310
|
+
uiRebalanceIntent.mint_data = mintData;
|
|
311
|
+
} else
|
|
312
|
+
if (formatted.rebalance_type == "withdraw" && redeemData.tokens.length > 0) {
|
|
313
|
+
uiRebalanceIntent.redeem_data = redeemData;
|
|
314
|
+
} else {
|
|
315
|
+
uiRebalanceIntent.claim_bounty_data = bountyData;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return uiRebalanceIntent;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export async function fetchRebalanceIntent(
|
|
324
|
+
connection: Connection,
|
|
325
|
+
rebalanceIntentAddress: PublicKey,
|
|
326
|
+
): Promise<UIRebalanceIntent> {
|
|
327
|
+
const rebalanceIntentAi = await connection.getAccountInfo(rebalanceIntentAddress);
|
|
328
|
+
if (!rebalanceIntentAi) throw new Error("Rebalance intent not found");
|
|
329
|
+
let rebalanceIntent: RebalanceIntent = RebalanceIntentLayout.decode(rebalanceIntentAi.data.slice(8));
|
|
330
|
+
rebalanceIntent.ownAddress = rebalanceIntentAddress;
|
|
331
|
+
rebalanceIntent.solBalance = rebalanceIntentAi.lamports ?? 0;
|
|
332
|
+
let basket = (await fetchBasket(connection, rebalanceIntent.basket)).formatted;
|
|
333
|
+
return formatRebalanceIntent(rebalanceIntent, basket);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
export async function fetchRebalanceIntentsMultiple(
|
|
337
|
+
connection: Connection,
|
|
338
|
+
rebalanceIntentAddresses: PublicKey[],
|
|
339
|
+
): Promise<Map<string, UIRebalanceIntent>> {
|
|
340
|
+
let multipleAccountsInfo = await getMultipleAccountsInfoBatched(connection, rebalanceIntentAddresses);
|
|
341
|
+
let rebalanceIntents: RebalanceIntent[] = rebalanceIntentAddresses.map(address => {
|
|
342
|
+
let ai = multipleAccountsInfo.get(address.toBase58());
|
|
343
|
+
if (!ai) return null;
|
|
344
|
+
return { ...RebalanceIntentLayout.decode(ai.data.slice(8)), ownAddress: address, solBalance: ai.lamports ?? 0 }
|
|
345
|
+
}).filter(rebalanceIntent => rebalanceIntent !== null);
|
|
346
|
+
let formattedRebalanceIntents: UIRebalanceIntent[] = rebalanceIntents.map(rebalanceIntent => formatRebalanceIntent(rebalanceIntent));
|
|
347
|
+
let rebalanceIntentsMap: Map<string, UIRebalanceIntent> = new Map();
|
|
348
|
+
for (let rebalanceIntent of formattedRebalanceIntents) rebalanceIntentsMap.set(rebalanceIntent.formatted_data.pubkey, rebalanceIntent);
|
|
349
|
+
return rebalanceIntentsMap;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export interface RebalanceIntentFilter {
|
|
353
|
+
type: "basket" | "owner";
|
|
354
|
+
pubkey: string;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
export async function fetchRebalanceIntents(
|
|
358
|
+
connection: Connection,
|
|
359
|
+
filter?: RebalanceIntentFilter,
|
|
360
|
+
): Promise<UIRebalanceIntent[]> {
|
|
361
|
+
let accountFilters: GetProgramAccountsFilter[] = [
|
|
362
|
+
{ dataSize: 8 + RebalanceIntentLayout.getSpan() },
|
|
363
|
+
];
|
|
364
|
+
if (filter?.type === "basket") {
|
|
365
|
+
accountFilters.push({ memcmp: {
|
|
366
|
+
offset: 8,
|
|
367
|
+
bytes: filter.pubkey
|
|
368
|
+
} });
|
|
369
|
+
} else if (filter?.type === "owner") {
|
|
370
|
+
accountFilters.push({ memcmp: {
|
|
371
|
+
offset: 8 + 32,
|
|
372
|
+
bytes: filter.pubkey
|
|
373
|
+
} });
|
|
374
|
+
}
|
|
375
|
+
const accounts: GetProgramAccountsResponse = await connection
|
|
376
|
+
.getProgramAccounts(
|
|
377
|
+
BASKETS_V3_PROGRAM_ID,
|
|
378
|
+
{
|
|
379
|
+
commitment: "confirmed",
|
|
380
|
+
filters: accountFilters,
|
|
381
|
+
encoding: 'base64'
|
|
382
|
+
}
|
|
383
|
+
);
|
|
384
|
+
let basket: FormattedBasket | undefined;
|
|
385
|
+
if (filter && filter.type === "basket") {
|
|
386
|
+
basket = (await fetchBasket(connection, new PublicKey(filter.pubkey))).formatted;
|
|
387
|
+
}
|
|
388
|
+
let rebalanceIntents: UIRebalanceIntent[] = accounts.map(account => {
|
|
389
|
+
let rebalanceIntent: RebalanceIntent = RebalanceIntentLayout.decode(account.account.data.slice(8));
|
|
390
|
+
rebalanceIntent.ownAddress = account.pubkey;
|
|
391
|
+
rebalanceIntent.solBalance = account.account.lamports ?? 0;
|
|
392
|
+
return formatRebalanceIntent(rebalanceIntent, basket);
|
|
393
|
+
});
|
|
394
|
+
return rebalanceIntents;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
class RebalanceIntentRustClass {
|
|
399
|
+
self: RebalanceIntent;
|
|
400
|
+
|
|
401
|
+
constructor(rebalanceIntent: RebalanceIntent) {
|
|
402
|
+
this.self = rebalanceIntent;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
findTokenIndex(mint: PublicKey): number | undefined {
|
|
406
|
+
for (let i = 0; i < this.self.priceUpdateTasks.length; i++)
|
|
407
|
+
if (this.self.tokens[i].mint.equals(mint)) return i;
|
|
408
|
+
return undefined;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
getSelfTvl(): Fraction {
|
|
412
|
+
let selfTvl = { high: new BN(0), low: new BN(0) };
|
|
413
|
+
for (let i = 0; i < this.self.priceUpdateTasks.length; i++) {
|
|
414
|
+
if (this.self.priceUpdateTasks[i].completedTime.isZero()) {
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
const tokenAuction = this.self.tokens[i];
|
|
418
|
+
const tokenPrice = tokenAuction.price; // contains .price (mid)
|
|
419
|
+
if (!tokenPrice || !tokenAuction.amount) continue;
|
|
420
|
+
// price * amount
|
|
421
|
+
const tokenValue = fractionMul(tokenPrice.price, { high: new BN(tokenAuction.amount), low: new BN(0) });
|
|
422
|
+
selfTvl = fractionAdd(selfTvl, tokenValue);
|
|
423
|
+
}
|
|
424
|
+
return selfTvl;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
getBasketTvlAndWeightSum(basket: Basket): { basketTvl: Fraction, weightSum: number } {
|
|
428
|
+
let basketTvl = { high: new BN(0), low: new BN(0) };
|
|
429
|
+
let weightSum = 0;
|
|
430
|
+
for (let i = 0; i < basket.numTokens; i++) {
|
|
431
|
+
const basketToken = basket.composition[i];
|
|
432
|
+
weightSum += basketToken.weight;
|
|
433
|
+
if (basketToken.amount.isZero()) {
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
const tokenIndex = this.findTokenIndex(basketToken.mint);
|
|
437
|
+
if (tokenIndex === undefined) throw new Error("TokenNotFound");
|
|
438
|
+
if (this.self.priceUpdateTasks[tokenIndex].completedTime.isZero()) throw new Error("PriceUpdateNotCompleted");
|
|
439
|
+
const tokenPrice = this.self.tokens[tokenIndex].price;
|
|
440
|
+
// price * amount
|
|
441
|
+
const tokenValue = fractionMul(tokenPrice.price, { high: new BN(basketToken.amount), low: new BN(0) });
|
|
442
|
+
basketTvl = fractionAdd(basketTvl, tokenValue);
|
|
443
|
+
}
|
|
444
|
+
return { basketTvl, weightSum };
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
private getPriceChange(price: OraclePriceOnChain, timeSinceAuctionStart: BN, auctionDuration: BN): Fraction {
|
|
448
|
+
// price_change = conf * 2 * time_since_auction_start / auction_duration
|
|
449
|
+
if (auctionDuration.isZero()) {
|
|
450
|
+
throw new Error("AuctionNotLive");
|
|
451
|
+
}
|
|
452
|
+
// conf * 2
|
|
453
|
+
const confTimes2 = fractionMul(price.conf, { high: new BN(2), low: new BN(0) });
|
|
454
|
+
// conf * 2 * time_since_auction_start
|
|
455
|
+
const confTimes2TimesTime = fractionMul(confTimes2, { high: new BN(timeSinceAuctionStart), low: new BN(0) });
|
|
456
|
+
// (conf * 2 * time_since_auction_start) / auction_duration
|
|
457
|
+
const auctionDurationFraction = { high: new BN(auctionDuration), low: new BN(0) };
|
|
458
|
+
return fractionDiv(confTimes2TimesTime, auctionDurationFraction);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
private getSellPrice(price: OraclePriceOnChain, timeSinceAuctionStart: BN, auctionDuration: BN): Fraction {
|
|
462
|
+
// sell_price = high() - get_price_change()
|
|
463
|
+
// high() = price + conf
|
|
464
|
+
const high = fractionAdd(price.price, price.conf);
|
|
465
|
+
const priceChange = this.getPriceChange(price, timeSinceAuctionStart, auctionDuration);
|
|
466
|
+
return fractionSub(high, priceChange);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
private getBuyPrice(price: OraclePriceOnChain, timeSinceAuctionStart: BN, auctionDuration: BN): Fraction {
|
|
470
|
+
// buy_price = low() + get_price_change()
|
|
471
|
+
// low() = price - conf
|
|
472
|
+
const low = fractionSub(price.price, price.conf);
|
|
473
|
+
const priceChange = this.getPriceChange(price, timeSinceAuctionStart, auctionDuration);
|
|
474
|
+
return fractionAdd(low, priceChange);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
private getExchangeRate(
|
|
478
|
+
outToken: TokenAuction,
|
|
479
|
+
inToken: TokenAuction,
|
|
480
|
+
timeSinceAuctionStart: BN,
|
|
481
|
+
auctionDuration: BN,
|
|
482
|
+
): { amountToSell: BN, amountToBuy: BN, exchangeValue: Fraction } {
|
|
483
|
+
const outAmount = outToken.amount;
|
|
484
|
+
const outTargetAmount = outToken.targetAmount;
|
|
485
|
+
const inAmount = inToken.amount;
|
|
486
|
+
const inTargetAmount = inToken.targetAmount;
|
|
487
|
+
|
|
488
|
+
if (outAmount.lte(outTargetAmount)) throw new Error("InvalidSwap");
|
|
489
|
+
if (inAmount.gte(inTargetAmount)) throw new Error("InvalidSwap");
|
|
490
|
+
|
|
491
|
+
const maxAmountToSell: BN = outAmount.sub(outTargetAmount);
|
|
492
|
+
const maxAmountToBuy: BN = inTargetAmount.sub(inAmount);
|
|
493
|
+
|
|
494
|
+
const sellRate: Fraction = this.getSellPrice(outToken.price, timeSinceAuctionStart, auctionDuration);
|
|
495
|
+
const buyRate: Fraction = this.getBuyPrice(inToken.price, timeSinceAuctionStart, auctionDuration);
|
|
496
|
+
|
|
497
|
+
// sellValue = sellRate * maxAmountToSell
|
|
498
|
+
const sellValue: Fraction = fractionMul(sellRate, { high: new BN(maxAmountToSell), low: new BN(0) });
|
|
499
|
+
// buyValue = buyRate * maxAmountToBuy
|
|
500
|
+
const buyValue: Fraction = fractionMul(buyRate, { high: new BN(maxAmountToBuy), low: new BN(0) });
|
|
501
|
+
|
|
502
|
+
let sellSideLimits: boolean;
|
|
503
|
+
if (fractionLte(sellValue, buyValue)) {
|
|
504
|
+
sellSideLimits = true;
|
|
505
|
+
} else if (!outTargetAmount.isZero()) {
|
|
506
|
+
sellSideLimits = false;
|
|
507
|
+
} else {
|
|
508
|
+
// adjustedSellValue = sellValue * (HUNDRED_PERCENT_BPS - 100) / HUNDRED_PERCENT_BPS
|
|
509
|
+
const adjustmentFactor = fractionDiv(
|
|
510
|
+
{ high: new BN(HUNDRED_PERCENT_BPS - 100), low: new BN(0) },
|
|
511
|
+
{ high: new BN(HUNDRED_PERCENT_BPS), low: new BN(0) }
|
|
512
|
+
);
|
|
513
|
+
const adjustedSellValue = fractionMul(sellValue, adjustmentFactor);
|
|
514
|
+
sellSideLimits = fractionLte(adjustedSellValue, buyValue);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
if (sellSideLimits) {
|
|
518
|
+
// amountToBuy = sellValue / buyRate (rounded up)
|
|
519
|
+
const amountToBuy = fractionRoundUp(fractionDiv(sellValue, buyRate));
|
|
520
|
+
return {
|
|
521
|
+
amountToSell: maxAmountToSell,
|
|
522
|
+
amountToBuy: amountToBuy,
|
|
523
|
+
exchangeValue: sellValue,
|
|
524
|
+
};
|
|
525
|
+
} else {
|
|
526
|
+
// amountToSell = buyValue / sellRate (rounded down)
|
|
527
|
+
const amountToSell = fractionRoundDown(fractionDiv(buyValue, sellRate));
|
|
528
|
+
return {
|
|
529
|
+
amountToSell: amountToSell,
|
|
530
|
+
amountToBuy: maxAmountToBuy,
|
|
531
|
+
exchangeValue: buyValue,
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
getSwapAmounts(
|
|
538
|
+
inTokenIndex: number,
|
|
539
|
+
outTokenIndex: number,
|
|
540
|
+
timeSinceAuctionStart: BN,
|
|
541
|
+
auctionDuration: BN,
|
|
542
|
+
): { amountToSell: BN, amountToBuy: BN, exchangeValue: Fraction } {
|
|
543
|
+
if (inTokenIndex === outTokenIndex) throw new Error("InvalidSwap");
|
|
544
|
+
if (this.self.priceUpdateTasks[inTokenIndex].completedTime.isZero()) throw new Error("PriceUpdateNotCompleted");
|
|
545
|
+
if (this.self.priceUpdateTasks[outTokenIndex].completedTime.isZero()) throw new Error("PriceUpdateNotCompleted");
|
|
546
|
+
const inToken = this.self.tokens[inTokenIndex];
|
|
547
|
+
const outToken = this.self.tokens[outTokenIndex];
|
|
548
|
+
|
|
549
|
+
const exchangeRate = this.getExchangeRate(
|
|
550
|
+
outToken,
|
|
551
|
+
inToken,
|
|
552
|
+
timeSinceAuctionStart,
|
|
553
|
+
auctionDuration,
|
|
554
|
+
);
|
|
555
|
+
|
|
556
|
+
let basketBuys = exchangeRate.amountToBuy;
|
|
557
|
+
let basketSells = exchangeRate.amountToSell;
|
|
558
|
+
|
|
559
|
+
// inValue = inTokenPrice * basketBuys
|
|
560
|
+
const inValue = fractionMul(inToken.price.price, { high: new BN(basketBuys), low: new BN(0) });
|
|
561
|
+
// outValue = outTokenPrice * basketSells
|
|
562
|
+
const outValue = fractionMul(outToken.price.price, { high: new BN(basketSells), low: new BN(0) });
|
|
563
|
+
|
|
564
|
+
// slippageFactor = (HUNDRED_PERCENT_BPS - bps) / HUNDRED_PERCENT_BPS
|
|
565
|
+
const slippageFactor = fractionDiv(
|
|
566
|
+
{ high: new BN(HUNDRED_PERCENT_BPS - this.self.perTradeRebalanceThresholdSlippageBps), low: new BN(0) },
|
|
567
|
+
{ high: new BN(HUNDRED_PERCENT_BPS), low: new BN(0) }
|
|
568
|
+
);
|
|
569
|
+
|
|
570
|
+
// Check: inValue < outValue * slippageFactor
|
|
571
|
+
const outValueWithSlippage = fractionMul(outValue, slippageFactor);
|
|
572
|
+
if (fractionLt(inValue, outValueWithSlippage)) {
|
|
573
|
+
throw new Error("RebalanceSlippageExceeded");
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
const selfTvl = this.getSelfTvl();
|
|
577
|
+
// minSelfTvl = initialTvl * (HUNDRED_PERCENT_BPS - rebalanceThresholdSlippageBps) / HUNDRED_PERCENT_BPS
|
|
578
|
+
const tvlSlippageFactor = fractionDiv(
|
|
579
|
+
{ high: new BN(HUNDRED_PERCENT_BPS - this.self.rebalanceThresholdSlippageBps), low: new BN(0) },
|
|
580
|
+
{ high: new BN(HUNDRED_PERCENT_BPS), low: new BN(0) }
|
|
581
|
+
);
|
|
582
|
+
const minSelfTvl = fractionMul(this.self.initialTvl, tvlSlippageFactor);
|
|
583
|
+
if (fractionLt(selfTvl, minSelfTvl)) {
|
|
584
|
+
throw new Error("RebalanceSlippageExceeded");
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
return exchangeRate;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
updateTargetAmounts(basket: Basket): void {
|
|
591
|
+
this.self.auctionUpdateTimestamp = new BN(Math.floor(Date.now() / 1000));
|
|
592
|
+
|
|
593
|
+
const selfTvl = this.getSelfTvl();
|
|
594
|
+
const { basketTvl, weightSum } = this.getBasketTvlAndWeightSum(basket);
|
|
595
|
+
|
|
596
|
+
for (let tokenIndex = 0; tokenIndex < this.self.priceUpdateTasks.length; tokenIndex++) {
|
|
597
|
+
if (this.self.tokens[tokenIndex].mint.equals(PublicKey.default)) {
|
|
598
|
+
continue;
|
|
599
|
+
}
|
|
600
|
+
if (this.self.priceUpdateTasks[tokenIndex].completedTime.isZero()) {
|
|
601
|
+
continue;
|
|
602
|
+
}
|
|
603
|
+
const auctionToken = this.self.tokens[tokenIndex];
|
|
604
|
+
const auctionTokenPrice = auctionToken.price;
|
|
605
|
+
const tokenPrice = auctionTokenPrice.price;
|
|
606
|
+
if (this.self.rebalanceType === RebalanceType.Withdraw) {
|
|
607
|
+
this.self.tokens[tokenIndex].targetAmount = new BN(0);
|
|
608
|
+
if (auctionToken.keepToken === 1) {
|
|
609
|
+
// targetAmount = selfTvl / tokenPrice (rounded down) * 2
|
|
610
|
+
const targetAmount = fractionRoundDown(fractionDiv(selfTvl, tokenPrice));
|
|
611
|
+
this.self.tokens[tokenIndex].targetAmount = targetAmount.mul(new BN(2));
|
|
612
|
+
}
|
|
613
|
+
continue;
|
|
614
|
+
}
|
|
615
|
+
// Find token index in basket composition
|
|
616
|
+
let tokenIndexInBasket: number | undefined = undefined;
|
|
617
|
+
for (let i = 0; i < basket.numTokens; i++) {
|
|
618
|
+
if (basket.composition[i].mint.equals(this.self.tokens[tokenIndex].mint)) {
|
|
619
|
+
tokenIndexInBasket = i;
|
|
620
|
+
break;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
if (tokenIndexInBasket === undefined) {
|
|
624
|
+
this.self.tokens[tokenIndex].targetAmount = new BN(0);
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
const basketToken = basket.composition[tokenIndexInBasket];
|
|
628
|
+
|
|
629
|
+
if (this.self.rebalanceType === RebalanceType.Deposit && basket.supplyOutstanding.gt(new BN(0))) {
|
|
630
|
+
// targetAmount = (basketToken.amount * selfTvl) / basketTvl (rounded down)
|
|
631
|
+
const basketTokenAmountFraction = { high: new BN(basketToken.amount), low: new BN(0) };
|
|
632
|
+
const numerator = fractionMul(basketTokenAmountFraction, selfTvl);
|
|
633
|
+
const targetAmount = fractionRoundDown(fractionDiv(numerator, basketTvl));
|
|
634
|
+
this.self.tokens[tokenIndex].targetAmount = targetAmount;
|
|
635
|
+
}
|
|
636
|
+
if (this.self.rebalanceType === RebalanceType.Deposit && basket.supplyOutstanding.isZero()) {
|
|
637
|
+
// targetValue = selfTvl * weight / weightSum
|
|
638
|
+
// targetAmount = targetValue / tokenPrice (rounded down)
|
|
639
|
+
const targetValue = fractionDiv(
|
|
640
|
+
fractionMul(selfTvl, { high: new BN(basketToken.weight), low: new BN(0) }),
|
|
641
|
+
{ high: new BN(weightSum), low: new BN(0) }
|
|
642
|
+
);
|
|
643
|
+
const targetAmount = fractionRoundDown(fractionDiv(targetValue, tokenPrice));
|
|
644
|
+
this.self.tokens[tokenIndex].targetAmount = targetAmount;
|
|
645
|
+
}
|
|
646
|
+
if (this.self.rebalanceType === RebalanceType.Basket || this.self.rebalanceType === RebalanceType.BasketCustom) {
|
|
647
|
+
// targetValue = basketTvl * weight / weightSum
|
|
648
|
+
// targetAmount = targetValue / tokenPrice (rounded down)
|
|
649
|
+
const targetValue = fractionDiv(
|
|
650
|
+
fractionMul(basketTvl, { high: new BN(basketToken.weight), low: new BN(0) }),
|
|
651
|
+
{ high: new BN(weightSum), low: new BN(0) }
|
|
652
|
+
);
|
|
653
|
+
let targetAmount = fractionRoundDown(fractionDiv(targetValue, tokenPrice));
|
|
654
|
+
if (this.self.bounty.bountyMint.equals(auctionToken.mint)) {
|
|
655
|
+
targetAmount = targetAmount.add(this.self.bountyAdjustmentAmount);
|
|
656
|
+
}
|
|
657
|
+
this.self.tokens[tokenIndex].targetAmount = targetAmount;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* Returns all valid swap pairs available during the rebalance auction phase.
|
|
665
|
+
*
|
|
666
|
+
* **Swap direction (basket perspective):**
|
|
667
|
+
* - `inMint` / `inAmount`: what the basket is supposed to **receive**.
|
|
668
|
+
* - `outMint` / `outAmount`: what the basket wants to **swap** (give away).
|
|
669
|
+
* So each pair describes an **outMint → inMint** swap. When generating swap
|
|
670
|
+
* transactions, use this direction: swap `outMint` → `inMint` (sell outAmount of
|
|
671
|
+
* outMint, receive inAmount of inMint).
|
|
672
|
+
*
|
|
673
|
+
* Only runs when the rebalance is in Auction action and the current time falls
|
|
674
|
+
* within one of the three auction windows. For the active auction, target amounts
|
|
675
|
+
* are refreshed if they haven't been updated since the auction started.
|
|
676
|
+
*
|
|
677
|
+
* Iterates over completed price-update tokens to compute exchange rates via
|
|
678
|
+
* getSwapAmounts (using time-since-start and auction duration). Pairs with
|
|
679
|
+
* positive in/out amounts are collected and sorted by value descending
|
|
680
|
+
* (highest value first).
|
|
681
|
+
*
|
|
682
|
+
* @param rebalanceIntent - The rebalance intent state
|
|
683
|
+
* @param basket - The basket (used to update target amounts when needed)
|
|
684
|
+
* @returns Array of swap pairs, or empty array when not in auction or outside auction windows
|
|
685
|
+
*/
|
|
686
|
+
export function getSwapPairs(
|
|
687
|
+
rebalanceIntent: RebalanceIntent,
|
|
688
|
+
basket: Basket,
|
|
689
|
+
): {
|
|
690
|
+
inMint: string;
|
|
691
|
+
outMint: string;
|
|
692
|
+
inAmount: number;
|
|
693
|
+
outAmount: number;
|
|
694
|
+
value: number;
|
|
695
|
+
}[] {
|
|
696
|
+
let rebalanceIntentRustClass = new RebalanceIntentRustClass(rebalanceIntent);
|
|
697
|
+
if (rebalanceIntentRustClass.self.currentAction !== RebalanceAction.Auction) return [];
|
|
698
|
+
|
|
699
|
+
const timestamp = new BN(Math.floor(Date.now() / 1000));
|
|
700
|
+
let currentAuction;
|
|
701
|
+
if (rebalanceIntentRustClass.self.auctions[0].startTime.lte(timestamp) && timestamp.lt(rebalanceIntentRustClass.self.auctions[0].endTime))
|
|
702
|
+
currentAuction = rebalanceIntentRustClass.self.auctions[0]; else
|
|
703
|
+
if (rebalanceIntentRustClass.self.auctions[1].startTime.lte(timestamp) && timestamp.lt(rebalanceIntentRustClass.self.auctions[1].endTime))
|
|
704
|
+
currentAuction = rebalanceIntentRustClass.self.auctions[1]; else
|
|
705
|
+
if (rebalanceIntentRustClass.self.auctions[2].startTime.lte(timestamp) && timestamp.lt(rebalanceIntentRustClass.self.auctions[2].endTime))
|
|
706
|
+
currentAuction = rebalanceIntentRustClass.self.auctions[2]; else
|
|
707
|
+
return [];
|
|
708
|
+
|
|
709
|
+
let start_time = currentAuction.startTime;
|
|
710
|
+
if (rebalanceIntentRustClass.self.auctionUpdateTimestamp.lt(start_time))
|
|
711
|
+
rebalanceIntentRustClass.updateTargetAmounts(basket);
|
|
712
|
+
|
|
713
|
+
let timeSinceAuctionStart = timestamp.sub(currentAuction.startTime);
|
|
714
|
+
let auctionDuration = currentAuction.endTime.sub(currentAuction.startTime);
|
|
715
|
+
|
|
716
|
+
let allPairs: {
|
|
717
|
+
inMint: string;
|
|
718
|
+
outMint: string;
|
|
719
|
+
inAmount: number;
|
|
720
|
+
outAmount: number;
|
|
721
|
+
value: number;
|
|
722
|
+
}[] = [];
|
|
723
|
+
|
|
724
|
+
for (let inTokenIndex = 0; inTokenIndex < rebalanceIntentRustClass.self.priceUpdateTasks.length; inTokenIndex++) {
|
|
725
|
+
if (rebalanceIntentRustClass.self.priceUpdateTasks[inTokenIndex].completedTime.isZero()) continue;
|
|
726
|
+
for (let outTokenIndex = 0; outTokenIndex < rebalanceIntentRustClass.self.priceUpdateTasks.length; outTokenIndex++) {
|
|
727
|
+
if (inTokenIndex === outTokenIndex) continue;
|
|
728
|
+
if (rebalanceIntentRustClass.self.priceUpdateTasks[outTokenIndex].completedTime.isZero()) continue;
|
|
729
|
+
|
|
730
|
+
try {
|
|
731
|
+
let exchangeRate = rebalanceIntentRustClass.getSwapAmounts(
|
|
732
|
+
inTokenIndex,
|
|
733
|
+
outTokenIndex,
|
|
734
|
+
timeSinceAuctionStart,
|
|
735
|
+
auctionDuration,
|
|
736
|
+
);
|
|
737
|
+
if (parseInt(exchangeRate.amountToBuy.toString()) > 0 && parseInt(exchangeRate.amountToSell.toString()) > 0) {
|
|
738
|
+
allPairs.push({
|
|
739
|
+
inMint: rebalanceIntentRustClass.self.tokens[inTokenIndex].mint.toBase58(),
|
|
740
|
+
outMint: rebalanceIntentRustClass.self.tokens[outTokenIndex].mint.toBase58(),
|
|
741
|
+
inAmount: parseInt(exchangeRate.amountToBuy.toString()),
|
|
742
|
+
outAmount: parseInt(exchangeRate.amountToSell.toString()),
|
|
743
|
+
value: fractionToDecimal(exchangeRate.exchangeValue).toNumber(),
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
} catch {}
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
allPairs.sort((a, b) => b.value - a.value);
|
|
750
|
+
return allPairs;
|
|
751
|
+
}
|