@symmetry-hq/sdk 1.0.1 → 1.0.2

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.
@@ -7,7 +7,7 @@ import { FormattedOraclePrice, FormattedRebalanceAction, FormattedRebalanceInten
7
7
  import { FormattedOracleType } from './layouts/oracle';
8
8
  import { BasketFilter } from './states/basket';
9
9
  import { IntentFilter } from './states/intents/intent';
10
- import { RebalanceIntentFilter } from './states/intents/rebalanceIntent';
10
+ import { getSwapPairs, isRebalanceRequired, RebalanceIntentFilter } from './states/intents/rebalanceIntent';
11
11
  import { WithdrawBasketFeesFilter } from './states/withdrawBasketFees';
12
12
  import { BasketCreationTx, TxPayloadBatchSequence, VersionedTxs, Wallet } from './txUtils';
13
13
  export declare class SymmetryCore {
@@ -800,5 +800,5 @@ export { FormattedGlobalConfig, FormattedIntentStatus, FormattedTaskType, Format
800
800
  export { FormattedRebalanceType, FormattedRebalanceAction, FormattedOraclePrice, FormattedTokenAuction, FormattedTaskCompletion, FormattedRebalanceIntent, UIRebalanceIntent, DepositData, PriceUpdatesData, AuctionData, MintData, RedeemData, ClaimBountyData, };
801
801
  export { FormattedCreatorSettings, FormattedManagersSettings, FormattedFeeSettings, FormattedScheduleSettings, FormattedAutomationSettings, FormattedLpSettings, FormattedMetadataSettings, FormattedDepositsSettings, FormattedForceRebalanceSettings, FormattedCustomRebalanceSettings, FormattedAddTokenSettings, FormattedUpdateWeightsSettings, FormattedMakeDirectSwapSettings, FormattedAccumulatedFees, FormattedLookupTables, FormattedAsset, FormattedOracleSettings, FormattedOracleAggregator, FormattedOracle, FormattedOracleType, FormattedBasket, };
802
802
  export { EditCreatorSettings, EditManagerSettings, EditFeeSettings, EditScheduleSettings, EditAutomationSettings, EditLpSettings, EditMetadataSettings, EditDepositsSettings, EditForceRebalanceSettings, EditCustomRebalanceSettings, EditAddTokenSettings, EditUpdateWeightsSettings, EditMakeDirectSwapSettings, AddOrEditTokenInput, OracleInput, UpdateWeightsInput, MakeDirectSwapInput, Settings, TaskContext, TaskType, };
803
- export { getJupTokenLedgerAndSwapInstructions, };
803
+ export { getJupTokenLedgerAndSwapInstructions, getSwapPairs, isRebalanceRequired, };
804
804
  export { KeeperMonitor, RebalanceHandler, } from './keeperMonitor';
package/dist/src/index.js CHANGED
@@ -12,7 +12,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
12
12
  return (mod && mod.__esModule) ? mod : { "default": mod };
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.RebalanceHandler = exports.KeeperMonitor = exports.getJupTokenLedgerAndSwapInstructions = exports.TaskType = exports.SymmetryCore = void 0;
15
+ exports.RebalanceHandler = exports.KeeperMonitor = exports.isRebalanceRequired = exports.getSwapPairs = exports.getJupTokenLedgerAndSwapInstructions = exports.TaskType = exports.SymmetryCore = void 0;
16
16
  const decimal_js_1 = __importDefault(require("decimal.js"));
17
17
  const bn_js_1 = __importDefault(require("bn.js"));
18
18
  const spl_token_1 = require("@solana/spl-token");
@@ -42,6 +42,8 @@ const basket_1 = require("./states/basket");
42
42
  const config_1 = require("./states/config");
43
43
  const intent_2 = require("./states/intents/intent");
44
44
  const rebalanceIntent_3 = require("./states/intents/rebalanceIntent");
45
+ Object.defineProperty(exports, "getSwapPairs", { enumerable: true, get: function () { return rebalanceIntent_3.getSwapPairs; } });
46
+ Object.defineProperty(exports, "isRebalanceRequired", { enumerable: true, get: function () { return rebalanceIntent_3.isRebalanceRequired; } });
45
47
  const withdrawBasketFees_1 = require("./states/withdrawBasketFees");
46
48
  const pythOracle_1 = require("./states/oracles/pythOracle");
47
49
  const txUtils_1 = require("./txUtils");
@@ -0,0 +1,32 @@
1
+ import { Connection, PublicKey } from "@solana/web3.js";
2
+ import { Wallet } from "./txUtils";
3
+ import { Basket, UIRebalanceIntent } from ".";
4
+ export declare class RebalanceHandler {
5
+ private params;
6
+ private intent;
7
+ private basket;
8
+ constructor(params: {
9
+ intent: UIRebalanceIntent;
10
+ basket: Basket;
11
+ wallet: Wallet;
12
+ connection: Connection;
13
+ network: "devnet" | "mainnet";
14
+ jupiterApiKey: string;
15
+ maxAllowedAccounts: number;
16
+ priorityFee?: number;
17
+ simulateTransactions?: boolean;
18
+ });
19
+ delay: (ms: number) => Promise<unknown>;
20
+ refresh(): Promise<void>;
21
+ static run(params: {
22
+ intentPubkey: PublicKey;
23
+ wallet: Wallet;
24
+ connection: Connection;
25
+ network: "devnet" | "mainnet";
26
+ jupiterApiKey: string;
27
+ maxAllowedAccounts: number;
28
+ priorityFee?: number;
29
+ simulateTransactions?: boolean;
30
+ }): Promise<void>;
31
+ private execute;
32
+ }
@@ -0,0 +1,278 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.RebalanceHandler = void 0;
13
+ const web3_js_1 = require("@solana/web3.js");
14
+ const _1 = require(".");
15
+ const rebalanceIntent_1 = require("./states/intents/rebalanceIntent");
16
+ const constants_1 = require("./constants");
17
+ class RebalanceHandler {
18
+ constructor(params) {
19
+ var _a, _b;
20
+ this.delay = (ms) => __awaiter(this, void 0, void 0, function* () { return new Promise(resolve => setTimeout(resolve, ms)); });
21
+ this.params = {
22
+ wallet: params.wallet,
23
+ connection: params.connection,
24
+ symmetryCore: new _1.SymmetryCore({
25
+ connection: params.connection,
26
+ network: params.network,
27
+ priorityFee: (_a = params.priorityFee) !== null && _a !== void 0 ? _a : constants_1.PRIORITY_FEE,
28
+ }),
29
+ network: params.network,
30
+ jupiterApiKey: params.jupiterApiKey,
31
+ maxAllowedAccounts: params.maxAllowedAccounts,
32
+ simulateTransactions: (_b = params.simulateTransactions) !== null && _b !== void 0 ? _b : false,
33
+ };
34
+ this.intent = params.intent;
35
+ this.basket = params.basket;
36
+ }
37
+ refresh() {
38
+ return __awaiter(this, void 0, void 0, function* () {
39
+ this.intent = yield this.params.symmetryCore.fetchRebalanceIntent(this.intent.formatted_data.pubkey);
40
+ this.basket = yield this.params.symmetryCore.fetchBasket(this.intent.formatted_data.basket);
41
+ });
42
+ }
43
+ static run(params) {
44
+ return __awaiter(this, void 0, void 0, function* () {
45
+ var _a;
46
+ let symmetryCore = new _1.SymmetryCore({
47
+ connection: params.connection,
48
+ network: params.network,
49
+ priorityFee: (_a = params.priorityFee) !== null && _a !== void 0 ? _a : constants_1.PRIORITY_FEE,
50
+ });
51
+ let intent = yield symmetryCore.fetchRebalanceIntent(params.intentPubkey.toBase58());
52
+ let basket = yield symmetryCore.fetchBasket(intent.formatted_data.basket);
53
+ let handler = new RebalanceHandler({
54
+ intent, basket,
55
+ wallet: params.wallet,
56
+ connection: params.connection,
57
+ network: params.network,
58
+ jupiterApiKey: params.jupiterApiKey,
59
+ maxAllowedAccounts: params.maxAllowedAccounts,
60
+ simulateTransactions: params.simulateTransactions,
61
+ });
62
+ handler.execute();
63
+ for (let i = 0; i < 20; i++) {
64
+ yield handler.delay(15 * 1000);
65
+ try {
66
+ yield handler.refresh();
67
+ }
68
+ catch (e) {
69
+ break;
70
+ }
71
+ }
72
+ });
73
+ }
74
+ execute() {
75
+ return __awaiter(this, void 0, void 0, function* () {
76
+ var _a, _b;
77
+ console.log("Starting rebalance handler for intent:", this.intent.formatted_data.pubkey);
78
+ let nextCheckTime = 0;
79
+ let numTriesUpdatePrices = 0;
80
+ let numTriesMint = 0;
81
+ let numTriesRedeemTokens = 0;
82
+ let numTriesClaimBounty = 0;
83
+ let rebalancePairs = [];
84
+ let lastJupQuotesUpdate = 0;
85
+ let jupQuotes = [];
86
+ while (true) {
87
+ if (!this.intent)
88
+ break;
89
+ let intent = this.intent.formatted_data;
90
+ let chainData = this.intent.chain_data;
91
+ let now = Date.now() / 1000;
92
+ if (now < nextCheckTime) {
93
+ yield this.delay(Math.min(30 * 1000, Math.max(0, nextCheckTime - now + 0.2) * 1000));
94
+ continue;
95
+ }
96
+ nextCheckTime = now + 35;
97
+ if (intent.current_action == "not_active") {
98
+ console.log("Intent not active, stopping");
99
+ break;
100
+ }
101
+ if (intent.current_action == "deposit_tokens") {
102
+ console.log("Waiting for deposit...");
103
+ continue;
104
+ }
105
+ if (intent.current_action == "update_prices" && intent.last_action_timestamp > now) {
106
+ nextCheckTime = intent.last_action_timestamp;
107
+ continue;
108
+ }
109
+ if (intent.current_action == "update_prices") {
110
+ if (numTriesUpdatePrices >= 3) {
111
+ console.log("Max retries for update_prices");
112
+ break;
113
+ }
114
+ numTriesUpdatePrices += 1;
115
+ try {
116
+ let tx = yield this.params.symmetryCore.updateTokenPricesTx({
117
+ keeper: this.params.wallet.publicKey.toBase58(),
118
+ basket: intent.basket,
119
+ rebalance_intent: intent.pubkey,
120
+ });
121
+ let res = yield this.params.symmetryCore.signAndSendTxPayloadBatchSequence({ txPayloadBatchSequence: tx, wallet: this.params.wallet, simulateTransactions: this.params.simulateTransactions });
122
+ console.log("Update Prices:", res);
123
+ }
124
+ catch (e) {
125
+ if (numTriesUpdatePrices == 3) {
126
+ console.log("Stop - update prices failed:", e);
127
+ }
128
+ }
129
+ continue;
130
+ }
131
+ if (intent.auctions[2].end_time > now) {
132
+ let auction0StartTime = intent.auctions[0].start_time;
133
+ let auction0EndTime = intent.auctions[0].end_time;
134
+ let auction1StartTime = intent.auctions[1].start_time;
135
+ let auction1EndTime = intent.auctions[1].end_time;
136
+ let auction2StartTime = intent.auctions[2].start_time;
137
+ let auction2EndTime = intent.auctions[2].end_time;
138
+ rebalancePairs = (0, rebalanceIntent_1.getSwapPairs)(chainData, this.basket);
139
+ if (Date.now() / 1000 > lastJupQuotesUpdate + 60) {
140
+ let usedValue = new Map();
141
+ jupQuotes = [];
142
+ for (let pair of rebalancePairs) {
143
+ let inValue = (_a = usedValue.get(pair.inMint)) !== null && _a !== void 0 ? _a : 0;
144
+ let outValue = (_b = usedValue.get(pair.outMint)) !== null && _b !== void 0 ? _b : 0;
145
+ let swapValue = Math.min(pair.value - inValue, pair.value - outValue);
146
+ if (swapValue < 0.005) {
147
+ jupQuotes.push(undefined);
148
+ continue;
149
+ }
150
+ usedValue.set(pair.inMint, inValue + swapValue);
151
+ usedValue.set(pair.outMint, outValue + swapValue);
152
+ let res = undefined;
153
+ if (this.params.network == "mainnet")
154
+ try {
155
+ res = Object.assign(Object.assign({}, (yield (0, _1.getJupTokenLedgerAndSwapInstructions)({
156
+ keeper: this.params.wallet.publicKey,
157
+ basketMintIn: new web3_js_1.PublicKey(pair.inMint),
158
+ basketMintOut: new web3_js_1.PublicKey(pair.outMint),
159
+ basketAmountIn: pair.inAmount,
160
+ basketAmountOut: pair.outAmount,
161
+ swapMode: "ioc",
162
+ apiKey: this.params.jupiterApiKey,
163
+ maxJupAccounts: this.params.maxAllowedAccounts,
164
+ }))), { inMint: pair.inMint, outMint: pair.outMint });
165
+ }
166
+ catch (_c) { }
167
+ ;
168
+ jupQuotes.push(res);
169
+ }
170
+ }
171
+ for (let index = 0; index < rebalancePairs.length; index++)
172
+ try {
173
+ let pair = rebalancePairs[index];
174
+ let quote = jupQuotes.find(q => q && q.inMint == pair.inMint && q.outMint == pair.outMint);
175
+ if (!quote)
176
+ continue;
177
+ if (pair.value < 0.005)
178
+ continue;
179
+ let { tokenLedgerInstruction, swapInstruction, addressLookupTableAddresses, quoteResponse } = quote;
180
+ if (!quoteResponse)
181
+ continue;
182
+ console.log(pair, "Jup Quote:", parseFloat(quoteResponse.outAmount), "Requested In:", pair.inAmount);
183
+ if (parseFloat(quoteResponse.outAmount) <= pair.inAmount && this.params.network == "mainnet")
184
+ continue;
185
+ try {
186
+ let tx = yield this.params.symmetryCore.flashSwapTx({
187
+ keeper: this.params.wallet.publicKey.toBase58(),
188
+ basket: this.basket.ownAddress.toBase58(),
189
+ rebalance_intent: intent.pubkey,
190
+ mint_in: pair.inMint,
191
+ mint_out: pair.outMint,
192
+ amount_in: pair.inAmount,
193
+ amount_out: pair.outAmount,
194
+ mode: 2,
195
+ jup_token_ledger_ix: tokenLedgerInstruction,
196
+ jup_swap_ix: swapInstruction,
197
+ jup_address_lookup_table_addresses: addressLookupTableAddresses,
198
+ });
199
+ let res = yield this.params.symmetryCore.signAndSendTxPayloadBatchSequence({ txPayloadBatchSequence: tx, wallet: this.params.wallet, simulateTransactions: this.params.simulateTransactions });
200
+ console.log("Flash Swap:", res);
201
+ rebalancePairs = rebalancePairs.splice(index, 1);
202
+ index -= 1;
203
+ }
204
+ catch (_d) { }
205
+ }
206
+ catch (_e) { }
207
+ nextCheckTime = (Date.now() / 1000) + 10;
208
+ continue;
209
+ }
210
+ if (intent.rebalance_type == "deposit") {
211
+ lastJupQuotesUpdate = 0;
212
+ if (numTriesMint >= 3) {
213
+ console.log("Max retries for mint");
214
+ break;
215
+ }
216
+ numTriesMint += 1;
217
+ try {
218
+ let tx = yield this.params.symmetryCore.mintTx({
219
+ keeper: this.params.wallet.publicKey.toBase58(),
220
+ rebalance_intent: intent.pubkey,
221
+ });
222
+ let res = yield this.params.symmetryCore.signAndSendTxPayloadBatchSequence({ txPayloadBatchSequence: tx, wallet: this.params.wallet, simulateTransactions: this.params.simulateTransactions });
223
+ console.log("Mint:", res);
224
+ }
225
+ catch (e) {
226
+ if (numTriesMint == 3) {
227
+ console.log("Stop - mint failed:", e);
228
+ }
229
+ }
230
+ continue;
231
+ }
232
+ let hasTokens = intent.tokens.find((token) => token.amount > 0);
233
+ if (hasTokens && intent.rebalance_type == "withdraw") {
234
+ if (numTriesRedeemTokens >= 3) {
235
+ console.log("Max retries for redeem");
236
+ break;
237
+ }
238
+ numTriesRedeemTokens += 1;
239
+ try {
240
+ let tx = yield this.params.symmetryCore.redeemTokensTx({
241
+ keeper: this.params.wallet.publicKey.toBase58(),
242
+ rebalance_intent: intent.pubkey,
243
+ });
244
+ let res = yield this.params.symmetryCore.signAndSendTxPayloadBatchSequence({ txPayloadBatchSequence: tx, wallet: this.params.wallet, simulateTransactions: this.params.simulateTransactions });
245
+ console.log("Redeem Tokens:", res);
246
+ }
247
+ catch (e) {
248
+ if (numTriesRedeemTokens == 3) {
249
+ console.log("Stop - redeem failed:", e);
250
+ }
251
+ }
252
+ continue;
253
+ }
254
+ if (numTriesClaimBounty >= 3) {
255
+ console.log("Max retries for claim bounty");
256
+ break;
257
+ }
258
+ numTriesClaimBounty += 1;
259
+ try {
260
+ let tx = yield this.params.symmetryCore.claimBountyTx({
261
+ keeper: this.params.wallet.publicKey.toBase58(),
262
+ rebalance_intent: intent.pubkey,
263
+ });
264
+ let res = yield this.params.symmetryCore.signAndSendTxPayloadBatchSequence({ txPayloadBatchSequence: tx, wallet: this.params.wallet, simulateTransactions: this.params.simulateTransactions });
265
+ console.log("Claim Bounty:", res);
266
+ nextCheckTime = now + 60;
267
+ }
268
+ catch (e) {
269
+ if (numTriesClaimBounty == 3) {
270
+ console.log("Stop - claim bounty failed:", e);
271
+ }
272
+ }
273
+ }
274
+ console.log("Rebalance handler finished for intent:", this.intent.formatted_data.pubkey);
275
+ });
276
+ }
277
+ }
278
+ exports.RebalanceHandler = RebalanceHandler;
@@ -40,3 +40,4 @@ export declare function getSwapPairs(rebalanceIntent: RebalanceIntent, basket: B
40
40
  outAmount: number;
41
41
  value: number;
42
42
  }[];
43
+ export declare function isRebalanceRequired(rebalanceIntent: RebalanceIntent, basket: Basket): boolean;
@@ -18,6 +18,7 @@ exports.fetchRebalanceIntent = fetchRebalanceIntent;
18
18
  exports.fetchRebalanceIntentsMultiple = fetchRebalanceIntentsMultiple;
19
19
  exports.fetchRebalanceIntents = fetchRebalanceIntents;
20
20
  exports.getSwapPairs = getSwapPairs;
21
+ exports.isRebalanceRequired = isRebalanceRequired;
21
22
  const web3_js_1 = require("@solana/web3.js");
22
23
  const rebalanceIntent_1 = require("../../layouts/intents/rebalanceIntent");
23
24
  const constants_1 = require("../../constants");
@@ -401,6 +402,33 @@ class RebalanceIntentRustClass {
401
402
  return i;
402
403
  return undefined;
403
404
  }
405
+ isRebalanceRequired(basket) {
406
+ let requiresRebalance = false;
407
+ for (let i = 0; i < basket.numTokens; i++) {
408
+ let tokenAuction = this.self.tokens[i];
409
+ if (tokenAuction.mint.equals(this.self.bounty.bountyMint)) {
410
+ continue;
411
+ }
412
+ let oraclePrice = tokenAuction.price;
413
+ let tokenPrice = oraclePrice.price;
414
+ let tokenValue = (0, fraction_1.fractionMul)(tokenPrice, { high: new bn_js_1.default(tokenAuction.amount), low: new bn_js_1.default(0) });
415
+ let targetValue = (0, fraction_1.fractionMul)(tokenPrice, { high: new bn_js_1.default(tokenAuction.targetAmount), low: new bn_js_1.default(0) });
416
+ let [valueDiff, maxValue] = (tokenValue < targetValue) ?
417
+ [(0, fraction_1.fractionSub)(targetValue, tokenValue), targetValue] :
418
+ [(0, fraction_1.fractionSub)(tokenValue, targetValue), tokenValue];
419
+ if (valueDiff.high.eq(new bn_js_1.default(0)) && valueDiff.low.eq(new bn_js_1.default(0))) {
420
+ continue;
421
+ }
422
+ let relBps = (0, fraction_1.fractionDiv)((0, fraction_1.fractionMul)(valueDiff, { high: new bn_js_1.default(constants_1.HUNDRED_PERCENT_BPS), low: new bn_js_1.default(0) }), maxValue);
423
+ let absBps = (0, fraction_1.fractionDiv)((0, fraction_1.fractionMul)(valueDiff, { high: new bn_js_1.default(constants_1.HUNDRED_PERCENT_BPS), low: new bn_js_1.default(0) }), this.self.initialTvl);
424
+ let requiredRelBps = { high: new bn_js_1.default(basket.settings.automation.rebalanceActivationThresholdRelBps), low: new bn_js_1.default(0) };
425
+ let requiredAbsBps = { high: new bn_js_1.default(basket.settings.automation.rebalanceActivationThresholdAbsBps), low: new bn_js_1.default(0) };
426
+ if ((0, fraction_1.fractionGte)(relBps, requiredRelBps) && (0, fraction_1.fractionGte)(absBps, requiredAbsBps)) {
427
+ requiresRebalance = true;
428
+ }
429
+ }
430
+ return requiresRebalance;
431
+ }
404
432
  getSelfTvl() {
405
433
  let selfTvl = { high: new bn_js_1.default(0), low: new bn_js_1.default(0) };
406
434
  for (let i = 0; i < this.self.priceUpdateTasks.length; i++) {
@@ -678,3 +706,23 @@ function getSwapPairs(rebalanceIntent, basket) {
678
706
  allPairs.sort((a, b) => b.value - a.value);
679
707
  return allPairs;
680
708
  }
709
+ function isRebalanceRequired(rebalanceIntent, basket) {
710
+ if (basket.settings.automation.allowAutomation !== 1)
711
+ return false;
712
+ let cycleTimestamp = new bn_js_1.default(0);
713
+ let currentTimestamp = new bn_js_1.default(Math.floor(Date.now() / 1000));
714
+ if (basket.settings.schedule.cycleStartTime.gt(currentTimestamp))
715
+ return false;
716
+ let timeSinceCycleStart = currentTimestamp.sub(basket.settings.schedule.cycleStartTime);
717
+ if (basket.settings.schedule.cycleDuration != new bn_js_1.default(0))
718
+ cycleTimestamp = timeSinceCycleStart.mod(basket.settings.schedule.cycleDuration);
719
+ if (!(cycleTimestamp.gte(basket.settings.schedule.automationStart) &&
720
+ cycleTimestamp.lt(basket.settings.schedule.automationEnd)))
721
+ return false;
722
+ let nextRebalanceTimestamp = basket.settings.lastAutomationExecutionTimestamp
723
+ .add(basket.settings.automation.rebalanceActivationCooldown);
724
+ if (nextRebalanceTimestamp.gte(currentTimestamp))
725
+ return false;
726
+ let rebalanceIntentRustClass = new RebalanceIntentRustClass(rebalanceIntent);
727
+ return rebalanceIntentRustClass.isRebalanceRequired(basket);
728
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symmetry-hq/sdk",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Symmetry V3 SDK",
5
5
  "main": "dist/src/index.js",
6
6
  "types": "dist/src/index.d.ts",
package/src/index.ts CHANGED
@@ -76,7 +76,7 @@ import {
76
76
  } from './states/intents/intent';
77
77
  import {
78
78
  computeRebalanceIntentBountyAmount, fetchRebalanceIntent, fetchRebalanceIntents,
79
- fetchRebalanceIntentsMultiple, RebalanceIntentFilter
79
+ fetchRebalanceIntentsMultiple, getSwapPairs, isRebalanceRequired, RebalanceIntentFilter
80
80
  } from './states/intents/rebalanceIntent';
81
81
  import {
82
82
  fetchWithdrawBasketFees, fetchWithdrawBasketFeesMultiple, fetchWithdrawBasketFeesList, WithdrawBasketFeesFilter
@@ -2423,6 +2423,8 @@ export {
2423
2423
 
2424
2424
  export {
2425
2425
  getJupTokenLedgerAndSwapInstructions,
2426
+ getSwapPairs,
2427
+ isRebalanceRequired,
2426
2428
  }
2427
2429
 
2428
2430
  export {
@@ -2,7 +2,7 @@ import { Connection, GetProgramAccountsResponse, PublicKey } from "@solana/web3.
2
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
3
  import { GetProgramAccountsFilter } from "@solana/web3.js";
4
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";
5
+ import { Fraction, fractionAdd, fractionDiv, fractionGte, fractionLt, fractionLte, fractionMul, fractionRoundDown, fractionRoundUp, fractionSub, fractionToDecimal } from "../../layouts/fraction";
6
6
  import { getMultipleAccountsInfoBatched } from "../../txUtils";
7
7
  import BN from "bn.js";
8
8
  import { Basket, FormattedBasket } from "../../layouts/basket";
@@ -408,6 +408,34 @@ class RebalanceIntentRustClass {
408
408
  return undefined;
409
409
  }
410
410
 
411
+ isRebalanceRequired(basket: Basket): boolean {
412
+ let requiresRebalance = false;
413
+ for (let i = 0; i < basket.numTokens; i++) {
414
+ let tokenAuction = this.self.tokens[i];
415
+ if (tokenAuction.mint.equals(this.self.bounty.bountyMint)) {
416
+ continue;
417
+ }
418
+ let oraclePrice = tokenAuction.price;
419
+ let tokenPrice = oraclePrice.price;
420
+ let tokenValue = fractionMul(tokenPrice, { high: new BN(tokenAuction.amount), low: new BN(0) });
421
+ let targetValue = fractionMul(tokenPrice, { high: new BN(tokenAuction.targetAmount), low: new BN(0) });
422
+ let [valueDiff, maxValue] = (tokenValue < targetValue) ?
423
+ [fractionSub(targetValue, tokenValue), targetValue] :
424
+ [fractionSub(tokenValue, targetValue), tokenValue];
425
+ if (valueDiff.high.eq(new BN(0)) && valueDiff.low.eq(new BN(0))) {
426
+ continue;
427
+ }
428
+ let relBps = fractionDiv(fractionMul(valueDiff, { high: new BN(HUNDRED_PERCENT_BPS), low: new BN(0) }), maxValue);
429
+ let absBps = fractionDiv(fractionMul(valueDiff, { high: new BN(HUNDRED_PERCENT_BPS), low: new BN(0) }), this.self.initialTvl);
430
+ let requiredRelBps = { high: new BN(basket.settings.automation.rebalanceActivationThresholdRelBps), low: new BN(0) };
431
+ let requiredAbsBps = { high: new BN(basket.settings.automation.rebalanceActivationThresholdAbsBps), low: new BN(0) };
432
+ if (fractionGte(relBps, requiredRelBps) && fractionGte(absBps, requiredAbsBps)) {
433
+ requiresRebalance = true;
434
+ }
435
+ }
436
+ return requiresRebalance;
437
+ }
438
+
411
439
  getSelfTvl(): Fraction {
412
440
  let selfTvl = { high: new BN(0), low: new BN(0) };
413
441
  for (let i = 0; i < this.self.priceUpdateTasks.length; i++) {
@@ -749,3 +777,28 @@ export function getSwapPairs(
749
777
  allPairs.sort((a, b) => b.value - a.value);
750
778
  return allPairs;
751
779
  }
780
+
781
+ export function isRebalanceRequired(
782
+ rebalanceIntent: RebalanceIntent,
783
+ basket: Basket
784
+ ): boolean {
785
+ if (basket.settings.automation.allowAutomation !== 1) return false;
786
+ let cycleTimestamp = new BN(0);
787
+ let currentTimestamp = new BN(Math.floor(Date.now() / 1000));
788
+ if (basket.settings.schedule.cycleStartTime.gt(currentTimestamp))
789
+ return false;
790
+ let timeSinceCycleStart = currentTimestamp.sub(basket.settings.schedule.cycleStartTime);
791
+ if (basket.settings.schedule.cycleDuration != new BN(0))
792
+ cycleTimestamp = timeSinceCycleStart.mod(basket.settings.schedule.cycleDuration);
793
+ if (!(
794
+ cycleTimestamp.gte(basket.settings.schedule.automationStart) &&
795
+ cycleTimestamp.lt(basket.settings.schedule.automationEnd)
796
+ ))
797
+ return false;
798
+ let nextRebalanceTimestamp = basket.settings.lastAutomationExecutionTimestamp
799
+ .add(basket.settings.automation.rebalanceActivationCooldown);
800
+ if (nextRebalanceTimestamp.gte(currentTimestamp))
801
+ return false;
802
+ let rebalanceIntentRustClass = new RebalanceIntentRustClass(rebalanceIntent);
803
+ return rebalanceIntentRustClass.isRebalanceRequired(basket);
804
+ }
@@ -1,11 +0,0 @@
1
- import { BN } from '@coral-xyz/anchor';
2
- import { PublicKey, TransactionInstruction } from '@solana/web3.js';
3
- export declare function rebalanceSwapIx(params: {
4
- keeper: PublicKey;
5
- basket: PublicKey;
6
- mintFrom: PublicKey;
7
- mintTo: PublicKey;
8
- amountIn: BN;
9
- amountOut: BN;
10
- mode: number;
11
- }): TransactionInstruction;
@@ -1,42 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.rebalanceSwapIx = rebalanceSwapIx;
4
- const spl_token_1 = require("@solana/spl-token");
5
- const web3_js_1 = require("@solana/web3.js");
6
- const constants_1 = require("../../constants");
7
- const pda_1 = require("../pda");
8
- const REBALANCE_SWAP_DISCRIMINATOR = Buffer.from([101, 122, 61, 201, 21, 165, 177, 213]);
9
- function rebalanceSwapIx(params) {
10
- const { keeper, basket, mintFrom, mintTo, amountIn, amountOut, mode } = params;
11
- let rebalanceIntent = (0, pda_1.getRebalanceIntentPda)(basket, keeper); // TODO: incorrect. Pass intent as func param
12
- let keeperFromATA = (0, pda_1.getAta)(keeper, mintFrom);
13
- let keeperToATA = (0, pda_1.getAta)(keeper, mintTo);
14
- let basketFromATA = (0, pda_1.getAta)(basket, mintFrom);
15
- let basketToATA = (0, pda_1.getAta)(basket, mintTo);
16
- const keys = [
17
- { pubkey: params.keeper, isWritable: true, isSigner: true },
18
- { pubkey: params.basket, isWritable: true, isSigner: false },
19
- { pubkey: rebalanceIntent, isWritable: true, isSigner: false },
20
- { pubkey: params.mintFrom, isWritable: false, isSigner: false },
21
- { pubkey: params.mintTo, isWritable: false, isSigner: false },
22
- { pubkey: keeperFromATA, isWritable: true, isSigner: false },
23
- { pubkey: keeperToATA, isWritable: true, isSigner: false },
24
- { pubkey: basketFromATA, isWritable: true, isSigner: false },
25
- { pubkey: basketToATA, isWritable: true, isSigner: false },
26
- { pubkey: (0, pda_1.getGlobalConfigPda)(), isWritable: false, isSigner: false },
27
- { pubkey: web3_js_1.SystemProgram.programId, isWritable: false, isSigner: false },
28
- { pubkey: spl_token_1.TOKEN_PROGRAM_ID, isWritable: false, isSigner: false },
29
- { pubkey: spl_token_1.TOKEN_2022_PROGRAM_ID, isWritable: false, isSigner: false },
30
- { pubkey: spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, isWritable: false, isSigner: false },
31
- ];
32
- const discriminator = REBALANCE_SWAP_DISCRIMINATOR;
33
- const amountInBuffer = Buffer.from(amountIn.toArray("le", 8));
34
- const amountOutBuffer = Buffer.from(amountOut.toArray("le", 8));
35
- const modeBuffer = Buffer.from([mode]);
36
- const data = Buffer.concat([discriminator, amountInBuffer, amountOutBuffer, modeBuffer]);
37
- return new web3_js_1.TransactionInstruction({
38
- keys,
39
- programId: constants_1.BASKETS_V3_PROGRAM_ID,
40
- data,
41
- });
42
- }