@@ -11,6 +11,8 @@ import type { ITokenMetadata } from '@hyperlane-xyz/sdk/token/ITokenMetadata';
1111import type { ChainName } from '@hyperlane-xyz/sdk/types' ;
1212import type { WarpTypedTransaction } from '@hyperlane-xyz/sdk/warp/types' ;
1313
14+ import { sleep } from '@hyperlane-xyz/utils' ;
15+
1416import { widgetLogger } from '../logger.js' ;
1517
1618import {
@@ -73,22 +75,63 @@ export function useSolanaTransactionFns(
7375 const connection = new Connection ( rpcUrl , 'confirmed' ) ;
7476 const {
7577 context : { slot : minContextSlot } ,
76- value : { blockhash , lastValidBlockHeight } ,
78+ value : { lastValidBlockHeight } ,
7779 } = await connection . getLatestBlockhashAndContext ( ) ;
7880
7981 logger . debug ( `Sending tx on chain ${ chainName } ` ) ;
8082 const signature = await sendSolTransaction ( tx . transaction , connection , {
8183 minContextSlot,
8284 } ) ;
8385
84- const confirm = ( ) : Promise < TypedTransactionReceipt > =>
85- connection
86- . confirmTransaction ( { blockhash, lastValidBlockHeight, signature } )
87- . then ( ( ) => connection . getTransaction ( signature ) )
88- . then ( ( r ) => ( {
89- type : ProviderType . SolanaWeb3 ,
90- receipt : r ! ,
91- } ) ) ;
86+ const confirm = async ( ) : Promise < TypedTransactionReceipt > => {
87+ // Poll via HTTP instead of connection.confirmTransaction which
88+ // relies on signatureSubscribe (WebSocket) — many RPC providers
89+ // (e.g. Alchemy) don't support that method.
90+ const POLL_INTERVAL_MS = 2000 ;
91+ while ( true ) {
92+ try {
93+ const { value } = await connection . getSignatureStatuses ( [
94+ signature ,
95+ ] ) ;
96+ const status = value ?. [ 0 ] ;
97+ if ( status ?. err ) {
98+ throw new Error (
99+ `Transaction failed: ${ JSON . stringify ( status . err ) } ` ,
100+ ) ;
101+ }
102+ if (
103+ status ?. confirmationStatus === 'confirmed' ||
104+ status ?. confirmationStatus === 'finalized'
105+ ) {
106+ break ;
107+ }
108+ const blockHeight = await connection . getBlockHeight ( ) ;
109+ if ( blockHeight > lastValidBlockHeight ) {
110+ throw new Error ( 'Transaction expired: block height exceeded' ) ;
111+ }
112+ } catch ( error ) {
113+ // Re-throw definitive failures (tx error, expiry)
114+ if (
115+ error instanceof Error &&
116+ ( error . message . startsWith ( 'Transaction failed' ) ||
117+ error . message . startsWith ( 'Transaction expired' ) )
118+ ) {
119+ throw error ;
120+ }
121+ // Tolerate transient RPC errors (timeouts, rate limits)
122+ logger . warn ( 'Transient error polling tx confirmation' , error ) ;
123+ }
124+ await sleep ( POLL_INTERVAL_MS ) ;
125+ }
126+ const tx = await connection . getTransaction ( signature , {
127+ commitment : 'confirmed' ,
128+ maxSupportedTransactionVersion : 0 ,
129+ } ) ;
130+ if ( ! tx ) {
131+ throw new Error ( `Transaction ${ signature } confirmed but not found` ) ;
132+ }
133+ return { type : ProviderType . SolanaWeb3 , receipt : tx } ;
134+ } ;
92135
93136 return { hash : signature , confirm } ;
94137 } ,
0 commit comments