@symmetry-hq/sdk 1.0.5 → 1.0.7

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,8 +7,7 @@ import { AuctionData, ClaimBountyData, DepositData, FormattedOraclePrice, Format
7
7
  import { FormattedOracleType } from './layouts/oracle';
8
8
  import { BasketFilter } from './states/basket';
9
9
  import { IntentFilter } from './states/intents/intent';
10
- import { getSwapPairs, RebalanceIntentFilter, isRebalanceRequired } from './states/intents/rebalanceIntent';
11
- import { OraclePrice } from './states/oracles/oracle';
10
+ import { getSwapPairs, isRebalanceRequired, RebalanceIntentFilter } from './states/intents/rebalanceIntent';
12
11
  import { WithdrawBasketFeesFilter } from './states/withdrawBasketFees';
13
12
  import { BasketCreationTx, TxPayloadBatchSequence, VersionedTxs, Wallet } from './txUtils';
14
13
  export declare class SymmetryCore {
@@ -91,19 +90,6 @@ export declare class SymmetryCore {
91
90
  * @returns {Promise<Basket>} The basket with the price.
92
91
  */
93
92
  loadBasketPrice(basket: Basket): Promise<Basket>;
94
- loadBasketPriceWithPythPriceOverrides(params: {
95
- basket: Basket;
96
- pyth_price_overrides?: Map<string, OraclePrice>;
97
- }): Promise<Basket>;
98
- /**
99
- * Checks whether a basket currently meets the automation rebalance trigger.
100
- * Uses the existing basket pricing flow.
101
- */
102
- isBasketRebalanceRequired(params: {
103
- basket: string | Basket;
104
- pyth_price_overrides?: Map<string, OraclePrice>;
105
- }): Promise<boolean>;
106
- private computeBasketRebalanceRequiredFromPricedBasket;
107
93
  /**
108
94
  * Fetches an intent by its public key.
109
95
  * @param {string} intentPubkey - The public key of the intent.
package/dist/src/index.js CHANGED
@@ -193,84 +193,6 @@ class SymmetryCore {
193
193
  return yield (0, basket_1.loadBasketPrice)(basket, this.sdkParams.connection);
194
194
  });
195
195
  }
196
- loadBasketPriceWithPythPriceOverrides(params) {
197
- return __awaiter(this, void 0, void 0, function* () {
198
- return yield (0, basket_1.loadBasketPriceWithPythPriceOverrides)(params.basket, this.sdkParams.connection, params.pyth_price_overrides);
199
- });
200
- }
201
- /**
202
- * Checks whether a basket currently meets the automation rebalance trigger.
203
- * Uses the existing basket pricing flow.
204
- */
205
- isBasketRebalanceRequired(params) {
206
- return __awaiter(this, void 0, void 0, function* () {
207
- var _a;
208
- let basket = typeof params.basket === 'string'
209
- ? yield this.fetchBasket(params.basket)
210
- : params.basket;
211
- if (!((_a = basket.formatted) === null || _a === void 0 ? void 0 : _a.automation_settings.enabled) && basket.settings.automation.allowAutomation !== 1) {
212
- return false;
213
- }
214
- if (params.pyth_price_overrides && params.pyth_price_overrides.size > 0) {
215
- basket = yield this.loadBasketPriceWithPythPriceOverrides({
216
- basket,
217
- pyth_price_overrides: params.pyth_price_overrides,
218
- });
219
- }
220
- else {
221
- basket = yield this.loadBasketPrice(basket);
222
- }
223
- return this.computeBasketRebalanceRequiredFromPricedBasket(basket);
224
- });
225
- }
226
- computeBasketRebalanceRequiredFromPricedBasket(basket) {
227
- var _a, _b, _c;
228
- const basketTvl = (_a = basket.tvl) !== null && _a !== void 0 ? _a : basket.composition
229
- .slice(0, basket.numTokens)
230
- .reduce((sum, asset) => {
231
- var _a, _b, _c;
232
- const value = (_c = (_a = asset.value) !== null && _a !== void 0 ? _a : (_b = asset.price) === null || _b === void 0 ? void 0 : _b.getMid().mul(new decimal_js_1.default(asset.amount.toString()))) !== null && _c !== void 0 ? _c : new decimal_js_1.default(0);
233
- return sum.add(value);
234
- }, new decimal_js_1.default(0));
235
- if (!basketTvl.isFinite() || basketTvl.lte(0)) {
236
- return false;
237
- }
238
- const weightSum = basket.composition
239
- .slice(0, basket.numTokens)
240
- .reduce((sum, asset) => sum + asset.weight, 0);
241
- if (weightSum <= 0) {
242
- return false;
243
- }
244
- const requiredRelBps = new decimal_js_1.default(basket.settings.automation.rebalanceActivationThresholdRelBps);
245
- const requiredAbsBps = new decimal_js_1.default(basket.settings.automation.rebalanceActivationThresholdAbsBps);
246
- const bountyMint = basket.settings.bountyMint;
247
- const hundredPercentBps = new decimal_js_1.default(10000);
248
- for (const asset of basket.composition.slice(0, basket.numTokens)) {
249
- if (asset.mint.equals(bountyMint)) {
250
- continue;
251
- }
252
- const tokenPrice = (_b = asset.price) === null || _b === void 0 ? void 0 : _b.getMid();
253
- if (!tokenPrice || !tokenPrice.isFinite() || tokenPrice.lte(0)) {
254
- continue;
255
- }
256
- const tokenValue = (_c = asset.value) !== null && _c !== void 0 ? _c : tokenPrice.mul(new decimal_js_1.default(asset.amount.toString()));
257
- const targetValue = basketTvl.mul(asset.weight).div(weightSum);
258
- const valueDiff = tokenValue.sub(targetValue).abs();
259
- if (valueDiff.eq(0)) {
260
- continue;
261
- }
262
- const maxValue = decimal_js_1.default.max(tokenValue, targetValue);
263
- if (!maxValue.isFinite() || maxValue.lte(0)) {
264
- continue;
265
- }
266
- const relBps = valueDiff.div(maxValue).mul(hundredPercentBps);
267
- const absBps = valueDiff.div(basketTvl).mul(hundredPercentBps);
268
- if (relBps.gte(requiredRelBps) && absBps.gte(requiredAbsBps)) {
269
- return true;
270
- }
271
- }
272
- return false;
273
- }
274
196
  /**
275
197
  * Fetches an intent by its public key.
276
198
  * @param {string} intentPubkey - The public key of the intent.
@@ -1866,13 +1788,41 @@ class SymmetryCore {
1866
1788
  return __awaiter(this, void 0, void 0, function* () {
1867
1789
  let keeper = new web3_js_1.PublicKey(params.keeper);
1868
1790
  let rebalanceIntent = (yield this.fetchRebalanceIntent(params.rebalance_intent)).chain_data;
1791
+ const tokensWithBalance = rebalanceIntent.tokens.filter(token => token.amount.gt(new bn_js_1.default(0)));
1792
+ const ownerIsKeeper = rebalanceIntent.owner.equals(keeper);
1793
+ let redeemableTokenMints = tokensWithBalance.map(token => token.mint);
1794
+ if (!ownerIsKeeper && tokensWithBalance.length > 0) {
1795
+ const mintInfos = yield this.sdkParams.connection.getMultipleAccountsInfo(tokensWithBalance.map(token => token.mint));
1796
+ const ataChecks = tokensWithBalance.map((token, index) => {
1797
+ const mintInfo = mintInfos[index];
1798
+ if (!mintInfo) {
1799
+ throw new Error(`Mint account not found for redeem token ${token.mint.toBase58()}`);
1800
+ }
1801
+ let tokenProgram;
1802
+ if (mintInfo.owner.equals(spl_token_1.TOKEN_PROGRAM_ID) || mintInfo.owner.equals(spl_token_1.TOKEN_2022_PROGRAM_ID)) {
1803
+ tokenProgram = mintInfo.owner;
1804
+ }
1805
+ else {
1806
+ throw new Error(`Unsupported token program for redeem token ${token.mint.toBase58()}: ${mintInfo.owner.toBase58()}`);
1807
+ }
1808
+ return {
1809
+ mint: token.mint,
1810
+ ata: (0, pda_1.getAta)(rebalanceIntent.owner, token.mint, tokenProgram),
1811
+ };
1812
+ });
1813
+ const ataInfos = yield this.sdkParams.connection.getMultipleAccountsInfo(ataChecks.map(item => item.ata));
1814
+ redeemableTokenMints = ataChecks
1815
+ .filter((_, index) => ataInfos[index] !== null)
1816
+ .map(item => item.mint);
1817
+ if (redeemableTokenMints.length === 0) {
1818
+ const blockedMints = ataChecks.map(item => item.mint.toBase58()).join(', ');
1819
+ throw new Error(`NoRedeemableTokens: owner ${rebalanceIntent.owner.toBase58()} has no ATA for remaining tokens [${blockedMints}]`);
1820
+ }
1821
+ }
1869
1822
  let batchSize = 7;
1870
1823
  let ixs = [];
1871
- for (let batchStart = 0; batchStart < rebalanceIntent.tokens.length; batchStart += batchSize) {
1872
- let tokenMints = [];
1873
- for (let i = batchStart; i < rebalanceIntent.tokens.length && i < batchStart + batchSize; i++)
1874
- if (rebalanceIntent.tokens[i].amount.gt(new bn_js_1.default(0)))
1875
- tokenMints.push(rebalanceIntent.tokens[i].mint);
1824
+ for (let batchStart = 0; batchStart < redeemableTokenMints.length; batchStart += batchSize) {
1825
+ let tokenMints = redeemableTokenMints.slice(batchStart, batchStart + batchSize);
1876
1826
  if (tokenMints.length > 0) {
1877
1827
  ixs.push((0, withdraw_1.redeemTokensIx)({
1878
1828
  keeper: keeper,
@@ -8,7 +8,7 @@ const web3_js_1 = require("@solana/web3.js");
8
8
  const constants_1 = require("../../constants");
9
9
  const pda_1 = require("../pda");
10
10
  const WITHDRAW_FEES_DISCRIMINATOR = Buffer.from([159, 222, 146, 66, 254, 108, 55, 51]);
11
- const CLAIM_FEE_TOKENS_FROM_BASKET_DISCRIMINATOR = Buffer.from([53, 109, 220, 141, 193, 193, 13, 32]);
11
+ const CLAIM_FEE_TOKENS_FROM_BASKET_DISCRIMINATOR = Buffer.from([53, 109, 220, 141, 110, 5, 193, 32]);
12
12
  function withdrawFeesIx(params) {
13
13
  let { claimer, basketTokenMint, feeType } = params;
14
14
  let basket = (0, pda_1.getBasketState)(basketTokenMint);
@@ -268,7 +268,7 @@ function createEditBasketIntentIx(params) {
268
268
  oracleSettings: {
269
269
  oracleType: (_b = (_a = [...oracle_1.ORACLE_TYPES_STRINGS.entries()].find(([, name]) => name === addTokenData.oracles[i].oracle_type)) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : 255,
270
270
  numRequiredAccounts: 0,
271
- weight: addTokenData.oracles[i].weight,
271
+ weight: addTokenData.oracles[i].weight_bps,
272
272
  isRequired: addTokenData.oracles[i].is_required ? 1 : 0,
273
273
  confThreshBps: addTokenData.oracles[i].conf_thresh_bps,
274
274
  volatilityThreshBps: addTokenData.oracles[i].volatility_thresh_bps,
@@ -279,7 +279,7 @@ function createEditBasketIntentIx(params) {
279
279
  tokenDecimals: addTokenData.oracles[i].token_decimals,
280
280
  twapSecondsAgo: new bn_js_1.default(addTokenData.oracles[i].twap_seconds_ago),
281
281
  twapSecondarySecondsAgo: new bn_js_1.default(addTokenData.oracles[i].twap_secondary_seconds_ago),
282
- quote: (_d = (_c = [...oracle_1.QUOTES_STRINGS.entries()].find(([, name]) => name === addTokenData.oracles[i].quote)) === null || _c === void 0 ? void 0 : _c[0]) !== null && _d !== void 0 ? _d : oracle_1.Quote.Usd,
282
+ quote: (_d = (_c = [...oracle_1.QUOTES_STRINGS.entries()].find(([, name]) => name === addTokenData.oracles[i].quote_token)) === null || _c === void 0 ? void 0 : _c[0]) !== null && _d !== void 0 ? _d : oracle_1.Quote.Usd,
283
283
  side: oracle_1.Side.Base,
284
284
  },
285
285
  accountsToLoadLutIds: new Array(oracle_1.MAX_ACCOUNTS_PER_ORACLE).fill(0),
@@ -107,6 +107,7 @@ export interface FormattedBasket {
107
107
  active_withdraws: number;
108
108
  active_managements: number;
109
109
  last_automation_execution_timestamp: number;
110
+ creation_timestamp: number;
110
111
  creator_settings: FormattedCreatorSettings;
111
112
  manager_settings: FormattedManagersSettings;
112
113
  fee_settings: FormattedFeeSettings;
@@ -190,6 +190,7 @@ export interface BasketSettings {
190
190
  makeDirectSwapLastUpdateTimestamp: BN;
191
191
  makeDirectSwapIntentAuthorityBitmask: number;
192
192
  extraData: BN[];
193
+ creationTimestamp: BN;
193
194
  }
194
195
  export declare const BasketSettingsLayout: any;
195
196
  export interface FormattedGlobalConfig {
@@ -190,7 +190,8 @@ exports.BasketSettingsLayout = (0, borsh_1.struct)([
190
190
  (0, borsh_1.u64)('makeDirectSwapDelay'),
191
191
  (0, borsh_1.u64)('makeDirectSwapLastUpdateTimestamp'),
192
192
  (0, borsh_1.u16)('makeDirectSwapIntentAuthorityBitmask'),
193
- (0, borsh_1.array)((0, borsh_1.u64)(), 32, 'extraData'),
193
+ (0, borsh_1.array)((0, borsh_1.u64)(), 31, 'extraData'),
194
+ (0, borsh_1.u64)('creationTimestamp'),
194
195
  ]);
195
196
  exports.GlobalConfigLayout = (0, borsh_1.struct)([
196
197
  (0, borsh_1.publicKey)('admin'),
@@ -172,7 +172,7 @@ export interface OracleInput {
172
172
  account_lut_id: number;
173
173
  account_lut_index: number;
174
174
  account: string;
175
- weight: number;
175
+ weight_bps: number;
176
176
  is_required: boolean;
177
177
  conf_thresh_bps: number;
178
178
  volatility_thresh_bps: number;
@@ -183,7 +183,7 @@ export interface OracleInput {
183
183
  token_decimals: number;
184
184
  twap_seconds_ago: number;
185
185
  twap_secondary_seconds_ago: number;
186
- quote: FormattedQuote;
186
+ quote_token: FormattedQuote;
187
187
  }
188
188
  export interface AddOrEditTokenInput {
189
189
  token_mint: string;
@@ -228,7 +228,7 @@ function addFieldsToBasket(basket) {
228
228
  account_lut_index: oracle.accountsToLoadLutIndices[0],
229
229
  account: lookup_tables.active[oracle.accountsToLoadLutIds[0]].contents[oracle.accountsToLoadLutIndices[0]],
230
230
  num_required_accounts: oracle.oracleSettings.numRequiredAccounts,
231
- weight: oracle.oracleSettings.weight,
231
+ weight_bps: oracle.oracleSettings.weight,
232
232
  is_required: oracle.oracleSettings.isRequired == 1 ? true : false,
233
233
  conf_thresh_bps: oracle.oracleSettings.confThreshBps,
234
234
  volatility_thresh_bps: oracle.oracleSettings.volatilityThreshBps,
@@ -239,7 +239,7 @@ function addFieldsToBasket(basket) {
239
239
  token_decimals: oracle.oracleSettings.tokenDecimals,
240
240
  twap_seconds_ago: parseInt(oracle.oracleSettings.twapSecondsAgo.toString()),
241
241
  twap_secondary_seconds_ago: parseInt(oracle.oracleSettings.twapSecondarySecondsAgo.toString()),
242
- quote: (_b = oracle_1.QUOTES_STRINGS.get(oracle.oracleSettings.quote)) !== null && _b !== void 0 ? _b : "usd",
242
+ quote_token: (_b = oracle_1.QUOTES_STRINGS.get(oracle.oracleSettings.quote)) !== null && _b !== void 0 ? _b : "usd",
243
243
  side: oracle.oracleSettings.side == 0 ? "base" : "quote",
244
244
  },
245
245
  accounts_to_load_lut_ids: oracle.accountsToLoadLutIds.map(id => id),
@@ -271,6 +271,7 @@ function addFieldsToBasket(basket) {
271
271
  active_withdraws: parseInt(basket.settings.activeWithdraws.toString()),
272
272
  active_managements: parseInt(basket.settings.activeManagements.toString()),
273
273
  last_automation_execution_timestamp: parseInt(basket.settings.lastAutomationExecutionTimestamp.toString()),
274
+ creation_timestamp: parseInt(basket.settings.creationTimestamp.toString()),
274
275
  creator_settings: creator_settings,
275
276
  manager_settings: manager_settings,
276
277
  fee_settings: fee_settings,
@@ -183,7 +183,7 @@ function decodeTaskDataForType(intent) {
183
183
  account_lut_index: oracle.accountsToLoadLutIndices[0],
184
184
  account: "",
185
185
  num_required_accounts: oracle.oracleSettings.numRequiredAccounts,
186
- weight: oracle.oracleSettings.weight,
186
+ weight_bps: oracle.oracleSettings.weight,
187
187
  is_required: oracle.oracleSettings.isRequired == 1 ? true : false,
188
188
  conf_thresh_bps: oracle.oracleSettings.confThreshBps,
189
189
  volatility_thresh_bps: oracle.oracleSettings.volatilityThreshBps,
@@ -194,7 +194,7 @@ function decodeTaskDataForType(intent) {
194
194
  token_decimals: oracle.oracleSettings.tokenDecimals,
195
195
  twap_seconds_ago: parseInt(oracle.oracleSettings.twapSecondsAgo.toString()),
196
196
  twap_secondary_seconds_ago: parseInt(oracle.oracleSettings.twapSecondarySecondsAgo.toString()),
197
- quote: (_b = oracle_1.QUOTES_STRINGS.get(oracle.oracleSettings.quote)) !== null && _b !== void 0 ? _b : "usd",
197
+ quote_token: (_b = oracle_1.QUOTES_STRINGS.get(oracle.oracleSettings.quote)) !== null && _b !== void 0 ? _b : "usd",
198
198
  });
199
199
  }),
200
200
  };
@@ -27,6 +27,7 @@ const txUtils_1 = require("../../txUtils");
27
27
  const bn_js_1 = __importDefault(require("bn.js"));
28
28
  const basket_1 = require("../basket");
29
29
  const decimal_js_1 = __importDefault(require("decimal.js"));
30
+ const pythOracle_1 = require("../oracles/pythOracle");
30
31
  function computeRebalanceIntentBountyAmount(rebalance_type, num_tokens, bounty_bond, bounty_per_task, bounty_per_price_update_task_max) {
31
32
  let num_tasks = 0;
32
33
  num_tasks += 1; // FinishPriceUpdates;
@@ -684,6 +685,10 @@ function isRebalanceRequired(basket, connection) {
684
685
  return __awaiter(this, void 0, void 0, function* () {
685
686
  if (basket.settings.automation.allowAutomation !== 1)
686
687
  return false;
688
+ if (parseInt(basket.settings.activeRebalance.toString()) > 0)
689
+ return false;
690
+ if (parseInt(basket.settings.bountyBalance.toString()) == 0)
691
+ return false;
687
692
  let cycleTimestamp = new bn_js_1.default(0);
688
693
  let currentTimestamp = new bn_js_1.default(Math.floor(Date.now() / 1000));
689
694
  if (basket.settings.schedule.cycleStartTime.gt(currentTimestamp))
@@ -698,7 +703,10 @@ function isRebalanceRequired(basket, connection) {
698
703
  .add(basket.settings.automation.rebalanceActivationCooldown);
699
704
  if (nextRebalanceTimestamp.gte(currentTimestamp))
700
705
  return false;
701
- basket = yield (0, basket_1.loadBasketPrice)(basket, connection);
706
+ const pythPriceOverrides = yield (0, pythOracle_1.buildFreshBasketPythPriceOverrides)(basket, connection);
707
+ basket = pythPriceOverrides.size > 0
708
+ ? yield (0, basket_1.loadBasketPriceWithPythPriceOverrides)(basket, connection, pythPriceOverrides)
709
+ : yield (0, basket_1.loadBasketPrice)(basket, connection);
702
710
  let basketTvl = new decimal_js_1.default(0);
703
711
  let weightSum = 0;
704
712
  for (let i = 0; i < basket.numTokens; i++) {
@@ -62,13 +62,15 @@ class PriceAggregator {
62
62
  const oracleData = agg.oracles[i];
63
63
  const settings = oracleData.oracleSettings;
64
64
  let loadedAccounts = [];
65
- let loadedAccountPubkeys = [];
65
+ let firstLoadedPubkey;
66
66
  for (let j = 0; j < settings.numRequiredAccounts; j++) {
67
67
  const lutId = oracleData.accountsToLoadLutIds[j];
68
68
  const lutIdx = oracleData.accountsToLoadLutIndices[j];
69
69
  const pubkey = lutAccounts[lutId].state.addresses[lutIdx];
70
70
  const account = accountInfoMap.get(pubkey.toBase58());
71
- loadedAccountPubkeys.push(pubkey);
71
+ if (!firstLoadedPubkey) {
72
+ firstLoadedPubkey = pubkey;
73
+ }
72
74
  // @ts-ignore
73
75
  loadedAccounts.push(account);
74
76
  }
@@ -93,8 +95,8 @@ class PriceAggregator {
93
95
  let result = (() => {
94
96
  switch (settings.oracleType) {
95
97
  case oracle_1.OracleType.Pyth:
96
- if (loadedAccountPubkeys.length > 0) {
97
- const override = pythPriceOverrides === null || pythPriceOverrides === void 0 ? void 0 : pythPriceOverrides.get(loadedAccountPubkeys[0].toBase58());
98
+ if (firstLoadedPubkey) {
99
+ const override = pythPriceOverrides === null || pythPriceOverrides === void 0 ? void 0 : pythPriceOverrides.get(firstLoadedPubkey.toBase58());
98
100
  if (override) {
99
101
  return override;
100
102
  }
@@ -4,6 +4,7 @@ import { AccountInfo, Connection, Keypair, PublicKey, TransactionInstruction } f
4
4
  import { OracleSettings } from '../../layouts/oracle';
5
5
  import { TxBatchData } from '../../txUtils';
6
6
  import { OraclePrice } from './oracle';
7
+ import { Basket } from '../../layouts/basket';
7
8
  export interface HermesPythPriceData {
8
9
  price: string | number;
9
10
  conf: string | number;
@@ -24,6 +25,7 @@ export declare function parseVerificationLevel(buf: Buffer, offset: number): {
24
25
  level: VerificationLevel;
25
26
  size: number;
26
27
  };
28
+ export declare function buildFreshBasketPythPriceOverrides(basket: Basket, connection: Connection): Promise<Map<string, OraclePrice>>;
27
29
  export declare const TIP_ACCOUNTS: string[];
28
30
  export declare function getRandomTipAccount(): PublicKey;
29
31
  export declare function buildJitoTipInstruction(payer: PublicKey, lamports: number): TransactionInstruction;
@@ -16,6 +16,7 @@ exports.PythOracle = exports.PythState = exports.PriceFeedMessage = exports.TIP_
16
16
  exports.fetchHermesPythPrices = fetchHermesPythPrices;
17
17
  exports.buildPythOraclePriceFromHermesPriceData = buildPythOraclePriceFromHermesPriceData;
18
18
  exports.parseVerificationLevel = parseVerificationLevel;
19
+ exports.buildFreshBasketPythPriceOverrides = buildFreshBasketPythPriceOverrides;
19
20
  exports.getRandomTipAccount = getRandomTipAccount;
20
21
  exports.buildJitoTipInstruction = buildJitoTipInstruction;
21
22
  exports.getPythPriceFeedAccountAddress = getPythPriceFeedAccountAddress;
@@ -131,6 +132,77 @@ function parseVerificationLevel(buf, offset) {
131
132
  throw new Error(`Unknown verification level: ${discr}`);
132
133
  }
133
134
  }
135
+ function buildFreshBasketPythPriceOverrides(basket, connection) {
136
+ return __awaiter(this, void 0, void 0, function* () {
137
+ var _a, _b;
138
+ const uniqueAccounts = new Map();
139
+ const settingsByAccount = new Map();
140
+ const addPythAccount = (pubkey, settings) => {
141
+ const key = pubkey.toBase58();
142
+ if (!uniqueAccounts.has(key)) {
143
+ uniqueAccounts.set(key, pubkey);
144
+ }
145
+ if (!settingsByAccount.has(key)) {
146
+ settingsByAccount.set(key, settings);
147
+ }
148
+ };
149
+ for (let i = 0; i < basket.numTokens; i++) {
150
+ const agg = basket.composition[i].oracleAggregator;
151
+ for (let j = 0; j < agg.numOracles; j++) {
152
+ const oracleData = agg.oracles[j];
153
+ if (oracleData.oracleSettings.oracleType !== oracle_1.OracleType.Pyth) {
154
+ continue;
155
+ }
156
+ const lutId = oracleData.accountsToLoadLutIds[0];
157
+ const lutIdx = oracleData.accountsToLoadLutIndices[0];
158
+ const pubkey = (_b = (_a = basket.lutPubkeys) === null || _a === void 0 ? void 0 : _a[lutId]) === null || _b === void 0 ? void 0 : _b.state.addresses[lutIdx];
159
+ if (!pubkey) {
160
+ continue;
161
+ }
162
+ addPythAccount(pubkey, oracleData.oracleSettings);
163
+ }
164
+ }
165
+ addPythAccount(constants_1.PYTHNET_CUSTODY_PRICE_WSOL_ACCOUNT, oracle_1.PYTH_WSOL_ORACLE_SETTINGS);
166
+ addPythAccount(constants_1.PYTHNET_CUSTODY_PRICE_USDC_ACCOUNT, oracle_1.PYTH_USDC_ORACLE_SETTINGS);
167
+ if (uniqueAccounts.size === 0) {
168
+ return new Map();
169
+ }
170
+ const { feedIds, feedIdToAccount } = yield fetchFeedIdsFromAccounts(connection, Array.from(uniqueAccounts.values()));
171
+ const hermesPrices = yield fetchHermesPythPrices(feedIds);
172
+ const unitQuotePrice = new oracle_2.OraclePrice(new decimal_js_1.default(1), new decimal_js_1.default(0), 0, true);
173
+ const wsolAccountKey = constants_1.PYTHNET_CUSTODY_PRICE_WSOL_ACCOUNT.toBase58();
174
+ const usdcAccountKey = constants_1.PYTHNET_CUSTODY_PRICE_USDC_ACCOUNT.toBase58();
175
+ let wsolPrice = unitQuotePrice;
176
+ let usdcPrice = unitQuotePrice;
177
+ for (const [feedId, account] of feedIdToAccount.entries()) {
178
+ const accountKey = account.toBase58();
179
+ const hermesPrice = hermesPrices.get(feedId);
180
+ if (!hermesPrice) {
181
+ continue;
182
+ }
183
+ if (accountKey === wsolAccountKey) {
184
+ wsolPrice = buildPythOraclePriceFromHermesPriceData(oracle_1.PYTH_WSOL_ORACLE_SETTINGS, hermesPrice, unitQuotePrice, unitQuotePrice);
185
+ }
186
+ else if (accountKey === usdcAccountKey) {
187
+ usdcPrice = buildPythOraclePriceFromHermesPriceData(oracle_1.PYTH_USDC_ORACLE_SETTINGS, hermesPrice, unitQuotePrice, unitQuotePrice);
188
+ }
189
+ }
190
+ const overrides = new Map();
191
+ for (const [feedId, hermesPrice] of hermesPrices.entries()) {
192
+ const account = feedIdToAccount.get(feedId);
193
+ if (!account) {
194
+ continue;
195
+ }
196
+ const accountKey = account.toBase58();
197
+ const settings = settingsByAccount.get(accountKey);
198
+ if (!settings) {
199
+ continue;
200
+ }
201
+ overrides.set(accountKey, buildPythOraclePriceFromHermesPriceData(settings, hermesPrice, wsolPrice, usdcPrice));
202
+ }
203
+ return overrides;
204
+ });
205
+ }
134
206
  // Pyth program IDs (mainnet)
135
207
  const DEFAULT_RECEIVER_PROGRAM_ID = new web3_js_1.PublicKey("rec5EKMGg6MxZYaMdyBfgwp4d5rB9T1VQH5pJv5LtFJ");
136
208
  const DEFAULT_PUSH_ORACLE_PROGRAM_ID = new web3_js_1.PublicKey("pythWSnswVUd12oZpeFP8e9CVaEqJg25g1Vtc2biRsT");
package/dist/test.js CHANGED
@@ -49,7 +49,7 @@ function testStates() {
49
49
  console.log("Total intents: ", (yield sdk.fetchAllIntents()).length);
50
50
  console.log("Total rebalance intents: ", (yield sdk.fetchAllRebalanceIntents()).length);
51
51
  console.log("Total withdraw basket fees: ", (yield sdk.fetchAllWithdrawBasketFees()).length);
52
- let basket = yield sdk.fetchBasket("GrBFFvtdRL25o7gcRnV1kGvz1Qc7iscUmDp1ZvyBSyUa");
52
+ let basket = yield sdk.fetchBasket("C2SpNsmPB91ne4JdQRYZZdTJXkMLWyHfMSaZCS9nB33J");
53
53
  basket = yield sdk.loadBasketPrice(basket);
54
54
  // let ri =(await sdk.fetchAllRebalanceIntents())[0];
55
55
  // ri = await sdk.fetchRebalanceIntent(ri.formatted_data.pubkey);
@@ -298,7 +298,7 @@ function testStates() {
298
298
  account_lut_id: 0,
299
299
  account_lut_index: 0,
300
300
  account: constants_1.PYTHNET_CUSTODY_PRICE_WSOL_ACCOUNT.toBase58(),
301
- weight: 100,
301
+ weight_bps: 10000,
302
302
  is_required: true,
303
303
  conf_thresh_bps: 200,
304
304
  volatility_thresh_bps: 200,
@@ -309,7 +309,7 @@ function testStates() {
309
309
  token_decimals: 9,
310
310
  twap_seconds_ago: 100,
311
311
  twap_secondary_seconds_ago: 100,
312
- quote: "usd",
312
+ quote_token: "usd",
313
313
  },
314
314
  ],
315
315
  };
@@ -480,7 +480,7 @@ function testStates() {
480
480
  if (tests.claimTokenFeesFromBasket) {
481
481
  let tx = yield sdk.claimTokenFeesFromBasketTx({
482
482
  claimer: wallet.publicKey.toBase58(),
483
- withdrawBasketFees: new web3_js_1.PublicKey("").toBase58(),
483
+ withdrawBasketFees: new web3_js_1.PublicKey("CiX2zEUhWRwirCwsfst49PeFoQXTthcjxj8YnFoYGQ4Y").toBase58(),
484
484
  });
485
485
  let res = yield sdk.signAndSendTxPayloadBatchSequence({ txPayloadBatchSequence: tx, wallet });
486
486
  console.log(res);
@@ -506,17 +506,17 @@ function testStates() {
506
506
  maxWithdrawFeeBps: 1000,
507
507
  maxManagementFeeBps: 2000,
508
508
  maxPerformanceFeeBps: 2000,
509
- symmetryFeeCollector: new web3_js_1.PublicKey("UserevMsvU5K9u6iW7DT9XJVyVLpmfDCEAfXixBbE7R"),
510
- symmetryDepositFeeBps: 10,
511
- symmetryDepositFeeShareBps: 50,
512
- symmetryWithdrawFeeBps: 50,
513
- symmetryWithdrawFeeShareBps: 50,
514
- symmetryManagementFeeBps: 50,
515
- symmetryManagementFeeShareBps: 50,
516
- symmetryPerformanceFeeBps: 50,
517
- symmetryPerformanceFeeShareBps: 50,
518
- symmetryTradeFeeBps: 50,
519
- symmetryLimitOrderFeeBps: 50,
509
+ symmetryFeeCollector: new web3_js_1.PublicKey("9A5V7smsUMRNNzvrawbDx3ZexZR3LY1bcEUXUiMJ2bxk"),
510
+ symmetryDepositFeeBps: 0,
511
+ symmetryDepositFeeShareBps: 0,
512
+ symmetryWithdrawFeeBps: 0,
513
+ symmetryWithdrawFeeShareBps: 0,
514
+ symmetryManagementFeeBps: 0,
515
+ symmetryManagementFeeShareBps: 0,
516
+ symmetryPerformanceFeeBps: 0,
517
+ symmetryPerformanceFeeShareBps: 0,
518
+ symmetryTradeFeeBps: 0,
519
+ symmetryLimitOrderFeeBps: 0,
520
520
  symmetryFeesExtraData: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,],
521
521
  bountyMint: new web3_js_1.PublicKey("So11111111111111111111111111111111111111112"),
522
522
  minBountyForBasketAutomation: new anchor_1.BN(200 * 50 * 10 ** 3),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symmetry-hq/sdk",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
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
@@ -68,7 +68,7 @@ import {
68
68
  import { FormattedOracleType, OracleType } from './layouts/oracle';
69
69
  import {
70
70
  BasketFilter, computeTokenMintsHash, fetchBasket, fetchBaskets, fetchBasketsMultiple,
71
- loadBasketPrice, loadBasketPriceWithPythPriceOverrides
71
+ loadBasketPrice
72
72
  } from './states/basket';
73
73
  import { fetchGlobalConfig } from './states/config';
74
74
  import {
@@ -76,9 +76,8 @@ import {
76
76
  } from './states/intents/intent';
77
77
  import {
78
78
  computeRebalanceIntentBountyAmount, fetchRebalanceIntent, fetchRebalanceIntents,
79
- fetchRebalanceIntentsMultiple, getSwapPairs, RebalanceIntentFilter, isRebalanceRequired
79
+ fetchRebalanceIntentsMultiple, getSwapPairs, isRebalanceRequired, RebalanceIntentFilter
80
80
  } from './states/intents/rebalanceIntent';
81
- import { OraclePrice } from './states/oracles/oracle';
82
81
  import { buildPythPriceFeedUpdateIxs, fetchFeedIdsFromAccounts } from './states/oracles/pythOracle';
83
82
  import {
84
83
  fetchWithdrawBasketFees, fetchWithdrawBasketFeesList, fetchWithdrawBasketFeesMultiple,
@@ -238,101 +237,6 @@ export class SymmetryCore {
238
237
  return await loadBasketPrice(basket, this.sdkParams.connection);
239
238
  }
240
239
 
241
- async loadBasketPriceWithPythPriceOverrides(params: {
242
- basket: Basket,
243
- pyth_price_overrides?: Map<string, OraclePrice>,
244
- }): Promise<Basket> {
245
- return await loadBasketPriceWithPythPriceOverrides(
246
- params.basket,
247
- this.sdkParams.connection,
248
- params.pyth_price_overrides,
249
- );
250
- }
251
-
252
- /**
253
- * Checks whether a basket currently meets the automation rebalance trigger.
254
- * Uses the existing basket pricing flow.
255
- */
256
- async isBasketRebalanceRequired(params: {
257
- basket: string | Basket,
258
- pyth_price_overrides?: Map<string, OraclePrice>,
259
- }): Promise<boolean> {
260
- let basket = typeof params.basket === 'string'
261
- ? await this.fetchBasket(params.basket)
262
- : params.basket;
263
-
264
- if (!basket.formatted?.automation_settings.enabled && basket.settings.automation.allowAutomation !== 1) {
265
- return false;
266
- }
267
-
268
- if (params.pyth_price_overrides && params.pyth_price_overrides.size > 0) {
269
- basket = await this.loadBasketPriceWithPythPriceOverrides({
270
- basket,
271
- pyth_price_overrides: params.pyth_price_overrides,
272
- });
273
- } else {
274
- basket = await this.loadBasketPrice(basket);
275
- }
276
-
277
- return this.computeBasketRebalanceRequiredFromPricedBasket(basket);
278
- }
279
-
280
- private computeBasketRebalanceRequiredFromPricedBasket(basket: Basket): boolean {
281
- const basketTvl = basket.tvl ?? basket.composition
282
- .slice(0, basket.numTokens)
283
- .reduce((sum, asset) => {
284
- const value = asset.value ?? asset.price?.getMid().mul(new Decimal(asset.amount.toString())) ?? new Decimal(0);
285
- return sum.add(value);
286
- }, new Decimal(0));
287
-
288
- if (!basketTvl.isFinite() || basketTvl.lte(0)) {
289
- return false;
290
- }
291
-
292
- const weightSum = basket.composition
293
- .slice(0, basket.numTokens)
294
- .reduce((sum, asset) => sum + asset.weight, 0);
295
- if (weightSum <= 0) {
296
- return false;
297
- }
298
-
299
- const requiredRelBps = new Decimal(basket.settings.automation.rebalanceActivationThresholdRelBps);
300
- const requiredAbsBps = new Decimal(basket.settings.automation.rebalanceActivationThresholdAbsBps);
301
- const bountyMint = basket.settings.bountyMint;
302
- const hundredPercentBps = new Decimal(10_000);
303
-
304
- for (const asset of basket.composition.slice(0, basket.numTokens)) {
305
- if (asset.mint.equals(bountyMint)) {
306
- continue;
307
- }
308
-
309
- const tokenPrice = asset.price?.getMid();
310
- if (!tokenPrice || !tokenPrice.isFinite() || tokenPrice.lte(0)) {
311
- continue;
312
- }
313
-
314
- const tokenValue = asset.value ?? tokenPrice.mul(new Decimal(asset.amount.toString()));
315
- const targetValue = basketTvl.mul(asset.weight).div(weightSum);
316
- const valueDiff = tokenValue.sub(targetValue).abs();
317
- if (valueDiff.eq(0)) {
318
- continue;
319
- }
320
-
321
- const maxValue = Decimal.max(tokenValue, targetValue);
322
- if (!maxValue.isFinite() || maxValue.lte(0)) {
323
- continue;
324
- }
325
-
326
- const relBps = valueDiff.div(maxValue).mul(hundredPercentBps);
327
- const absBps = valueDiff.div(basketTvl).mul(hundredPercentBps);
328
- if (relBps.gte(requiredRelBps) && absBps.gte(requiredAbsBps)) {
329
- return true;
330
- }
331
- }
332
-
333
- return false;
334
- }
335
-
336
240
  /**
337
241
  * Fetches an intent by its public key.
338
242
  * @param {string} intentPubkey - The public key of the intent.
@@ -2088,13 +1992,48 @@ export class SymmetryCore {
2088
1992
  }): Promise<TxPayloadBatchSequence> {
2089
1993
  let keeper = new PublicKey(params.keeper);
2090
1994
  let rebalanceIntent: RebalanceIntent = (await this.fetchRebalanceIntent(params.rebalance_intent)).chain_data;
1995
+ const tokensWithBalance = rebalanceIntent.tokens.filter(token => token.amount.gt(new BN(0)));
1996
+ const ownerIsKeeper = rebalanceIntent.owner.equals(keeper);
1997
+
1998
+ let redeemableTokenMints = tokensWithBalance.map(token => token.mint);
1999
+ if (!ownerIsKeeper && tokensWithBalance.length > 0) {
2000
+ const mintInfos = await this.sdkParams.connection.getMultipleAccountsInfo(tokensWithBalance.map(token => token.mint));
2001
+ const ataChecks = tokensWithBalance.map((token, index) => {
2002
+ const mintInfo = mintInfos[index];
2003
+ if (!mintInfo) {
2004
+ throw new Error(`Mint account not found for redeem token ${token.mint.toBase58()}`);
2005
+ }
2006
+
2007
+ let tokenProgram: PublicKey;
2008
+ if (mintInfo.owner.equals(TOKEN_PROGRAM_ID) || mintInfo.owner.equals(TOKEN_2022_PROGRAM_ID)) {
2009
+ tokenProgram = mintInfo.owner;
2010
+ } else {
2011
+ throw new Error(`Unsupported token program for redeem token ${token.mint.toBase58()}: ${mintInfo.owner.toBase58()}`);
2012
+ }
2013
+
2014
+ return {
2015
+ mint: token.mint,
2016
+ ata: getAta(rebalanceIntent.owner, token.mint, tokenProgram),
2017
+ };
2018
+ });
2019
+
2020
+ const ataInfos = await this.sdkParams.connection.getMultipleAccountsInfo(ataChecks.map(item => item.ata));
2021
+ redeemableTokenMints = ataChecks
2022
+ .filter((_, index) => ataInfos[index] !== null)
2023
+ .map(item => item.mint);
2024
+
2025
+ if (redeemableTokenMints.length === 0) {
2026
+ const blockedMints = ataChecks.map(item => item.mint.toBase58()).join(', ');
2027
+ throw new Error(
2028
+ `NoRedeemableTokens: owner ${rebalanceIntent.owner.toBase58()} has no ATA for remaining tokens [${blockedMints}]`
2029
+ );
2030
+ }
2031
+ }
2032
+
2091
2033
  let batchSize = 7;
2092
2034
  let ixs: TransactionInstruction[] = [];
2093
- for (let batchStart = 0; batchStart < rebalanceIntent.tokens.length; batchStart += batchSize) {
2094
- let tokenMints: PublicKey[] = [];
2095
- for (let i = batchStart; i < rebalanceIntent.tokens.length && i < batchStart + batchSize; i++)
2096
- if (rebalanceIntent.tokens[i].amount.gt(new BN(0)))
2097
- tokenMints.push(rebalanceIntent.tokens[i].mint);
2035
+ for (let batchStart = 0; batchStart < redeemableTokenMints.length; batchStart += batchSize) {
2036
+ let tokenMints = redeemableTokenMints.slice(batchStart, batchStart + batchSize);
2098
2037
  if (tokenMints.length > 0) {
2099
2038
  ixs.push(
2100
2039
  redeemTokensIx({
@@ -2106,6 +2045,7 @@ export class SymmetryCore {
2106
2045
  );
2107
2046
  }
2108
2047
  }
2048
+
2109
2049
  let txBatchData: TxBatchData = {batches: [ixs.map(ix => ({
2110
2050
  payer: keeper,
2111
2051
  instructions: [
@@ -10,7 +10,7 @@ import {
10
10
 
11
11
  const WITHDRAW_FEES_DISCRIMINATOR = Buffer.from([159, 222, 146, 66, 254, 108, 55, 51]);
12
12
 
13
- const CLAIM_FEE_TOKENS_FROM_BASKET_DISCRIMINATOR = Buffer.from([53, 109, 220, 141, 193, 193, 13, 32]);
13
+ const CLAIM_FEE_TOKENS_FROM_BASKET_DISCRIMINATOR = Buffer.from([53, 109, 220, 141, 110, 5, 193, 32]);
14
14
 
15
15
  export function withdrawFeesIx(params: {
16
16
  claimer: PublicKey,
@@ -350,7 +350,7 @@ export function createEditBasketIntentIx(params: {
350
350
  oracleSettings: {
351
351
  oracleType: [...ORACLE_TYPES_STRINGS.entries()].find(([, name]) => name === addTokenData.oracles[i].oracle_type)?.[0] ?? 255,
352
352
  numRequiredAccounts: 0,
353
- weight: addTokenData.oracles[i].weight,
353
+ weight: addTokenData.oracles[i].weight_bps,
354
354
  isRequired: addTokenData.oracles[i].is_required ? 1 : 0,
355
355
  confThreshBps: addTokenData.oracles[i].conf_thresh_bps,
356
356
  volatilityThreshBps: addTokenData.oracles[i].volatility_thresh_bps,
@@ -361,7 +361,7 @@ export function createEditBasketIntentIx(params: {
361
361
  tokenDecimals: addTokenData.oracles[i].token_decimals,
362
362
  twapSecondsAgo: new BN(addTokenData.oracles[i].twap_seconds_ago),
363
363
  twapSecondarySecondsAgo: new BN(addTokenData.oracles[i].twap_secondary_seconds_ago),
364
- quote: [...QUOTES_STRINGS.entries()].find(([, name]) => name === addTokenData.oracles[i].quote)?.[0] ?? Quote.Usd,
364
+ quote: [...QUOTES_STRINGS.entries()].find(([, name]) => name === addTokenData.oracles[i].quote_token)?.[0] ?? Quote.Usd,
365
365
  side: Side.Base,
366
366
  },
367
367
  accountsToLoadLutIds: new Array(MAX_ACCOUNTS_PER_ORACLE).fill(0),
@@ -102,6 +102,7 @@ export interface FormattedBasket {
102
102
  active_withdraws: number;
103
103
  active_managements: number;
104
104
  last_automation_execution_timestamp: number;
105
+ creation_timestamp: number;
105
106
  creator_settings: FormattedCreatorSettings;
106
107
  manager_settings: FormattedManagersSettings;
107
108
  fee_settings: FormattedFeeSettings;
@@ -350,6 +350,7 @@ export interface BasketSettings {
350
350
  makeDirectSwapIntentAuthorityBitmask: number; // u16,
351
351
 
352
352
  extraData: BN[]; // [u64; 32],
353
+ creationTimestamp: BN; // u64,
353
354
  }
354
355
 
355
356
  export const BasketSettingsLayout = struct<BasketSettings>([
@@ -420,7 +421,8 @@ export const BasketSettingsLayout = struct<BasketSettings>([
420
421
  u64('makeDirectSwapLastUpdateTimestamp'),
421
422
  u16('makeDirectSwapIntentAuthorityBitmask'),
422
423
 
423
- array(u64(), 32, 'extraData'),
424
+ array(u64(), 31, 'extraData'),
425
+ u64('creationTimestamp'),
424
426
  ]);
425
427
 
426
428
 
@@ -265,7 +265,7 @@ export interface OracleInput {
265
265
  account_lut_id: number,
266
266
  account_lut_index: number,
267
267
  account: string,
268
- weight: number,
268
+ weight_bps: number,
269
269
  is_required: boolean,
270
270
  conf_thresh_bps: number,
271
271
  volatility_thresh_bps: number,
@@ -276,7 +276,7 @@ export interface OracleInput {
276
276
  token_decimals: number,
277
277
  twap_seconds_ago: number,
278
278
  twap_secondary_seconds_ago: number,
279
- quote: FormattedQuote,
279
+ quote_token: FormattedQuote,
280
280
  }
281
281
 
282
282
  export interface AddOrEditTokenInput {
@@ -226,7 +226,7 @@ export function addFieldsToBasket(basket: Basket): Basket {
226
226
  account_lut_index: oracle.accountsToLoadLutIndices[0],
227
227
  account: lookup_tables.active[oracle.accountsToLoadLutIds[0]].contents[oracle.accountsToLoadLutIndices[0]],
228
228
  num_required_accounts: oracle.oracleSettings.numRequiredAccounts,
229
- weight: oracle.oracleSettings.weight,
229
+ weight_bps: oracle.oracleSettings.weight,
230
230
  is_required: oracle.oracleSettings.isRequired == 1 ? true : false,
231
231
  conf_thresh_bps: oracle.oracleSettings.confThreshBps,
232
232
  volatility_thresh_bps: oracle.oracleSettings.volatilityThreshBps,
@@ -237,7 +237,7 @@ export function addFieldsToBasket(basket: Basket): Basket {
237
237
  token_decimals: oracle.oracleSettings.tokenDecimals,
238
238
  twap_seconds_ago: parseInt(oracle.oracleSettings.twapSecondsAgo.toString()),
239
239
  twap_secondary_seconds_ago: parseInt(oracle.oracleSettings.twapSecondarySecondsAgo.toString()),
240
- quote: QUOTES_STRINGS.get(oracle.oracleSettings.quote) ?? "usd",
240
+ quote_token: QUOTES_STRINGS.get(oracle.oracleSettings.quote) ?? "usd",
241
241
  side: oracle.oracleSettings.side == 0 ? "base" : "quote",
242
242
  },
243
243
  accounts_to_load_lut_ids: oracle.accountsToLoadLutIds.map(id => id),
@@ -270,6 +270,7 @@ export function addFieldsToBasket(basket: Basket): Basket {
270
270
  active_withdraws: parseInt(basket.settings.activeWithdraws.toString()),
271
271
  active_managements: parseInt(basket.settings.activeManagements.toString()),
272
272
  last_automation_execution_timestamp: parseInt(basket.settings.lastAutomationExecutionTimestamp.toString()),
273
+ creation_timestamp: parseInt(basket.settings.creationTimestamp.toString()),
273
274
  creator_settings: creator_settings,
274
275
  manager_settings: manager_settings,
275
276
  fee_settings: fee_settings,
@@ -170,7 +170,7 @@ function decodeTaskDataForType(intent: Intent): Settings {
170
170
  account_lut_index: oracle.accountsToLoadLutIndices[0],
171
171
  account: "",
172
172
  num_required_accounts: oracle.oracleSettings.numRequiredAccounts,
173
- weight: oracle.oracleSettings.weight,
173
+ weight_bps: oracle.oracleSettings.weight,
174
174
  is_required: oracle.oracleSettings.isRequired == 1 ? true : false,
175
175
  conf_thresh_bps: oracle.oracleSettings.confThreshBps,
176
176
  volatility_thresh_bps: oracle.oracleSettings.volatilityThreshBps,
@@ -181,7 +181,7 @@ function decodeTaskDataForType(intent: Intent): Settings {
181
181
  token_decimals: oracle.oracleSettings.tokenDecimals,
182
182
  twap_seconds_ago: parseInt(oracle.oracleSettings.twapSecondsAgo.toString()),
183
183
  twap_secondary_seconds_ago: parseInt(oracle.oracleSettings.twapSecondarySecondsAgo.toString()),
184
- quote: QUOTES_STRINGS.get(oracle.oracleSettings.quote) ?? "usd",
184
+ quote_token: QUOTES_STRINGS.get(oracle.oracleSettings.quote) ?? "usd",
185
185
  })),
186
186
  }
187
187
  return add_token_input;
@@ -7,8 +7,9 @@ import { getMultipleAccountsInfoBatched } from "../../txUtils";
7
7
  import BN from "bn.js";
8
8
  import { Basket, FormattedBasket } from "../../layouts/basket";
9
9
  import { OraclePriceOnChain } from "../../layouts/oracle";
10
- import { fetchBasket, loadBasketPrice } from "../basket";
10
+ import { fetchBasket, loadBasketPrice, loadBasketPriceWithPythPriceOverrides } from "../basket";
11
11
  import Decimal from "decimal.js";
12
+ import { buildFreshBasketPythPriceOverrides } from "../oracles/pythOracle";
12
13
 
13
14
  export function computeRebalanceIntentBountyAmount(
14
15
  rebalance_type: RebalanceType,
@@ -756,6 +757,8 @@ export async function isRebalanceRequired(
756
757
  connection: Connection,
757
758
  ): Promise<boolean> {
758
759
  if (basket.settings.automation.allowAutomation !== 1) return false;
760
+ if (parseInt(basket.settings.activeRebalance.toString()) > 0) return false;
761
+ if (parseInt(basket.settings.bountyBalance.toString()) == 0) return false;
759
762
  let cycleTimestamp = new BN(0);
760
763
  let currentTimestamp = new BN(Math.floor(Date.now() / 1000));
761
764
  if (basket.settings.schedule.cycleStartTime.gt(currentTimestamp))
@@ -773,7 +776,10 @@ export async function isRebalanceRequired(
773
776
  if (nextRebalanceTimestamp.gte(currentTimestamp))
774
777
  return false;
775
778
 
776
- basket = await loadBasketPrice(basket, connection);
779
+ const pythPriceOverrides = await buildFreshBasketPythPriceOverrides(basket, connection);
780
+ basket = pythPriceOverrides.size > 0
781
+ ? await loadBasketPriceWithPythPriceOverrides(basket, connection, pythPriceOverrides)
782
+ : await loadBasketPrice(basket, connection);
777
783
  let basketTvl = new Decimal(0);
778
784
  let weightSum = 0;
779
785
  for (let i = 0; i < basket.numTokens; i++) {
@@ -93,13 +93,15 @@ export class PriceAggregator{
93
93
  const settings = oracleData.oracleSettings;
94
94
 
95
95
  let loadedAccounts: AccountInfo<Buffer>[] = [];
96
- let loadedAccountPubkeys: PublicKey[] = [];
96
+ let firstLoadedPubkey: PublicKey | undefined;
97
97
  for (let j = 0; j < settings.numRequiredAccounts; j++) {
98
98
  const lutId = oracleData.accountsToLoadLutIds[j];
99
99
  const lutIdx = oracleData.accountsToLoadLutIndices[j];
100
100
  const pubkey = lutAccounts[lutId].state.addresses[lutIdx];
101
101
  const account = accountInfoMap.get(pubkey.toBase58());
102
- loadedAccountPubkeys.push(pubkey);
102
+ if (!firstLoadedPubkey) {
103
+ firstLoadedPubkey = pubkey;
104
+ }
103
105
  // @ts-ignore
104
106
  loadedAccounts.push(account);
105
107
  }
@@ -125,8 +127,8 @@ export class PriceAggregator{
125
127
  let result = (() => {
126
128
  switch (settings.oracleType) {
127
129
  case OracleType.Pyth:
128
- if (loadedAccountPubkeys.length > 0) {
129
- const override = pythPriceOverrides?.get(loadedAccountPubkeys[0].toBase58());
130
+ if (firstLoadedPubkey) {
131
+ const override = pythPriceOverrides?.get(firstLoadedPubkey.toBase58());
130
132
  if (override) {
131
133
  return override;
132
134
  }
@@ -7,11 +7,12 @@ import {
7
7
  SystemProgram, Transaction, TransactionInstruction, TransactionMessage, VersionedTransaction
8
8
  } from '@solana/web3.js';
9
9
 
10
- import { HUNDRED_PERCENT_BPS } from '../../constants';
11
- import { OracleSettings, Quote } from '../../layouts/oracle';
10
+ import { HUNDRED_PERCENT_BPS, PYTHNET_CUSTODY_PRICE_USDC_ACCOUNT, PYTHNET_CUSTODY_PRICE_WSOL_ACCOUNT } from '../../constants';
11
+ import { OracleSettings, OracleType, PYTH_USDC_ORACLE_SETTINGS, PYTH_WSOL_ORACLE_SETTINGS, Quote } from '../../layouts/oracle';
12
12
  import { TxBatchData, TxData } from '../../txUtils';
13
13
  import { HERMES_PUBLIC_ENDPOINT, SOLANA_DEVNET_ENDPOINT } from './constants';
14
14
  import { OraclePrice } from './oracle';
15
+ import { Basket } from '../../layouts/basket';
15
16
 
16
17
  export interface HermesPythPriceData {
17
18
  price: string | number;
@@ -157,6 +158,107 @@ export function parseVerificationLevel(buf: Buffer, offset: number): { level: Ve
157
158
  }
158
159
  }
159
160
 
161
+ export async function buildFreshBasketPythPriceOverrides(
162
+ basket: Basket,
163
+ connection: Connection,
164
+ ): Promise<Map<string, OraclePrice>> {
165
+ const uniqueAccounts = new Map<string, PublicKey>();
166
+ const settingsByAccount = new Map<string, typeof PYTH_WSOL_ORACLE_SETTINGS>();
167
+
168
+ const addPythAccount = (pubkey: PublicKey, settings: typeof PYTH_WSOL_ORACLE_SETTINGS) => {
169
+ const key = pubkey.toBase58();
170
+ if (!uniqueAccounts.has(key)) {
171
+ uniqueAccounts.set(key, pubkey);
172
+ }
173
+ if (!settingsByAccount.has(key)) {
174
+ settingsByAccount.set(key, settings);
175
+ }
176
+ };
177
+
178
+ for (let i = 0; i < basket.numTokens; i++) {
179
+ const agg = basket.composition[i].oracleAggregator;
180
+ for (let j = 0; j < agg.numOracles; j++) {
181
+ const oracleData = agg.oracles[j];
182
+ if (oracleData.oracleSettings.oracleType !== OracleType.Pyth) {
183
+ continue;
184
+ }
185
+
186
+ const lutId = oracleData.accountsToLoadLutIds[0];
187
+ const lutIdx = oracleData.accountsToLoadLutIndices[0];
188
+ const pubkey = basket.lutPubkeys?.[lutId]?.state.addresses[lutIdx];
189
+ if (!pubkey) {
190
+ continue;
191
+ }
192
+
193
+ addPythAccount(pubkey, oracleData.oracleSettings);
194
+ }
195
+ }
196
+
197
+ addPythAccount(PYTHNET_CUSTODY_PRICE_WSOL_ACCOUNT, PYTH_WSOL_ORACLE_SETTINGS);
198
+ addPythAccount(PYTHNET_CUSTODY_PRICE_USDC_ACCOUNT, PYTH_USDC_ORACLE_SETTINGS);
199
+
200
+ if (uniqueAccounts.size === 0) {
201
+ return new Map();
202
+ }
203
+
204
+ const { feedIds, feedIdToAccount } = await fetchFeedIdsFromAccounts(
205
+ connection,
206
+ Array.from(uniqueAccounts.values()),
207
+ );
208
+ const hermesPrices = await fetchHermesPythPrices(feedIds);
209
+
210
+ const unitQuotePrice = new OraclePrice(new Decimal(1), new Decimal(0), 0, true);
211
+ const wsolAccountKey = PYTHNET_CUSTODY_PRICE_WSOL_ACCOUNT.toBase58();
212
+ const usdcAccountKey = PYTHNET_CUSTODY_PRICE_USDC_ACCOUNT.toBase58();
213
+ let wsolPrice = unitQuotePrice;
214
+ let usdcPrice = unitQuotePrice;
215
+
216
+ for (const [feedId, account] of feedIdToAccount.entries()) {
217
+ const accountKey = account.toBase58();
218
+ const hermesPrice = hermesPrices.get(feedId);
219
+ if (!hermesPrice) {
220
+ continue;
221
+ }
222
+
223
+ if (accountKey === wsolAccountKey) {
224
+ wsolPrice = buildPythOraclePriceFromHermesPriceData(
225
+ PYTH_WSOL_ORACLE_SETTINGS,
226
+ hermesPrice,
227
+ unitQuotePrice,
228
+ unitQuotePrice,
229
+ );
230
+ } else if (accountKey === usdcAccountKey) {
231
+ usdcPrice = buildPythOraclePriceFromHermesPriceData(
232
+ PYTH_USDC_ORACLE_SETTINGS,
233
+ hermesPrice,
234
+ unitQuotePrice,
235
+ unitQuotePrice,
236
+ );
237
+ }
238
+ }
239
+
240
+ const overrides = new Map<string, OraclePrice>();
241
+ for (const [feedId, hermesPrice] of hermesPrices.entries()) {
242
+ const account = feedIdToAccount.get(feedId);
243
+ if (!account) {
244
+ continue;
245
+ }
246
+
247
+ const accountKey = account.toBase58();
248
+ const settings = settingsByAccount.get(accountKey);
249
+ if (!settings) {
250
+ continue;
251
+ }
252
+
253
+ overrides.set(
254
+ accountKey,
255
+ buildPythOraclePriceFromHermesPriceData(settings, hermesPrice, wsolPrice, usdcPrice),
256
+ );
257
+ }
258
+
259
+ return overrides;
260
+ }
261
+
160
262
  // Pyth program IDs (mainnet)
161
263
  const DEFAULT_RECEIVER_PROGRAM_ID = new PublicKey("rec5EKMGg6MxZYaMdyBfgwp4d5rB9T1VQH5pJv5LtFJ");
162
264
  const DEFAULT_PUSH_ORACLE_PROGRAM_ID = new PublicKey("pythWSnswVUd12oZpeFP8e9CVaEqJg25g1Vtc2biRsT");
package/test.ts CHANGED
@@ -47,7 +47,7 @@ async function testStates() {
47
47
  console.log("Total rebalance intents: ", (await sdk.fetchAllRebalanceIntents()).length);
48
48
  console.log("Total withdraw basket fees: ", (await sdk.fetchAllWithdrawBasketFees()).length);
49
49
 
50
- let basket = await sdk.fetchBasket("GrBFFvtdRL25o7gcRnV1kGvz1Qc7iscUmDp1ZvyBSyUa");
50
+ let basket = await sdk.fetchBasket("C2SpNsmPB91ne4JdQRYZZdTJXkMLWyHfMSaZCS9nB33J");
51
51
  basket = await sdk.loadBasketPrice(basket);
52
52
  // let ri =(await sdk.fetchAllRebalanceIntents())[0];
53
53
  // ri = await sdk.fetchRebalanceIntent(ri.formatted_data.pubkey);
@@ -327,7 +327,7 @@ async function testStates() {
327
327
  account_lut_id: 0,
328
328
  account_lut_index: 0,
329
329
  account: PYTHNET_CUSTODY_PRICE_WSOL_ACCOUNT.toBase58(),
330
- weight: 100,
330
+ weight_bps: 10000,
331
331
  is_required: true,
332
332
  conf_thresh_bps: 200,
333
333
  volatility_thresh_bps: 200,
@@ -338,7 +338,7 @@ async function testStates() {
338
338
  token_decimals: 9,
339
339
  twap_seconds_ago: 100,
340
340
  twap_secondary_seconds_ago: 100,
341
- quote: "usd",
341
+ quote_token: "usd",
342
342
  },
343
343
  ],
344
344
  };
@@ -518,7 +518,7 @@ async function testStates() {
518
518
  if (tests.claimTokenFeesFromBasket) {
519
519
  let tx = await sdk.claimTokenFeesFromBasketTx({
520
520
  claimer: wallet.publicKey.toBase58(),
521
- withdrawBasketFees: new PublicKey("").toBase58(),
521
+ withdrawBasketFees: new PublicKey("CiX2zEUhWRwirCwsfst49PeFoQXTthcjxj8YnFoYGQ4Y").toBase58(),
522
522
  });
523
523
  let res = await sdk.signAndSendTxPayloadBatchSequence({txPayloadBatchSequence: tx, wallet});
524
524
  console.log(res);
@@ -549,17 +549,17 @@ async function testStates() {
549
549
  maxManagementFeeBps: 2000,
550
550
  maxPerformanceFeeBps: 2000,
551
551
 
552
- symmetryFeeCollector: new PublicKey("UserevMsvU5K9u6iW7DT9XJVyVLpmfDCEAfXixBbE7R"),
553
- symmetryDepositFeeBps: 10,
554
- symmetryDepositFeeShareBps: 50,
555
- symmetryWithdrawFeeBps: 50,
556
- symmetryWithdrawFeeShareBps: 50,
557
- symmetryManagementFeeBps: 50,
558
- symmetryManagementFeeShareBps: 50,
559
- symmetryPerformanceFeeBps: 50,
560
- symmetryPerformanceFeeShareBps: 50,
561
- symmetryTradeFeeBps: 50,
562
- symmetryLimitOrderFeeBps: 50,
552
+ symmetryFeeCollector: new PublicKey("9A5V7smsUMRNNzvrawbDx3ZexZR3LY1bcEUXUiMJ2bxk"),
553
+ symmetryDepositFeeBps: 0,
554
+ symmetryDepositFeeShareBps: 0,
555
+ symmetryWithdrawFeeBps: 0,
556
+ symmetryWithdrawFeeShareBps: 0,
557
+ symmetryManagementFeeBps: 0,
558
+ symmetryManagementFeeShareBps: 0,
559
+ symmetryPerformanceFeeBps: 0,
560
+ symmetryPerformanceFeeShareBps: 0,
561
+ symmetryTradeFeeBps: 0,
562
+ symmetryLimitOrderFeeBps: 0,
563
563
  symmetryFeesExtraData: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,],
564
564
 
565
565
  bountyMint: new PublicKey("So11111111111111111111111111111111111111112"),