diff --git a/platforms/solana/protocols/cctp/src/circleBridge.ts b/platforms/solana/protocols/cctp/src/circleBridge.ts index 6f20cbef10..837388413b 100644 --- a/platforms/solana/protocols/cctp/src/circleBridge.ts +++ b/platforms/solana/protocols/cctp/src/circleBridge.ts @@ -18,6 +18,7 @@ import { SolanaChains, SolanaPlatform, SolanaPlatformType, + SolanaTransaction, SolanaUnsignedTransaction, } from '@wormhole-foundation/connect-sdk-solana'; import { MessageTransmitter, TokenMessenger } from '.'; @@ -105,13 +106,10 @@ export class SolanaCircleBridge senderPk, ); - const { blockhash } = await SolanaPlatform.latestBlock(this.connection); const transaction = new Transaction(); - transaction.recentBlockhash = blockhash; transaction.feePayer = senderPk; transaction.add(ix); - - yield this.createUnsignedTx(transaction, 'CircleBridge.Redeem'); + yield this.createUnsignedTx({ transaction }, 'CircleBridge.Redeem'); } async *transfer( @@ -140,13 +138,11 @@ export class SolanaCircleBridge amount, ); - const { blockhash } = await SolanaPlatform.latestBlock(this.connection); const transaction = new Transaction(); - transaction.recentBlockhash = blockhash; transaction.feePayer = senderPk; transaction.add(ix); - yield this.createUnsignedTx(transaction, 'CircleBridge.Transfer'); + yield this.createUnsignedTx({ transaction }, 'CircleBridge.Transfer'); } async isTransferCompleted(message: CircleBridge.Message): Promise { @@ -216,7 +212,7 @@ export class SolanaCircleBridge } private createUnsignedTx( - txReq: Transaction, + txReq: SolanaTransaction, description: string, parallelizable: boolean = false, ): SolanaUnsignedTransaction { diff --git a/platforms/solana/protocols/core/src/core.ts b/platforms/solana/protocols/core/src/core.ts index a5350c16b3..822239d534 100644 --- a/platforms/solana/protocols/core/src/core.ts +++ b/platforms/solana/protocols/core/src/core.ts @@ -15,6 +15,7 @@ import { SolanaPlatform, SolanaPlatformType, SolanaUnsignedTransaction, + SolanaTransaction, } from '@wormhole-foundation/connect-sdk-solana'; import { ChainId, @@ -127,24 +128,20 @@ export class SolanaWormholeCore fee, ); - const { blockhash } = await SolanaPlatform.latestBlock(this.connection); const transaction = new Transaction(); - transaction.recentBlockhash = blockhash; transaction.feePayer = payer; transaction.add(feeTransferIx, postMsgIx); - transaction.partialSign(messageAccount); - - yield this.createUnsignedTx(transaction, 'Core.PublishMessage'); + yield this.createUnsignedTx( + { transaction, signers: [messageAccount] }, + 'Core.PublishMessage', + ); } async *verifyMessage(sender: AnySolanaAddress, vaa: VAA) { yield* this.postVaa(sender, vaa); } - async *postVaa(sender: AnySolanaAddress, vaa: VAA, blockhash?: string) { - if (!blockhash) - ({ blockhash } = await SolanaPlatform.latestBlock(this.connection)); - + async *postVaa(sender: AnySolanaAddress, vaa: VAA) { const postedVaaAddress = derivePostedVaaKey( this.coreBridge.programId, Buffer.from(vaa.hash), @@ -171,11 +168,12 @@ export class SolanaWormholeCore const verifySigTx = new Transaction().add( ...verifySignaturesInstructions.slice(i, i + 2), ); - verifySigTx.recentBlockhash = blockhash; verifySigTx.feePayer = senderAddr; - verifySigTx.partialSign(signatureSet); - - yield this.createUnsignedTx(verifySigTx, 'Core.VerifySignature', true); + yield this.createUnsignedTx( + { transaction: verifySigTx, signers: [signatureSet] }, + 'Core.VerifySignature', + true, + ); } // Finally create the VAA posting transaction @@ -188,10 +186,9 @@ export class SolanaWormholeCore signatureSet.publicKey, ), ); - postVaaTx.recentBlockhash = blockhash; postVaaTx.feePayer = senderAddr; - yield this.createUnsignedTx(postVaaTx, 'Core.PostVAA'); + yield this.createUnsignedTx({ transaction: postVaaTx }, 'Core.PostVAA'); } static parseSequenceFromLog( @@ -328,7 +325,7 @@ export class SolanaWormholeCore } private createUnsignedTx( - txReq: Transaction, + txReq: SolanaTransaction, description: string, parallelizable: boolean = false, ): SolanaUnsignedTransaction { diff --git a/platforms/solana/protocols/tokenBridge/src/automaticTokenBridge.ts b/platforms/solana/protocols/tokenBridge/src/automaticTokenBridge.ts index d233685c76..9e26a174fa 100644 --- a/platforms/solana/protocols/tokenBridge/src/automaticTokenBridge.ts +++ b/platforms/solana/protocols/tokenBridge/src/automaticTokenBridge.ts @@ -16,6 +16,7 @@ import { SolanaChains, SolanaPlatform, SolanaPlatformType, + SolanaTransaction, SolanaUnsignedTransaction, } from '@wormhole-foundation/connect-sdk-solana'; @@ -173,18 +174,18 @@ export class SolanaAutomaticTokenBridge< nonce, ); - const { blockhash } = await SolanaPlatform.latestBlock(this.connection); - transaction.add(transferIx); - transaction.recentBlockhash = blockhash; transaction.feePayer = senderAddress; - yield this.createUnsignedTx(transaction, 'AutomaticTokenBridge.Transfer'); + yield this.createUnsignedTx( + { transaction }, + 'AutomaticTokenBridge.Transfer', + ); } async *redeem(sender: AccountAddress, vaa: AutomaticTokenBridge.VAA) { - const redeemTx = new Transaction(); - yield this.createUnsignedTx(redeemTx, 'AutomaticTokenBridge.Redeem'); + const transaction = new Transaction(); + yield this.createUnsignedTx({ transaction }, 'AutomaticTokenBridge.Redeem'); throw new Error('Method not implemented.'); } @@ -327,7 +328,7 @@ export class SolanaAutomaticTokenBridge< } private createUnsignedTx( - txReq: Transaction, + txReq: SolanaTransaction, description: string, parallelizable: boolean = false, ): SolanaUnsignedTransaction { diff --git a/platforms/solana/protocols/tokenBridge/src/tokenBridge.ts b/platforms/solana/protocols/tokenBridge/src/tokenBridge.ts index 2744b2bbd2..38594120d8 100644 --- a/platforms/solana/protocols/tokenBridge/src/tokenBridge.ts +++ b/platforms/solana/protocols/tokenBridge/src/tokenBridge.ts @@ -21,6 +21,7 @@ import { SolanaChains, SolanaPlatform, SolanaPlatformType, + SolanaTransaction, SolanaUnsignedTransaction, } from '@wormhole-foundation/connect-sdk-solana'; import { @@ -211,7 +212,6 @@ export class SolanaTokenBridge ): AsyncGenerator> { if (!payer) throw new Error('Payer required to create attestation'); - const { blockhash } = await SolanaPlatform.latestBlock(this.connection); const senderAddress = new SolanaAddress(payer).unwrap(); // TODO: createNonce().readUInt32LE(0); const nonce = 0; @@ -235,11 +235,11 @@ export class SolanaTokenBridge ); const transaction = new Transaction().add(transferIx, attestIx); - transaction.recentBlockhash = blockhash; transaction.feePayer = senderAddress; - transaction.partialSign(messageKey); - - yield this.createUnsignedTx(transaction, 'Solana.AttestToken'); + yield this.createUnsignedTx( + { transaction, signers: [messageKey] }, + 'Solana.AttestToken', + ); } async *submitAttestation( @@ -248,11 +248,10 @@ export class SolanaTokenBridge ): AsyncGenerator> { if (!payer) throw new Error('Payer required to create attestation'); - const { blockhash } = await SolanaPlatform.latestBlock(this.connection); const senderAddress = new SolanaAddress(payer).unwrap(); // Yield transactions to verify sigs and post the VAA - yield* this.coreBridge.postVaa(senderAddress, vaa, blockhash); + yield* this.coreBridge.postVaa(senderAddress, vaa); // Now yield the transaction to actually create the token const transaction = new Transaction().add( @@ -264,10 +263,9 @@ export class SolanaTokenBridge vaa, ), ); - transaction.recentBlockhash = blockhash; transaction.feePayer = senderAddress; - yield this.createUnsignedTx(transaction, 'Solana.CreateWrapped'); + yield this.createUnsignedTx({ transaction }, 'Solana.CreateWrapped'); } private async transferSol( @@ -278,7 +276,6 @@ export class SolanaTokenBridge ): Promise> { // https://github.com/wormhole-foundation/wormhole-connect/blob/development/sdk/src/contexts/solana/context.ts#L245 - const { blockhash } = await SolanaPlatform.latestBlock(this.connection); const senderAddress = new SolanaAddress(sender).unwrap(); // TODO: the payer can actually be different from the sender. We need to allow the user to pass in an optional payer @@ -368,7 +365,6 @@ export class SolanaTokenBridge ); const transaction = new Transaction(); - transaction.recentBlockhash = blockhash; transaction.feePayer = payerPublicKey; transaction.add( createAncillaryAccountIx, @@ -378,9 +374,10 @@ export class SolanaTokenBridge tokenBridgeTransferIx, closeAccountIx, ); - transaction.partialSign(message, ancillaryKeypair); - - return this.createUnsignedTx(transaction, 'TokenBridge.TransferNative'); + return this.createUnsignedTx( + { transaction, signers: [message, ancillaryKeypair] }, + 'TokenBridge.TransferNative', + ); } async *transfer( @@ -397,7 +394,6 @@ export class SolanaTokenBridge return; } - const { blockhash } = await SolanaPlatform.latestBlock(this.connection); const tokenAddress = new SolanaAddress(token).unwrap(); const senderAddress = new SolanaAddress(sender).unwrap(); const senderTokenAddress = await getAssociatedTokenAddress( @@ -497,17 +493,16 @@ export class SolanaTokenBridge tokenBridgeTransferIx, ); - transaction.recentBlockhash = blockhash; transaction.feePayer = senderAddress; - transaction.partialSign(message); - - yield this.createUnsignedTx(transaction, 'TokenBridge.TransferTokens'); + yield this.createUnsignedTx( + { transaction, signers: [message] }, + 'TokenBridge.TransferTokens', + ); } private async *redeemAndUnwrap( sender: AnySolanaAddress, vaa: TokenBridge.TransferVAA, - blockhash: string, ) { // sender, fee payer const payerPublicKey = new SolanaAddress(sender).unwrap(); @@ -566,7 +561,6 @@ export class SolanaTokenBridge ); const transaction = new Transaction(); - transaction.recentBlockhash = blockhash; transaction.feePayer = payerPublicKey; transaction.add( completeTransferIx, @@ -575,15 +569,13 @@ export class SolanaTokenBridge balanceTransferIx, closeAccountIx, ); - transaction.partialSign(ancillaryKeypair); - yield this.createUnsignedTx(transaction, 'TokenBridge.RedeemAndUnwrap'); + yield this.createUnsignedTx( + { transaction, signers: [ancillaryKeypair] }, + 'TokenBridge.RedeemAndUnwrap', + ); } - private async *createAta( - sender: AnySolanaAddress, - token: AnySolanaAddress, - blockhash: string, - ) { + private async *createAta(sender: AnySolanaAddress, token: AnySolanaAddress) { const senderAddress = new SolanaAddress(sender).unwrap(); const tokenAddress = new SolanaAddress(token).unwrap(); @@ -592,7 +584,7 @@ export class SolanaTokenBridge // If the ata doesn't exist yet, create it const acctInfo = await this.connection.getAccountInfo(ata); if (acctInfo === null) { - const ataCreationTx = new Transaction().add( + const transaction = new Transaction().add( createAssociatedTokenAccountInstruction( senderAddress, ata, @@ -600,9 +592,8 @@ export class SolanaTokenBridge tokenAddress, ), ); - ataCreationTx.feePayer = senderAddress; - ataCreationTx.recentBlockhash = blockhash; - yield this.createUnsignedTx(ataCreationTx, 'Redeem.CreateATA'); + transaction.feePayer = senderAddress; + yield this.createUnsignedTx({ transaction }, 'Redeem.CreateATA'); } } @@ -611,8 +602,6 @@ export class SolanaTokenBridge vaa: TokenBridge.TransferVAA, unwrapNative: boolean = true, ) { - const { blockhash } = await SolanaPlatform.latestBlock(this.connection); - // Find the token address local to this chain const nativeAddress = vaa.payload.token.chain === this.chain @@ -620,10 +609,10 @@ export class SolanaTokenBridge : (await this.getWrappedAsset(vaa.payload.token)).toUniversalAddress(); // Create an ATA if necessary - yield* this.createAta(sender, nativeAddress, blockhash); + yield* this.createAta(sender, nativeAddress); // Post the VAA if necessary - yield* this.coreBridge.postVaa(sender, vaa, blockhash); + yield* this.coreBridge.postVaa(sender, vaa); // redeem vaa and unwrap to native sol from wrapped sol if (unwrapNative) { @@ -635,7 +624,7 @@ export class SolanaTokenBridge wrappedNative.toUint8Array(), ) ) { - yield* this.redeemAndUnwrap(sender, vaa, blockhash); + yield* this.redeemAndUnwrap(sender, vaa); return; } } @@ -656,14 +645,12 @@ export class SolanaTokenBridge vaa, ), ); - - transaction.recentBlockhash = blockhash; transaction.feePayer = senderAddress; - yield this.createUnsignedTx(transaction, 'Solana.RedeemTransfer'); + yield this.createUnsignedTx({ transaction }, 'Solana.RedeemTransfer'); } private createUnsignedTx( - txReq: Transaction, + txReq: SolanaTransaction, description: string, parallelizable: boolean = false, ): SolanaUnsignedTransaction { diff --git a/platforms/solana/src/platform.ts b/platforms/solana/src/platform.ts index c06f62f336..1d28eab4a4 100644 --- a/platforms/solana/src/platform.ts +++ b/platforms/solana/src/platform.ts @@ -19,11 +19,10 @@ import { TOKEN_PROGRAM_ID } from '@solana/spl-token'; import { Commitment, Connection, + ConnectionConfig, ParsedAccountData, PublicKey, SendOptions, - SendTransactionError, - TransactionExpiredBlockheightExceededError, } from '@solana/web3.js'; import { SolanaAddress, SolanaZeroAddress } from './address'; import { @@ -51,10 +50,13 @@ export class SolanaPlatform extends PlatformContext< getRpc( chain: C, - commitment: Commitment = 'confirmed', + config: ConnectionConfig = { + commitment: 'confirmed', + disableRetryOnRateLimit: true, + }, ): Connection { if (chain in this.config) - return new Connection(this.config[chain]!.rpc, commitment); + return new Connection(this.config[chain]!.rpc, config); throw new Error('No configuration available for chain: ' + chain); } @@ -162,53 +164,6 @@ export class SolanaPlatform extends PlatformContext< return balancesArr.reduce((obj, item) => Object.assign(obj, item), {}); } - // Handles retrying a Transaction if the error is deemed to be - // recoverable. Currently handles: - // - Blockhash not found (blockhash too new for the node we submitted to) - // - Not enough bytes (storage account not seen yet) - private static async sendWithRetry( - rpc: Connection, - stxn: SignedTx, - opts: SendOptions, - retries: number = 3, - ): Promise { - // Shouldnt get hit but just in case - if (!retries) throw new Error('Too many retries'); - - try { - const txid = await rpc.sendRawTransaction(stxn, opts); - return txid; - } catch (e) { - retries -= 1; - if (!retries) throw e; - - // Would require re-signing, for now bail - if (e instanceof TransactionExpiredBlockheightExceededError) throw e; - - // Only handle SendTransactionError - if (!(e instanceof SendTransactionError)) throw e; - const emsg = e.message; - - // Only handle simulation errors - if (!emsg.includes('Transaction simulation failed')) throw e; - - // Blockhash not found _yet_ - if (emsg.includes('Blockhash not found')) - return this.sendWithRetry(rpc, stxn, opts, retries); - - // Find the log message with the error details - const loggedErr = e.logs.find((log) => - log.startsWith('Program log: Error: '), - ); - - // Probably caused by storage account not seen yet - if (loggedErr && loggedErr.includes('Not enough bytes')) - return this.sendWithRetry(rpc, stxn, opts, retries); - - throw e; - } - } - static async sendWait( chain: Chain, rpc: Connection, @@ -216,11 +171,9 @@ export class SolanaPlatform extends PlatformContext< opts?: SendOptions, ): Promise { const { blockhash, lastValidBlockHeight } = await this.latestBlock(rpc); - const txhashes = await Promise.all( stxns.map((stxn) => - this.sendWithRetry( - rpc, + rpc.sendRawTransaction( stxn, // Set the commitment level to match the rpc commitment level // otherwise, it defaults to finalized @@ -229,7 +182,7 @@ export class SolanaPlatform extends PlatformContext< ), ); - await Promise.all( + const results = await Promise.all( txhashes.map((signature) => { return rpc.confirmTransaction( { @@ -242,6 +195,13 @@ export class SolanaPlatform extends PlatformContext< }), ); + const erroredTxs = results + .filter((result) => result.value.err) + .map((result) => result.value.err); + + if (erroredTxs.length > 0) + throw new Error(`Failed to confirm transaction: ${erroredTxs}`); + return txhashes; } diff --git a/platforms/solana/src/testing/debug.ts b/platforms/solana/src/testing/debug.ts new file mode 100644 index 0000000000..67a853e32d --- /dev/null +++ b/platforms/solana/src/testing/debug.ts @@ -0,0 +1,14 @@ +import { Transaction } from '@solana/web3.js'; + +export function logTxDetails(transaction: Transaction) { + console.log(transaction.signatures); + console.log(transaction.feePayer); + transaction.instructions.forEach((ix) => { + console.log('Program', ix.programId.toBase58()); + console.log('Data: ', ix.data.toString('hex')); + console.log( + 'Keys: ', + ix.keys.map((k) => [k, k.pubkey.toBase58()]), + ); + }); +} diff --git a/platforms/solana/src/testing/index.ts b/platforms/solana/src/testing/index.ts index b53f40df41..768a910ee3 100644 --- a/platforms/solana/src/testing/index.ts +++ b/platforms/solana/src/testing/index.ts @@ -13,6 +13,7 @@ export async function getSolanaSigner( return new SolanaSigner( chain, Keypair.fromSecretKey(encoding.b58.decode(privateKey)), + rpc, ); } diff --git a/platforms/solana/src/testing/sendSigner.ts b/platforms/solana/src/testing/sendSigner.ts index a0764dfc7b..9d4fc28fb9 100644 --- a/platforms/solana/src/testing/sendSigner.ts +++ b/platforms/solana/src/testing/sendSigner.ts @@ -1,4 +1,10 @@ -import { Connection, Keypair } from '@solana/web3.js'; +import { + Connection, + Keypair, + SendOptions, + SendTransactionError, + TransactionExpiredBlockheightExceededError, +} from '@solana/web3.js'; import { SignAndSendSigner, UnsignedTransaction, @@ -7,6 +13,7 @@ import { Network } from '@wormhole-foundation/sdk-base/src'; import { SolanaPlatform } from '../platform'; import { SolanaChains } from '../types'; import { SolanaUnsignedTransaction } from '../unsignedTransaction'; +import { logTxDetails } from './debug'; export class SolanaSendSigner< N extends Network, @@ -18,7 +25,12 @@ export class SolanaSendSigner< private _chain: C, private _keypair: Keypair, private _debug: boolean = false, - ) {} + private _sendOpts?: SendOptions, + ) { + this._sendOpts = this._sendOpts ?? { + preflightCommitment: this._rpc.commitment, + }; + } chain(): C { return this._chain; @@ -28,50 +40,89 @@ export class SolanaSendSigner< return this._keypair.publicKey.toBase58(); } - async signAndSend(tx: UnsignedTransaction[]): Promise { - const { blockhash, lastValidBlockHeight } = - await SolanaPlatform.latestBlock(this._rpc, 'finalized'); + // Handles retrying a Transaction if the error is deemed to be + // recoverable. Currently handles: + // - Blockhash not found + // - Not enough bytes (storage account not seen yet) + private retryable(e: any): boolean { + // Tx expired, set a new block hash and retry + if (e instanceof TransactionExpiredBlockheightExceededError) return true; + + // Besides tx expiry, only handle SendTransactionError + if (!(e instanceof SendTransactionError)) return false; + + // Only handle simulation errors + if (!e.message.includes('Transaction simulation failed')) return false; + + // Blockhash not found, similar to expired, resend with new blockhash + if (e.message.includes('Blockhash not found')) return true; - const txPromises: Promise[] = []; + // Find the log message with the error details + const loggedErr = e.logs.find((log) => + log.startsWith('Program log: Error: '), + ); + + // who knows + if (!loggedErr) return false; + + // Probably caused by storage account not seen yet + if (loggedErr.includes('Not enough bytes')) return true; + } + + async signAndSend(tx: UnsignedTransaction[]): Promise { + let { blockhash, lastValidBlockHeight } = await SolanaPlatform.latestBlock( + this._rpc, + 'finalized', + ); + const txids: string[] = []; for (const txn of tx) { - const { description, transaction } = txn as SolanaUnsignedTransaction< - N, - C - >; + const { + description, + transaction: { transaction, signers: extraSigners }, + } = txn as SolanaUnsignedTransaction; console.log(`Signing: ${description} for ${this.address()}`); - if (this._debug) { - console.log(transaction.signatures); - console.log(transaction.feePayer); - transaction.instructions.forEach((ix) => { - console.log('Program', ix.programId.toBase58()); - console.log('Data: ', ix.data.toString('hex')); - console.log( - 'Keys: ', - ix.keys.map((k) => [k, k.pubkey.toBase58()]), - ); - }); - } + if (this._debug) logTxDetails(transaction); - transaction.partialSign(this._keypair); + for (;;) { + try { + transaction.recentBlockhash = blockhash; + transaction.partialSign(this._keypair, ...(extraSigners ?? [])); - txPromises.push( - this._rpc.sendRawTransaction(transaction.serialize(), { - preflightCommitment: this._rpc.commitment, - }), - ); + const txid = await this._rpc.sendRawTransaction( + transaction.serialize(), + this._sendOpts, + ); + txids.push(txid); + break; + } catch (e) { + if (!this.retryable(e)) throw e; + + // If it is retryable, we should grab a new block hash + ({ blockhash, lastValidBlockHeight } = + await SolanaPlatform.latestBlock(this._rpc, 'finalized')); + } + } } - const txids = await Promise.all(txPromises); // Wait for finalization - for (const signature of txids) { - await this._rpc.confirmTransaction({ - signature, - blockhash, - lastValidBlockHeight, - }); - } + const results = await Promise.all( + txids.map((signature) => + this._rpc.confirmTransaction({ + signature, + blockhash, + lastValidBlockHeight, + }), + ), + ); + + const erroredTxs = results + .filter((result) => result.value.err) + .map((result) => result.value.err); + + if (erroredTxs.length > 0) + throw new Error(`Failed to confirm transaction: ${erroredTxs}`); return txids; } diff --git a/platforms/solana/src/testing/signer.ts b/platforms/solana/src/testing/signer.ts index 4c1a196af1..fcc15fd266 100644 --- a/platforms/solana/src/testing/signer.ts +++ b/platforms/solana/src/testing/signer.ts @@ -1,10 +1,12 @@ -import { Keypair, Transaction } from '@solana/web3.js'; +import { Connection, Keypair } from '@solana/web3.js'; import { SignOnlySigner, UnsignedTransaction, } from '@wormhole-foundation/connect-sdk'; import { Network } from '@wormhole-foundation/sdk-base/src'; +import { SolanaPlatform } from '../platform'; import { SolanaChains } from '../types'; +import { logTxDetails } from './debug'; export class SolanaSigner implements SignOnlySigner @@ -12,6 +14,7 @@ export class SolanaSigner constructor( private _chain: C, private _keypair: Keypair, + private _rpc: Connection, private _debug: boolean = false, ) {} @@ -24,25 +27,24 @@ export class SolanaSigner } async sign(tx: UnsignedTransaction[]): Promise { + const { blockhash } = await SolanaPlatform.latestBlock( + this._rpc, + 'finalized', + ); + const signed = []; for (const txn of tx) { - const { description, transaction } = txn; + const { + description, + transaction: { transaction, signers: extraSigners }, + } = txn; + console.log(`Signing: ${description} for ${this.address()}`); - if (this._debug) { - const st = transaction as Transaction; - console.log(st.signatures); - console.log(st.feePayer); - st.instructions.forEach((ix) => { - console.log('Program', ix.programId.toBase58()); - console.log('Data: ', ix.data.toString('hex')); - ix.keys.forEach((k) => { - console.log(k, k.pubkey.toBase58()); - }); - }); - } - - transaction.partialSign(this._keypair); + if (this._debug) logTxDetails(transaction); + + transaction.recentBlockhash = blockhash; + transaction.partialSign(this._keypair, ...(extraSigners ?? [])); signed.push(transaction.serialize()); } return signed; diff --git a/platforms/solana/src/unsignedTransaction.ts b/platforms/solana/src/unsignedTransaction.ts index ecb0306324..df718fc1b0 100644 --- a/platforms/solana/src/unsignedTransaction.ts +++ b/platforms/solana/src/unsignedTransaction.ts @@ -1,14 +1,19 @@ -import { Transaction } from '@solana/web3.js'; +import { Keypair, Transaction } from '@solana/web3.js'; import { Network, UnsignedTransaction } from '@wormhole-foundation/connect-sdk'; import { SolanaChains } from './types'; +export type SolanaTransaction = { + transaction: Transaction; + signers?: Keypair[]; +}; + export class SolanaUnsignedTransaction< N extends Network, C extends SolanaChains = SolanaChains, > implements UnsignedTransaction { constructor( - readonly transaction: Transaction, + readonly transaction: SolanaTransaction, readonly network: N, readonly chain: C, readonly description: string,