feat: jito pusher (#1444)
* feat: jito script * Go * Go * Checkpoint * Checkpoint * Rename * Make tip account random * Go * Jito pusher * Go * lint * Lint * Bump
This commit is contained in:
parent
0aeae8ca40
commit
c727195e9c
|
@ -56725,7 +56725,7 @@
|
|||
},
|
||||
"price_pusher": {
|
||||
"name": "@pythnetwork/price-pusher",
|
||||
"version": "6.4.3",
|
||||
"version": "6.5.0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@injectivelabs/sdk-ts": "1.10.72",
|
||||
|
@ -56736,6 +56736,7 @@
|
|||
"@pythnetwork/pyth-sui-js": "*",
|
||||
"@truffle/hdwallet-provider": "^2.1.3",
|
||||
"aptos": "^1.8.5",
|
||||
"jito-ts": "^3.0.1",
|
||||
"joi": "^17.6.0",
|
||||
"near-api-js": "^3.0.2",
|
||||
"web3": "^1.8.1",
|
||||
|
@ -69382,6 +69383,7 @@
|
|||
"aptos": "^1.8.5",
|
||||
"eslint": "^8.13.0",
|
||||
"jest": "^29.7.0",
|
||||
"jito-ts": "^3.0.1",
|
||||
"joi": "^17.6.0",
|
||||
"near-api-js": "^3.0.2",
|
||||
"prettier": "^2.6.2",
|
||||
|
@ -71551,7 +71553,7 @@
|
|||
"bs58": "^5.0.0",
|
||||
"eslint": "^8.13.0",
|
||||
"jest": "^29.4.0",
|
||||
"jito-ts": "*",
|
||||
"jito-ts": "^3.0.1",
|
||||
"prettier": "^2.6.2",
|
||||
"quicktype": "^23.0.76",
|
||||
"ts-jest": "^29.0.5",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@pythnetwork/price-pusher",
|
||||
"version": "6.4.3",
|
||||
"version": "6.5.0",
|
||||
"description": "Pyth Price Pusher",
|
||||
"homepage": "https://pyth.network",
|
||||
"main": "lib/index.js",
|
||||
|
@ -52,13 +52,14 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@injectivelabs/sdk-ts": "1.10.72",
|
||||
"@pythnetwork/pyth-solana-receiver": "*",
|
||||
"@mysten/sui.js": "^0.49.1",
|
||||
"@pythnetwork/price-service-client": "*",
|
||||
"@pythnetwork/pyth-sdk-solidity": "*",
|
||||
"@pythnetwork/pyth-solana-receiver": "*",
|
||||
"@pythnetwork/pyth-sui-js": "*",
|
||||
"@truffle/hdwallet-provider": "^2.1.3",
|
||||
"aptos": "^1.8.5",
|
||||
"jito-ts": "^3.0.1",
|
||||
"joi": "^17.6.0",
|
||||
"near-api-js": "^3.0.2",
|
||||
"web3": "^1.8.1",
|
||||
|
|
|
@ -3,13 +3,21 @@ import * as options from "../options";
|
|||
import { readPriceConfigFile } from "../price-config";
|
||||
import { PriceServiceConnection } from "@pythnetwork/price-service-client";
|
||||
import { PythPriceListener } from "../pyth-price-listener";
|
||||
import { SolanaPriceListener, SolanaPricePusher } from "./solana";
|
||||
import {
|
||||
SolanaPriceListener,
|
||||
SolanaPricePusher,
|
||||
SolanaPricePusherJito,
|
||||
} from "./solana";
|
||||
import { Controller } from "../controller";
|
||||
import { PythSolanaReceiver } from "@pythnetwork/pyth-solana-receiver";
|
||||
import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet";
|
||||
import { Keypair, Connection } from "@solana/web3.js";
|
||||
import fs from "fs";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import {
|
||||
SearcherClient,
|
||||
searcherClient,
|
||||
} from "jito-ts/dist/sdk/block-engine/searcher";
|
||||
|
||||
export default {
|
||||
command: "solana",
|
||||
|
@ -35,6 +43,27 @@ export default {
|
|||
type: "number",
|
||||
default: 50000,
|
||||
} as Options,
|
||||
"jito-endpoint": {
|
||||
description: "Jito endpoint",
|
||||
type: "string",
|
||||
optional: true,
|
||||
} as Options,
|
||||
"jito-keypair-file": {
|
||||
description:
|
||||
"Path to the jito keypair file (need for grpc authentication)",
|
||||
type: "string",
|
||||
optional: true,
|
||||
} as Options,
|
||||
"jito-tip-lamports": {
|
||||
description: "Lamports to tip the jito builder",
|
||||
type: "number",
|
||||
optional: true,
|
||||
} as Options,
|
||||
"jito-bundle-size": {
|
||||
description: "Number of transactions in each bundle",
|
||||
type: "number",
|
||||
default: 2,
|
||||
} as Options,
|
||||
...options.priceConfigFile,
|
||||
...options.priceServiceEndpoint,
|
||||
...options.pythContractAddress,
|
||||
|
@ -52,6 +81,10 @@ export default {
|
|||
pythContractAddress,
|
||||
pushingFrequency,
|
||||
pollingFrequency,
|
||||
jitoEndpoint,
|
||||
jitoKeypairFile,
|
||||
jitoTipLamports,
|
||||
jitoBundleSize,
|
||||
} = argv;
|
||||
|
||||
const priceConfigs = readPriceConfigFile(priceConfigFile);
|
||||
|
@ -89,12 +122,32 @@ export default {
|
|||
pushOracleProgramId: new PublicKey(pythContractAddress),
|
||||
});
|
||||
|
||||
const solanaPricePusher = new SolanaPricePusher(
|
||||
pythSolanaReceiver,
|
||||
priceServiceConnection,
|
||||
shardId,
|
||||
computeUnitPriceMicroLamports
|
||||
);
|
||||
let solanaPricePusher;
|
||||
if (jitoTipLamports) {
|
||||
const jitoKeypair = Keypair.fromSecretKey(
|
||||
Uint8Array.from(JSON.parse(fs.readFileSync(jitoKeypairFile, "ascii")))
|
||||
);
|
||||
|
||||
const jitoClient = searcherClient(jitoEndpoint, jitoKeypair);
|
||||
solanaPricePusher = new SolanaPricePusherJito(
|
||||
pythSolanaReceiver,
|
||||
priceServiceConnection,
|
||||
shardId,
|
||||
jitoTipLamports,
|
||||
jitoClient,
|
||||
jitoBundleSize
|
||||
);
|
||||
|
||||
onBundleResult(jitoClient);
|
||||
} else {
|
||||
solanaPricePusher = new SolanaPricePusher(
|
||||
pythSolanaReceiver,
|
||||
priceServiceConnection,
|
||||
shardId,
|
||||
computeUnitPriceMicroLamports
|
||||
);
|
||||
}
|
||||
|
||||
const solanaPriceListener = new SolanaPriceListener(
|
||||
pythSolanaReceiver,
|
||||
shardId,
|
||||
|
@ -113,3 +166,12 @@ export default {
|
|||
controller.start();
|
||||
},
|
||||
};
|
||||
|
||||
export const onBundleResult = (c: SearcherClient) => {
|
||||
c.onBundleResult(
|
||||
() => undefined,
|
||||
(e) => {
|
||||
console.log("Error in bundle result: ", e);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
|
|
@ -7,7 +7,11 @@ import {
|
|||
} from "../interface";
|
||||
import { DurationInSeconds } from "../utils";
|
||||
import { PriceServiceConnection } from "@pythnetwork/price-service-client";
|
||||
import { sendTransactions } from "@pythnetwork/solana-utils";
|
||||
import {
|
||||
sendTransactions,
|
||||
sendTransactionsJito,
|
||||
} from "@pythnetwork/solana-utils";
|
||||
import { SearcherClient } from "jito-ts/dist/sdk/block-engine/searcher";
|
||||
|
||||
export class SolanaPriceListener extends ChainPriceListener {
|
||||
constructor(
|
||||
|
@ -110,3 +114,74 @@ export class SolanaPricePusher implements IPricePusher {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SolanaPricePusherJito implements IPricePusher {
|
||||
constructor(
|
||||
private pythSolanaReceiver: PythSolanaReceiver,
|
||||
private priceServiceConnection: PriceServiceConnection,
|
||||
private shardId: number,
|
||||
private jitoTipLamports: number,
|
||||
private searcherClient: SearcherClient,
|
||||
private jitoBundleSize: number
|
||||
) {}
|
||||
|
||||
async updatePriceFeed(
|
||||
priceIds: string[],
|
||||
pubTimesToPush: number[]
|
||||
): Promise<void> {
|
||||
let priceFeedUpdateData;
|
||||
try {
|
||||
priceFeedUpdateData = await this.priceServiceConnection.getLatestVaas(
|
||||
priceIds
|
||||
);
|
||||
} catch (e: any) {
|
||||
console.error(new Date(), "getPriceFeedsUpdateData failed:", e);
|
||||
return;
|
||||
}
|
||||
|
||||
const transactionBuilder = this.pythSolanaReceiver.newTransactionBuilder({
|
||||
closeUpdateAccounts: false,
|
||||
});
|
||||
await transactionBuilder.addUpdatePriceFeed(
|
||||
priceFeedUpdateData,
|
||||
this.shardId
|
||||
);
|
||||
|
||||
const transactions = await transactionBuilder.buildVersionedTransactions({
|
||||
jitoTipLamports: this.jitoTipLamports,
|
||||
tightComputeBudget: true,
|
||||
jitoBundleSize: this.jitoBundleSize,
|
||||
});
|
||||
|
||||
const firstSignature = await sendTransactionsJito(
|
||||
transactions.slice(0, this.jitoBundleSize),
|
||||
this.searcherClient,
|
||||
this.pythSolanaReceiver.wallet
|
||||
);
|
||||
|
||||
const blockhashResult =
|
||||
await this.pythSolanaReceiver.connection.getLatestBlockhashAndContext({
|
||||
commitment: "confirmed",
|
||||
});
|
||||
await this.pythSolanaReceiver.connection.confirmTransaction(
|
||||
{
|
||||
signature: firstSignature,
|
||||
blockhash: blockhashResult.value.blockhash,
|
||||
lastValidBlockHeight: blockhashResult.value.lastValidBlockHeight,
|
||||
},
|
||||
"confirmed"
|
||||
);
|
||||
|
||||
for (
|
||||
let i = this.jitoBundleSize;
|
||||
i < transactions.length;
|
||||
i += this.jitoBundleSize
|
||||
) {
|
||||
await sendTransactionsJito(
|
||||
transactions.slice(i, i + 2),
|
||||
this.searcherClient,
|
||||
this.pythSolanaReceiver.wallet
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { Wallet } from "@coral-xyz/anchor";
|
||||
import { PublicKey, Signer, VersionedTransaction } from "@solana/web3.js";
|
||||
import bs58 from "bs58";
|
||||
import { SearcherClient } from "jito-ts/dist/sdk/block-engine/searcher";
|
||||
import { Bundle } from "jito-ts/dist/sdk/block-engine/types";
|
||||
|
||||
|
@ -26,7 +27,7 @@ export async function sendTransactionsJito(
|
|||
}[],
|
||||
searcherClient: SearcherClient,
|
||||
wallet: Wallet
|
||||
) {
|
||||
): Promise<string> {
|
||||
const signedTransactions = [];
|
||||
|
||||
for (const transaction of transactions) {
|
||||
|
@ -41,6 +42,12 @@ export async function sendTransactionsJito(
|
|||
signedTransactions.push(tx);
|
||||
}
|
||||
|
||||
const firstTransactionSignature = bs58.encode(
|
||||
signedTransactions[0].signatures[0]
|
||||
);
|
||||
|
||||
const bundle = new Bundle(signedTransactions, 2);
|
||||
await searcherClient.sendBundle(bundle);
|
||||
|
||||
return firstTransactionSignature;
|
||||
}
|
||||
|
|
|
@ -45,6 +45,7 @@ export type PriorityFeeConfig = {
|
|||
computeUnitPriceMicroLamports?: number;
|
||||
tightComputeBudget?: boolean;
|
||||
jitoTipLamports?: number;
|
||||
jitoBundleSize?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -225,6 +226,9 @@ export class TransactionBuilder {
|
|||
await this.connection.getLatestBlockhash({ commitment: "confirmed" })
|
||||
).blockhash;
|
||||
|
||||
const jitoBundleSize =
|
||||
args.jitoBundleSize || this.transactionInstructions.length;
|
||||
|
||||
return this.transactionInstructions.map(
|
||||
({ instructions, signers, computeUnits }, index) => {
|
||||
const instructionsWithComputeBudget: TransactionInstruction[] = [
|
||||
|
@ -247,7 +251,7 @@ export class TransactionBuilder {
|
|||
}
|
||||
if (
|
||||
args.jitoTipLamports &&
|
||||
index == this.transactionInstructions.length - 1
|
||||
index % jitoBundleSize === jitoBundleSize - 1
|
||||
) {
|
||||
instructionsWithComputeBudget.push(
|
||||
SystemProgram.transfer({
|
||||
|
@ -280,6 +284,9 @@ export class TransactionBuilder {
|
|||
buildLegacyTransactions(
|
||||
args: PriorityFeeConfig
|
||||
): { tx: Transaction; signers: Signer[] }[] {
|
||||
const jitoBundleSize =
|
||||
args.jitoBundleSize || this.transactionInstructions.length;
|
||||
|
||||
return this.transactionInstructions.map(
|
||||
({ instructions, signers, computeUnits }, index) => {
|
||||
const instructionsWithComputeBudget: TransactionInstruction[] = [
|
||||
|
@ -302,7 +309,7 @@ export class TransactionBuilder {
|
|||
}
|
||||
if (
|
||||
args.jitoTipLamports &&
|
||||
index == this.transactionInstructions.length - 1
|
||||
index % jitoBundleSize === jitoBundleSize - 1
|
||||
) {
|
||||
instructionsWithComputeBudget.push(
|
||||
SystemProgram.transfer({
|
||||
|
|
Loading…
Reference in New Issue