@@ -18,6 +18,7 @@ import type {
1818 SendOptions ,
1919 SignatureResult ,
2020 Transaction ,
21+ TransactionError ,
2122} from '@solana/web3.js' ;
2223import { clusterApiUrl , Connection } from '@solana/web3.js' ;
2324
@@ -104,9 +105,16 @@ export function fetchFogoOptions() {
104105 } ;
105106}
106107
107- // This function signs and sends the transaction while constantly checking for confirmation
108- // and resending the transaction if it hasn't been confirmed after the specified interval
109- // See https://docs.triton.one/chains/solana/sending-txs for more information
108+ /**
109+ * This function signs and sends the transaction while constantly checking for confirmation
110+ * and resending the transaction if it hasn't been confirmed after the specified interval
111+ * See https://docs.triton.one/chains/solana/sending-txs for more information.
112+ *
113+ * @param request The unsigned transaction to sign and send
114+ * @param wallet The wallet to use for signing and sending the transaction
115+ * @param options Optional confirmation options
116+ * @returns The transaction signature
117+ */
110118export async function signAndSendTransactionWithResends (
111119 request : SolanaUnsignedTransaction < Network > ,
112120 wallet : Wallet | undefined ,
@@ -132,12 +140,15 @@ export async function signAndSendTransactionWithResends(
132140 commitment ,
133141 ) ;
134142
143+ const confirmationPromiseTimer = 1_000 ; // How long to wait for confirmation before resending
144+
135145 const signature = await signAndConfirmTransactionWhilstResending (
136146 { serializedTransaction, sendOptions } ,
137147 connection ,
138148 blockhash ,
139149 lastValidBlockHeight ,
140150 commitment ,
151+ confirmationPromiseTimer ,
141152 ) ;
142153
143154 return signature ;
@@ -152,9 +163,7 @@ async function signAndConfirmTransactionWhilstResending(
152163 blockHash : string ,
153164 lastValidBlockHeight : number ,
154165 commitment : Commitment ,
155- {
156- initialDelay = 1000 , // 1 second
157- } : { retries ?: number ; initialDelay ?: number ; maxDelay ?: number } = { } ,
166+ confirmationPromiseTimer : number ,
158167) : Promise < string > {
159168 const { signature, confirmPromise } =
160169 await sendTransactionAndGetConfirmPromise (
@@ -169,7 +178,7 @@ async function signAndConfirmTransactionWhilstResending(
169178 signature ,
170179 connection ,
171180 transaction ,
172- initialDelay ,
181+ confirmationPromiseTimer ,
173182 confirmPromise ,
174183 ) ;
175184
@@ -225,9 +234,9 @@ async function resendTransactionUntilConfirmed(
225234 serializedTransaction : Uint8Array | Buffer | number [ ] ;
226235 sendOptions ?: SendOptions ;
227236 } ,
228- txRetryInterval = 1000 ,
237+ confirmationPromiseTimer : number ,
229238 confirmTransactionPromise : Promise < RpcResponseAndContext < SignatureResult > > ,
230- ) : Promise < void > {
239+ ) {
231240 let isTransactionConfirmed : RpcResponseAndContext < SignatureResult > | null =
232241 null ;
233242 try {
@@ -237,7 +246,7 @@ async function resendTransactionUntilConfirmed(
237246 new Promise < null > ( ( resolve ) =>
238247 setTimeout ( ( ) => {
239248 resolve ( null ) ;
240- } , txRetryInterval ) ,
249+ } , confirmationPromiseTimer ) ,
241250 ) ,
242251 ] ) ;
243252 if ( isTransactionConfirmed ) {
@@ -260,30 +269,42 @@ async function resendTransactionUntilConfirmed(
260269 }
261270 }
262271
263- return ;
272+ if ( isTransactionConfirmed && isTransactionConfirmed . value . err ) {
273+ const errorMessage = formatConfirmationError (
274+ isTransactionConfirmed . value . err ,
275+ ) ;
276+ throw new Error ( `Transaction failed: ${ errorMessage } ` ) ;
277+ }
264278}
265279
280+ /**
281+ * Attempt to recover a transaction that exceeded its blockheight,
282+ * by polling until it appears on chain.
283+ *
284+ * @param connection Solana RPC connection
285+ * @param signature The transaction signature to look up
286+ * @param retries Number of retries (default: 5)
287+ * @param delay Delay between retries in ms (default: 2000)
288+ * @returns The signature once found, or throws if retries are exhausted
289+ */
266290async function recoverBlockheightExceededTransaction (
267291 e : unknown ,
268292 connection : Connection ,
269293 signature : string ,
270294 { retries = 5 , delay = 2000 } : { retries ?: number ; delay ?: number } = { } ,
271295) : Promise < string | null > {
272- return retry (
273- async ( ) => {
274- const tx = await connection . getTransaction ( signature , {
275- commitment : 'confirmed' ,
276- maxSupportedTransactionVersion : 0 ,
277- } ) ;
278-
279- if ( ! tx ) {
280- throw new Error ( 'Transaction not yet found on chain' ) ;
281- }
296+ const findTransaction = async ( ) : Promise < string > => {
297+ const tx = await connection . getTransaction ( signature , {
298+ commitment : 'confirmed' ,
299+ maxSupportedTransactionVersion : 0 ,
300+ } ) ;
282301
283- return signature ;
284- } ,
285- { retries, delay } ,
286- ) ;
302+ if ( tx ) return signature ;
303+
304+ throw new Error ( 'Transaction not yet found on chain' ) ;
305+ } ;
306+
307+ return retry ( findTransaction , { retries, delay } ) ;
287308}
288309
289310async function createSolanaTransaction (
@@ -313,3 +334,19 @@ async function createSolanaTransaction(
313334 } ;
314335 return { serializedTransaction : serializedTx , sendOptions } ;
315336}
337+
338+ function formatConfirmationError ( err : TransactionError ) : string {
339+ if ( ! err ) return 'Unknown error' ;
340+
341+ if ( typeof err === 'object' ) {
342+ try {
343+ return JSON . stringify ( err , ( _key , value ) =>
344+ typeof value === 'bigint' ? value . toString ( ) : value ,
345+ ) ;
346+ } catch {
347+ return 'Unstringifiable error object' ;
348+ }
349+ }
350+
351+ return String ( err ) ;
352+ }
0 commit comments