From a8e254ddfb96935cd30c5d44f7111286a607e614 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Tue, 2 Jan 2024 09:35:02 -0500 Subject: [PATCH] add sign and send signer for solana --- .../solana/protocols/cctp/src/circleBridge.ts | 4 +- platforms/solana/protocols/core/src/core.ts | 9 +-- .../tokenBridge/src/automaticTokenBridge.ts | 2 +- .../protocols/tokenBridge/src/tokenBridge.ts | 10 +-- platforms/solana/src/platform.ts | 13 ++- platforms/solana/src/testing/index.ts | 32 ++++++++ platforms/solana/src/testing/sendSigner.ts | 79 +++++++++++++++++++ platforms/solana/src/testing/signer.ts | 17 +--- 8 files changed, 128 insertions(+), 38 deletions(-) create mode 100644 platforms/solana/src/testing/sendSigner.ts diff --git a/platforms/solana/protocols/cctp/src/circleBridge.ts b/platforms/solana/protocols/cctp/src/circleBridge.ts index c6a87636b..6f20cbef1 100644 --- a/platforms/solana/protocols/cctp/src/circleBridge.ts +++ b/platforms/solana/protocols/cctp/src/circleBridge.ts @@ -105,7 +105,7 @@ export class SolanaCircleBridge senderPk, ); - const { blockhash } = await SolanaPlatform.latestBlockhash(this.connection); + const { blockhash } = await SolanaPlatform.latestBlock(this.connection); const transaction = new Transaction(); transaction.recentBlockhash = blockhash; transaction.feePayer = senderPk; @@ -140,7 +140,7 @@ export class SolanaCircleBridge amount, ); - const { blockhash } = await SolanaPlatform.latestBlockhash(this.connection); + const { blockhash } = await SolanaPlatform.latestBlock(this.connection); const transaction = new Transaction(); transaction.recentBlockhash = blockhash; transaction.feePayer = senderPk; diff --git a/platforms/solana/protocols/core/src/core.ts b/platforms/solana/protocols/core/src/core.ts index fe0e543c5..abfc6f6da 100644 --- a/platforms/solana/protocols/core/src/core.ts +++ b/platforms/solana/protocols/core/src/core.ts @@ -104,7 +104,7 @@ export class SolanaWormholeCore consistencyLevel, ); - const { blockhash } = await SolanaPlatform.latestBlockhash(this.connection); + const { blockhash } = await SolanaPlatform.latestBlock(this.connection); const transaction = new Transaction(); transaction.recentBlockhash = blockhash; transaction.feePayer = payer; @@ -120,7 +120,7 @@ export class SolanaWormholeCore async *postVaa(sender: AnySolanaAddress, vaa: VAA, blockhash?: string) { if (!blockhash) - ({ blockhash } = await SolanaPlatform.latestBlockhash(this.connection)); + ({ blockhash } = await SolanaPlatform.latestBlock(this.connection)); const postedVaaAddress = derivePostedVaaKey( this.coreBridge.programId, @@ -143,7 +143,7 @@ export class SolanaWormholeCore signatureSet.publicKey, ); - // Create a new transaction for every 2 signatures we have to Verify + // Create a new transaction for every 2 instructions for (let i = 0; i < verifySignaturesInstructions.length; i += 2) { const verifySigTx = new Transaction().add( ...verifySignaturesInstructions.slice(i, i + 2), @@ -155,9 +155,6 @@ export class SolanaWormholeCore yield this.createUnsignedTx(verifySigTx, 'Core.VerifySignature', true); } - // TODO: if VAA is already posted, just, like, dont post it again man - // if(this.connection.getAccountInfo(postedVaaAddress)) - // Finally create the VAA posting transaction const postVaaTx = new Transaction().add( createPostVaaInstruction( diff --git a/platforms/solana/protocols/tokenBridge/src/automaticTokenBridge.ts b/platforms/solana/protocols/tokenBridge/src/automaticTokenBridge.ts index 37c76340b..d233685c7 100644 --- a/platforms/solana/protocols/tokenBridge/src/automaticTokenBridge.ts +++ b/platforms/solana/protocols/tokenBridge/src/automaticTokenBridge.ts @@ -173,7 +173,7 @@ export class SolanaAutomaticTokenBridge< nonce, ); - const { blockhash } = await SolanaPlatform.latestBlockhash(this.connection); + const { blockhash } = await SolanaPlatform.latestBlock(this.connection); transaction.add(transferIx); transaction.recentBlockhash = blockhash; diff --git a/platforms/solana/protocols/tokenBridge/src/tokenBridge.ts b/platforms/solana/protocols/tokenBridge/src/tokenBridge.ts index 02dece97c..3cd60ec43 100644 --- a/platforms/solana/protocols/tokenBridge/src/tokenBridge.ts +++ b/platforms/solana/protocols/tokenBridge/src/tokenBridge.ts @@ -231,7 +231,7 @@ export class SolanaTokenBridge ); const transaction = new Transaction().add(transferIx, attestIx); - const { blockhash } = await SolanaPlatform.latestBlockhash(this.connection); + const { blockhash } = await SolanaPlatform.latestBlock(this.connection); transaction.recentBlockhash = blockhash; transaction.feePayer = senderAddress; transaction.partialSign(messageKey); @@ -246,7 +246,7 @@ export class SolanaTokenBridge if (!payer) throw new Error('Payer required to create attestation'); const senderAddress = new SolanaAddress(payer).unwrap(); - const { blockhash } = await SolanaPlatform.latestBlockhash(this.connection); + const { blockhash } = await SolanaPlatform.latestBlock(this.connection); // Yield transactions to verify sigs and post the VAA yield* this.coreBridge.postVaa(senderAddress, vaa, blockhash); @@ -362,7 +362,7 @@ export class SolanaTokenBridge payerPublicKey, //authority ); - const { blockhash } = await SolanaPlatform.latestBlockhash(this.connection); + const { blockhash } = await SolanaPlatform.latestBlock(this.connection); const transaction = new Transaction(); transaction.recentBlockhash = blockhash; transaction.feePayer = payerPublicKey; @@ -493,7 +493,7 @@ export class SolanaTokenBridge tokenBridgeTransferIx, ); - const { blockhash } = await SolanaPlatform.latestBlockhash(this.connection); + const { blockhash } = await SolanaPlatform.latestBlock(this.connection); transaction.recentBlockhash = blockhash; transaction.feePayer = senderAddress; transaction.partialSign(message); @@ -608,7 +608,7 @@ export class SolanaTokenBridge vaa: TokenBridge.TransferVAA, unwrapNative: boolean = true, ) { - const { blockhash } = await SolanaPlatform.latestBlockhash(this.connection); + const { blockhash } = await SolanaPlatform.latestBlock(this.connection); // Find the token address local to this chain const nativeAddress = diff --git a/platforms/solana/src/platform.ts b/platforms/solana/src/platform.ts index 7e2f022d6..70c1e2f60 100644 --- a/platforms/solana/src/platform.ts +++ b/platforms/solana/src/platform.ts @@ -165,14 +165,14 @@ export class SolanaPlatform extends PlatformContext< rpc: Connection, stxns: SignedTx[], ): Promise { + const { blockhash, lastValidBlockHeight } = await this.latestBlock(rpc); + const txhashes = await Promise.all( stxns.map((stxn) => { return rpc.sendRawTransaction(stxn); }), ); - const { blockhash, lastValidBlockHeight } = await this.latestBlockhash(rpc); - await Promise.all( txhashes.map((txid) => { const bhs: BlockheightBasedTransactionConfirmationStrategy = { @@ -187,7 +187,7 @@ export class SolanaPlatform extends PlatformContext< return txhashes; } - static async latestBlockhash( + static async latestBlock( rpc: Connection, commitment?: Commitment, ): Promise<{ blockhash: string; lastValidBlockHeight: number }> { @@ -195,15 +195,12 @@ export class SolanaPlatform extends PlatformContext< } static async getLatestBlock(rpc: Connection): Promise { - const { lastValidBlockHeight } = await this.latestBlockhash(rpc); + const { lastValidBlockHeight } = await this.latestBlock(rpc); return lastValidBlockHeight; } static async getLatestFinalizedBlock(rpc: Connection): Promise { - const { lastValidBlockHeight } = await this.latestBlockhash( - rpc, - 'finalized', - ); + const { lastValidBlockHeight } = await this.latestBlock(rpc, 'finalized'); return lastValidBlockHeight; } diff --git a/platforms/solana/src/testing/index.ts b/platforms/solana/src/testing/index.ts index af5d8964d..b53f40df4 100644 --- a/platforms/solana/src/testing/index.ts +++ b/platforms/solana/src/testing/index.ts @@ -1 +1,33 @@ +import { Connection, Keypair } from '@solana/web3.js'; +import { SolanaPlatform } from '../platform'; +import { SolanaSigner } from './signer'; +import { Signer, encoding } from '@wormhole-foundation/connect-sdk'; +import { SolanaSendSigner } from './sendSigner'; + +// returns a SignOnlySigner for the Solana platform +export async function getSolanaSigner( + rpc: Connection, + privateKey: string, +): Promise { + const [_, chain] = await SolanaPlatform.chainFromRpc(rpc); + return new SolanaSigner( + chain, + Keypair.fromSecretKey(encoding.b58.decode(privateKey)), + ); +} + +// returns a SignAndSendSigner for the Solana platform +export async function getSolanaSignAndSendSigner( + rpc: Connection, + privateKey: string, +): Promise { + const [_, chain] = await SolanaPlatform.chainFromRpc(rpc); + return new SolanaSendSigner( + rpc, + chain, + Keypair.fromSecretKey(encoding.b58.decode(privateKey)), + ); +} + export * from './signer'; +export * from './sendSigner'; diff --git a/platforms/solana/src/testing/sendSigner.ts b/platforms/solana/src/testing/sendSigner.ts new file mode 100644 index 000000000..5b0285bad --- /dev/null +++ b/platforms/solana/src/testing/sendSigner.ts @@ -0,0 +1,79 @@ +import { Connection, Keypair, Transaction } from '@solana/web3.js'; +import { + SignAndSendSigner, + UnsignedTransaction, +} from '@wormhole-foundation/connect-sdk'; +import { Network } from '@wormhole-foundation/sdk-base/src'; +import { SolanaPlatform } from '../platform'; +import { SolanaChains } from '../types'; +import { SolanaUnsignedTransaction } from '../unsignedTransaction'; + +export class SolanaSendSigner< + N extends Network, + C extends SolanaChains = 'Solana', +> implements SignAndSendSigner +{ + constructor( + private _rpc: Connection, + private _chain: C, + private _keypair: Keypair, + private _debug: boolean = false, + ) {} + + chain(): C { + return this._chain; + } + + address(): string { + return this._keypair.publicKey.toBase58(); + } + + async signAndSend(tx: UnsignedTransaction[]): Promise { + const txids: string[] = []; + + const { blockhash, lastValidBlockHeight } = + await SolanaPlatform.latestBlock(this._rpc); + + for (const txn of tx) { + const { description, transaction } = txn as SolanaUnsignedTransaction< + N, + C + >; + 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')); + console.log( + 'Keys: ', + ix.keys.map((k) => [k, k.pubkey.toBase58()]), + ); + }); + } + + transaction.partialSign(this._keypair); + + const txid = await this._rpc.sendRawTransaction(transaction.serialize(), { + // skipPreflight: true, + // preflightCommitment: this._rpc.commitment, + maxRetries: 5, + }); + + console.log(`Sent: ${description} for ${this.address()}`); + + await this._rpc.confirmTransaction({ + signature: txid, + blockhash, + lastValidBlockHeight, + }); + + txids.push(txid); + } + + return txids; + } +} diff --git a/platforms/solana/src/testing/signer.ts b/platforms/solana/src/testing/signer.ts index b7eb40fc6..4c1a196af 100644 --- a/platforms/solana/src/testing/signer.ts +++ b/platforms/solana/src/testing/signer.ts @@ -1,26 +1,11 @@ -import { Connection, Keypair, Transaction } from '@solana/web3.js'; +import { Keypair, Transaction } from '@solana/web3.js'; import { SignOnlySigner, - Signer, UnsignedTransaction, - encoding, } from '@wormhole-foundation/connect-sdk'; import { Network } from '@wormhole-foundation/sdk-base/src'; -import { SolanaPlatform } from '../platform'; import { SolanaChains } from '../types'; -// returns a SignOnlySigner for the Solana platform -export async function getSolanaSigner( - rpc: Connection, - privateKey: string, -): Promise { - const [_, chain] = await SolanaPlatform.chainFromRpc(rpc); - return new SolanaSigner( - chain, - Keypair.fromSecretKey(encoding.b58.decode(privateKey)), - ); -} - export class SolanaSigner implements SignOnlySigner {