import { SendTransactionError , SignTransactionError , TransactionTimeoutError , WalletNotConnectedError , WalletSigner , } from '@oyster/common' ; import { Account , Commitment , Connection , Keypair , RpcResponseAndContext , SimulatedTransactionResponse , Transaction , TransactionSignature , } from '@solana/web3.js' ; export async function sleep ( ms ) { return new Promise ( resolve => setTimeout ( resolve , ms ) ) ; } export function getUnixTs ( ) { return new Date ( ) . getTime ( ) / 1000 ; } export const DEFAULT_TX_TIMEOUT = 31000 ; export async function sendTransaction2 ( { transaction , wallet , signers = [ ] , connection , timeout = DEFAULT_TX_TIMEOUT , } ) { const signedTransaction = await signTransaction ( { transaction , wallet , signers , connection , } ) ; return await sendSignedTransaction ( { signedTransaction , connection , timeout , } ) ; } export async function signTransaction ( { transaction , wallet , signers = [ ] , connection , } ) { const { publicKey , signTransaction } = wallet ; if ( ! publicKey ) throw new WalletNotConnectedError ( ) ; transaction . recentBlockhash = ( await connection . getRecentBlockhash ( 'max' ) ) . blockhash ; transaction . setSigners ( publicKey , ... signers . map ( s => s . publicKey ) ) ; if ( signers . length > 0 ) { transaction . partialSign ( ... signers ) ; } try { return await signTransaction ( transaction ) ; } catch ( ex ) { let message = '' ; if ( ex instanceof Error ) { message = ex . message ; } else if ( ex ) { message = JSON . stringify ( ex ) ; } throw new SignTransactionError ( message ) ; } } export async function signTransactions ( { transactionsAndSigners , wallet , connection , } ) { const { publicKey , signAllTransactions } = wallet ; if ( ! publicKey ) throw new WalletNotConnectedError ( ) ; const blockhash = ( await connection . getRecentBlockhash ( 'max' ) ) . blockhash ; transactionsAndSigners . forEach ( ( { transaction , signers = [ ] } ) => { transaction . recentBlockhash = blockhash ; transaction . setSigners ( publicKey , ... signers . map ( s => s . publicKey ) ) ; if ( signers ?. length > 0 ) { transaction . partialSign ( ... signers ) ; } } ) ; return await signAllTransactions ( transactionsAndSigners . map ( ( { transaction } ) => transaction ) ) ; } export async function sendSignedTransaction ( { signedTransaction , connection , timeout = DEFAULT_TX_TIMEOUT , } ) { const rawTransaction = signedTransaction . serialize ( ) ; const startTime = getUnixTs ( ) ; const txid = await connection . sendRawTransaction ( rawTransaction , { skipPreflight : true , } ) ; console . log ( 'Started awaiting confirmation for' , txid ) ; let done = false ; ( async ( ) => { while ( ! done && getUnixTs ( ) - startTime < timeout ) { connection . sendRawTransaction ( rawTransaction , { skipPreflight : true , } ) ; await sleep ( 3000 ) ; } } ) ( ) ; try { await awaitTransactionSignatureConfirmation ( txid , timeout , connection ) ; } catch ( err ) { if ( ( err as any ) . timeout ) { throw new TransactionTimeoutError ( txid ) ; } let simulateResult = null ; try { simulateResult = ( await simulateTransaction ( connection , signedTransaction , 'single' ) ) . value ; } catch ( e ) { console . log ( 'Error: ' , e ) ; } if ( simulateResult && simulateResult . err ) { if ( simulateResult . logs ) { for ( let i = simulateResult . logs . length - 1 ; i >= 0 ; -- i ) { const line = simulateResult . logs [ i ] ; if ( line . startsWith ( 'Program log: ' ) ) { throw new SendTransactionError ( 'Transaction failed: ' + line . slice ( 'Program log: ' . length ) , txid , simulateResult . err ) ; } } } throw new SendTransactionError ( JSON . stringify ( simulateResult . err ) , txid , simulateResult . err ) ; } throw new SendTransactionError ( 'Transaction failed' , txid ) ; } finally { done = true ; } console . log ( 'Latency' , txid , getUnixTs ( ) - startTime ) ; return txid ; } async function awaitTransactionSignatureConfirmation ( txid , timeout , connection ) { let done = false ; const result = await new Promise ( ( resolve , reject ) => { ( async ( ) => { setTimeout ( ( ) => { if ( done ) { return ; } done = true ; console . log ( 'Timed out for txid' , txid ) ; reject ( { timeout : true } ) ; } , timeout ) ; try { connection . onSignature ( txid , result => { console . log ( 'WS confirmed' , txid , result ) ; done = true ; if ( result . err ) { reject ( result . err ) ; } else { resolve ( result ) ; } } , connection . commitment ) ; console . log ( 'Set up WS connection' , txid ) ; } catch ( e ) { done = true ; console . log ( 'WS error in setup' , txid , e ) ; } while ( ! done ) { ( async ( ) => { try { const signatureStatuses = await connection . getSignatureStatuses ( [ txid , ] ) ; const result = signatureStatuses && signatureStatuses . value [ 0 ] ; if ( ! done ) { if ( ! result ) { } else if ( result . err ) { console . log ( 'REST error for' , txid , result ) ; done = true ; reject ( result . err ) ; } else if ( ! ( result . confirmations || result . confirmationStatus === 'confirmed' || result . confirmationStatus === 'finalized' ) ) { console . log ( 'REST not confirmed' , txid , result ) ; } else { console . log ( 'REST confirmed' , txid , result ) ; done = true ; resolve ( result ) ; } } } catch ( e ) { if ( ! done ) { console . log ( 'REST connection error: txid' , txid , e ) ; } } } ) ( ) ; await sleep ( 3000 ) ; } } ) ( ) ; } ) ; done = true ; return result ; } /** Copy of Connection.simulateTransaction that takes a commitment parameter. */ export async function simulateTransaction ( connection , transaction , commitment ) { transaction . recentBlockhash = await connection . _recentBlockhash ( connection . _disableBlockhashCaching ) ; const signData = transaction . serializeMessage ( ) ; const wireTransaction = transaction . _serialize ( signData ) ; const encodedTransaction = wireTransaction . toString ( 'base64' ) ; const config = { encoding : 'base64' , commitment } ; const args = [ encodedTransaction , config ] ; const res = await connection . _rpcRequest ( 'simulateTransaction' , args ) ; if ( res . error ) { throw new Error ( 'failed to simulate transaction: ' + res . error . message ) ; } return res . result ; }