Skip to content

Commit c727195

Browse files
authored
feat: jito pusher (#1444)
* feat: jito script * Go * Go * Checkpoint * Checkpoint * Rename * Make tip account random * Go * Jito pusher * Go * lint * Lint * Bump
1 parent 0aeae8c commit c727195

File tree

6 files changed

+169
-15
lines changed

6 files changed

+169
-15
lines changed

package-lock.json

Lines changed: 4 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

price_pusher/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@pythnetwork/price-pusher",
3-
"version": "6.4.3",
3+
"version": "6.5.0",
44
"description": "Pyth Price Pusher",
55
"homepage": "https://pyth.network",
66
"main": "lib/index.js",
@@ -52,13 +52,14 @@
5252
},
5353
"dependencies": {
5454
"@injectivelabs/sdk-ts": "1.10.72",
55-
"@pythnetwork/pyth-solana-receiver": "*",
5655
"@mysten/sui.js": "^0.49.1",
5756
"@pythnetwork/price-service-client": "*",
5857
"@pythnetwork/pyth-sdk-solidity": "*",
58+
"@pythnetwork/pyth-solana-receiver": "*",
5959
"@pythnetwork/pyth-sui-js": "*",
6060
"@truffle/hdwallet-provider": "^2.1.3",
6161
"aptos": "^1.8.5",
62+
"jito-ts": "^3.0.1",
6263
"joi": "^17.6.0",
6364
"near-api-js": "^3.0.2",
6465
"web3": "^1.8.1",

price_pusher/src/solana/command.ts

Lines changed: 69 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,21 @@ import * as options from "../options";
33
import { readPriceConfigFile } from "../price-config";
44
import { PriceServiceConnection } from "@pythnetwork/price-service-client";
55
import { PythPriceListener } from "../pyth-price-listener";
6-
import { SolanaPriceListener, SolanaPricePusher } from "./solana";
6+
import {
7+
SolanaPriceListener,
8+
SolanaPricePusher,
9+
SolanaPricePusherJito,
10+
} from "./solana";
711
import { Controller } from "../controller";
812
import { PythSolanaReceiver } from "@pythnetwork/pyth-solana-receiver";
913
import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet";
1014
import { Keypair, Connection } from "@solana/web3.js";
1115
import fs from "fs";
1216
import { PublicKey } from "@solana/web3.js";
17+
import {
18+
SearcherClient,
19+
searcherClient,
20+
} from "jito-ts/dist/sdk/block-engine/searcher";
1321

1422
export default {
1523
command: "solana",
@@ -35,6 +43,27 @@ export default {
3543
type: "number",
3644
default: 50000,
3745
} as Options,
46+
"jito-endpoint": {
47+
description: "Jito endpoint",
48+
type: "string",
49+
optional: true,
50+
} as Options,
51+
"jito-keypair-file": {
52+
description:
53+
"Path to the jito keypair file (need for grpc authentication)",
54+
type: "string",
55+
optional: true,
56+
} as Options,
57+
"jito-tip-lamports": {
58+
description: "Lamports to tip the jito builder",
59+
type: "number",
60+
optional: true,
61+
} as Options,
62+
"jito-bundle-size": {
63+
description: "Number of transactions in each bundle",
64+
type: "number",
65+
default: 2,
66+
} as Options,
3867
...options.priceConfigFile,
3968
...options.priceServiceEndpoint,
4069
...options.pythContractAddress,
@@ -52,6 +81,10 @@ export default {
5281
pythContractAddress,
5382
pushingFrequency,
5483
pollingFrequency,
84+
jitoEndpoint,
85+
jitoKeypairFile,
86+
jitoTipLamports,
87+
jitoBundleSize,
5588
} = argv;
5689

5790
const priceConfigs = readPriceConfigFile(priceConfigFile);
@@ -89,12 +122,32 @@ export default {
89122
pushOracleProgramId: new PublicKey(pythContractAddress),
90123
});
91124

92-
const solanaPricePusher = new SolanaPricePusher(
93-
pythSolanaReceiver,
94-
priceServiceConnection,
95-
shardId,
96-
computeUnitPriceMicroLamports
97-
);
125+
let solanaPricePusher;
126+
if (jitoTipLamports) {
127+
const jitoKeypair = Keypair.fromSecretKey(
128+
Uint8Array.from(JSON.parse(fs.readFileSync(jitoKeypairFile, "ascii")))
129+
);
130+
131+
const jitoClient = searcherClient(jitoEndpoint, jitoKeypair);
132+
solanaPricePusher = new SolanaPricePusherJito(
133+
pythSolanaReceiver,
134+
priceServiceConnection,
135+
shardId,
136+
jitoTipLamports,
137+
jitoClient,
138+
jitoBundleSize
139+
);
140+
141+
onBundleResult(jitoClient);
142+
} else {
143+
solanaPricePusher = new SolanaPricePusher(
144+
pythSolanaReceiver,
145+
priceServiceConnection,
146+
shardId,
147+
computeUnitPriceMicroLamports
148+
);
149+
}
150+
98151
const solanaPriceListener = new SolanaPriceListener(
99152
pythSolanaReceiver,
100153
shardId,
@@ -113,3 +166,12 @@ export default {
113166
controller.start();
114167
},
115168
};
169+
170+
export const onBundleResult = (c: SearcherClient) => {
171+
c.onBundleResult(
172+
() => undefined,
173+
(e) => {
174+
console.log("Error in bundle result: ", e);
175+
}
176+
);
177+
};

price_pusher/src/solana/solana.ts

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ import {
77
} from "../interface";
88
import { DurationInSeconds } from "../utils";
99
import { PriceServiceConnection } from "@pythnetwork/price-service-client";
10-
import { sendTransactions } from "@pythnetwork/solana-utils";
10+
import {
11+
sendTransactions,
12+
sendTransactionsJito,
13+
} from "@pythnetwork/solana-utils";
14+
import { SearcherClient } from "jito-ts/dist/sdk/block-engine/searcher";
1115

1216
export class SolanaPriceListener extends ChainPriceListener {
1317
constructor(
@@ -110,3 +114,74 @@ export class SolanaPricePusher implements IPricePusher {
110114
}
111115
}
112116
}
117+
118+
export class SolanaPricePusherJito implements IPricePusher {
119+
constructor(
120+
private pythSolanaReceiver: PythSolanaReceiver,
121+
private priceServiceConnection: PriceServiceConnection,
122+
private shardId: number,
123+
private jitoTipLamports: number,
124+
private searcherClient: SearcherClient,
125+
private jitoBundleSize: number
126+
) {}
127+
128+
async updatePriceFeed(
129+
priceIds: string[],
130+
pubTimesToPush: number[]
131+
): Promise<void> {
132+
let priceFeedUpdateData;
133+
try {
134+
priceFeedUpdateData = await this.priceServiceConnection.getLatestVaas(
135+
priceIds
136+
);
137+
} catch (e: any) {
138+
console.error(new Date(), "getPriceFeedsUpdateData failed:", e);
139+
return;
140+
}
141+
142+
const transactionBuilder = this.pythSolanaReceiver.newTransactionBuilder({
143+
closeUpdateAccounts: false,
144+
});
145+
await transactionBuilder.addUpdatePriceFeed(
146+
priceFeedUpdateData,
147+
this.shardId
148+
);
149+
150+
const transactions = await transactionBuilder.buildVersionedTransactions({
151+
jitoTipLamports: this.jitoTipLamports,
152+
tightComputeBudget: true,
153+
jitoBundleSize: this.jitoBundleSize,
154+
});
155+
156+
const firstSignature = await sendTransactionsJito(
157+
transactions.slice(0, this.jitoBundleSize),
158+
this.searcherClient,
159+
this.pythSolanaReceiver.wallet
160+
);
161+
162+
const blockhashResult =
163+
await this.pythSolanaReceiver.connection.getLatestBlockhashAndContext({
164+
commitment: "confirmed",
165+
});
166+
await this.pythSolanaReceiver.connection.confirmTransaction(
167+
{
168+
signature: firstSignature,
169+
blockhash: blockhashResult.value.blockhash,
170+
lastValidBlockHeight: blockhashResult.value.lastValidBlockHeight,
171+
},
172+
"confirmed"
173+
);
174+
175+
for (
176+
let i = this.jitoBundleSize;
177+
i < transactions.length;
178+
i += this.jitoBundleSize
179+
) {
180+
await sendTransactionsJito(
181+
transactions.slice(i, i + 2),
182+
this.searcherClient,
183+
this.pythSolanaReceiver.wallet
184+
);
185+
}
186+
}
187+
}

target_chains/solana/sdk/js/solana_utils/src/jito.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Wallet } from "@coral-xyz/anchor";
22
import { PublicKey, Signer, VersionedTransaction } from "@solana/web3.js";
3+
import bs58 from "bs58";
34
import { SearcherClient } from "jito-ts/dist/sdk/block-engine/searcher";
45
import { Bundle } from "jito-ts/dist/sdk/block-engine/types";
56

@@ -26,7 +27,7 @@ export async function sendTransactionsJito(
2627
}[],
2728
searcherClient: SearcherClient,
2829
wallet: Wallet
29-
) {
30+
): Promise<string> {
3031
const signedTransactions = [];
3132

3233
for (const transaction of transactions) {
@@ -41,6 +42,12 @@ export async function sendTransactionsJito(
4142
signedTransactions.push(tx);
4243
}
4344

45+
const firstTransactionSignature = bs58.encode(
46+
signedTransactions[0].signatures[0]
47+
);
48+
4449
const bundle = new Bundle(signedTransactions, 2);
4550
await searcherClient.sendBundle(bundle);
51+
52+
return firstTransactionSignature;
4653
}

target_chains/solana/sdk/js/solana_utils/src/transaction.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export type PriorityFeeConfig = {
4545
computeUnitPriceMicroLamports?: number;
4646
tightComputeBudget?: boolean;
4747
jitoTipLamports?: number;
48+
jitoBundleSize?: number;
4849
};
4950

5051
/**
@@ -225,6 +226,9 @@ export class TransactionBuilder {
225226
await this.connection.getLatestBlockhash({ commitment: "confirmed" })
226227
).blockhash;
227228

229+
const jitoBundleSize =
230+
args.jitoBundleSize || this.transactionInstructions.length;
231+
228232
return this.transactionInstructions.map(
229233
({ instructions, signers, computeUnits }, index) => {
230234
const instructionsWithComputeBudget: TransactionInstruction[] = [
@@ -247,7 +251,7 @@ export class TransactionBuilder {
247251
}
248252
if (
249253
args.jitoTipLamports &&
250-
index == this.transactionInstructions.length - 1
254+
index % jitoBundleSize === jitoBundleSize - 1
251255
) {
252256
instructionsWithComputeBudget.push(
253257
SystemProgram.transfer({
@@ -280,6 +284,9 @@ export class TransactionBuilder {
280284
buildLegacyTransactions(
281285
args: PriorityFeeConfig
282286
): { tx: Transaction; signers: Signer[] }[] {
287+
const jitoBundleSize =
288+
args.jitoBundleSize || this.transactionInstructions.length;
289+
283290
return this.transactionInstructions.map(
284291
({ instructions, signers, computeUnits }, index) => {
285292
const instructionsWithComputeBudget: TransactionInstruction[] = [
@@ -302,7 +309,7 @@ export class TransactionBuilder {
302309
}
303310
if (
304311
args.jitoTipLamports &&
305-
index == this.transactionInstructions.length - 1
312+
index % jitoBundleSize === jitoBundleSize - 1
306313
) {
307314
instructionsWithComputeBudget.push(
308315
SystemProgram.transfer({

0 commit comments

Comments
 (0)