@symmetry-hq/sdk 1.0.11 → 1.0.13

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/index.js CHANGED
@@ -729,6 +729,12 @@ class SymmetryCore {
729
729
  let maxBountyPerTask = parseInt(maxBounty.toString());
730
730
  bountyWsolAmount = maxBountyPerTask + boundBounty;
731
731
  }
732
+ if (editType === intent_1.TaskType.EditAutomationSettings && editData.enabled == true) {
733
+ let bountyBalance = parseInt(vault.settings.bountyBalance.toString());
734
+ let requiredBounty = parseInt(globalConfig.minBountyForVaultAutomation.toString());
735
+ if (bountyBalance < requiredBounty)
736
+ bountyWsolAmount += 2 * requiredBounty - bountyBalance;
737
+ }
732
738
  let wsolIxs = yield (0, txUtils_1.wrapWsolIxs)(this.sdkParams.connection, manager, bountyWsolAmount);
733
739
  let intentSeedArray = web3_js_1.Keypair.generate().publicKey.toBytes();
734
740
  let tokenProgram = undefined;
@@ -1367,6 +1373,16 @@ class SymmetryCore {
1367
1373
  maxBountyPerTask = maxBountyAmount;
1368
1374
  bountyWsolAmount = (0, rebalanceIntent_3.computeRebalanceIntentBountyAmount)(rebalanceIntent_2.RebalanceType.Vault, vault.numTokens, boundBounty, maxBountyPerTask, Math.floor(maxBountyPerTask / bountyPerPriceUpdateTaskDivisor));
1369
1375
  }
1376
+ let isManager = vault.settings.creator.equals(keeper);
1377
+ for (let i = 0; i < vault.settings.managers.managers.length; i++)
1378
+ if (vault.settings.managers.managers[i].equals(keeper))
1379
+ isManager = true;
1380
+ if (isManager) {
1381
+ let bountyBalance = parseInt(vault.settings.bountyBalance.toString());
1382
+ let requiredBounty = parseInt(globalConfig.minBountyForVaultAutomation.toString());
1383
+ if (bountyBalance < requiredBounty)
1384
+ bountyWsolAmount += 2 * requiredBounty - bountyBalance;
1385
+ }
1370
1386
  let wsolIxs = yield (0, txUtils_1.wrapWsolIxs)(this.sdkParams.connection, keeper, bountyWsolAmount);
1371
1387
  const rebalanceIntent = (0, pda_1.getRebalanceIntentPda)(vault.ownAddress, vault.ownAddress);
1372
1388
  let rentPayer = (0, pda_1.getRentPayerPda)();
@@ -153,23 +153,33 @@ function formatRebalanceIntent(rebalanceIntent, vault) {
153
153
  })),
154
154
  };
155
155
  if (vault) {
156
+ const auctionTokenByMint = new Map(auctionData.tokens.map(token => [token.mint, token]));
157
+ const mintDataTokenByMint = new Map(mintData.tokens.map(token => [token.mint, token]));
156
158
  let minRatio = 1;
157
- for (let i = 0; i < vault.composition.length; i++) {
158
- if (vault.composition[i].amount == 0)
159
+ for (const vaultToken of vault.composition) {
160
+ if (vaultToken.amount == 0)
159
161
  continue;
160
- let ratio = auctionData.tokens[i].amount / vault.composition[i].amount;
162
+ const auctionToken = auctionTokenByMint.get(vaultToken.mint);
163
+ if (!auctionToken)
164
+ continue;
165
+ let ratio = auctionToken.amount / vaultToken.amount;
161
166
  minRatio = Math.min(minRatio, ratio);
162
167
  }
163
168
  let mintValue = 0;
164
169
  let remainingValue = 0;
165
170
  let mintAmount = Math.floor(vault.supply_outstanding * minRatio);
166
- for (let i = 0; i < vault.composition.length; i++) {
167
- let contributionAmount = Math.floor(vault.composition[i].amount * minRatio);
168
- contributionAmount = Math.min(contributionAmount, auctionData.tokens[i].amount);
169
- mintData.tokens[i].contribution_amount = contributionAmount;
170
- mintData.tokens[i].remaining_amount = auctionData.tokens[i].amount - contributionAmount;
171
- mintValue += contributionAmount * auctionData.tokens[i].price;
172
- remainingValue += (auctionData.tokens[i].amount - contributionAmount) * auctionData.tokens[i].price;
171
+ for (const vaultToken of vault.composition) {
172
+ const mint = vaultToken.mint;
173
+ const auctionToken = auctionTokenByMint.get(mint);
174
+ const mintDataToken = mintDataTokenByMint.get(mint);
175
+ if (!auctionToken || !mintDataToken)
176
+ continue;
177
+ let contributionAmount = Math.floor(vaultToken.amount * minRatio);
178
+ contributionAmount = Math.min(contributionAmount, auctionToken.amount);
179
+ mintDataToken.contribution_amount = contributionAmount;
180
+ mintDataToken.remaining_amount = auctionToken.amount - contributionAmount;
181
+ mintValue += contributionAmount * auctionToken.price;
182
+ remainingValue += (auctionToken.amount - contributionAmount) * auctionToken.price;
173
183
  }
174
184
  mintData.mintValue = mintValue;
175
185
  mintData.mintAmount = mintAmount;
@@ -20,5 +20,5 @@ export declare class PriceAggregator {
20
20
  minOraclesThresh: number;
21
21
  confMultiplier: Fraction;
22
22
  constructor(numOracles: number, oracles: OracleData[], minConfBps: number, confThreshBps: number, minOraclesThresh: number, confMultiplier: Fraction);
23
- static fetch(oracleAggregator: OracleAggregator, lutAccounts: AddressLookupTableAccount[], accountInfoMap: Map<string, AccountInfo<Buffer> | null>, solPrice: OraclePrice, usdPrice: OraclePrice, pythPriceOverrides?: Map<string, OraclePrice>): OraclePrice;
23
+ static fetch(oracleAggregator: OracleAggregator, lutAccounts: AddressLookupTableAccount[], accountInfoMap: Map<string, AccountInfo<Buffer> | null>, wsolPrice: OraclePrice, usdcPrice: OraclePrice, pythPriceOverrides?: Map<string, OraclePrice>): OraclePrice;
24
24
  }
@@ -54,7 +54,7 @@ class PriceAggregator {
54
54
  if (!(0 <= this.minOraclesThresh && this.minOraclesThresh <= constants_2.U32_MAX.toNumber()))
55
55
  throw new Error(`Minimum number of oracles must be between 0 and ${constants_2.U32_MAX}`);
56
56
  }
57
- static fetch(oracleAggregator, lutAccounts, accountInfoMap, solPrice, usdPrice, pythPriceOverrides) {
57
+ static fetch(oracleAggregator, lutAccounts, accountInfoMap, wsolPrice, usdcPrice, pythPriceOverrides) {
58
58
  let validated = true;
59
59
  const agg = oracleAggregator;
60
60
  const oracleResults = [];
@@ -62,15 +62,11 @@ class PriceAggregator {
62
62
  const oracleData = agg.oracles[i];
63
63
  const settings = oracleData.oracleSettings;
64
64
  let loadedAccounts = [];
65
- let firstLoadedPubkey;
66
65
  for (let j = 0; j < settings.numRequiredAccounts; j++) {
67
66
  const lutId = oracleData.accountsToLoadLutIds[j];
68
67
  const lutIdx = oracleData.accountsToLoadLutIndices[j];
69
68
  const pubkey = lutAccounts[lutId].state.addresses[lutIdx];
70
69
  const account = accountInfoMap.get(pubkey.toBase58());
71
- if (!firstLoadedPubkey) {
72
- firstLoadedPubkey = pubkey;
73
- }
74
70
  // @ts-ignore
75
71
  loadedAccounts.push(account);
76
72
  }
@@ -95,17 +91,16 @@ class PriceAggregator {
95
91
  let result = (() => {
96
92
  switch (settings.oracleType) {
97
93
  case oracle_1.OracleType.Pyth:
98
- if (firstLoadedPubkey) {
99
- const override = pythPriceOverrides === null || pythPriceOverrides === void 0 ? void 0 : pythPriceOverrides.get(firstLoadedPubkey.toBase58());
100
- if (override) {
101
- return override;
102
- }
103
- }
104
- return pythOracle_1.PythOracle.fetch(settings, loadedAccounts, solPrice, usdPrice);
94
+ const lutId = oracleData.accountsToLoadLutIds[0];
95
+ const lutIdx = oracleData.accountsToLoadLutIndices[0];
96
+ const pubkey = lutAccounts[lutId].state.addresses[lutIdx];
97
+ const override = pythPriceOverrides === null || pythPriceOverrides === void 0 ? void 0 : pythPriceOverrides.get(pubkey.toBase58());
98
+ const price = pythOracle_1.PythOracle.fetch(settings, loadedAccounts, wsolPrice, usdcPrice);
99
+ return (override ? override : price);
105
100
  case oracle_1.OracleType.RaydiumClmm:
106
- return raydiumClmmOracle_1.RaydiumCLMMOracle.fetch(settings, loadedAccounts, solPrice, usdPrice);
101
+ return raydiumClmmOracle_1.RaydiumCLMMOracle.fetch(settings, loadedAccounts, wsolPrice, usdcPrice);
107
102
  case oracle_1.OracleType.RaydiumCpmm:
108
- return raydiumCpmmOracle_1.RaydiumCPMMOracle.fetch(settings, loadedAccounts, solPrice, usdPrice);
103
+ return raydiumCpmmOracle_1.RaydiumCPMMOracle.fetch(settings, loadedAccounts, wsolPrice, usdcPrice);
109
104
  default:
110
105
  throw new Error(`Invalid oracle type: ${settings.oracleType}`);
111
106
  }
@@ -14,7 +14,7 @@ export interface HermesPythPriceData {
14
14
  emaConf?: string | number;
15
15
  }
16
16
  export declare function fetchHermesPythPrices(priceFeedIds: string[]): Promise<Map<string, HermesPythPriceData>>;
17
- export declare function buildPythOraclePriceFromHermesPriceData(oracleParams: OracleSettings, priceData: HermesPythPriceData, solPrice: OraclePrice, usdPrice: OraclePrice): OraclePrice;
17
+ export declare function buildPythOraclePriceFromHermesPriceData(oracleParams: OracleSettings, priceData: HermesPythPriceData, wsolPrice: OraclePrice, usdcPrice: OraclePrice): OraclePrice;
18
18
  export type VerificationLevel = {
19
19
  kind: 'Partial';
20
20
  numSignatures: number;
@@ -38,6 +38,10 @@ export interface InstructionWithSigners {
38
38
  signers: Keypair[];
39
39
  computeUnits?: number;
40
40
  }
41
+ /**
42
+ * Build closeEncodedVaa instruction
43
+ */
44
+ export declare function buildCloseEncodedVaaInstruction(writeAuthority: PublicKey, encodedVaa: PublicKey, wormholeProgramId?: PublicKey): TransactionInstruction;
41
45
  export interface PythUpdateResult {
42
46
  postInstructions: InstructionWithSigners[];
43
47
  closeInstructions: InstructionWithSigners[];
@@ -140,5 +144,5 @@ export declare class PythState {
140
144
  static decode(buf: Buffer, offset?: number): [PythState, number];
141
145
  }
142
146
  export declare class PythOracle {
143
- static fetch(oracleParams: OracleSettings, accountInfos: AccountInfo<Buffer>[], solPrice?: OraclePrice, usdPrice?: OraclePrice): OraclePrice;
147
+ static fetch(oracleParams: OracleSettings, accountInfos: AccountInfo<Buffer>[], wsolPrice?: OraclePrice, usdcPrice?: OraclePrice): OraclePrice;
144
148
  }
@@ -20,6 +20,7 @@ exports.buildFreshVaultPythPriceOverrides = buildFreshVaultPythPriceOverrides;
20
20
  exports.getRandomTipAccount = getRandomTipAccount;
21
21
  exports.buildJitoTipInstruction = buildJitoTipInstruction;
22
22
  exports.getPythPriceFeedAccountAddress = getPythPriceFeedAccountAddress;
23
+ exports.buildCloseEncodedVaaInstruction = buildCloseEncodedVaaInstruction;
23
24
  exports.parsePriceFeedMessage = parsePriceFeedMessage;
24
25
  exports.parseAccumulatorUpdateData = parseAccumulatorUpdateData;
25
26
  exports.getSizeOfTransaction = getSizeOfTransaction;
@@ -72,17 +73,19 @@ function fetchHermesPythPrices(priceFeedIds) {
72
73
  return result;
73
74
  });
74
75
  }
75
- function buildPythOraclePriceFromHermesPriceData(oracleParams, priceData, solPrice, usdPrice) {
76
+ function buildPythOraclePriceFromHermesPriceData(oracleParams, priceData, wsolPrice, usdcPrice) {
76
77
  var _a;
78
+ let quoteDecimals = (oracleParams.quote == oracle_1.Quote.Usdc) ? constants_1.USDC_DECIMALS : ((oracleParams.quote == oracle_1.Quote.Wsol) ? constants_1.WSOL_DECIMALS : 0);
77
79
  let pr = new decimal_js_1.default(priceData.price.toString())
78
- .mul(decimal_js_1.default.pow(10, priceData.expo - oracleParams.tokenDecimals));
80
+ .mul(decimal_js_1.default.pow(10, priceData.expo - oracleParams.tokenDecimals + quoteDecimals));
79
81
  let ema = new decimal_js_1.default(((_a = priceData.emaPrice) !== null && _a !== void 0 ? _a : priceData.price).toString())
80
- .mul(decimal_js_1.default.pow(10, priceData.expo - oracleParams.tokenDecimals));
82
+ .mul(decimal_js_1.default.pow(10, priceData.expo - oracleParams.tokenDecimals + quoteDecimals));
81
83
  let cf = new decimal_js_1.default(priceData.conf.toString())
82
- .mul(decimal_js_1.default.pow(10, priceData.expo - oracleParams.tokenDecimals));
84
+ .mul(decimal_js_1.default.pow(10, priceData.expo - oracleParams.tokenDecimals + quoteDecimals));
83
85
  const lastUpdateTimestamp = priceData.publishTime;
84
- let quotePrice = (oracleParams.quote === oracle_1.Quote.Usdc) ? usdPrice :
85
- ((oracleParams.quote === oracle_1.Quote.Wsol) ? solPrice : new oracle_2.OraclePrice(new decimal_js_1.default(1), new decimal_js_1.default(0), 0));
86
+ let quotePrice = (oracleParams.quote === oracle_1.Quote.Usdc) ? usdcPrice :
87
+ ((oracleParams.quote === oracle_1.Quote.Wsol) ? wsolPrice :
88
+ new oracle_2.OraclePrice(new decimal_js_1.default(1), new decimal_js_1.default(0), 0));
86
89
  pr = pr.mul(quotePrice.price);
87
90
  ema = ema.mul(quotePrice.price);
88
91
  cf = cf.mul(quotePrice.price);
@@ -711,7 +714,7 @@ class PythState {
711
714
  }
712
715
  exports.PythState = PythState;
713
716
  class PythOracle {
714
- static fetch(oracleParams, accountInfos, solPrice, usdPrice) {
717
+ static fetch(oracleParams, accountInfos, wsolPrice, usdcPrice) {
715
718
  //@ts-ignore
716
719
  let state = null;
717
720
  try {
@@ -729,7 +732,7 @@ class PythOracle {
729
732
  publishTime: parseInt(state.priceMessage.publishTime.toString()),
730
733
  emaPrice: state.priceMessage.emaPrice.toString(),
731
734
  emaConf: state.priceMessage.emaConf.toString(),
732
- }, solPrice !== null && solPrice !== void 0 ? solPrice : new oracle_2.OraclePrice(new decimal_js_1.default(1), new decimal_js_1.default(0), 0), usdPrice !== null && usdPrice !== void 0 ? usdPrice : new oracle_2.OraclePrice(new decimal_js_1.default(1), new decimal_js_1.default(0), 0));
735
+ }, wsolPrice !== null && wsolPrice !== void 0 ? wsolPrice : new oracle_2.OraclePrice(new decimal_js_1.default(1), new decimal_js_1.default(0), 0), usdcPrice !== null && usdcPrice !== void 0 ? usdcPrice : new oracle_2.OraclePrice(new decimal_js_1.default(1), new decimal_js_1.default(0), 0));
733
736
  }
734
737
  }
735
738
  exports.PythOracle = PythOracle;
@@ -115,6 +115,6 @@ export declare class RaydiumCPMMOracle {
115
115
  * - SELL: delta_quote_out = netQuote * delta_base / (netBase + delta_base)
116
116
  */
117
117
  static calculateMaxPriceImpactForMinLiquidity(oracleParams: OracleSettings, netBase: Decimal, netQuote: Decimal, spotPriceUsd: Decimal, quotePrice: OraclePrice): Decimal;
118
- static fetch(oracleParams: OracleSettings, accountInfos: AccountInfo<Buffer>[], solPrice: OraclePrice, usdPrice: OraclePrice): OraclePrice;
118
+ static fetch(oracleParams: OracleSettings, accountInfos: AccountInfo<Buffer>[], wsolPrice: OraclePrice, usdcPrice: OraclePrice): OraclePrice;
119
119
  }
120
120
  export {};
@@ -462,7 +462,7 @@ class RaydiumCPMMOracle {
462
462
  const maxImpact = decimal_js_1.default.max(impactBuy, impactSell);
463
463
  return maxImpact.mul(new decimal_js_1.default(10000)); // Convert to bps
464
464
  }
465
- static fetch(oracleParams, accountInfos, solPrice, usdPrice) {
465
+ static fetch(oracleParams, accountInfos, wsolPrice, usdcPrice) {
466
466
  const poolStateInfo = accountInfos[0];
467
467
  const vault0Info = accountInfos[1];
468
468
  const vault1Info = accountInfos[2];
@@ -473,8 +473,9 @@ class RaydiumCPMMOracle {
473
473
  const [decodedObservationState, ____] = ObservationState.decode(observationStateInfo.data, 8);
474
474
  const currentTime = new bn_js_1.default(Math.floor(Date.now() / 1000));
475
475
  // Determine quote oracle
476
- let quotePrice = (oracleParams.quote === oracle_1.Quote.Usdc) ? usdPrice :
477
- ((oracleParams.quote === oracle_1.Quote.Wsol) ? solPrice : new oracle_2.OraclePrice(new decimal_js_1.default(1), new decimal_js_1.default(0), 0));
476
+ let quotePrice = (oracleParams.quote === oracle_1.Quote.Usdc) ? usdcPrice :
477
+ ((oracleParams.quote === oracle_1.Quote.Wsol) ? wsolPrice :
478
+ new oracle_2.OraclePrice(new decimal_js_1.default(1), new decimal_js_1.default(0), 0));
478
479
  // Get spot price with reserves
479
480
  const { netBase, netQuote, spotPrice } = this.getSpotPriceWithReserves(decodedPoolState, decodedVault0State, decodedVault1State, oracleParams.side, quotePrice);
480
481
  // Get TWAP prices
@@ -56,10 +56,17 @@ export declare function getMultipleAccountsInfoBatched(connection: Connection, p
56
56
  export declare function getAddressLookupTableAccounts(connection: Connection, keys: PublicKey[]): Promise<Map<string, AddressLookupTableAccount>>;
57
57
  export declare function getMultipleAddressLookupTableAccounts(connection: Connection, batches: PublicKey[][][]): Promise<AddressLookupTableAccount[][][]>;
58
58
  export declare function compileVersionedTransaction(blockhash: string, addressLookupTableAccounts: AddressLookupTableAccount[], payerPubkey: PublicKey, ixs: TransactionInstruction[]): VersionedTransaction;
59
- export declare function sendVersionedTransaction(connection: Connection, tx: VersionedTransaction, blockhash: string, lastValidBlockHeight: number, simulateTransactions: boolean): Promise<TransactionSignature>;
59
+ /**
60
+ * Send + confirm a batch of versioned transactions with parallel rebroadcasting.
61
+ * All txs within each batch are sent and confirmed concurrently.
62
+ * Batches are processed sequentially (batch 1 must all confirm before batch 2 starts).
63
+ */
64
+ export declare function sendVersionedTxs(connection: Connection, versionedTxs: VersionedTxs, simulateTransactions?: boolean, rebroadcastIntervalMs?: number, confirmTimeoutMs?: number): Promise<TransactionSignature[][]>;
65
+ /**
66
+ * Sign + send a TxPayloadBatchSequence using fast parallel rebroadcasting.
67
+ */
68
+ export declare function sendTxPayloadBatchSequence(connection: Connection, txPayloadBatchSequence: TxPayloadBatchSequence, simulateTransactions?: boolean): Promise<TransactionSignature[][]>;
60
69
  export declare function prepareVersionedTxs(connection: Connection, txBatchData: TxBatchData): Promise<VersionedTxs>;
61
- export declare function sendVersionedTxs(connection: Connection, versionedTxs: VersionedTxs, simulateTransactions?: boolean): Promise<TransactionSignature[][]>;
62
70
  export declare function signVersionedTxs(wallet: Wallet, versionedTxs: VersionedTxs): Promise<VersionedTxs>;
63
71
  export declare function signTxPayloadBatchSequence(wallet: Wallet, txPayloadBatchSequence: TxPayloadBatchSequence): Promise<TxPayloadBatchSequence>;
64
- export declare function sendTxPayloadBatchSequence(connection: Connection, txPayloadBatchSequence: TxPayloadBatchSequence, simulateTransactions?: boolean): Promise<TransactionSignature[][]>;
65
72
  export {};
@@ -16,12 +16,11 @@ exports.getMultipleAccountsInfoBatched = getMultipleAccountsInfoBatched;
16
16
  exports.getAddressLookupTableAccounts = getAddressLookupTableAccounts;
17
17
  exports.getMultipleAddressLookupTableAccounts = getMultipleAddressLookupTableAccounts;
18
18
  exports.compileVersionedTransaction = compileVersionedTransaction;
19
- exports.sendVersionedTransaction = sendVersionedTransaction;
20
- exports.prepareVersionedTxs = prepareVersionedTxs;
21
19
  exports.sendVersionedTxs = sendVersionedTxs;
20
+ exports.sendTxPayloadBatchSequence = sendTxPayloadBatchSequence;
21
+ exports.prepareVersionedTxs = prepareVersionedTxs;
22
22
  exports.signVersionedTxs = signVersionedTxs;
23
23
  exports.signTxPayloadBatchSequence = signTxPayloadBatchSequence;
24
- exports.sendTxPayloadBatchSequence = sendTxPayloadBatchSequence;
25
24
  const spl_token_1 = require("@solana/spl-token");
26
25
  const web3_js_1 = require("@solana/web3.js");
27
26
  const constants_1 = require("./constants");
@@ -165,57 +164,164 @@ function compileVersionedTransaction(blockhash, addressLookupTableAccounts, paye
165
164
  }
166
165
  return versionedTx;
167
166
  }
168
- function sendVersionedTransaction(connection, tx, blockhash, lastValidBlockHeight, simulateTransactions) {
169
- return __awaiter(this, void 0, void 0, function* () {
170
- var _a;
171
- const serializedTx = tx.serialize();
172
- let txId;
173
- if (simulateTransactions) {
174
- txId = yield connection.sendRawTransaction(serializedTx, { preflightCommitment: "confirmed", maxRetries: 0 }).catch(e => {
175
- console.log(e.message);
176
- throw new Error("Simulation failed");
177
- });
178
- for (let i = 0; i < 4; i++) {
179
- yield delay(3000).then(() => connection.sendRawTransaction(serializedTx, { preflightCommitment: "confirmed", maxRetries: 0 }).catch(() => { }));
180
- }
181
- console.log("Simulation txId:", txId);
182
- }
183
- else {
184
- txId = yield connection.sendRawTransaction(serializedTx, { skipPreflight: true, maxRetries: 0 });
185
- for (let i = 0; i < 4; i++) {
186
- yield delay(3000).then(() => connection.sendRawTransaction(serializedTx, { preflightCommitment: "confirmed", maxRetries: 0 }).catch(() => { }));
187
- }
188
- console.log("Sending tx:", txId);
189
- }
190
- let confirmation = null;
191
- let result = null;
192
- connection.confirmTransaction({
193
- blockhash,
194
- lastValidBlockHeight,
195
- signature: txId,
196
- }, "confirmed").catch(() => null).then((res) => confirmation = res);
197
- let iterations = 10;
198
- while (confirmation === null && result === null && iterations > 0) {
199
- yield delay(1000);
200
- result = yield connection.getTransaction(txId, {
201
- commitment: "confirmed",
202
- maxSupportedTransactionVersion: 0,
203
- }).catch(() => null);
204
- if (result && result.meta && ((_a = result.meta) === null || _a === void 0 ? void 0 : _a.err)) {
205
- console.log(result.meta.err);
206
- throw new Error(txId);
207
- }
208
- iterations--;
167
+ const REBROADCAST_INTERVAL_MS = 500;
168
+ const TX_CONFIRM_TIMEOUT_MS = 15000;
169
+ // export async function sendVersionedTransaction(
170
+ // connection: Connection,
171
+ // tx: VersionedTransaction,
172
+ // blockhash: string,
173
+ // lastValidBlockHeight: number,
174
+ // simulateTransactions: boolean,
175
+ // ): Promise<TransactionSignature> {
176
+ // const serializedTx = tx.serialize();
177
+ // let txId: TransactionSignature;
178
+ // if (simulateTransactions) {
179
+ // txId = await connection.sendRawTransaction(
180
+ // serializedTx,
181
+ // { preflightCommitment: "confirmed", maxRetries: 0 }
182
+ // ).catch(e => {
183
+ // console.log(e.message);
184
+ // throw new Error("Simulation failed");
185
+ // });
186
+ // for (let i = 0; i < 4; i++) {
187
+ // await delay(3000).then(() =>
188
+ // connection.sendRawTransaction(
189
+ // serializedTx,
190
+ // {preflightCommitment: "confirmed", maxRetries: 0}
191
+ // ).catch(() => {})
192
+ // );
193
+ // }
194
+ // console.log("Simulation txId:", txId);
195
+ // } else {
196
+ // txId = await connection.sendRawTransaction(
197
+ // serializedTx,
198
+ // { skipPreflight: true, maxRetries: 0 }
199
+ // );
200
+ // for (let i = 0; i < 4; i++) {
201
+ // await delay(3000).then(() =>
202
+ // connection.sendRawTransaction(
203
+ // serializedTx,
204
+ // {preflightCommitment: "confirmed", maxRetries: 0}
205
+ // ).catch(() => {})
206
+ // );
207
+ // }
208
+ // console.log("Sending tx:", txId);
209
+ // }
210
+ // let confirmation = null;
211
+ // let result = null;
212
+ // connection.confirmTransaction({
213
+ // blockhash,
214
+ // lastValidBlockHeight,
215
+ // signature: txId,
216
+ // }, "confirmed").catch(() => null).then((res) => confirmation = res);
217
+ // let iterations = 10;
218
+ // while (confirmation === null && result === null && iterations > 0) {
219
+ // await delay(1000);
220
+ // result = await connection.getTransaction(txId, {
221
+ // commitment: "confirmed",
222
+ // maxSupportedTransactionVersion: 0,
223
+ // }).catch(() => null);
224
+ // if (result && result.meta && result.meta?.err) {
225
+ // console.log(result.meta.err);
226
+ // throw new Error(txId);
227
+ // }
228
+ // iterations--;
229
+ // }
230
+ // if (result) return txId;
231
+ // //@ts-ignore
232
+ // if (!confirmation || confirmation.value.err) {
233
+ // //@ts-ignore
234
+ // console.log(confirmation?.value.err);
235
+ // throw new Error(txId);
236
+ // }
237
+ // return txId;
238
+ // }
239
+ /**
240
+ * Send + confirm a batch of versioned transactions with parallel rebroadcasting.
241
+ * All txs within each batch are sent and confirmed concurrently.
242
+ * Batches are processed sequentially (batch 1 must all confirm before batch 2 starts).
243
+ */
244
+ function sendVersionedTxs(connection_1, versionedTxs_1) {
245
+ return __awaiter(this, arguments, void 0, function* (connection, versionedTxs, simulateTransactions = false, rebroadcastIntervalMs = REBROADCAST_INTERVAL_MS, confirmTimeoutMs = TX_CONFIRM_TIMEOUT_MS) {
246
+ const { batches, blockhash, lastValidBlockHeight } = versionedTxs;
247
+ const batchTxIds = [];
248
+ for (const batch of batches) {
249
+ const txIds = yield Promise.all(batch.map((tx) => __awaiter(this, void 0, void 0, function* () {
250
+ var _a, _b, _c;
251
+ const serializedTx = tx.serialize();
252
+ let txId;
253
+ if (simulateTransactions) {
254
+ txId = yield connection.sendRawTransaction(serializedTx, { preflightCommitment: "confirmed", maxRetries: 0 }).catch(e => {
255
+ console.log(e.message);
256
+ throw new Error("Simulation failed");
257
+ });
258
+ }
259
+ else {
260
+ txId = yield connection.sendRawTransaction(serializedTx, { skipPreflight: true, maxRetries: 0 });
261
+ }
262
+ const rebroadcastInterval = setInterval(() => {
263
+ connection.sendRawTransaction(serializedTx, { skipPreflight: true, maxRetries: 0 }).catch(() => { });
264
+ }, rebroadcastIntervalMs);
265
+ let timeoutId;
266
+ try {
267
+ const confirmPromise = connection.confirmTransaction({
268
+ blockhash,
269
+ lastValidBlockHeight,
270
+ signature: txId,
271
+ }, "confirmed");
272
+ const timeoutPromise = new Promise((_, reject) => {
273
+ timeoutId = setTimeout(() => reject(new Error("timeout")), confirmTimeoutMs);
274
+ });
275
+ const confirmation = yield Promise.race([confirmPromise, timeoutPromise]);
276
+ if (confirmation.value.err) {
277
+ console.log("Tx error:", txId, confirmation.value.err);
278
+ return "Error";
279
+ }
280
+ return txId;
281
+ }
282
+ catch (e) {
283
+ if ((e === null || e === void 0 ? void 0 : e.message) === "timeout") {
284
+ const result = yield connection.getTransaction(txId, {
285
+ commitment: "confirmed",
286
+ maxSupportedTransactionVersion: 0,
287
+ }).catch(() => null);
288
+ if (result && !((_a = result.meta) === null || _a === void 0 ? void 0 : _a.err))
289
+ return txId;
290
+ if ((_b = result === null || result === void 0 ? void 0 : result.meta) === null || _b === void 0 ? void 0 : _b.err)
291
+ console.log("Tx error:", txId, result.meta.err);
292
+ }
293
+ console.log("Error sending tx:", (_c = e === null || e === void 0 ? void 0 : e.message) !== null && _c !== void 0 ? _c : e);
294
+ return "Error";
295
+ }
296
+ finally {
297
+ clearInterval(rebroadcastInterval);
298
+ if (timeoutId !== undefined)
299
+ clearTimeout(timeoutId);
300
+ }
301
+ })));
302
+ batchTxIds.push(txIds);
209
303
  }
210
- if (result)
211
- return txId;
212
- //@ts-ignore
213
- if (!confirmation || confirmation.value.err) {
214
- //@ts-ignore
215
- console.log(confirmation === null || confirmation === void 0 ? void 0 : confirmation.value.err);
216
- throw new Error(txId);
304
+ return batchTxIds;
305
+ });
306
+ }
307
+ /**
308
+ * Sign + send a TxPayloadBatchSequence using fast parallel rebroadcasting.
309
+ */
310
+ function sendTxPayloadBatchSequence(connection_1, txPayloadBatchSequence_1) {
311
+ return __awaiter(this, arguments, void 0, function* (connection, txPayloadBatchSequence, simulateTransactions = false) {
312
+ var _a, _b, _c, _d;
313
+ const txs = [];
314
+ for (const batch of txPayloadBatchSequence.batches) {
315
+ const batchTxs = [];
316
+ for (const tx of batch.transactions)
317
+ batchTxs.push(web3_js_1.VersionedTransaction.deserialize(Buffer.from(tx.tx_b64, 'base64')));
318
+ txs.push(batchTxs);
217
319
  }
218
- return txId;
320
+ return yield sendVersionedTxs(connection, {
321
+ blockhash: (_b = (_a = txPayloadBatchSequence.batches[0].transactions[0]) === null || _a === void 0 ? void 0 : _a.recent_blockhash) !== null && _b !== void 0 ? _b : "",
322
+ lastValidBlockHeight: (_d = (_c = txPayloadBatchSequence.batches[0].transactions[0]) === null || _c === void 0 ? void 0 : _c.last_valid_block_height) !== null && _d !== void 0 ? _d : 0,
323
+ batches: txs,
324
+ }, simulateTransactions);
219
325
  });
220
326
  }
221
327
  function prepareVersionedTxs(connection, txBatchData) {
@@ -242,17 +348,29 @@ function prepareVersionedTxs(connection, txBatchData) {
242
348
  };
243
349
  });
244
350
  }
245
- function sendVersionedTxs(connection_1, versionedTxs_1) {
246
- return __awaiter(this, arguments, void 0, function* (connection, versionedTxs, simulateTransactions = false) {
247
- const { batches, blockhash, lastValidBlockHeight } = versionedTxs;
248
- let batchTxIds = [];
249
- for (const batch of batches) {
250
- let txIds = yield Promise.all(batch.map(tx => sendVersionedTransaction(connection, tx, blockhash, lastValidBlockHeight, simulateTransactions).catch(e => { console.log("Error sending tx:", e.message); return "Error"; })));
251
- batchTxIds.push(txIds);
252
- }
253
- return batchTxIds;
254
- });
255
- }
351
+ // export async function sendVersionedTxs(
352
+ // connection: Connection,
353
+ // versionedTxs: VersionedTxs,
354
+ // simulateTransactions: boolean = false,
355
+ // ): Promise<TransactionSignature[][]> {
356
+ // const { batches, blockhash, lastValidBlockHeight } = versionedTxs;
357
+ // let batchTxIds: TransactionSignature[][] = [];
358
+ // for (const batch of batches) {
359
+ // let txIds = await Promise.all(
360
+ // batch.map(tx =>
361
+ // sendVersionedTransaction(
362
+ // connection,
363
+ // tx,
364
+ // blockhash,
365
+ // lastValidBlockHeight,
366
+ // simulateTransactions
367
+ // ).catch(e => {console.log("Error sending tx:", e.message); return "Error"})
368
+ // )
369
+ // );
370
+ // batchTxIds.push(txIds);
371
+ // }
372
+ // return batchTxIds;
373
+ // }
256
374
  function signVersionedTxs(wallet, versionedTxs) {
257
375
  return __awaiter(this, void 0, void 0, function* () {
258
376
  let txs = [];
@@ -285,20 +403,21 @@ function signTxPayloadBatchSequence(wallet, txPayloadBatchSequence) {
285
403
  return txPayloadBatchSequence;
286
404
  });
287
405
  }
288
- function sendTxPayloadBatchSequence(connection_1, txPayloadBatchSequence_1) {
289
- return __awaiter(this, arguments, void 0, function* (connection, txPayloadBatchSequence, simulateTransactions = false) {
290
- var _a, _b, _c, _d;
291
- let txs = [];
292
- for (const batch of txPayloadBatchSequence.batches) {
293
- let batchTxs = [];
294
- for (const tx of batch.transactions)
295
- batchTxs.push(web3_js_1.VersionedTransaction.deserialize(Buffer.from(tx.tx_b64, 'base64')));
296
- txs.push(batchTxs);
297
- }
298
- return yield sendVersionedTxs(connection, {
299
- blockhash: (_b = (_a = txPayloadBatchSequence.batches[0].transactions[0]) === null || _a === void 0 ? void 0 : _a.recent_blockhash) !== null && _b !== void 0 ? _b : "",
300
- lastValidBlockHeight: (_d = (_c = txPayloadBatchSequence.batches[0].transactions[0]) === null || _c === void 0 ? void 0 : _c.last_valid_block_height) !== null && _d !== void 0 ? _d : 0,
301
- batches: txs,
302
- }, simulateTransactions);
303
- });
304
- }
406
+ // export async function sendTxPayloadBatchSequence(
407
+ // connection: Connection,
408
+ // txPayloadBatchSequence: TxPayloadBatchSequence,
409
+ // simulateTransactions: boolean = false,
410
+ // ): Promise<TransactionSignature[][]> {
411
+ // let txs: VersionedTransaction[][] = [];
412
+ // for (const batch of txPayloadBatchSequence.batches) {
413
+ // let batchTxs: VersionedTransaction[] = [];
414
+ // for (const tx of batch.transactions)
415
+ // batchTxs.push(VersionedTransaction.deserialize(Buffer.from(tx.tx_b64, 'base64')));
416
+ // txs.push(batchTxs);
417
+ // }
418
+ // return await sendVersionedTxs(connection, {
419
+ // blockhash: txPayloadBatchSequence.batches[0].transactions[0]?.recent_blockhash ?? "",
420
+ // lastValidBlockHeight: txPayloadBatchSequence.batches[0].transactions[0]?.last_valid_block_height ?? 0,
421
+ // batches: txs,
422
+ // }, simulateTransactions);
423
+ // }
package/dist/test.js CHANGED
@@ -72,7 +72,7 @@ function testStates() {
72
72
  editGlobalConfig: false, // TESTED
73
73
  createVault: false, // TESTED
74
74
  editPrivateVaultSettings: false, // NOT TESTED
75
- addBounty: true, // TESTED
75
+ addBounty: false, // TESTED
76
76
  editCreator: false, // TESTED
77
77
  editManagerSettings: false, // TESTED
78
78
  editFeeSettings: false, // TESTED
@@ -521,13 +521,13 @@ function testStates() {
521
521
  symmetryLimitOrderFeeBps: 0,
522
522
  symmetryFeesExtraData: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,],
523
523
  bountyMint: new web3_js_1.PublicKey("So11111111111111111111111111111111111111112"),
524
- minBountyForVaultAutomation: new anchor_1.BN(200 * 50 * 10 ** 3),
525
- bountyBondAmount: new anchor_1.BN(5 * 50 * 10 ** 3),
524
+ minBountyForVaultAutomation: new anchor_1.BN(2000 * 5000),
525
+ bountyBondAmount: new anchor_1.BN(20 * 5000),
526
526
  bountyPerTask: {
527
- minBounty: new anchor_1.BN(10 * 50 * 10 ** 3),
528
- maxBounty: new anchor_1.BN(20 * 50 * 10 ** 3),
529
- minBountyUntil: new anchor_1.BN(1),
530
- maxBountyAfter: new anchor_1.BN(101),
527
+ minBounty: new anchor_1.BN(20 * 5000),
528
+ maxBounty: new anchor_1.BN(50 * 5000),
529
+ minBountyUntil: new anchor_1.BN(10),
530
+ maxBountyAfter: new anchor_1.BN(40),
531
531
  },
532
532
  bountyPerPriceUpdateTaskDivisor: new anchor_1.BN(3),
533
533
  minRemainingValue: { high: new anchor_1.BN(0), low: new anchor_1.BN("1844674407370955161") },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symmetry-hq/sdk",
3
- "version": "1.0.11",
3
+ "version": "1.0.13",
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
@@ -762,6 +762,14 @@ export class SymmetryCore {
762
762
  let maxBountyPerTask = parseInt(maxBounty.toString());
763
763
  bountyWsolAmount = maxBountyPerTask + boundBounty;
764
764
  }
765
+
766
+ if (editType === TaskType.EditAutomationSettings && (editData as EditAutomationSettings).enabled == true) {
767
+ let bountyBalance = parseInt(vault.settings.bountyBalance.toString());
768
+ let requiredBounty = parseInt(globalConfig.minBountyForVaultAutomation.toString());
769
+ if (bountyBalance < requiredBounty)
770
+ bountyWsolAmount += 2 * requiredBounty - bountyBalance;
771
+ }
772
+
765
773
  let wsolIxs = await wrapWsolIxs(
766
774
  this.sdkParams.connection,
767
775
  manager,
@@ -1525,7 +1533,19 @@ export class SymmetryCore {
1525
1533
  Math.floor(maxBountyPerTask / bountyPerPriceUpdateTaskDivisor),
1526
1534
  );
1527
1535
  }
1528
-
1536
+
1537
+ let isManager = vault.settings.creator.equals(keeper);
1538
+ for (let i = 0; i < vault.settings.managers.managers.length; i++)
1539
+ if (vault.settings.managers.managers[i].equals(keeper))
1540
+ isManager = true;
1541
+
1542
+ if (isManager) {
1543
+ let bountyBalance = parseInt(vault.settings.bountyBalance.toString());
1544
+ let requiredBounty = parseInt(globalConfig.minBountyForVaultAutomation.toString());
1545
+ if (bountyBalance < requiredBounty)
1546
+ bountyWsolAmount += 2 * requiredBounty - bountyBalance;
1547
+ }
1548
+
1529
1549
  let wsolIxs = await wrapWsolIxs(
1530
1550
  this.sdkParams.connection,
1531
1551
  keeper,
@@ -147,23 +147,33 @@ export function formatRebalanceIntent(rebalanceIntent: RebalanceIntent, vault?:
147
147
  })),
148
148
  }
149
149
  if (vault) {
150
+ const auctionTokenByMint = new Map(auctionData.tokens.map(token => [token.mint, token]));
151
+ const mintDataTokenByMint = new Map(mintData.tokens.map(token => [token.mint, token]));
150
152
  let minRatio = 1;
151
- for (let i = 0; i < vault.composition.length; i++) {
152
- if (vault.composition[i].amount == 0)
153
+ for (const vaultToken of vault.composition) {
154
+ if (vaultToken.amount == 0)
153
155
  continue;
154
- let ratio = auctionData.tokens[i].amount / vault.composition[i].amount;
156
+ const auctionToken = auctionTokenByMint.get(vaultToken.mint);
157
+ if (!auctionToken) continue;
158
+
159
+ let ratio = auctionToken.amount / vaultToken.amount;
155
160
  minRatio = Math.min(minRatio, ratio);
156
161
  }
157
162
  let mintValue = 0;
158
163
  let remainingValue = 0;
159
164
  let mintAmount = Math.floor(vault.supply_outstanding * minRatio);
160
- for (let i = 0; i < vault.composition.length; i++) {
161
- let contributionAmount = Math.floor(vault.composition[i].amount * minRatio);
162
- contributionAmount = Math.min(contributionAmount, auctionData.tokens[i].amount);
163
- mintData.tokens[i].contribution_amount = contributionAmount;
164
- mintData.tokens[i].remaining_amount = auctionData.tokens[i].amount - contributionAmount;
165
- mintValue += contributionAmount * auctionData.tokens[i].price;
166
- remainingValue += (auctionData.tokens[i].amount - contributionAmount) * auctionData.tokens[i].price;
165
+ for (const vaultToken of vault.composition) {
166
+ const mint = vaultToken.mint;
167
+ const auctionToken = auctionTokenByMint.get(mint);
168
+ const mintDataToken = mintDataTokenByMint.get(mint);
169
+ if (!auctionToken || !mintDataToken) continue;
170
+
171
+ let contributionAmount = Math.floor(vaultToken.amount * minRatio);
172
+ contributionAmount = Math.min(contributionAmount, auctionToken.amount);
173
+ mintDataToken.contribution_amount = contributionAmount;
174
+ mintDataToken.remaining_amount = auctionToken.amount - contributionAmount;
175
+ mintValue += contributionAmount * auctionToken.price;
176
+ remainingValue += (auctionToken.amount - contributionAmount) * auctionToken.price;
167
177
  }
168
178
  mintData.mintValue = mintValue;
169
179
  mintData.mintAmount = mintAmount;
@@ -6,7 +6,7 @@ import { AccountInfo, AddressLookupTableAccount, PublicKey } from '@solana/web3.
6
6
  import { HUNDRED_PERCENT_BPS } from '../../constants';
7
7
  import { Fraction, fractionToDecimal } from '../../layouts/fraction';
8
8
  import {
9
- MAX_ACCOUNTS_PER_ORACLE, OracleAggregator, OracleData, OracleSettings, OracleType
9
+ OracleAggregator, OracleData, OracleType,
10
10
  } from '../../layouts/oracle';
11
11
  import { U32_MAX } from './constants';
12
12
  import { PythOracle } from './pythOracle';
@@ -79,8 +79,8 @@ export class PriceAggregator{
79
79
  oracleAggregator: OracleAggregator,
80
80
  lutAccounts: AddressLookupTableAccount[],
81
81
  accountInfoMap: Map<string, AccountInfo<Buffer> | null>,
82
- solPrice: OraclePrice,
83
- usdPrice: OraclePrice,
82
+ wsolPrice: OraclePrice,
83
+ usdcPrice: OraclePrice,
84
84
  pythPriceOverrides?: Map<string, OraclePrice>,
85
85
  ): OraclePrice {
86
86
  let validated: boolean = true;
@@ -93,15 +93,11 @@ export class PriceAggregator{
93
93
  const settings = oracleData.oracleSettings;
94
94
 
95
95
  let loadedAccounts: AccountInfo<Buffer>[] = [];
96
- let firstLoadedPubkey: PublicKey | undefined;
97
96
  for (let j = 0; j < settings.numRequiredAccounts; j++) {
98
97
  const lutId = oracleData.accountsToLoadLutIds[j];
99
98
  const lutIdx = oracleData.accountsToLoadLutIndices[j];
100
99
  const pubkey = lutAccounts[lutId].state.addresses[lutIdx];
101
100
  const account = accountInfoMap.get(pubkey.toBase58());
102
- if (!firstLoadedPubkey) {
103
- firstLoadedPubkey = pubkey;
104
- }
105
101
  // @ts-ignore
106
102
  loadedAccounts.push(account);
107
103
  }
@@ -127,17 +123,16 @@ export class PriceAggregator{
127
123
  let result = (() => {
128
124
  switch (settings.oracleType) {
129
125
  case OracleType.Pyth:
130
- if (firstLoadedPubkey) {
131
- const override = pythPriceOverrides?.get(firstLoadedPubkey.toBase58());
132
- if (override) {
133
- return override;
134
- }
135
- }
136
- return PythOracle.fetch(settings, loadedAccounts, solPrice, usdPrice);
126
+ const lutId = oracleData.accountsToLoadLutIds[0];
127
+ const lutIdx = oracleData.accountsToLoadLutIndices[0];
128
+ const pubkey = lutAccounts[lutId].state.addresses[lutIdx];
129
+ const override = pythPriceOverrides?.get(pubkey.toBase58());
130
+ const price = PythOracle.fetch(settings, loadedAccounts, wsolPrice, usdcPrice);
131
+ return (override ? override : price);
137
132
  case OracleType.RaydiumClmm:
138
- return RaydiumCLMMOracle.fetch(settings, loadedAccounts, solPrice, usdPrice);
133
+ return RaydiumCLMMOracle.fetch(settings, loadedAccounts, wsolPrice, usdcPrice);
139
134
  case OracleType.RaydiumCpmm:
140
- return RaydiumCPMMOracle.fetch(settings, loadedAccounts, solPrice, usdPrice);
135
+ return RaydiumCPMMOracle.fetch(settings, loadedAccounts, wsolPrice, usdcPrice);
141
136
  default:
142
137
  throw new Error(`Invalid oracle type: ${settings.oracleType}`);
143
138
  }
@@ -7,7 +7,7 @@ import {
7
7
  SystemProgram, Transaction, TransactionInstruction, TransactionMessage, VersionedTransaction
8
8
  } from '@solana/web3.js';
9
9
 
10
- import { HUNDRED_PERCENT_BPS, PYTHNET_CUSTODY_PRICE_USDC_ACCOUNT, PYTHNET_CUSTODY_PRICE_WSOL_ACCOUNT } from '../../constants';
10
+ import { HUNDRED_PERCENT_BPS, PYTHNET_CUSTODY_PRICE_USDC_ACCOUNT, PYTHNET_CUSTODY_PRICE_WSOL_ACCOUNT, USDC_DECIMALS, WSOL_DECIMALS } from '../../constants';
11
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';
@@ -87,20 +87,25 @@ export async function fetchHermesPythPrices(priceFeedIds: string[]): Promise<Map
87
87
  export function buildPythOraclePriceFromHermesPriceData(
88
88
  oracleParams: OracleSettings,
89
89
  priceData: HermesPythPriceData,
90
- solPrice: OraclePrice,
91
- usdPrice: OraclePrice,
90
+ wsolPrice: OraclePrice,
91
+ usdcPrice: OraclePrice,
92
92
  ): OraclePrice {
93
+ let quoteDecimals =
94
+ (oracleParams.quote == Quote.Usdc) ? USDC_DECIMALS : (
95
+ (oracleParams.quote == Quote.Wsol) ? WSOL_DECIMALS : 0);
96
+
93
97
  let pr = new Decimal(priceData.price.toString())
94
- .mul(Decimal.pow(10, priceData.expo - oracleParams.tokenDecimals));
98
+ .mul(Decimal.pow(10, priceData.expo - oracleParams.tokenDecimals + quoteDecimals));
95
99
  let ema = new Decimal((priceData.emaPrice ?? priceData.price).toString())
96
- .mul(Decimal.pow(10, priceData.expo - oracleParams.tokenDecimals));
100
+ .mul(Decimal.pow(10, priceData.expo - oracleParams.tokenDecimals + quoteDecimals));
97
101
  let cf = new Decimal(priceData.conf.toString())
98
- .mul(Decimal.pow(10, priceData.expo - oracleParams.tokenDecimals));
102
+ .mul(Decimal.pow(10, priceData.expo - oracleParams.tokenDecimals + quoteDecimals));
99
103
  const lastUpdateTimestamp = priceData.publishTime;
100
-
104
+
101
105
  let quotePrice =
102
- (oracleParams.quote === Quote.Usdc) ? usdPrice :
103
- ((oracleParams.quote === Quote.Wsol) ? solPrice : new OraclePrice(new Decimal(1), new Decimal(0), 0));
106
+ (oracleParams.quote === Quote.Usdc) ? usdcPrice :
107
+ ((oracleParams.quote === Quote.Wsol) ? wsolPrice :
108
+ new OraclePrice(new Decimal(1), new Decimal(0), 0));
104
109
 
105
110
  pr = pr.mul(quotePrice.price);
106
111
  ema = ema.mul(quotePrice.price);
@@ -490,7 +495,7 @@ function buildVerifyEncodedVaaV1Instruction(
490
495
  /**
491
496
  * Build closeEncodedVaa instruction
492
497
  */
493
- function buildCloseEncodedVaaInstruction(
498
+ export function buildCloseEncodedVaaInstruction(
494
499
  writeAuthority: PublicKey,
495
500
  encodedVaa: PublicKey,
496
501
  wormholeProgramId: PublicKey = DEFAULT_WORMHOLE_PROGRAM_ID
@@ -976,8 +981,8 @@ export class PythOracle {
976
981
  static fetch(
977
982
  oracleParams: OracleSettings,
978
983
  accountInfos: AccountInfo<Buffer>[],
979
- solPrice?: OraclePrice,
980
- usdPrice?: OraclePrice,
984
+ wsolPrice?: OraclePrice,
985
+ usdcPrice?: OraclePrice,
981
986
  ): OraclePrice {
982
987
  //@ts-ignore
983
988
  let state: PythState = null;
@@ -999,8 +1004,8 @@ export class PythOracle {
999
1004
  emaPrice: state.priceMessage.emaPrice.toString(),
1000
1005
  emaConf: state.priceMessage.emaConf.toString(),
1001
1006
  },
1002
- solPrice ?? new OraclePrice(new Decimal(1), new Decimal(0), 0),
1003
- usdPrice ?? new OraclePrice(new Decimal(1), new Decimal(0), 0),
1007
+ wsolPrice ?? new OraclePrice(new Decimal(1), new Decimal(0), 0),
1008
+ usdcPrice ?? new OraclePrice(new Decimal(1), new Decimal(0), 0),
1004
1009
  );
1005
1010
  }
1006
1011
 
@@ -667,8 +667,8 @@ export class RaydiumCPMMOracle {
667
667
  static fetch(
668
668
  oracleParams: OracleSettings,
669
669
  accountInfos: AccountInfo<Buffer>[],
670
- solPrice: OraclePrice,
671
- usdPrice: OraclePrice,
670
+ wsolPrice: OraclePrice,
671
+ usdcPrice: OraclePrice,
672
672
  ): OraclePrice {
673
673
  const poolStateInfo = accountInfos[0];
674
674
  const vault0Info = accountInfos[1];
@@ -684,8 +684,9 @@ export class RaydiumCPMMOracle {
684
684
 
685
685
  // Determine quote oracle
686
686
  let quotePrice =
687
- (oracleParams.quote === Quote.Usdc) ? usdPrice :
688
- ((oracleParams.quote === Quote.Wsol) ? solPrice : new OraclePrice(new Decimal(1), new Decimal(0), 0));
687
+ (oracleParams.quote === Quote.Usdc) ? usdcPrice :
688
+ ((oracleParams.quote === Quote.Wsol) ? wsolPrice :
689
+ new OraclePrice(new Decimal(1), new Decimal(0), 0));
689
690
 
690
691
  // Get spot price with reserves
691
692
  const { netBase, netQuote, spotPrice } = this.getSpotPriceWithReserves(
package/src/txUtils.ts CHANGED
@@ -232,81 +232,190 @@ export function compileVersionedTransaction(
232
232
  return versionedTx;
233
233
  }
234
234
 
235
- export async function sendVersionedTransaction(
235
+ const REBROADCAST_INTERVAL_MS = 500;
236
+ const TX_CONFIRM_TIMEOUT_MS = 15_000;
237
+
238
+ // export async function sendVersionedTransaction(
239
+ // connection: Connection,
240
+ // tx: VersionedTransaction,
241
+ // blockhash: string,
242
+ // lastValidBlockHeight: number,
243
+ // simulateTransactions: boolean,
244
+ // ): Promise<TransactionSignature> {
245
+ // const serializedTx = tx.serialize();
246
+ // let txId: TransactionSignature;
247
+ // if (simulateTransactions) {
248
+ // txId = await connection.sendRawTransaction(
249
+ // serializedTx,
250
+ // { preflightCommitment: "confirmed", maxRetries: 0 }
251
+ // ).catch(e => {
252
+ // console.log(e.message);
253
+ // throw new Error("Simulation failed");
254
+ // });
255
+ // for (let i = 0; i < 4; i++) {
256
+ // await delay(3000).then(() =>
257
+ // connection.sendRawTransaction(
258
+ // serializedTx,
259
+ // {preflightCommitment: "confirmed", maxRetries: 0}
260
+ // ).catch(() => {})
261
+ // );
262
+ // }
263
+ // console.log("Simulation txId:", txId);
264
+ // } else {
265
+ // txId = await connection.sendRawTransaction(
266
+ // serializedTx,
267
+ // { skipPreflight: true, maxRetries: 0 }
268
+ // );
269
+ // for (let i = 0; i < 4; i++) {
270
+ // await delay(3000).then(() =>
271
+ // connection.sendRawTransaction(
272
+ // serializedTx,
273
+ // {preflightCommitment: "confirmed", maxRetries: 0}
274
+ // ).catch(() => {})
275
+ // );
276
+ // }
277
+ // console.log("Sending tx:", txId);
278
+ // }
279
+
280
+ // let confirmation = null;
281
+ // let result = null;
282
+
283
+ // connection.confirmTransaction({
284
+ // blockhash,
285
+ // lastValidBlockHeight,
286
+ // signature: txId,
287
+ // }, "confirmed").catch(() => null).then((res) => confirmation = res);
288
+
289
+ // let iterations = 10;
290
+ // while (confirmation === null && result === null && iterations > 0) {
291
+ // await delay(1000);
292
+ // result = await connection.getTransaction(txId, {
293
+ // commitment: "confirmed",
294
+ // maxSupportedTransactionVersion: 0,
295
+ // }).catch(() => null);
296
+ // if (result && result.meta && result.meta?.err) {
297
+ // console.log(result.meta.err);
298
+ // throw new Error(txId);
299
+ // }
300
+ // iterations--;
301
+ // }
302
+
303
+ // if (result) return txId;
304
+
305
+ // //@ts-ignore
306
+ // if (!confirmation || confirmation.value.err) {
307
+ // //@ts-ignore
308
+ // console.log(confirmation?.value.err);
309
+ // throw new Error(txId);
310
+ // }
311
+
312
+ // return txId;
313
+ // }
314
+
315
+ /**
316
+ * Send + confirm a batch of versioned transactions with parallel rebroadcasting.
317
+ * All txs within each batch are sent and confirmed concurrently.
318
+ * Batches are processed sequentially (batch 1 must all confirm before batch 2 starts).
319
+ */
320
+ export async function sendVersionedTxs(
236
321
  connection: Connection,
237
- tx: VersionedTransaction,
238
- blockhash: string,
239
- lastValidBlockHeight: number,
240
- simulateTransactions: boolean,
241
- ): Promise<TransactionSignature> {
242
- const serializedTx = tx.serialize();
243
- let txId: TransactionSignature;
244
- if (simulateTransactions) {
245
- txId = await connection.sendRawTransaction(
246
- serializedTx,
247
- { preflightCommitment: "confirmed", maxRetries: 0 }
248
- ).catch(e => {
249
- console.log(e.message);
250
- throw new Error("Simulation failed");
251
- });
252
- for (let i = 0; i < 4; i++) {
253
- await delay(3000).then(() =>
254
- connection.sendRawTransaction(
255
- serializedTx,
256
- {preflightCommitment: "confirmed", maxRetries: 0}
257
- ).catch(() => {})
258
- );
259
- }
260
- console.log("Simulation txId:", txId);
261
- } else {
262
- txId = await connection.sendRawTransaction(
263
- serializedTx,
264
- { skipPreflight: true, maxRetries: 0 }
265
- );
266
- for (let i = 0; i < 4; i++) {
267
- await delay(3000).then(() =>
268
- connection.sendRawTransaction(
269
- serializedTx,
270
- {preflightCommitment: "confirmed", maxRetries: 0}
271
- ).catch(() => {})
272
- );
273
- }
274
- console.log("Sending tx:", txId);
275
- }
322
+ versionedTxs: VersionedTxs,
323
+ simulateTransactions: boolean = false,
324
+ rebroadcastIntervalMs: number = REBROADCAST_INTERVAL_MS,
325
+ confirmTimeoutMs: number = TX_CONFIRM_TIMEOUT_MS,
326
+ ): Promise<TransactionSignature[][]> {
327
+ const { batches, blockhash, lastValidBlockHeight } = versionedTxs;
328
+ const batchTxIds: TransactionSignature[][] = [];
276
329
 
277
- let confirmation = null;
278
- let result = null;
330
+ for (const batch of batches) {
331
+ const txIds = await Promise.all(
332
+ batch.map(async (tx) => {
333
+ const serializedTx = tx.serialize();
334
+ let txId: TransactionSignature;
279
335
 
280
- connection.confirmTransaction({
281
- blockhash,
282
- lastValidBlockHeight,
283
- signature: txId,
284
- }, "confirmed").catch(() => null).then((res) => confirmation = res);
285
-
286
- let iterations = 10;
287
- while (confirmation === null && result === null && iterations > 0) {
288
- await delay(1000);
289
- result = await connection.getTransaction(txId, {
290
- commitment: "confirmed",
291
- maxSupportedTransactionVersion: 0,
292
- }).catch(() => null);
293
- if (result && result.meta && result.meta?.err) {
294
- console.log(result.meta.err);
295
- throw new Error(txId);
296
- }
297
- iterations--;
298
- }
336
+ if (simulateTransactions) {
337
+ txId = await connection.sendRawTransaction(
338
+ serializedTx,
339
+ { preflightCommitment: "confirmed", maxRetries: 0 },
340
+ ).catch(e => {
341
+ console.log(e.message);
342
+ throw new Error("Simulation failed");
343
+ });
344
+ } else {
345
+ txId = await connection.sendRawTransaction(
346
+ serializedTx,
347
+ { skipPreflight: true, maxRetries: 0 },
348
+ );
349
+ }
299
350
 
300
- if (result) return txId;
351
+ const rebroadcastInterval = setInterval(() => {
352
+ connection.sendRawTransaction(
353
+ serializedTx,
354
+ { skipPreflight: true, maxRetries: 0 },
355
+ ).catch(() => {});
356
+ }, rebroadcastIntervalMs);
301
357
 
302
- //@ts-ignore
303
- if (!confirmation || confirmation.value.err) {
304
- //@ts-ignore
305
- console.log(confirmation?.value.err);
306
- throw new Error(txId);
358
+ let timeoutId: ReturnType<typeof setTimeout> | undefined;
359
+ try {
360
+ const confirmPromise = connection.confirmTransaction({
361
+ blockhash,
362
+ lastValidBlockHeight,
363
+ signature: txId,
364
+ }, "confirmed");
365
+
366
+ const timeoutPromise = new Promise<never>((_, reject) => {
367
+ timeoutId = setTimeout(() => reject(new Error("timeout")), confirmTimeoutMs);
368
+ });
369
+
370
+ const confirmation = await Promise.race([confirmPromise, timeoutPromise]);
371
+
372
+ if (confirmation.value.err) {
373
+ console.log("Tx error:", txId, confirmation.value.err);
374
+ return "Error";
375
+ }
376
+ return txId;
377
+ } catch (e: any) {
378
+ if (e?.message === "timeout") {
379
+ const result = await connection.getTransaction(txId, {
380
+ commitment: "confirmed",
381
+ maxSupportedTransactionVersion: 0,
382
+ }).catch(() => null);
383
+ if (result && !result.meta?.err) return txId;
384
+ if (result?.meta?.err) console.log("Tx error:", txId, result.meta.err);
385
+ }
386
+ console.log("Error sending tx:", e?.message ?? e);
387
+ return "Error";
388
+ } finally {
389
+ clearInterval(rebroadcastInterval);
390
+ if (timeoutId !== undefined) clearTimeout(timeoutId);
391
+ }
392
+ }),
393
+ );
394
+ batchTxIds.push(txIds);
307
395
  }
396
+ return batchTxIds;
397
+ }
308
398
 
309
- return txId;
399
+ /**
400
+ * Sign + send a TxPayloadBatchSequence using fast parallel rebroadcasting.
401
+ */
402
+ export async function sendTxPayloadBatchSequence(
403
+ connection: Connection,
404
+ txPayloadBatchSequence: TxPayloadBatchSequence,
405
+ simulateTransactions: boolean = false,
406
+ ): Promise<TransactionSignature[][]> {
407
+ const txs: VersionedTransaction[][] = [];
408
+ for (const batch of txPayloadBatchSequence.batches) {
409
+ const batchTxs: VersionedTransaction[] = [];
410
+ for (const tx of batch.transactions)
411
+ batchTxs.push(VersionedTransaction.deserialize(Buffer.from(tx.tx_b64, 'base64')));
412
+ txs.push(batchTxs);
413
+ }
414
+ return await sendVersionedTxs(connection, {
415
+ blockhash: txPayloadBatchSequence.batches[0].transactions[0]?.recent_blockhash ?? "",
416
+ lastValidBlockHeight: txPayloadBatchSequence.batches[0].transactions[0]?.last_valid_block_height ?? 0,
417
+ batches: txs,
418
+ }, simulateTransactions);
310
419
  }
311
420
 
312
421
  export async function prepareVersionedTxs(
@@ -342,30 +451,30 @@ export async function prepareVersionedTxs(
342
451
  };
343
452
  }
344
453
 
345
- export async function sendVersionedTxs(
346
- connection: Connection,
347
- versionedTxs: VersionedTxs,
348
- simulateTransactions: boolean = false,
349
- ): Promise<TransactionSignature[][]> {
350
- const { batches, blockhash, lastValidBlockHeight } = versionedTxs;
351
- let batchTxIds: TransactionSignature[][] = [];
454
+ // export async function sendVersionedTxs(
455
+ // connection: Connection,
456
+ // versionedTxs: VersionedTxs,
457
+ // simulateTransactions: boolean = false,
458
+ // ): Promise<TransactionSignature[][]> {
459
+ // const { batches, blockhash, lastValidBlockHeight } = versionedTxs;
460
+ // let batchTxIds: TransactionSignature[][] = [];
352
461
 
353
- for (const batch of batches) {
354
- let txIds = await Promise.all(
355
- batch.map(tx =>
356
- sendVersionedTransaction(
357
- connection,
358
- tx,
359
- blockhash,
360
- lastValidBlockHeight,
361
- simulateTransactions
362
- ).catch(e => {console.log("Error sending tx:", e.message); return "Error"})
363
- )
364
- );
365
- batchTxIds.push(txIds);
366
- }
367
- return batchTxIds;
368
- }
462
+ // for (const batch of batches) {
463
+ // let txIds = await Promise.all(
464
+ // batch.map(tx =>
465
+ // sendVersionedTransaction(
466
+ // connection,
467
+ // tx,
468
+ // blockhash,
469
+ // lastValidBlockHeight,
470
+ // simulateTransactions
471
+ // ).catch(e => {console.log("Error sending tx:", e.message); return "Error"})
472
+ // )
473
+ // );
474
+ // batchTxIds.push(txIds);
475
+ // }
476
+ // return batchTxIds;
477
+ // }
369
478
 
370
479
 
371
480
  export async function signVersionedTxs(
@@ -406,21 +515,21 @@ export async function signTxPayloadBatchSequence(
406
515
  return txPayloadBatchSequence;
407
516
  }
408
517
 
409
- export async function sendTxPayloadBatchSequence(
410
- connection: Connection,
411
- txPayloadBatchSequence: TxPayloadBatchSequence,
412
- simulateTransactions: boolean = false,
413
- ): Promise<TransactionSignature[][]> {
414
- let txs: VersionedTransaction[][] = [];
415
- for (const batch of txPayloadBatchSequence.batches) {
416
- let batchTxs: VersionedTransaction[] = [];
417
- for (const tx of batch.transactions)
418
- batchTxs.push(VersionedTransaction.deserialize(Buffer.from(tx.tx_b64, 'base64')));
419
- txs.push(batchTxs);
420
- }
421
- return await sendVersionedTxs(connection, {
422
- blockhash: txPayloadBatchSequence.batches[0].transactions[0]?.recent_blockhash ?? "",
423
- lastValidBlockHeight: txPayloadBatchSequence.batches[0].transactions[0]?.last_valid_block_height ?? 0,
424
- batches: txs,
425
- }, simulateTransactions);
426
- }
518
+ // export async function sendTxPayloadBatchSequence(
519
+ // connection: Connection,
520
+ // txPayloadBatchSequence: TxPayloadBatchSequence,
521
+ // simulateTransactions: boolean = false,
522
+ // ): Promise<TransactionSignature[][]> {
523
+ // let txs: VersionedTransaction[][] = [];
524
+ // for (const batch of txPayloadBatchSequence.batches) {
525
+ // let batchTxs: VersionedTransaction[] = [];
526
+ // for (const tx of batch.transactions)
527
+ // batchTxs.push(VersionedTransaction.deserialize(Buffer.from(tx.tx_b64, 'base64')));
528
+ // txs.push(batchTxs);
529
+ // }
530
+ // return await sendVersionedTxs(connection, {
531
+ // blockhash: txPayloadBatchSequence.batches[0].transactions[0]?.recent_blockhash ?? "",
532
+ // lastValidBlockHeight: txPayloadBatchSequence.batches[0].transactions[0]?.last_valid_block_height ?? 0,
533
+ // batches: txs,
534
+ // }, simulateTransactions);
535
+ // }
package/test.ts CHANGED
@@ -1,13 +1,15 @@
1
- import { Connection, Keypair, PublicKey } from "@solana/web3.js";
1
+ import { ComputeBudgetProgram, Connection, Keypair, PublicKey } from "@solana/web3.js";
2
2
  import { BN, Wallet } from "@coral-xyz/anchor";
3
3
  import { bs58 } from "@coral-xyz/anchor/dist/cjs/utils/bytes";
4
- import { Vault, Intent, RebalanceIntent, SymmetryCore, TaskContext, KeeperMonitor } from "./src";
4
+ import { Vault, Intent, RebalanceIntent, SymmetryCore, TaskContext, KeeperMonitor, isRebalanceRequired } from "./src";
5
5
  import { AddOrEditTokenInput, UpdateWeightsInput, MakeDirectSwapInput } from "./src";
6
6
  import { EditCreatorSettings, EditManagerSettings, EditFeeSettings, EditScheduleSettings, EditAutomationSettings, EditLpSettings, EditMetadataSettings, EditDepositsSettings, EditForceRebalanceSettings, EditCustomRebalanceSettings, EditAddTokenSettings, EditUpdateWeightsSettings, EditMakeDirectSwapSettings } from "./src";
7
7
  import { getRebalanceIntentPda } from "./src/instructions/pda";
8
- import { PYTHNET_CUSTODY_PRICE_WSOL_ACCOUNT } from "./src/constants";
8
+ import { COMPUTE_UNITS, PYTHNET_CUSTODY_PRICE_WSOL_ACCOUNT } from "./src/constants";
9
9
  import { getJupTokenLedgerAndSwapInstructions } from "./src/jup";
10
10
  import { getSwapPairs } from "./src/states/intents/rebalanceIntent";
11
+ import { buildCloseEncodedVaaInstruction } from "./src/states/oracles/pythOracle";
12
+ import { prepareTxPayloadBatchSequence, prepareVersionedTxs, TxBatchData } from "./src/txUtils";
11
13
 
12
14
 
13
15
  let kp = Array.from(Keypair.generate().secretKey);
@@ -73,7 +75,7 @@ async function testStates() {
73
75
 
74
76
  createVault: false, // TESTED
75
77
  editPrivateVaultSettings: false, // NOT TESTED
76
- addBounty: true, // TESTED
78
+ addBounty: false, // TESTED
77
79
 
78
80
  editCreator: false, // TESTED
79
81
  editManagerSettings: false, // TESTED
@@ -565,13 +567,13 @@ async function testStates() {
565
567
  symmetryFeesExtraData: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,],
566
568
 
567
569
  bountyMint: new PublicKey("So11111111111111111111111111111111111111112"),
568
- minBountyForVaultAutomation: new BN(200 * 50 * 10 ** 3),
569
- bountyBondAmount: new BN(5 * 50 * 10 ** 3),
570
+ minBountyForVaultAutomation: new BN(2000 * 5000),
571
+ bountyBondAmount: new BN(20 * 5000),
570
572
  bountyPerTask: {
571
- minBounty: new BN(10 * 50 * 10 ** 3),
572
- maxBounty: new BN(20 * 50 * 10 ** 3),
573
- minBountyUntil: new BN(1),
574
- maxBountyAfter: new BN(101),
573
+ minBounty: new BN(20 * 5000),
574
+ maxBounty: new BN(50 * 5000),
575
+ minBountyUntil: new BN(10),
576
+ maxBountyAfter: new BN(40),
575
577
  },
576
578
  bountyPerPriceUpdateTaskDivisor: new BN(3),
577
579
  minRemainingValue: {high: new BN(0), low: new BN("1844674407370955161")},