From 8277e128f25b2d7a2340b32a51474086ff1b6aee Mon Sep 17 00:00:00 2001 From: Felipe Mendes Date: Wed, 7 Aug 2024 16:53:19 -0300 Subject: [PATCH 1/2] refactor: update solana rpc interface for serialized transactions --- advanced/wallets/react-wallet-v2/package.json | 3 +- .../react-wallet-v2/src/lib/SolanaLib.ts | 104 ++++++++---------- .../src/utils/SolanaRequestHandlerUtil.ts | 17 +-- advanced/wallets/react-wallet-v2/yarn.lock | 50 +-------- 4 files changed, 52 insertions(+), 122 deletions(-) diff --git a/advanced/wallets/react-wallet-v2/package.json b/advanced/wallets/react-wallet-v2/package.json index 29837d164..2b46a6d67 100644 --- a/advanced/wallets/react-wallet-v2/package.json +++ b/advanced/wallets/react-wallet-v2/package.json @@ -54,7 +54,6 @@ "react-dom": "17.0.2", "react-hot-toast": "^2.4.1", "react-qr-reader-es6": "2.2.1-2", - "solana-wallet": "^1.0.2", "tronweb": "^4.4.0", "valtio": "1.13.2", "viem": "2.17.8", @@ -71,4 +70,4 @@ "prettier": "2.6.2", "typescript": "5.2.2" } -} \ No newline at end of file +} diff --git a/advanced/wallets/react-wallet-v2/src/lib/SolanaLib.ts b/advanced/wallets/react-wallet-v2/src/lib/SolanaLib.ts index 8abc3fd7a..706bec927 100644 --- a/advanced/wallets/react-wallet-v2/src/lib/SolanaLib.ts +++ b/advanced/wallets/react-wallet-v2/src/lib/SolanaLib.ts @@ -1,14 +1,6 @@ -import { - Keypair, - Connection, - Transaction, - TransactionInstruction, - PublicKey, - SendOptions -} from '@solana/web3.js' +import { Keypair, Connection, SendOptions, VersionedTransaction } from '@solana/web3.js' import bs58 from 'bs58' import nacl from 'tweetnacl' -import SolanaWallet, { SolanaSignTransaction } from 'solana-wallet' import { SOLANA_MAINNET_CHAINS, SOLANA_TEST_CHAINS } from '@/data/SolanaData' /** @@ -23,11 +15,9 @@ interface IInitArguments { */ export default class SolanaLib { keypair: Keypair - solanaWallet: SolanaWallet constructor(keypair: Keypair) { this.keypair = keypair - this.solanaWallet = new SolanaWallet(Buffer.from(keypair.secretKey)) } static init({ secretKey }: IInitArguments) { @@ -44,35 +34,28 @@ export default class SolanaLib { return this.keypair.secretKey.toString() } - public async signMessage(message: string) { - const signature = nacl.sign.detached(bs58.decode(message), this.keypair.secretKey) + public async signMessage( + params: SolanaLib.SignMessage['params'] + ): Promise { + const signature = nacl.sign.detached(bs58.decode(params.message), this.keypair.secretKey) const bs58Signature = bs58.encode(signature) return { signature: bs58Signature } } public async signTransaction( - feePayer: SolanaSignTransaction['feePayer'], - recentBlockhash: SolanaSignTransaction['recentBlockhash'], - instructions: SolanaSignTransaction['instructions'], - partialSignatures?: SolanaSignTransaction['partialSignatures'] - ) { - const { signature } = await this.solanaWallet.signTransaction(feePayer, { - feePayer, - instructions, - recentBlockhash, - partialSignatures: partialSignatures ?? [] - }) + params: SolanaLib.SignTransaction['params'] + ): Promise { + const transaction = this.deserialize(params.transaction) + this.sign(transaction) - return { signature } + return { transaction: this.serialize(transaction) } } public async signAndSendTransaction( - feePayer: SolanaSignTransaction['feePayer'], - instructions: SolanaSignTransaction['instructions'], - chainId: string, - options: SendOptions = {} - ) { + params: SolanaLib.SignAndSendTransaction['params'], + chainId: string + ): Promise { const rpc = { ...SOLANA_TEST_CHAINS, ...SOLANA_MAINNET_CHAINS }[chainId]?.rpc if (!rpc) { @@ -80,38 +63,39 @@ export default class SolanaLib { } const connection = new Connection(rpc) + const transaction = this.deserialize(params.transaction) + this.sign(transaction) - const parsedInstructions = instructions.map(instruction => { - const keys = instruction.keys.map(key => ({ - pubkey: new PublicKey(key.pubkey), - isSigner: key.isSigner, - isWritable: key.isWritable - })) - const programId = new PublicKey(instruction.programId) - const data = - typeof instruction.data === 'string' - ? Buffer.from(bs58.decode(instruction.data).buffer) - : instruction.data - - return new TransactionInstruction({ - keys, - programId, - data - }) - }) - - const transaction = new Transaction().add(...parsedInstructions) - transaction.feePayer = new PublicKey(feePayer) - transaction.recentBlockhash = (await connection.getLatestBlockhash()).blockhash - transaction.sign(this.keypair) - - const signature = await connection.sendRawTransaction(transaction.serialize()) - const confirmation = await connection.confirmTransaction(signature, options.preflightCommitment) - - if (confirmation.value.err) { - throw new Error(confirmation.value.err.toString()) - } + const signature = await connection.sendTransaction(transaction, params.options) return { signature } } + + private serialize(transaction: VersionedTransaction): string { + return bs58.encode(transaction.serialize()) + } + + private deserialize(transaction: string): VersionedTransaction { + return VersionedTransaction.deserialize(bs58.decode(transaction)) + } + + private sign(transaction: VersionedTransaction) { + transaction.sign([this.keypair]) + } +} + +export namespace SolanaLib { + type RPCRequest = { + params: Params + result: Result + } + + export type SignMessage = RPCRequest<{ message: string }, { signature: string }> + + export type SignTransaction = RPCRequest<{ transaction: string }, { transaction: string }> + + export type SignAndSendTransaction = RPCRequest< + { transaction: string; options?: SendOptions }, + { signature: string } + > } diff --git a/advanced/wallets/react-wallet-v2/src/utils/SolanaRequestHandlerUtil.ts b/advanced/wallets/react-wallet-v2/src/utils/SolanaRequestHandlerUtil.ts index 152a3b6f7..58b5ca111 100644 --- a/advanced/wallets/react-wallet-v2/src/utils/SolanaRequestHandlerUtil.ts +++ b/advanced/wallets/react-wallet-v2/src/utils/SolanaRequestHandlerUtil.ts @@ -14,26 +14,15 @@ export async function approveSolanaRequest( switch (request.method) { case SOLANA_SIGNING_METHODS.SOLANA_SIGN_MESSAGE: - const signedMessage = await wallet.signMessage(request.params.message) + const signedMessage = await wallet.signMessage(request.params) return formatJsonRpcResult(id, signedMessage) case SOLANA_SIGNING_METHODS.SOLANA_SIGN_TRANSACTION: - const signedTransaction = await wallet.signTransaction( - request.params.feePayer, - request.params.recentBlockhash, - request.params.instructions - ) - + const signedTransaction = await wallet.signTransaction(request.params) return formatJsonRpcResult(id, signedTransaction) case SOLANA_SIGNING_METHODS.SOLANA_SIGN_AND_SEND_TRANSACTION: - const signedAndSentTransaction = await wallet.signAndSendTransaction( - request.params.feePayer, - request.params.instructions, - chainId, - request.params.options - ) - + const signedAndSentTransaction = await wallet.signAndSendTransaction(request.params, chainId) return formatJsonRpcResult(id, signedAndSentTransaction) default: diff --git a/advanced/wallets/react-wallet-v2/yarn.lock b/advanced/wallets/react-wallet-v2/yarn.lock index c724037be..b7e93b11c 100644 --- a/advanced/wallets/react-wallet-v2/yarn.lock +++ b/advanced/wallets/react-wallet-v2/yarn.lock @@ -1304,7 +1304,7 @@ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.0.tgz#085fd70f6d7d9d109671090ccae1d3bec62554a1" integrity sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg== -"@noble/hashes@1.3.3", "@noble/hashes@^1", "@noble/hashes@^1.2.0", "@noble/hashes@^1.3.1", "@noble/hashes@^1.3.2", "@noble/hashes@~1.3.2": +"@noble/hashes@1.3.3", "@noble/hashes@^1", "@noble/hashes@^1.2.0", "@noble/hashes@^1.3.2", "@noble/hashes@~1.3.2": version "1.3.3" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.3.tgz#39908da56a4adc270147bb07968bf3b16cfe1699" integrity sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA== @@ -2172,7 +2172,7 @@ "@noble/hashes" "~1.4.0" "@scure/base" "~1.1.6" -"@solana/buffer-layout@^4.0.0", "@solana/buffer-layout@^4.0.1": +"@solana/buffer-layout@^4.0.1": version "4.0.1" resolved "https://registry.yarnpkg.com/@solana/buffer-layout/-/buffer-layout-4.0.1.tgz#b996235eaec15b1e0b5092a8ed6028df77fa6c15" integrity sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA== @@ -2200,27 +2200,6 @@ rpc-websockets "^7.5.1" superstruct "^0.14.2" -"@solana/web3.js@^1.66.2": - version "1.87.6" - resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.87.6.tgz#6744cfc5f4fc81e0f58241c0a92648a7320bb3bf" - integrity sha512-LkqsEBgTZztFiccZZXnawWa8qNCATEqE97/d0vIwjTclmVlc8pBpD1DmjfVHtZ1HS5fZorFlVhXfpwnCNDZfyg== - dependencies: - "@babel/runtime" "^7.23.2" - "@noble/curves" "^1.2.0" - "@noble/hashes" "^1.3.1" - "@solana/buffer-layout" "^4.0.0" - agentkeepalive "^4.3.0" - bigint-buffer "^1.1.5" - bn.js "^5.2.1" - borsh "^0.7.0" - bs58 "^4.0.1" - buffer "6.0.3" - fast-stable-stringify "^1.0.0" - jayson "^4.1.0" - node-fetch "^2.6.12" - rpc-websockets "^7.5.1" - superstruct "^0.14.2" - "@stablelib/aead@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@stablelib/aead/-/aead-1.0.1.tgz#c4b1106df9c23d1b867eb9b276d8f42d5fc4c0c3" @@ -3116,7 +3095,7 @@ aes-js@3.0.0: resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.0.0.tgz#e21df10ad6c2053295bcbb8dab40b09dbea87e4d" integrity sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw== -agentkeepalive@^4.3.0, agentkeepalive@^4.5.0: +agentkeepalive@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.5.0.tgz#2673ad1389b3c418c5a20c5d7364f93ca04be923" integrity sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew== @@ -3323,11 +3302,6 @@ base-x@^3.0.2: dependencies: safe-buffer "^5.0.1" -base-x@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/base-x/-/base-x-4.0.0.tgz#d0e3b7753450c73f8ad2389b5c018a4af7b2224a" - integrity sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw== - base-x@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/base-x/-/base-x-5.0.0.tgz#6d835ceae379130e1a4cb846a70ac4746f28ea9b" @@ -3482,13 +3456,6 @@ bs58@^4.0.0, bs58@^4.0.1: dependencies: base-x "^3.0.2" -bs58@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/bs58/-/bs58-5.0.0.tgz#865575b4d13c09ea2a84622df6c8cbeb54ffc279" - integrity sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ== - dependencies: - base-x "^4.0.0" - bs58check@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/bs58check/-/bs58check-2.1.2.tgz#53b018291228d82a5aa08e7d796fdafda54aebfc" @@ -5782,7 +5749,7 @@ node-fetch-native@^1.4.0, node-fetch-native@^1.4.1: resolved "https://registry.yarnpkg.com/node-fetch-native/-/node-fetch-native-1.4.1.tgz#5a336e55b4e1b1e72b9927da09fecd2b374c9be5" integrity sha512-NsXBU0UgBxo2rQLOeWNZqS3fvflWePMECr8CoSWoSTqCqGbVVsvl9vZu1HfQicYN0g5piV9Gh8RTEvo/uP752w== -node-fetch@^2.6.1, node-fetch@^2.6.12, node-fetch@^2.7.0: +node-fetch@^2.6.1, node-fetch@^2.7.0: version "2.7.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== @@ -6630,15 +6597,6 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== -solana-wallet@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/solana-wallet/-/solana-wallet-1.0.2.tgz#1b8157974a0ec5228ac45d8414c656dcb3a94d47" - integrity sha512-oZnLJvwBFnQ0Hf0vTuAUFizq59AhxDfoMpdDUuqo02seNsV7AbYl3QGJZBJ1uCr36cRJnXFr2NqI3RM2IDq62Q== - dependencies: - "@solana/web3.js" "^1.66.2" - bs58 "^5.0.0" - tweetnacl "^1.0.3" - sonic-boom@^2.2.1: version "2.8.0" resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-2.8.0.tgz#c1def62a77425090e6ad7516aad8eb402e047611" From 82cb7f835e30fdd801d2fe394429f93cb58abcb0 Mon Sep 17 00:00:00 2001 From: Felipe Mendes Date: Wed, 7 Aug 2024 17:29:58 -0300 Subject: [PATCH 2/2] feat: handle error response for failed solana requests --- .../react-wallet-v2/src/lib/SolanaLib.ts | 6 +++- .../src/utils/SolanaRequestHandlerUtil.ts | 31 ++++++++++++------- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/advanced/wallets/react-wallet-v2/src/lib/SolanaLib.ts b/advanced/wallets/react-wallet-v2/src/lib/SolanaLib.ts index 706bec927..65691248e 100644 --- a/advanced/wallets/react-wallet-v2/src/lib/SolanaLib.ts +++ b/advanced/wallets/react-wallet-v2/src/lib/SolanaLib.ts @@ -66,7 +66,11 @@ export default class SolanaLib { const transaction = this.deserialize(params.transaction) this.sign(transaction) - const signature = await connection.sendTransaction(transaction, params.options) + const signature = await connection.sendTransaction(transaction, { + maxRetries: 3, + preflightCommitment: 'recent', + ...params.options + }) return { signature } } diff --git a/advanced/wallets/react-wallet-v2/src/utils/SolanaRequestHandlerUtil.ts b/advanced/wallets/react-wallet-v2/src/utils/SolanaRequestHandlerUtil.ts index 58b5ca111..296995300 100644 --- a/advanced/wallets/react-wallet-v2/src/utils/SolanaRequestHandlerUtil.ts +++ b/advanced/wallets/react-wallet-v2/src/utils/SolanaRequestHandlerUtil.ts @@ -12,21 +12,28 @@ export async function approveSolanaRequest( const { request, chainId } = params const wallet = solanaWallets[getWalletAddressFromParams(solanaAddresses, params)] - switch (request.method) { - case SOLANA_SIGNING_METHODS.SOLANA_SIGN_MESSAGE: - const signedMessage = await wallet.signMessage(request.params) - return formatJsonRpcResult(id, signedMessage) + try { + switch (request.method) { + case SOLANA_SIGNING_METHODS.SOLANA_SIGN_MESSAGE: + const signedMessage = await wallet.signMessage(request.params) + return formatJsonRpcResult(id, signedMessage) - case SOLANA_SIGNING_METHODS.SOLANA_SIGN_TRANSACTION: - const signedTransaction = await wallet.signTransaction(request.params) - return formatJsonRpcResult(id, signedTransaction) + case SOLANA_SIGNING_METHODS.SOLANA_SIGN_TRANSACTION: + const signedTransaction = await wallet.signTransaction(request.params) + return formatJsonRpcResult(id, signedTransaction) - case SOLANA_SIGNING_METHODS.SOLANA_SIGN_AND_SEND_TRANSACTION: - const signedAndSentTransaction = await wallet.signAndSendTransaction(request.params, chainId) - return formatJsonRpcResult(id, signedAndSentTransaction) + case SOLANA_SIGNING_METHODS.SOLANA_SIGN_AND_SEND_TRANSACTION: + const signedAndSentTransaction = await wallet.signAndSendTransaction( + request.params, + chainId + ) + return formatJsonRpcResult(id, signedAndSentTransaction) - default: - throw new Error(getSdkError('INVALID_METHOD').message) + default: + throw new Error(getSdkError('INVALID_METHOD').message) + } + } catch (error) { + return formatJsonRpcError(id, (error as Error)?.message) } }