1- import { AnchorProvider , Wallet } from "@coral-xyz/anchor" ;
1+ import { Wallet } from "@coral-xyz/anchor" ;
22import {
33 ComputeBudgetProgram ,
4- ConfirmOptions ,
54 Connection ,
65 PACKET_DATA_SIZE ,
76 PublicKey ,
@@ -11,6 +10,7 @@ import {
1110 TransactionMessage ,
1211 VersionedTransaction ,
1312} from "@solana/web3.js" ;
13+ import bs58 from "bs58" ;
1414
1515/**
1616 * If the transaction doesn't contain a `setComputeUnitLimit` instruction, the default compute budget is 200,000 units per instruction.
@@ -40,6 +40,7 @@ export type InstructionWithEphemeralSigners = {
4040export type PriorityFeeConfig = {
4141 /** This is the priority fee in micro lamports, it gets passed down to `setComputeUnitPrice` */
4242 computeUnitPriceMicroLamports ?: number ;
43+ tightComputeBudget ?: boolean ;
4344} ;
4445
4546/**
@@ -186,14 +187,19 @@ export class TransactionBuilder {
186187 async buildVersionedTransactions (
187188 args : PriorityFeeConfig
188189 ) : Promise < { tx : VersionedTransaction ; signers : Signer [ ] } [ ] > {
189- const blockhash = ( await this . connection . getLatestBlockhash ( ) ) . blockhash ;
190+ const blockhash = (
191+ await this . connection . getLatestBlockhash ( { commitment : "confirmed" } )
192+ ) . blockhash ;
190193
191194 return this . transactionInstructions . map (
192195 ( { instructions, signers, computeUnits } ) => {
193196 const instructionsWithComputeBudget : TransactionInstruction [ ] = [
194197 ...instructions ,
195198 ] ;
196- if ( computeUnits > DEFAULT_COMPUTE_BUDGET_UNITS * instructions . length ) {
199+ if (
200+ computeUnits > DEFAULT_COMPUTE_BUDGET_UNITS * instructions . length ||
201+ args . tightComputeBudget
202+ ) {
197203 instructionsWithComputeBudget . push (
198204 ComputeBudgetProgram . setComputeUnitLimit ( { units : computeUnits } )
199205 ) ;
@@ -226,21 +232,33 @@ export class TransactionBuilder {
226232 buildLegacyTransactions (
227233 args : PriorityFeeConfig
228234 ) : { tx : Transaction ; signers : Signer [ ] } [ ] {
229- return this . transactionInstructions . map ( ( { instructions, signers } ) => {
230- const instructionsWithComputeBudget = args . computeUnitPriceMicroLamports
231- ? [
232- ...instructions ,
235+ return this . transactionInstructions . map (
236+ ( { instructions, signers, computeUnits } ) => {
237+ const instructionsWithComputeBudget : TransactionInstruction [ ] = [
238+ ...instructions ,
239+ ] ;
240+ if (
241+ computeUnits > DEFAULT_COMPUTE_BUDGET_UNITS * instructions . length ||
242+ args . tightComputeBudget
243+ ) {
244+ instructionsWithComputeBudget . push (
245+ ComputeBudgetProgram . setComputeUnitLimit ( { units : computeUnits } )
246+ ) ;
247+ }
248+ if ( args . computeUnitPriceMicroLamports ) {
249+ instructionsWithComputeBudget . push (
233250 ComputeBudgetProgram . setComputeUnitPrice ( {
234251 microLamports : args . computeUnitPriceMicroLamports ,
235- } ) ,
236- ]
237- : instructions ;
252+ } )
253+ ) ;
254+ }
238255
239- return {
240- tx : new Transaction ( ) . add ( ...instructionsWithComputeBudget ) ,
241- signers : signers ,
242- } ;
243- } ) ;
256+ return {
257+ tx : new Transaction ( ) . add ( ...instructionsWithComputeBudget ) ,
258+ signers : signers ,
259+ } ;
260+ }
261+ ) ;
244262 }
245263
246264 /**
@@ -295,8 +313,16 @@ export class TransactionBuilder {
295313 }
296314}
297315
316+ export const isVersionedTransaction = (
317+ tx : Transaction | VersionedTransaction
318+ ) : tx is VersionedTransaction => {
319+ return "version" in tx ;
320+ } ;
321+
322+ const TX_RETRY_INTERVAL = 500 ;
323+
298324/**
299- * Send a set of transactions to the network
325+ * Send a set of transactions to the network based on https://github.com/rpcpool/optimized-txs-examples
300326 */
301327export async function sendTransactions (
302328 transactions : {
@@ -305,12 +331,97 @@ export async function sendTransactions(
305331 } [ ] ,
306332 connection : Connection ,
307333 wallet : Wallet ,
308- opts ?: ConfirmOptions
334+ maxRetries ?: number
309335) {
310- if ( opts === undefined ) {
311- opts = AnchorProvider . defaultOptions ( ) ;
312- }
336+ const blockhashResult = await connection . getLatestBlockhashAndContext ( {
337+ commitment : "confirmed" ,
338+ } ) ;
339+
340+ // Signing logic for versioned transactions is different from legacy transactions
341+ for ( const transaction of transactions ) {
342+ const { signers } = transaction ;
343+ let tx = transaction . tx ;
344+ if ( isVersionedTransaction ( tx ) ) {
345+ if ( signers ) {
346+ tx . sign ( signers ) ;
347+ }
348+ } else {
349+ tx . feePayer = tx . feePayer ?? wallet . publicKey ;
350+ tx . recentBlockhash = blockhashResult . value . blockhash ;
351+
352+ if ( signers ) {
353+ for ( const signer of signers ) {
354+ tx . partialSign ( signer ) ;
355+ }
356+ }
357+ }
358+
359+ tx = await wallet . signTransaction ( tx ) ;
360+
361+ // In the following section, we wait and constantly check for the transaction to be confirmed
362+ // and resend the transaction if it is not confirmed within a certain time interval
363+ // thus handling tx retries on the client side rather than relying on the RPC
364+ let confirmedTx = null ;
365+ let retryCount = 0 ;
366+
367+ try {
368+ // Get the signature of the transaction with different logic for versioned transactions
369+ const txSignature = bs58 . encode (
370+ isVersionedTransaction ( tx )
371+ ? tx . signatures ?. [ 0 ] || new Uint8Array ( )
372+ : tx . signature ?? new Uint8Array ( )
373+ ) ;
313374
314- const provider = new AnchorProvider ( connection , wallet , opts ) ;
315- await provider . sendAll ( transactions ) ;
375+ const confirmTransactionPromise = connection . confirmTransaction (
376+ {
377+ signature : txSignature ,
378+ blockhash : blockhashResult . value . blockhash ,
379+ lastValidBlockHeight : blockhashResult . value . lastValidBlockHeight ,
380+ } ,
381+ "confirmed"
382+ ) ;
383+
384+ confirmedTx = null ;
385+ while ( ! confirmedTx ) {
386+ confirmedTx = await Promise . race ( [
387+ confirmTransactionPromise ,
388+ new Promise ( ( resolve ) =>
389+ setTimeout ( ( ) => {
390+ resolve ( null ) ;
391+ } , TX_RETRY_INTERVAL )
392+ ) ,
393+ ] ) ;
394+ if ( confirmedTx ) {
395+ break ;
396+ }
397+ if ( maxRetries && maxRetries < retryCount ) {
398+ break ;
399+ }
400+ console . log (
401+ "Retrying transaction: " ,
402+ txSignature ,
403+ " Retry count: " ,
404+ retryCount
405+ ) ;
406+ retryCount ++ ;
407+
408+ await connection . sendRawTransaction ( tx . serialize ( ) , {
409+ // Skipping preflight i.e. tx simulation by RPC as we simulated the tx above
410+ // This allows Triton RPCs to send the transaction through multiple pathways for the fastest delivery
411+ skipPreflight : true ,
412+ // Setting max retries to 0 as we are handling retries manually
413+ // Set this manually so that the default is skipped
414+ maxRetries : 0 ,
415+ preflightCommitment : "confirmed" ,
416+ minContextSlot : blockhashResult . context . slot ,
417+ } ) ;
418+ }
419+ } catch ( error ) {
420+ console . error ( error ) ;
421+ }
422+
423+ if ( ! confirmedTx ) {
424+ throw new Error ( "Failed to land the transaction" ) ;
425+ }
426+ }
316427}
0 commit comments