@haven-fi/solauto-sdk 1.0.310 → 1.0.311

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.
@@ -28,8 +28,8 @@ import {
28
28
  TransactionExpiredBlockheightExceededError,
29
29
  } from "@solana/web3.js";
30
30
  import { SWITCHBOARD_PRICE_FEED_IDS } from "../constants/switchboardConstants";
31
- import { buildSwbSubmitResponseTx, getSwitchboardPrices } from "../utils";
32
- // import { sendJitoBundledTransactions } from "../utils/jitoUtils";
31
+ import { buildSwbSubmitResponseTx, getSwitchboardFeedData } from "../utils";
32
+ import { sendJitoBundledTransactions } from "../utils/jitoUtils";
33
33
 
34
34
  const CHORES_TX_NAME = "account chores";
35
35
 
@@ -220,6 +220,7 @@ export class TransactionsManager {
220
220
  private statusCallback?: (statuses: TransactionManagerStatuses) => void,
221
221
  private txType?: TransactionRunType,
222
222
  private priorityFeeSetting: PriorityFeeSetting = PriorityFeeSetting.Min,
223
+ private atomically: boolean = false,
223
224
  private errorsToThrow?: ErrorsToThrow,
224
225
  private retries: number = 4,
225
226
  private retryDelay: number = 150
@@ -404,7 +405,7 @@ export class TransactionsManager {
404
405
  (x) => SWITCHBOARD_PRICE_FEED_IDS[x] === swbOracle
405
406
  )!
406
407
  );
407
- const stale = (await getSwitchboardPrices(client.connection, [mint]))[0]
408
+ const stale = (await getSwitchboardFeedData(client.connection, [mint]))[0]
408
409
  .stale;
409
410
 
410
411
  if (stale) {
@@ -489,15 +490,81 @@ export class TransactionsManager {
489
490
  return [];
490
491
  }
491
492
 
492
- let currentIndex = 0;
493
- while (currentIndex < itemSets.length) {
494
- await this.processTransactionSet(itemSets, currentIndex);
495
- currentIndex++;
493
+ if (itemSets.length > 1 && this.atomically) {
494
+ await this.processTransactionsAtomically(itemSets);
495
+ } else {
496
+ let currentIndex = 0;
497
+ while (currentIndex < itemSets.length) {
498
+ await this.processTransactionSet(itemSets, currentIndex);
499
+ currentIndex++;
500
+ }
496
501
  }
497
502
 
498
503
  return this.statuses;
499
504
  }
500
505
 
506
+ private async processTransactionsAtomically(itemSets: TransactionSet[]) {
507
+ let num = 0;
508
+
509
+ await retryWithExponentialBackoff(
510
+ async (attemptNum) => {
511
+ num = attemptNum;
512
+
513
+ let transactions = [];
514
+ for (const set of itemSets) {
515
+ transactions.push(await set.getSingleTransaction());
516
+ }
517
+
518
+ itemSets.forEach((x) =>
519
+ this.updateStatus(x.name(), TransactionStatus.Processing, attemptNum)
520
+ );
521
+ const txSigs = await sendJitoBundledTransactions(
522
+ this.txHandler.umi,
523
+ this.txHandler.signer,
524
+ transactions,
525
+ false,
526
+ this.priorityFeeSetting
527
+ );
528
+ if (txSigs) {
529
+ itemSets.forEach((x, i) =>
530
+ this.updateStatus(
531
+ x.name(),
532
+ TransactionStatus.Successful,
533
+ attemptNum,
534
+ txSigs[i]
535
+ )
536
+ );
537
+ } else {
538
+ itemSets.forEach((x) =>
539
+ this.updateStatus(
540
+ x.name(),
541
+ TransactionStatus.Failed,
542
+ attemptNum,
543
+ undefined,
544
+ true
545
+ )
546
+ );
547
+ throw new Error("Unknown error");
548
+ }
549
+ },
550
+ this.retries,
551
+ this.retryDelay,
552
+ this.errorsToThrow
553
+ ).catch((e: Error) => {
554
+ itemSets.forEach((x) =>
555
+ this.updateStatus(
556
+ x.name(),
557
+ TransactionStatus.Failed,
558
+ num,
559
+ undefined,
560
+ true,
561
+ e.message
562
+ )
563
+ );
564
+ throw e;
565
+ });
566
+ }
567
+
501
568
  private async processTransactionSet(
502
569
  itemSets: TransactionSet[],
503
570
  currentIndex: number
@@ -1,68 +1,47 @@
1
- // import { SolautoClient } from "../clients/solautoClient";
2
- // import { Keypair, PublicKey, VersionedTransaction } from "@solana/web3.js";
3
- // import { SimulatedBundleTransactionResult } from "jito-ts";
4
- // import { toWeb3JsTransaction } from "@metaplex-foundation/umi-web3js-adapters";
5
- // import {
6
- // JITO_BLOCK_ENGINE,
7
- // JITO_CONNECTION,
8
- // UMI,
9
- // } from "../constants/solautoConstants";
10
- // import {
11
- // Signer,
12
- // TransactionBuilder,
13
- // WrappedInstruction,
14
- // } from "@metaplex-foundation/umi";
15
- // import {
16
- // assembleFinalTransaction,
17
- // getComputeUnitPriceEstimate,
18
- // getSecretKey,
19
- // systemTransferUmiIx,
20
- // } from "./solanaUtils";
21
- // import { Bundle } from "jito-ts/dist/sdk/block-engine/types";
22
- // import {
23
- // SearcherClient,
24
- // searcherClient,
25
- // } from "jito-ts/dist/sdk/block-engine/searcher";
26
- // import { BundleResult } from "jito-ts/dist/gen/block-engine/bundle";
27
-
28
- // export function getSearcherClient(): SearcherClient {
29
- // return searcherClient(
30
- // JITO_BLOCK_ENGINE,
31
- // Keypair.fromSecretKey(getSecretKey("jito-bundles"))
32
- // );
33
- // }
34
-
35
- // export async function getRandomTipAccount(): Promise<PublicKey> {
36
- // const tipAccounts = await getSearcherClient().getTipAccounts();
37
- // const randomInt = Math.floor(Math.random() * tipAccounts.length);
38
- // return new PublicKey(tipAccounts[randomInt]);
39
- // }
40
-
41
- // export async function waitUntilJitoNextLeader(
42
- // distanceFromJitoSlot: number = 5
43
- // ) {
44
- // let searcher = getSearcherClient();
45
- // let isLeaderSlot = false;
46
-
47
- // while (!isLeaderSlot) {
48
- // const nextLeader = await searcher.getNextScheduledLeader();
49
- // const numSlots = nextLeader.nextLeaderSlot - nextLeader.currentSlot;
50
- // isLeaderSlot = numSlots <= distanceFromJitoSlot && numSlots > 1;
51
- // consoleLog(`Next jito leader slot in ${numSlots} slots`);
52
- // await new Promise((r) => setTimeout(r, 500));
53
- // }
54
- // }
55
-
56
- // async function getTipInstruction(
57
- // client: SolautoClient,
58
- // tipLamports: number
59
- // ): Promise<WrappedInstruction> {
60
- // return systemTransferUmiIx(
61
- // client.signer,
62
- // await getRandomTipAccount(),
63
- // BigInt(tipLamports)
64
- // );
65
- // }
1
+ import { PublicKey, VersionedTransaction } from "@solana/web3.js";
2
+ import { toWeb3JsTransaction } from "@metaplex-foundation/umi-web3js-adapters";
3
+ import { JITO_BLOCK_ENGINE } from "../constants/solautoConstants";
4
+ import {
5
+ Signer,
6
+ TransactionBuilder,
7
+ Umi,
8
+ WrappedInstruction,
9
+ } from "@metaplex-foundation/umi";
10
+ import {
11
+ assembleFinalTransaction,
12
+ getComputeUnitPriceEstimate,
13
+ systemTransferUmiIx,
14
+ } from "./solanaUtils";
15
+ import { consoleLog } from "./generalUtils";
16
+ import { PriorityFeeSetting } from "../types";
17
+ import axios from "axios";
18
+ import base58 from "bs58";
19
+
20
+ export async function getRandomTipAccount(): Promise<PublicKey> {
21
+ const tipAccounts = [
22
+ "96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5",
23
+ "HFqU5x63VTqvQss8hp11i4wVV8bD44PvwucfZ2bU7gRe",
24
+ "Cw8CFyM9FkoMi7K7Crf6HNQqf4uEMzpKw6QNghXLvLkY",
25
+ "ADaUMid9yfUytqMBgopwjb2DTLSokTSzL1zt6iGPaS49",
26
+ "DfXygSm4jCyNCybVYYK6DwvWqjKee8pbDmJGcLWNDXjh",
27
+ "ADuUkR4vqLUMWXxW9gh6D6L8pMSawimctcNZ5pGwDcEt",
28
+ "DttWaMuVvTiduZRnguLF7jNxTgiMBZ1hyAumKUiL2KRL",
29
+ "3AVi9Tg9Uo68tJfuvoKvqKNWKkC5wPdSSdeBnizKZ6jT",
30
+ ];
31
+ const randomInt = Math.floor(Math.random() * tipAccounts.length);
32
+ return new PublicKey(tipAccounts[randomInt]);
33
+ }
34
+
35
+ async function getTipInstruction(
36
+ signer: Signer,
37
+ tipLamports: number
38
+ ): Promise<WrappedInstruction> {
39
+ return systemTransferUmiIx(
40
+ signer,
41
+ await getRandomTipAccount(),
42
+ BigInt(tipLamports)
43
+ );
44
+ }
66
45
 
67
46
  // async function simulateJitoBundle(
68
47
  // txs: VersionedTransaction[]
@@ -85,104 +64,125 @@
85
64
  // return simulationResult.value.transactionResults;
86
65
  // }
87
66
 
88
- // async function umiToVersionedTransactions(
89
- // signer: Signer,
90
- // txs: TransactionBuilder[],
91
- // feeEstimates: number[],
92
- // computeUnitLimits?: number[]
93
- // ): Promise<VersionedTransaction[]> {
94
- // return await Promise.all(
95
- // txs.map(async (tx, i) => {
96
- // const versionedTx = toWeb3JsTransaction(
97
- // await (
98
- // await assembleFinalTransaction(
99
- // signer,
100
- // tx,
101
- // feeEstimates[i],
102
- // computeUnitLimits ? computeUnitLimits[i] : undefined
103
- // ).setLatestBlockhash(UMI)
104
- // ).buildAndSign(UMI)
105
- // );
106
- // return versionedTx;
107
- // })
108
- // );
109
- // }
110
-
111
- // async function sendJitoBundle(bundle: Bundle): Promise<BundleResult> {
112
- // await waitUntilJitoNextLeader();
113
- // let searcher = getSearcherClient();
114
-
115
- // consoleLog("Sending bundle...");
116
- // try {
117
- // const resp = await searcher.sendBundle(bundle);
118
- // consoleLog("Send bundle response:", resp);
119
- // } catch (e) {
120
- // console.error("Error sending bundle:", e);
121
- // }
122
-
123
- // return await new Promise((resolve, reject) => {
124
- // searcher.onBundleResult(
125
- // (res) => {
126
- // if (res.accepted || res.processed || res.finalized) {
127
- // resolve(res);
128
- // } else {
129
- // consoleLog(res);
130
- // return reject("Bundle not accepted");
131
- // }
132
- // },
133
- // (err) => {
134
- // consoleLog("Error: ", err);
135
- // return reject(err);
136
- // }
137
- // );
138
- // });
139
- // }
140
-
141
- // interface JitoTransactionsResult {
142
- // bundleResult: BundleResult;
143
- // txSigs: Uint8Array[];
144
- // }
145
-
146
- // export async function sendJitoBundledTransactions(
147
- // client: SolautoClient,
148
- // txs: TransactionBuilder[],
149
- // simulateOnly?: boolean
150
- // ): Promise<JitoTransactionsResult | undefined> {
151
- // client.log("Sending Jito bundle...");
152
- // client.log("Transactions: ", txs.length);
153
- // client.log(
154
- // "Transaction sizes: ",
155
- // txs.map((x) => x.getTransactionSize(UMI))
156
- // );
157
-
158
- // txs[0] = txs[0].prepend(await getTipInstruction(client, 1000000));
159
- // const feeEstimates = await Promise.all(txs.map(getComputeUnitPriceEstimate));
160
-
161
- // let builtTxs = await umiToVersionedTransactions(
162
- // client.signer,
163
- // txs,
164
- // feeEstimates,
165
- // // Array(txs.length).fill(1_400_000)
166
- // );
167
- // // // TODO: Skip over this for now, and instead don't specify a compute unit limit in the final bundle transactions
168
- // // const simulationResults = await simulateJitoBundle(builtTxs);
169
-
170
- // if (!simulateOnly) {
171
- // // let builtTxs = await umiToVersionedTransactions(
172
- // // client.signer,
173
- // // txs,
174
- // // feeEstimates,
175
- // // simulationResults.map((x) => x.unitsConsumed! * 1.15)
176
- // // );
177
-
178
- // const bundleResult = await sendJitoBundle(
179
- // new Bundle(builtTxs, 100)
180
- // );
181
- // return {
182
- // bundleResult,
183
- // txSigs: builtTxs.map((x) => x.signatures).flat(),
184
- // };
185
- // }
186
-
187
- // return undefined;
188
- // }
67
+ async function umiToVersionedTransactions(
68
+ umi: Umi,
69
+ signer: Signer,
70
+ txs: TransactionBuilder[],
71
+ feeEstimates: number[],
72
+ computeUnitLimits?: number[]
73
+ ): Promise<VersionedTransaction[]> {
74
+ return await Promise.all(
75
+ txs.map(async (tx, i) => {
76
+ const versionedTx = toWeb3JsTransaction(
77
+ await (
78
+ await assembleFinalTransaction(
79
+ signer,
80
+ tx,
81
+ feeEstimates[i],
82
+ computeUnitLimits ? computeUnitLimits[i] : undefined
83
+ ).setLatestBlockhash(umi)
84
+ ).buildAndSign(umi)
85
+ );
86
+ return versionedTx;
87
+ })
88
+ );
89
+ }
90
+
91
+ async function getBundleStatus(bundleId: string) {
92
+ const res = await axios.post(`${JITO_BLOCK_ENGINE}/api/v1/bundles`, {
93
+ jsonrpc: "2.0",
94
+ id: 1,
95
+ method: "getBundleStatuses",
96
+ params: [[bundleId]],
97
+ });
98
+ if (res.data.error) {
99
+ throw new Error(`Failed to get bundle status: ${res.data.error}`);
100
+ }
101
+
102
+ return res.data.result;
103
+ }
104
+
105
+ async function pollBundleStatus(
106
+ bundleId: string,
107
+ interval = 1000,
108
+ timeout = 40000
109
+ ): Promise<string[]> {
110
+ const endTime = Date.now() + timeout;
111
+ while (Date.now() < endTime) {
112
+ await new Promise((resolve) => setTimeout(resolve, interval));
113
+ const statuses = await getBundleStatus(bundleId);
114
+ if (statuses?.value?.length > 0) {
115
+ const status = statuses.value[0].confirmation_status;
116
+ if (status === "confirmed") {
117
+ return statuses?.value[0].transactions as string[];
118
+ }
119
+ }
120
+ }
121
+ return [];
122
+ }
123
+
124
+ async function sendJitoBundle(transactions: string[]): Promise<string[]> {
125
+ consoleLog("Sending bundle...");
126
+ const resp = await axios.post<{ result: string }>(
127
+ `${JITO_BLOCK_ENGINE}/api/v1/bundles`,
128
+ {
129
+ jsonrpc: "2.0",
130
+ id: 1,
131
+ method: "sendBundle",
132
+ params: [transactions],
133
+ }
134
+ );
135
+
136
+ const bundleId = resp.data.result;
137
+ return await pollBundleStatus(bundleId);
138
+ }
139
+
140
+ export async function sendJitoBundledTransactions(
141
+ umi: Umi,
142
+ signer: Signer,
143
+ txs: TransactionBuilder[],
144
+ simulateOnly?: boolean,
145
+ priorityFeeSetting: PriorityFeeSetting = PriorityFeeSetting.Min
146
+ ): Promise<string[] | undefined> {
147
+ consoleLog("Sending Jito bundle...");
148
+ consoleLog("Transactions: ", txs.length);
149
+ consoleLog(
150
+ "Transaction sizes: ",
151
+ txs.map((x) => x.getTransactionSize(umi))
152
+ );
153
+
154
+ txs[0] = txs[0].prepend(await getTipInstruction(signer, 150_000));
155
+ const feeEstimates = await Promise.all(
156
+ txs.map(
157
+ async (x) =>
158
+ (await getComputeUnitPriceEstimate(umi, x, priorityFeeSetting)) ??
159
+ 1000000
160
+ )
161
+ );
162
+
163
+ let builtTxs = await umiToVersionedTransactions(
164
+ umi,
165
+ signer,
166
+ txs,
167
+ feeEstimates
168
+ // Array(txs.length).fill(1_400_000)
169
+ );
170
+ // // TODO: Skip over this for now, and instead don't specify a compute unit limit in the final bundle transactions
171
+ // const simulationResults = await simulateJitoBundle(builtTxs);
172
+
173
+ if (!simulateOnly) {
174
+ // let builtTxs = await umiToVersionedTransactions(
175
+ // client.signer,
176
+ // txs,
177
+ // feeEstimates,
178
+ // simulationResults.map((x) => x.unitsConsumed! * 1.15)
179
+ // );
180
+
181
+ const txSigs = await sendJitoBundle(
182
+ builtTxs.map((x) => base58.encode(x.serialize()))
183
+ );
184
+ return txSigs.length > 0 ? txSigs : undefined;
185
+ }
186
+
187
+ return undefined;
188
+ }
@@ -1,4 +1,4 @@
1
- import { Connection, PublicKey } from "@solana/web3.js";
1
+ import { PublicKey } from "@solana/web3.js";
2
2
  import { PublicKey as UmiPublicKey } from "@metaplex-foundation/umi";
3
3
  import { PYTH_PRICE_FEED_IDS } from "../constants/pythConstants";
4
4
  import { fromBaseUnit, toBaseUnit } from "./numberUtils";
@@ -9,7 +9,7 @@ import {
9
9
  retryWithExponentialBackoff,
10
10
  zip,
11
11
  } from "./generalUtils";
12
- import { getPullFeed } from "./switchboardUtils";
12
+ import { getSwitchboardPrices } from "./switchboardUtils";
13
13
 
14
14
  export async function fetchTokenPrices(
15
15
  mints: PublicKey[]
@@ -32,7 +32,7 @@ export async function fetchTokenPrices(
32
32
 
33
33
  const [pythData, switchboardData] = await Promise.all([
34
34
  zip(pythMints, await getPythPrices(pythMints)),
35
- zip(switchboardMints, await getJupTokenPrices(switchboardMints)),
35
+ zip(switchboardMints, await getSwitchboardPrices(switchboardMints)),
36
36
  ]);
37
37
 
38
38
  const prices = mints.map((mint) => {
@@ -94,34 +94,6 @@ export async function getPythPrices(mints: PublicKey[]) {
94
94
  return prices;
95
95
  }
96
96
 
97
- export async function getSwitchboardPrices(
98
- conn: Connection,
99
- mints: PublicKey[]
100
- ): Promise<{ mint: PublicKey; price: number; stale: boolean }[]> {
101
- if (mints.length === 0) {
102
- return [];
103
- }
104
-
105
- const currSlot = await retryWithExponentialBackoff(
106
- async () => await conn.getSlot("confirmed"),
107
- 5
108
- );
109
-
110
- const results = await Promise.all(
111
- mints.map(async (mint) => {
112
- const feed = getPullFeed(conn, mint);
113
- const result = await feed.loadData();
114
- const price = Number(result.result.value) / Math.pow(10, 18);
115
- const stale =
116
- currSlot > result.result.slot.toNumber() + result.maxStaleness;
117
-
118
- return { mint, price, stale };
119
- })
120
- );
121
-
122
- return results;
123
- }
124
-
125
97
  export function safeGetPrice(
126
98
  mint: PublicKey | UmiPublicKey | undefined
127
99
  ): number | undefined {
@@ -140,12 +112,14 @@ export async function getJupTokenPrices(mints: PublicKey[]) {
140
112
  const res = (
141
113
  await fetch(
142
114
  "https://api.jup.ag/price/v2?ids=" +
143
- mints.map((x) => x.toString()).join(",")
115
+ mints.map((x) => x.toString()).join(",") + "&showExtraInfo=true"
144
116
  )
145
117
  ).json();
146
118
  return res;
147
119
  }, 6);
148
120
 
121
+ console.log(data.data[mints[0].toString()].extraInfo.quotedPrice);
122
+
149
123
  const prices = Object.values(data.data as { [key: string]: any }).map(
150
124
  (x) => parseFloat(x.price as string) as number
151
125
  );
@@ -14,6 +14,7 @@ import {
14
14
  fromWeb3JsInstruction,
15
15
  toWeb3JsPublicKey,
16
16
  } from "@metaplex-foundation/umi-web3js-adapters";
17
+ import { retryWithExponentialBackoff } from "./generalUtils";
17
18
 
18
19
  export function getPullFeed(
19
20
  conn: Connection,
@@ -49,9 +50,14 @@ export async function buildSwbSubmitResponseTx(
49
50
  ): Promise<TransactionItemInputs | undefined> {
50
51
  const crossbar = new CrossbarClient("https://crossbar.switchboard.xyz");
51
52
  const feed = getPullFeed(conn, mint, toWeb3JsPublicKey(signer.publicKey));
52
- const [pullIx, responses] = await feed.fetchUpdateIx({
53
- crossbarClient: crossbar,
54
- });
53
+ const [pullIx, responses] = await retryWithExponentialBackoff(
54
+ async () =>
55
+ await feed.fetchUpdateIx({
56
+ crossbarClient: crossbar,
57
+ }),
58
+ 8,
59
+ 200
60
+ );
55
61
 
56
62
  return {
57
63
  tx: transactionBuilder().add({
@@ -64,3 +70,52 @@ export async function buildSwbSubmitResponseTx(
64
70
  .map((x) => x.oracle.lut!.key.toString()),
65
71
  };
66
72
  }
73
+
74
+ export async function getSwitchboardFeedData(
75
+ conn: Connection,
76
+ mints: PublicKey[]
77
+ ): Promise<{ mint: PublicKey; price: number; stale: boolean }[]> {
78
+ if (mints.length === 0) {
79
+ return [];
80
+ }
81
+
82
+ const currSlot = await retryWithExponentialBackoff(
83
+ async () => await conn.getSlot("confirmed"),
84
+ 5
85
+ );
86
+
87
+ const results = await Promise.all(
88
+ mints.map(async (mint) => {
89
+ const feed = getPullFeed(conn, mint);
90
+ const result = await feed.loadData();
91
+ const price = Number(result.result.value) / Math.pow(10, 18);
92
+ const stale =
93
+ currSlot > result.result.slot.toNumber() + result.maxStaleness;
94
+
95
+ return { mint, price, stale };
96
+ })
97
+ );
98
+
99
+ return results;
100
+ }
101
+
102
+ export async function getSwitchboardPrices(
103
+ mints: PublicKey[]
104
+ ): Promise<number[]> {
105
+ if (mints.length === 0) {
106
+ return [];
107
+ }
108
+
109
+ const crossbar = new CrossbarClient("https://crossbar.switchboard.xyz");
110
+ const results = await retryWithExponentialBackoff(
111
+ async () =>
112
+ await crossbar.simulateSolanaFeeds(
113
+ "mainnet",
114
+ mints.map((x) => SWITCHBOARD_PRICE_FEED_IDS[x.toString()])
115
+ ),
116
+ 8,
117
+ 200
118
+ );
119
+
120
+ return results.flatMap((x) => x.results[0]);
121
+ }