diff --git a/package.json b/package.json index 5db53618..c264af6a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@zetamarkets/sdk", "repository": "https://github.com/zetamarkets/sdk/", - "version": "1.26.4", + "version": "1.27.3", "description": "Zeta SDK", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -17,12 +17,13 @@ "@solana/spl-token": "0.1.6", "@solana/web3.js": "1.87.6", "@zetamarkets/anchor": "0.26.1-versioned", + "axios": "^1.6.8", "bs58": "^4.0.1", "cross-fetch": "^3.1.6", "lodash": "^4.17.21", "lodash.clonedeep": "^4.5.0", - "zeta-solana-web3": "1.87.7", - "obscenity": "0.2.0" + "obscenity": "0.2.0", + "zeta-solana-web3": "1.87.7" }, "devDependencies": { "@types/mocha": "^9.0.0", diff --git a/src/exchange.ts b/src/exchange.ts index 2b4dc898..65c630bf 100644 --- a/src/exchange.ts +++ b/src/exchange.ts @@ -310,6 +310,18 @@ export class Exchange { } private _priorityFee: number = 0; + // toggle to use jito bundles + public get useJitoBundle(): boolean { + return this._useJitoBundle; + } + private _useJitoBundle: boolean = false; + + // jito tip + public get jitoTip(): number { + return this._jitoTip; + } + private _jitoTip: number = 0; + public get useAutoPriorityFee(): boolean { return this._useAutoPriorityFee; } @@ -355,6 +367,14 @@ export class Exchange { this._priorityFee = microLamportsPerCU; } + public setUseJitoBundle(option: boolean) { + this._useJitoBundle = option; + } + + public updateJitoTip(tipAmountInLamports: number) { + this._jitoTip = tipAmountInLamports; + } + public updateAutoPriorityFeeUpperLimit(microLamportsPerCU: number) { this._autoPriorityFeeUpperLimit = microLamportsPerCU; } diff --git a/src/idl/zeta.json b/src/idl/zeta.json index 0f5733da..a7a1e7dd 100644 --- a/src/idl/zeta.json +++ b/src/idl/zeta.json @@ -444,7 +444,7 @@ { "name": "authority", "isMut": true, - "isSigner": true + "isSigner": false } ], "args": [] @@ -465,7 +465,7 @@ { "name": "authority", "isMut": true, - "isSigner": true + "isSigner": false } ], "args": [ @@ -2763,7 +2763,7 @@ { "name": "authority", "isMut": true, - "isSigner": true + "isSigner": false }, { "name": "market", @@ -9721,7 +9721,7 @@ "name": "STRK" }, { - "name": "W" + "name": "TNSR" }, { "name": "UNDEFINED" diff --git a/src/types/zeta.ts b/src/types/zeta.ts index 63bf25e8..1d1bdb48 100644 --- a/src/types/zeta.ts +++ b/src/types/zeta.ts @@ -444,7 +444,7 @@ export type Zeta = { { "name": "authority", "isMut": true, - "isSigner": true + "isSigner": false } ], "args": [] @@ -465,7 +465,7 @@ export type Zeta = { { "name": "authority", "isMut": true, - "isSigner": true + "isSigner": false } ], "args": [ @@ -2763,7 +2763,7 @@ export type Zeta = { { "name": "authority", "isMut": true, - "isSigner": true + "isSigner": false }, { "name": "market", @@ -9721,7 +9721,7 @@ export type Zeta = { "name": "STRK" }, { - "name": "W" + "name": "TNSR" }, { "name": "UNDEFINED" @@ -11705,7 +11705,7 @@ export const IDL: Zeta = { { "name": "authority", "isMut": true, - "isSigner": true + "isSigner": false } ], "args": [] @@ -11726,7 +11726,7 @@ export const IDL: Zeta = { { "name": "authority", "isMut": true, - "isSigner": true + "isSigner": false } ], "args": [ @@ -14024,7 +14024,7 @@ export const IDL: Zeta = { { "name": "authority", "isMut": true, - "isSigner": true + "isSigner": false }, { "name": "market", @@ -20982,7 +20982,7 @@ export const IDL: Zeta = { "name": "STRK" }, { - "name": "W" + "name": "TNSR" }, { "name": "UNDEFINED" diff --git a/src/utils.ts b/src/utils.ts index c0a021e9..57f18f8c 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -50,6 +50,7 @@ import { Network } from "./network"; import cloneDeep from "lodash.clonedeep"; import * as os from "os"; import { OpenOrders, _OPEN_ORDERS_LAYOUT_V2 } from "./serum/market"; +import axios from "axios"; export function getNativeTickSize(asset: Asset): number { return Exchange.state.tickSizes[assets.assetToIndex(asset)]; @@ -950,6 +951,122 @@ function txConfirmationCheck(expectedLevel: string, currentLevel: string) { return false; } +export async function processTransactionJito( + provider: anchor.AnchorProvider, + tx: Transaction, + signers?: Array, + opts?: ConfirmOptions, + lutAccs?: AddressLookupTableAccount[], + blockhash?: { blockhash: string; lastValidBlockHeight: number } +): Promise { + if (Exchange.jitoTip == 0) { + throw Error("Jito bundle tip has not been set."); + } + + if (Exchange.priorityFee != 0) { + tx.instructions.unshift( + ComputeBudgetProgram.setComputeUnitPrice({ + microLamports: Math.round(Exchange.priorityFee), + }) + ); + } + + tx.instructions.push( + SystemProgram.transfer({ + fromPubkey: Exchange.provider.publicKey, + toPubkey: new PublicKey( + "DttWaMuVvTiduZRnguLF7jNxTgiMBZ1hyAumKUiL2KRL" // Jito tip account + ), + lamports: Exchange.jitoTip, // tip + }) + ); + + let recentBlockhash = + blockhash ?? + (await provider.connection.getLatestBlockhash( + Exchange.blockhashCommitment + )); + + let vTx: VersionedTransaction = new VersionedTransaction( + new TransactionMessage({ + payerKey: provider.wallet.publicKey, + recentBlockhash: recentBlockhash.blockhash, + instructions: tx.instructions, + }).compileToV0Message(lutAccs) + ); + + vTx.sign( + (signers ?? []) + .filter((s) => s !== undefined) + .map((kp) => { + return kp; + }) + ); + vTx = (await provider.wallet.signTransaction(vTx)) as VersionedTransaction; + + let rawTx = vTx.serialize(); + + const encodedTx = bs58.encode(rawTx); + const jitoURL = "https://mainnet.block-engine.jito.wtf/api/v1/transactions"; + const payload = { + jsonrpc: "2.0", + id: 1, + method: "sendTransaction", + params: [encodedTx], + }; + + let txOpts = opts || commitmentConfig(provider.connection.commitment); + let txSig: string; + try { + const response = await axios.post(jitoURL, payload, { + headers: { "Content-Type": "application/json" }, + }); + txSig = response.data.result; + } catch (error) { + console.error("Error:", error); + throw new Error("Jito Bundle Error: cannot send."); + } + + if (Exchange.skipRpcConfirmation) { + return txSig; + } + + let currentBlockHeight = await provider.connection.getBlockHeight( + provider.connection.commitment + ); + + while (currentBlockHeight < recentBlockhash.lastValidBlockHeight) { + // Keep resending to maximise the chance of confirmation + await provider.connection.sendRawTransaction(rawTx, { + skipPreflight: true, + preflightCommitment: provider.connection.commitment, + }); + + let status = await provider.connection.getSignatureStatus(txSig); + currentBlockHeight = await provider.connection.getBlockHeight( + provider.connection.commitment + ); + if (status.value != null) { + if (status.value.err != null) { + // Gets caught and parsed in the later catch + let err = parseInt(status.value.err["InstructionError"][1]["Custom"]); + let parsedErr = parseError(err); + throw parsedErr; + } + if ( + txConfirmationCheck( + txOpts.commitment ? txOpts.commitment.toString() : "confirmed", + status.value.confirmationStatus.toString() + ) + ) { + return txSig; + } + } + await sleep(500); // Don't spam the RPC + } + throw Error(`Transaction ${txSig} was not confirmed`); +} + export async function processTransaction( provider: anchor.AnchorProvider, tx: Transaction, @@ -960,6 +1077,17 @@ export async function processTransaction( blockhash?: { blockhash: string; lastValidBlockHeight: number }, retries?: number ): Promise { + if (Exchange.useJitoBundle) { + return processTransactionJito( + provider, + tx, + signers, + opts, + lutAccs, + blockhash + ); + } + let failures = 0; while (true) { let rawTx: Buffer | Uint8Array; @@ -1322,7 +1450,7 @@ export async function cleanZetaMarketHalted(asset: Asset) { */ export async function crankMarket( asset: Asset, - openOrdersToMargin?: Map, + openOrdersToMargin?: Map, crankLimit?: number ): Promise { let ix = await createCrankMarketIx(asset, openOrdersToMargin, crankLimit); @@ -1340,7 +1468,7 @@ export async function crankMarket( export async function createCrankMarketIx( asset: Asset, - openOrdersToMargin?: Map, + openOrdersToMargin?: Map, crankLimit?: number ): Promise { let market = Exchange.getPerpMarket(asset); @@ -1373,11 +1501,17 @@ export async function createCrankMarketIx( await Promise.all( uniqueOpenOrders.map(async (openOrders, index) => { let marginAccount: PublicKey; - if (openOrdersToMargin && !openOrdersToMargin.has(openOrders)) { + if ( + openOrdersToMargin && + !openOrdersToMargin.has(openOrders.toBase58()) + ) { marginAccount = await getAccountFromOpenOrders(openOrders, asset); - openOrdersToMargin.set(openOrders, marginAccount); - } else if (openOrdersToMargin && openOrdersToMargin.has(openOrders)) { - marginAccount = openOrdersToMargin.get(openOrders); + openOrdersToMargin.set(openOrders.toBase58(), marginAccount); + } else if ( + openOrdersToMargin && + openOrdersToMargin.has(openOrders.toBase58()) + ) { + marginAccount = openOrdersToMargin.get(openOrders.toBase58()); } else { marginAccount = await getAccountFromOpenOrders(openOrders, asset); }