From 2f8bf83380a16c5e8434661cbff216b80db2f72d Mon Sep 17 00:00:00 2001 From: Felipe Mendes Date: Fri, 30 Aug 2024 08:20:30 -0300 Subject: [PATCH 01/11] feat: add signAllTransactions in solana provider interface --- packages/scaffold-utils/src/solana/SolanaTypesUtil.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/scaffold-utils/src/solana/SolanaTypesUtil.ts b/packages/scaffold-utils/src/solana/SolanaTypesUtil.ts index c65f6b376e..d7d3891cfc 100644 --- a/packages/scaffold-utils/src/solana/SolanaTypesUtil.ts +++ b/packages/scaffold-utils/src/solana/SolanaTypesUtil.ts @@ -56,6 +56,7 @@ export interface Provider extends ProviderEventEmitterMethods { connection: Connection, options?: SendTransactionOptions ) => Promise + singAllTransactions: (transactions: AnyTransaction[]) => Promise } export interface ProviderEventEmitterMethods { From 6a8f2892fc48b5e3eda23d6e187a98fc52474ecd Mon Sep 17 00:00:00 2001 From: Felipe Mendes Date: Fri, 30 Aug 2024 08:24:38 -0300 Subject: [PATCH 02/11] feat: add generic test for signAllTransactions --- .../solana/web3js/tests/GenericProvider.test.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/base/adapters/solana/web3js/tests/GenericProvider.test.ts b/packages/base/adapters/solana/web3js/tests/GenericProvider.test.ts index 5811f21e2b..2fa128edf4 100644 --- a/packages/base/adapters/solana/web3js/tests/GenericProvider.test.ts +++ b/packages/base/adapters/solana/web3js/tests/GenericProvider.test.ts @@ -99,4 +99,19 @@ describe.each(providers)('Generic provider tests for $name', ({ provider }) => { expect(result).toBeTypeOf('string') }) + + it('should signAllTransactions with AnyTransaction', async () => { + const transactions = [ + mockLegacyTransaction(), + mockVersionedTransaction(), + mockLegacyTransaction(), + mockVersionedTransaction() + ] + const result = await provider.singAllTransactions(transactions) + + expect(result).toHaveLength(transactions.length) + result.forEach(transaction => { + expect(transaction.signatures).toHaveLength(1) + }) + }) }) From 5c134af87ced8295c3bc6cf32b86b40a0ab78260 Mon Sep 17 00:00:00 2001 From: Felipe Mendes Date: Fri, 30 Aug 2024 08:37:02 -0300 Subject: [PATCH 03/11] fix: misspelled method --- .../base/adapters/solana/web3js/tests/GenericProvider.test.ts | 2 +- packages/scaffold-utils/src/solana/SolanaTypesUtil.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/base/adapters/solana/web3js/tests/GenericProvider.test.ts b/packages/base/adapters/solana/web3js/tests/GenericProvider.test.ts index 2fa128edf4..209983b048 100644 --- a/packages/base/adapters/solana/web3js/tests/GenericProvider.test.ts +++ b/packages/base/adapters/solana/web3js/tests/GenericProvider.test.ts @@ -107,7 +107,7 @@ describe.each(providers)('Generic provider tests for $name', ({ provider }) => { mockLegacyTransaction(), mockVersionedTransaction() ] - const result = await provider.singAllTransactions(transactions) + const result = await provider.signAllTransactions(transactions) expect(result).toHaveLength(transactions.length) result.forEach(transaction => { diff --git a/packages/scaffold-utils/src/solana/SolanaTypesUtil.ts b/packages/scaffold-utils/src/solana/SolanaTypesUtil.ts index d7d3891cfc..c7373cea99 100644 --- a/packages/scaffold-utils/src/solana/SolanaTypesUtil.ts +++ b/packages/scaffold-utils/src/solana/SolanaTypesUtil.ts @@ -56,7 +56,7 @@ export interface Provider extends ProviderEventEmitterMethods { connection: Connection, options?: SendTransactionOptions ) => Promise - singAllTransactions: (transactions: AnyTransaction[]) => Promise + signAllTransactions: (transactions: AnyTransaction[]) => Promise } export interface ProviderEventEmitterMethods { From 0645ec466fc6a7d5b792592ef8cfd41632a9ea75 Mon Sep 17 00:00:00 2001 From: Felipe Mendes Date: Fri, 30 Aug 2024 08:37:47 -0300 Subject: [PATCH 04/11] feat: add method for WalletStandardProvider and stub for other providers --- .../solana/web3js/providers/AuthProvider.ts | 5 ++++ .../web3js/providers/WalletConnectProvider.ts | 5 ++++ .../providers/WalletStandardProvider.ts | 29 +++++++++++++++++++ 3 files changed, 39 insertions(+) diff --git a/packages/base/adapters/solana/web3js/providers/AuthProvider.ts b/packages/base/adapters/solana/web3js/providers/AuthProvider.ts index 7c33a23dc7..b50c256913 100644 --- a/packages/base/adapters/solana/web3js/providers/AuthProvider.ts +++ b/packages/base/adapters/solana/web3js/providers/AuthProvider.ts @@ -131,6 +131,11 @@ export class AuthProvider extends ProviderEventEmitter implements Provider, Prov return signature } + public async signAllTransactions(_transactions: AnyTransaction[]) { + // To be implemented + return Promise.resolve([]) + } + // -- W3mFrameProvider methods ------------------------------------------- // connectEmail: ProviderAuthMethods['connectEmail'] = args => this.provider.connectEmail(args) connectOtp: ProviderAuthMethods['connectOtp'] = args => this.provider.connectOtp(args) diff --git a/packages/base/adapters/solana/web3js/providers/WalletConnectProvider.ts b/packages/base/adapters/solana/web3js/providers/WalletConnectProvider.ts index 5acbc03418..0f769410e9 100644 --- a/packages/base/adapters/solana/web3js/providers/WalletConnectProvider.ts +++ b/packages/base/adapters/solana/web3js/providers/WalletConnectProvider.ts @@ -177,6 +177,11 @@ export class WalletConnectProvider extends ProviderEventEmitter implements Provi return signature } + public async signAllTransactions(_transactions: AnyTransaction[]) { + // To be implemented + return Promise.resolve([]) + } + // -- Private ------------------------------------------ // private request( method: Method, diff --git a/packages/base/adapters/solana/web3js/providers/WalletStandardProvider.ts b/packages/base/adapters/solana/web3js/providers/WalletStandardProvider.ts index d91963fbdd..2a9af94e4c 100644 --- a/packages/base/adapters/solana/web3js/providers/WalletStandardProvider.ts +++ b/packages/base/adapters/solana/web3js/providers/WalletStandardProvider.ts @@ -183,6 +183,35 @@ export class WalletStandardProvider extends ProviderEventEmitter implements Prov return signature } + public async signAllTransactions(transactions: AnyTransaction[]) { + const feature = this.getWalletFeature(SolanaSignTransaction) + + const account = this.getAccount(true) + const chain = this.getActiveChainName() + + const result = await feature.signTransaction( + ...transactions.map(transaction => ({ + transaction: this.serializeTransaction(transaction), + account, + chain + })) + ) + + return result.map(({ signedTransaction }, index) => { + const transaction = transactions[index] + + if (!transaction) { + throw new WalletSignTransactionError('Invalid transaction signature response') + } + + if (isVersionedTransaction(transaction)) { + return VersionedTransaction.deserialize(signedTransaction) as AnyTransaction + } + + return Transaction.from(signedTransaction) as AnyTransaction + }) + } + // -- Private ------------------------------------------- // private serializeTransaction(transaction: AnyTransaction) { return transaction.serialize({ verifySignatures: false }) From 9e062b2d90d4a10c5d9a5abd47900675e36269cb Mon Sep 17 00:00:00 2001 From: Felipe Mendes Date: Fri, 30 Aug 2024 09:45:06 -0300 Subject: [PATCH 05/11] feat: add SolanaSignAllTransactionsTest lab component --- .../Solana/SolanaSignAllTransactionsTest.tsx | 124 ++++++++++++++++++ .../Solana/SolanaSignTransactionTest.tsx | 13 +- .../src/components/Solana/SolanaTests.tsx | 13 ++ pnpm-lock.yaml | 118 ++++++++++++++--- 4 files changed, 238 insertions(+), 30 deletions(-) create mode 100644 apps/laboratory/src/components/Solana/SolanaSignAllTransactionsTest.tsx diff --git a/apps/laboratory/src/components/Solana/SolanaSignAllTransactionsTest.tsx b/apps/laboratory/src/components/Solana/SolanaSignAllTransactionsTest.tsx new file mode 100644 index 0000000000..dc6fe42852 --- /dev/null +++ b/apps/laboratory/src/components/Solana/SolanaSignAllTransactionsTest.tsx @@ -0,0 +1,124 @@ +import { useState } from 'react' +import { Button, Stack, Text, Spacer } from '@chakra-ui/react' +import { + PublicKey, + Transaction, + TransactionMessage, + VersionedTransaction, + SystemProgram +} from '@solana/web3.js' + +import { useWeb3ModalAccount, useWeb3ModalProvider, type Provider } from '@web3modal/solana/react' + +import { solana } from '../../utils/ChainsUtil' +import { useChakraToast } from '../Toast' +import type { Connection } from '@web3modal/base/adapters/solana/web3js' +import bs58 from 'bs58' + +const PHANTOM_DEVNET_ADDRESS = '8vCyX7oB6Pc3pbWMGYYZF5pbSnAdQ7Gyr32JqxqCy8ZR' +const recipientAddress = new PublicKey(PHANTOM_DEVNET_ADDRESS) +const amountInLamports = 1_000_000 + +export function SolanaSignAllTransactionsTest() { + const toast = useChakraToast() + const { chainId } = useWeb3ModalAccount() + const { walletProvider, connection } = useWeb3ModalProvider() + const [loading, setLoading] = useState(false) + + async function onSignTransaction(type: 'legacy' | 'versioned') { + try { + setLoading(true) + if (!walletProvider?.publicKey) { + throw Error('user is disconnected') + } + + if (!connection) { + throw Error('no connection set') + } + + const transactions = await Promise.all( + Array.from({ length: 5 }, () => createTransaction(walletProvider, connection, type)) + ) + const response = await walletProvider.signAllTransactions(transactions) + const description = response + .map(transaction => bs58.encode(transaction.signatures[0] as Buffer)) + .join('\n\n') + + toast({ + title: 'Success', + description, + type: 'success' + }) + } catch (err) { + toast({ + title: 'Error', + description: (err as Error).message, + type: 'error' + }) + } finally { + setLoading(false) + } + } + + if (chainId === solana.chainId) { + return ( + + Switch to Solana Devnet or Testnet to test this feature + + ) + } + + return ( + + + + + + ) +} + +async function createTransaction( + provider: Provider, + connection: Connection, + type: 'legacy' | 'versioned' +) { + if (!provider.publicKey) { + throw Error('No public key found') + } + + const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash() + + const instructions = [ + SystemProgram.transfer({ + fromPubkey: provider.publicKey, + toPubkey: recipientAddress, + lamports: amountInLamports + }) + ] + + if (type === 'legacy') { + return new Transaction({ feePayer: provider.publicKey, blockhash, lastValidBlockHeight }).add( + ...instructions + ) + } + + return new VersionedTransaction( + new TransactionMessage({ + payerKey: provider.publicKey, + recentBlockhash: blockhash, + instructions + }).compileToV0Message() + ) +} diff --git a/apps/laboratory/src/components/Solana/SolanaSignTransactionTest.tsx b/apps/laboratory/src/components/Solana/SolanaSignTransactionTest.tsx index a189d5e985..f7b8ec5d60 100644 --- a/apps/laboratory/src/components/Solana/SolanaSignTransactionTest.tsx +++ b/apps/laboratory/src/components/Solana/SolanaSignTransactionTest.tsx @@ -1,5 +1,5 @@ import { useState } from 'react' -import { Button, Stack, Text, Spacer, Link } from '@chakra-ui/react' +import { Button, Stack, Text, Spacer } from '@chakra-ui/react' import { PublicKey, Transaction, @@ -12,6 +12,7 @@ import { useWeb3ModalAccount, useWeb3ModalProvider } from '@web3modal/solana/rea import { solana } from '../../utils/ChainsUtil' import { useChakraToast } from '../Toast' +import bs58 from 'bs58' const PHANTOM_DEVNET_ADDRESS = '8vCyX7oB6Pc3pbWMGYYZF5pbSnAdQ7Gyr32JqxqCy8ZR' const recipientAddress = new PublicKey(PHANTOM_DEVNET_ADDRESS) @@ -56,7 +57,7 @@ export function SolanaSignTransactionTest() { toast({ title: 'Success', - description: Uint8Array.from(signature), + description: bs58.encode(signature), type: 'success' }) } catch (err) { @@ -108,7 +109,7 @@ export function SolanaSignTransactionTest() { toast({ title: 'Success', - description: signature, + description: bs58.encode(signature), type: 'success' }) } catch (err) { @@ -147,12 +148,6 @@ export function SolanaSignTransactionTest() { Sign Versioned Transaction - - - - ) } diff --git a/apps/laboratory/src/components/Solana/SolanaTests.tsx b/apps/laboratory/src/components/Solana/SolanaTests.tsx index b940c1d24b..0414d51e28 100644 --- a/apps/laboratory/src/components/Solana/SolanaTests.tsx +++ b/apps/laboratory/src/components/Solana/SolanaTests.tsx @@ -17,6 +17,7 @@ import { SolanaSignMessageTest } from './SolanaSignMessageTest' import { SolanaWriteContractTest } from './SolanaWriteContractTest' import { solana, solanaDevnet, solanaTestnet } from '../../utils/ChainsUtil' import { SolanaSignAndSendTransaction } from './SolanaSignAndSendTransactionTest' +import { SolanaSignAllTransactionsTest } from './SolanaSignAllTransactionsTest' export function SolanaTests() { const { isConnected, currentChain } = useWeb3ModalAccount() @@ -48,6 +49,18 @@ export function SolanaTests() { + + + Sign All Transactions + + + ℹ️ + + + + + + Sign and Send Transaction (Dapp) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1f19e3ae94..d7d5d927bd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -66,7 +66,7 @@ importers: version: 0.22.0(rollup@4.21.1)(vite@5.2.11(@types/node@20.11.5)(terser@5.31.6)) vitest: specifier: 2.0.3 - version: 2.0.3(@types/node@20.11.5)(jsdom@24.1.0)(terser@5.31.6) + version: 2.0.3(@types/node@20.11.5)(jsdom@24.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.31.6) apps/demo: dependencies: @@ -108,7 +108,7 @@ importers: version: 2.19.6(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10)(zod@3.22.4) wagmi: specifier: 2.12.5 - version: 2.12.5(@tanstack/query-core@5.24.8)(@tanstack/react-query@5.24.8(react@18.2.0))(@types/react@18.2.62)(bufferutil@4.0.8)(react-dom@18.2.0(react@18.2.0))(react-native@0.75.2(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2))(@types/react@18.2.62)(bufferutil@4.0.8)(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10))(react@18.2.0)(rollup@4.21.1)(typescript@5.3.3)(utf-8-validate@5.0.10)(viem@2.19.6(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10)(zod@3.22.4))(zod@3.22.4) + version: 2.12.5(@tanstack/query-core@5.24.8)(@tanstack/react-query@5.24.8(react@18.2.0))(@types/react@18.2.62)(bufferutil@4.0.8)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(rollup@4.21.1)(typescript@5.3.3)(utf-8-validate@5.0.10)(viem@2.19.6(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10)(zod@3.22.4))(zod@3.22.4) zustand: specifier: 4.5.2 version: 4.5.2(@types/react@18.2.62)(react@18.2.0) @@ -374,7 +374,7 @@ importers: version: 2.19.6(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10)(zod@3.22.4) wagmi: specifier: 2.12.5 - version: 2.12.5(@tanstack/query-core@5.24.8)(@tanstack/react-query@5.24.8(react@18.2.0))(@types/react@18.2.62)(bufferutil@4.0.8)(react-dom@18.2.0(react@18.2.0))(react-native@0.75.2(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2))(@types/react@18.2.62)(bufferutil@4.0.8)(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10))(react@18.2.0)(rollup@4.21.1)(typescript@5.3.3)(utf-8-validate@5.0.10)(viem@2.19.6(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10)(zod@3.22.4))(zod@3.22.4) + version: 2.12.5(@tanstack/query-core@5.24.8)(@tanstack/react-query@5.24.8(react@18.2.0))(@types/react@18.2.62)(bufferutil@4.0.8)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(rollup@4.21.1)(typescript@5.3.3)(utf-8-validate@5.0.10)(viem@2.19.6(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10)(zod@3.22.4))(zod@3.22.4) devDependencies: '@types/node': specifier: 20.11.5 @@ -510,7 +510,7 @@ importers: version: 5.2.11(@types/node@20.11.5)(terser@5.31.6) wagmi: specifier: 2.12.5 - version: 2.12.5(@tanstack/query-core@5.24.8)(@tanstack/react-query@5.24.8(react@18.2.0))(@types/react@18.2.62)(bufferutil@4.0.8)(react-dom@18.2.0(react@18.2.0))(react-native@0.75.2(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2))(@types/react@18.2.62)(bufferutil@4.0.8)(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10))(react@18.2.0)(rollup@4.21.1)(typescript@5.3.3)(utf-8-validate@5.0.10)(viem@2.19.6(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10)(zod@3.22.4))(zod@3.22.4) + version: 2.12.5(@tanstack/query-core@5.24.8)(@tanstack/react-query@5.24.8(react@18.2.0))(@types/react@18.2.62)(bufferutil@4.0.8)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(rollup@4.21.1)(typescript@5.3.3)(utf-8-validate@5.0.10)(viem@2.19.6(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10)(zod@3.22.4))(zod@3.22.4) devDependencies: '@types/react': specifier: 18.2.62 @@ -651,7 +651,7 @@ importers: version: 18.2.0 '@vitest/coverage-v8': specifier: 2.0.5 - version: 2.0.5(vitest@2.0.3(@types/node@20.11.5)(jsdom@24.1.0)(terser@5.31.6)) + version: 2.0.5(vitest@2.0.3(@types/node@20.11.5)(jsdom@24.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.31.6)) '@vue/runtime-core': specifier: 3.4.3 version: 3.4.3 @@ -699,7 +699,7 @@ importers: version: 2.19.6(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10)(zod@3.22.4) vitest: specifier: 2.0.3 - version: 2.0.3(@types/node@20.11.5)(jsdom@24.1.0)(terser@5.31.6) + version: 2.0.3(@types/node@20.11.5)(jsdom@24.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.31.6) vue: specifier: 3.4.3 version: 3.4.3(typescript@5.3.3) @@ -752,10 +752,10 @@ importers: devDependencies: '@vitest/coverage-v8': specifier: 2.0.5 - version: 2.0.5(vitest@2.0.3(@types/node@20.11.5)(jsdom@24.1.0)(terser@5.31.6)) + version: 2.0.5(vitest@2.0.3(@types/node@20.11.5)(jsdom@24.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.31.6)) vitest: specifier: 2.0.3 - version: 2.0.3(@types/node@20.11.5)(jsdom@24.1.0)(terser@5.31.6) + version: 2.0.3(@types/node@20.11.5)(jsdom@24.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.31.6) packages/core: dependencies: @@ -771,13 +771,13 @@ importers: devDependencies: '@vitest/coverage-v8': specifier: 2.0.5 - version: 2.0.5(vitest@2.0.3(@types/node@20.11.5)(jsdom@24.1.0)(terser@5.31.6)) + version: 2.0.5(vitest@2.0.3(@types/node@20.11.5)(jsdom@24.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.31.6)) viem: specifier: 2.19.6 version: 2.19.6(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10)(zod@3.22.4) vitest: specifier: 2.0.3 - version: 2.0.3(@types/node@20.11.5)(jsdom@24.1.0)(terser@5.31.6) + version: 2.0.3(@types/node@20.11.5)(jsdom@24.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.31.6) packages/ethers: dependencies: @@ -1037,13 +1037,13 @@ importers: version: 5.1.5 '@vitest/coverage-v8': specifier: 2.0.5 - version: 2.0.5(vitest@2.0.3(@types/node@20.11.5)(jsdom@24.1.0)(terser@5.31.6)) + version: 2.0.5(vitest@2.0.3(@types/node@20.11.5)(jsdom@24.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.31.6)) '@walletconnect/types': specifier: 2.14.0 version: 2.14.0 vitest: specifier: 2.0.3 - version: 2.0.3(@types/node@20.11.5)(jsdom@24.1.0)(terser@5.31.6) + version: 2.0.3(@types/node@20.11.5)(jsdom@24.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.31.6) packages/ui: dependencies: @@ -1059,7 +1059,7 @@ importers: version: 1.5.5 '@vitest/coverage-v8': specifier: 2.0.5 - version: 2.0.5(vitest@2.0.3(@types/node@20.11.5)(jsdom@24.1.0)(terser@5.31.6)) + version: 2.0.5(vitest@2.0.3(@types/node@20.11.5)(jsdom@24.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.31.6)) '@web3modal/common': specifier: workspace:* version: link:../common @@ -1077,7 +1077,7 @@ importers: version: 2.0.4(eslint@8.57.0) vitest: specifier: 2.0.3 - version: 2.0.3(@types/node@20.11.5)(jsdom@24.1.0)(terser@5.31.6) + version: 2.0.3(@types/node@20.11.5)(jsdom@24.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.31.6) packages/wagmi: dependencies: @@ -1123,7 +1123,7 @@ importers: version: 2.19.6(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10)(zod@3.22.4) vitest: specifier: 2.0.3 - version: 2.0.3(@types/node@20.11.5)(jsdom@24.1.0)(terser@5.31.6) + version: 2.0.3(@types/node@20.11.5)(jsdom@24.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.31.6) vue: specifier: 3.4.3 version: 3.4.3(typescript@5.3.3) @@ -1148,13 +1148,13 @@ importers: devDependencies: '@vitest/coverage-v8': specifier: 2.0.5 - version: 2.0.5(vitest@2.0.3(@types/node@20.11.5)(jsdom@24.1.0)(terser@5.31.6)) + version: 2.0.5(vitest@2.0.3(@types/node@20.11.5)(jsdom@24.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.31.6)) jsdom: specifier: 24.1.0 version: 24.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) vitest: specifier: 2.0.3 - version: 2.0.3(@types/node@20.11.5)(jsdom@24.1.0)(terser@5.31.6) + version: 2.0.3(@types/node@20.11.5)(jsdom@24.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.31.6) services/id-allocation-service: dependencies: @@ -19877,11 +19877,11 @@ snapshots: std-env: 3.7.0 test-exclude: 6.0.0 v8-to-istanbul: 9.3.0 - vitest: 2.0.3(@types/node@20.11.5)(jsdom@24.1.0)(terser@5.31.6) + vitest: 2.0.3(@types/node@20.11.5)(jsdom@24.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.31.6) transitivePeerDependencies: - supports-color - '@vitest/coverage-v8@2.0.5(vitest@2.0.3(@types/node@20.11.5)(jsdom@24.1.0)(terser@5.31.6))': + '@vitest/coverage-v8@2.0.5(vitest@2.0.3(@types/node@20.11.5)(jsdom@24.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.31.6))': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 0.2.3 @@ -19895,7 +19895,7 @@ snapshots: std-env: 3.7.0 test-exclude: 7.0.1 tinyrainbow: 1.2.0 - vitest: 2.0.3(@types/node@20.11.5)(jsdom@24.1.0)(terser@5.31.6) + vitest: 2.0.3(@types/node@20.11.5)(jsdom@24.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.31.6) transitivePeerDependencies: - supports-color @@ -20067,6 +20067,45 @@ snapshots: - utf-8-validate - zod + '@wagmi/connectors@5.1.5(@types/react@18.2.62)(@wagmi/core@2.13.4(@tanstack/query-core@5.24.8)(@types/react@18.2.62)(react@18.2.0)(typescript@5.3.3)(viem@2.19.6(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10)(zod@3.22.4)))(bufferutil@4.0.8)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(rollup@4.21.1)(typescript@5.3.3)(utf-8-validate@5.0.10)(viem@2.19.6(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10)(zod@3.22.4))(zod@3.22.4)': + dependencies: + '@coinbase/wallet-sdk': 4.0.4 + '@metamask/sdk': 0.27.0(bufferutil@4.0.8)(react-dom@18.2.0(react@18.2.0))(react-native@0.75.2(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2))(@types/react@18.2.0)(bufferutil@4.0.8)(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10))(react@18.2.0)(rollup@4.21.1)(utf-8-validate@5.0.10) + '@safe-global/safe-apps-provider': 0.18.3(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10)(zod@3.22.4) + '@safe-global/safe-apps-sdk': 9.1.0(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10)(zod@3.22.4) + '@wagmi/core': 2.13.4(@tanstack/query-core@5.24.8)(@types/react@18.2.62)(react@18.2.0)(typescript@5.3.3)(viem@2.19.6(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10)(zod@3.22.4)) + '@walletconnect/ethereum-provider': 2.14.0(@types/react@18.2.62)(bufferutil@4.0.8)(react@18.2.0)(utf-8-validate@5.0.10) + '@walletconnect/modal': 2.6.2(@types/react@18.2.62)(react@18.2.0) + cbw-sdk: '@coinbase/wallet-sdk@3.9.3' + viem: 2.19.6(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10)(zod@3.22.4) + optionalDependencies: + typescript: 5.3.3 + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@netlify/blobs' + - '@planetscale/database' + - '@react-native-async-storage/async-storage' + - '@types/react' + - '@upstash/redis' + - '@vercel/kv' + - bufferutil + - encoding + - ioredis + - react + - react-dom + - react-native + - rollup + - supports-color + - uWebSockets.js + - utf-8-validate + - zod + '@wagmi/core@2.13.4(@tanstack/query-core@5.24.8)(@types/react@18.2.0)(react@18.2.0)(typescript@5.3.3)(viem@2.19.6(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10)(zod@3.22.4))': dependencies: eventemitter3: 5.0.1 @@ -27784,7 +27823,7 @@ snapshots: fsevents: 2.3.3 terser: 5.31.6 - vitest@2.0.3(@types/node@20.11.5)(jsdom@24.1.0)(terser@5.31.6): + vitest@2.0.3(@types/node@20.11.5)(jsdom@24.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.31.6): dependencies: '@ampproject/remapping': 2.3.0 '@vitest/expect': 2.0.3 @@ -27909,6 +27948,43 @@ snapshots: - utf-8-validate - zod + wagmi@2.12.5(@tanstack/query-core@5.24.8)(@tanstack/react-query@5.24.8(react@18.2.0))(@types/react@18.2.62)(bufferutil@4.0.8)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(rollup@4.21.1)(typescript@5.3.3)(utf-8-validate@5.0.10)(viem@2.19.6(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10)(zod@3.22.4))(zod@3.22.4): + dependencies: + '@tanstack/react-query': 5.24.8(react@18.2.0) + '@wagmi/connectors': 5.1.5(@types/react@18.2.62)(@wagmi/core@2.13.4(@tanstack/query-core@5.24.8)(@types/react@18.2.62)(react@18.2.0)(typescript@5.3.3)(viem@2.19.6(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10)(zod@3.22.4)))(bufferutil@4.0.8)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(rollup@4.21.1)(typescript@5.3.3)(utf-8-validate@5.0.10)(viem@2.19.6(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10)(zod@3.22.4))(zod@3.22.4) + '@wagmi/core': 2.13.4(@tanstack/query-core@5.24.8)(@types/react@18.2.62)(react@18.2.0)(typescript@5.3.3)(viem@2.19.6(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10)(zod@3.22.4)) + react: 18.2.0 + use-sync-external-store: 1.2.0(react@18.2.0) + viem: 2.19.6(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10)(zod@3.22.4) + optionalDependencies: + typescript: 5.3.3 + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@netlify/blobs' + - '@planetscale/database' + - '@react-native-async-storage/async-storage' + - '@tanstack/query-core' + - '@types/react' + - '@upstash/redis' + - '@vercel/kv' + - bufferutil + - encoding + - immer + - ioredis + - react-dom + - react-native + - rollup + - supports-color + - uWebSockets.js + - utf-8-validate + - zod + walker@1.0.8: dependencies: makeerror: 1.0.12 From df2c7ac527a25a48bee9ed28028ffacac0629e89 Mon Sep 17 00:00:00 2001 From: Felipe Mendes Date: Fri, 30 Aug 2024 10:42:13 -0300 Subject: [PATCH 06/11] feat: add parallel implementation for WalletConnectProvider and AuthProvider --- .../Solana/SolanaSignAllTransactionsTest.tsx | 14 +++++++++++++- .../solana/web3js/providers/AuthProvider.ts | 5 ++--- .../web3js/providers/WalletConnectProvider.ts | 5 ++--- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/apps/laboratory/src/components/Solana/SolanaSignAllTransactionsTest.tsx b/apps/laboratory/src/components/Solana/SolanaSignAllTransactionsTest.tsx index dc6fe42852..bf704e9ce6 100644 --- a/apps/laboratory/src/components/Solana/SolanaSignAllTransactionsTest.tsx +++ b/apps/laboratory/src/components/Solana/SolanaSignAllTransactionsTest.tsx @@ -40,8 +40,20 @@ export function SolanaSignAllTransactionsTest() { Array.from({ length: 5 }, () => createTransaction(walletProvider, connection, type)) ) const response = await walletProvider.signAllTransactions(transactions) + const description = response - .map(transaction => bs58.encode(transaction.signatures[0] as Buffer)) + .map(transaction => { + const signature = + transaction.signatures[0] instanceof Uint8Array + ? transaction.signatures[0] + : transaction.signatures[0]?.signature + + if (!signature) { + throw Error('Empty signature') + } + + return bs58.encode(signature) + }) .join('\n\n') toast({ diff --git a/packages/base/adapters/solana/web3js/providers/AuthProvider.ts b/packages/base/adapters/solana/web3js/providers/AuthProvider.ts index b50c256913..c165cfd73a 100644 --- a/packages/base/adapters/solana/web3js/providers/AuthProvider.ts +++ b/packages/base/adapters/solana/web3js/providers/AuthProvider.ts @@ -131,9 +131,8 @@ export class AuthProvider extends ProviderEventEmitter implements Provider, Prov return signature } - public async signAllTransactions(_transactions: AnyTransaction[]) { - // To be implemented - return Promise.resolve([]) + public async signAllTransactions(transactions: AnyTransaction[]) { + return Promise.all(transactions.map(transaction => this.signTransaction(transaction))) } // -- W3mFrameProvider methods ------------------------------------------- // diff --git a/packages/base/adapters/solana/web3js/providers/WalletConnectProvider.ts b/packages/base/adapters/solana/web3js/providers/WalletConnectProvider.ts index 0f769410e9..209fe481ce 100644 --- a/packages/base/adapters/solana/web3js/providers/WalletConnectProvider.ts +++ b/packages/base/adapters/solana/web3js/providers/WalletConnectProvider.ts @@ -177,9 +177,8 @@ export class WalletConnectProvider extends ProviderEventEmitter implements Provi return signature } - public async signAllTransactions(_transactions: AnyTransaction[]) { - // To be implemented - return Promise.resolve([]) + public async signAllTransactions(transactions: AnyTransaction[]) { + return Promise.all(transactions.map(transaction => this.signTransaction(transaction))) } // -- Private ------------------------------------------ // From 3d795b0bc4d89e43230b0c1ad800b7e69149198a Mon Sep 17 00:00:00 2001 From: Felipe Mendes Date: Fri, 30 Aug 2024 11:03:54 -0300 Subject: [PATCH 07/11] test: ensure correct provider usages --- .../solana/web3js/tests/AuthProvider.test.ts | 21 ++++++ .../tests/WalletConnectProvider.test.ts | 71 +++++++++++++++++++ .../tests/WalletStandardProvider.test.ts | 13 ++++ .../web3js/tests/mocks/WalletStandard.ts | 10 +-- 4 files changed, 110 insertions(+), 5 deletions(-) diff --git a/packages/base/adapters/solana/web3js/tests/AuthProvider.test.ts b/packages/base/adapters/solana/web3js/tests/AuthProvider.test.ts index 4e9d3e397a..348342ac28 100644 --- a/packages/base/adapters/solana/web3js/tests/AuthProvider.test.ts +++ b/packages/base/adapters/solana/web3js/tests/AuthProvider.test.ts @@ -134,4 +134,25 @@ describe('AuthProvider specific tests', () => { expect(provider.switchNetwork).toHaveBeenCalledWith(newChain.chainId) expect(listener).toHaveBeenCalledWith(newChain.chainId) }) + + it('should call signTransaction correctly for signAllTransactions', async () => { + await authProvider.connect() + const transactions = [mockLegacyTransaction(), mockVersionedTransaction()] + await authProvider.signAllTransactions(transactions) + + expect(provider.request).toHaveBeenNthCalledWith(1, { + method: 'solana_signTransaction', + params: { + transaction: + 'AKhoybLLJS1deDJDyjELDNhfkBBX3k4dt4bBfmppjfPVVimhQdFEfDo8AiFcCBCC9VkYWV2r3jkh9n1DAXEhnJPwMmnsrx6huAVrhHAbmRUqfUuWZ9aWMGmdEWaeroCnPR6jkEnjJcn14a59TZhkiTXMygMqu4KaqD1TqzE8vNHSw3YgbW24cfqWfQczGysuy4ugxj4TGSpqRtNmf5D7zRRa76eJTeZEaBcBQGkqxb31vBRXDMdQzGEbq' + } + }) + expect(provider.request).toHaveBeenNthCalledWith(2, { + method: 'solana_signTransaction', + params: { + transaction: + '48ckoQL1HhH5aqU1ifKqpQkwq3WPDgMnsHHQkVfddisxYcapwAVXr8hejTi2jeJpMPkZMsF72SwmJFDByyfRtaknz4ytCYNAcdHrxtrHa9hTjMKckVQrFFqS8zG63Wj5mJ6wPfj8dv1wKu2XkU6GSXSGdQmuvfRv3K6LUSMbK5XSP3yBGb1SDZKCuoFX4qDKcKhCG7Awn3ssAWB1yRaXMd6mS6HQHKSF11FTp3jTH2HKUNbKyyuGh4tYtq8b' + } + }) + }) }) diff --git a/packages/base/adapters/solana/web3js/tests/WalletConnectProvider.test.ts b/packages/base/adapters/solana/web3js/tests/WalletConnectProvider.test.ts index c0baa6b04c..1400a9460b 100644 --- a/packages/base/adapters/solana/web3js/tests/WalletConnectProvider.test.ts +++ b/packages/base/adapters/solana/web3js/tests/WalletConnectProvider.test.ts @@ -258,4 +258,75 @@ describe('WalletConnectProvider specific tests', () => { 'solana:8E9rvCKLFQia2Y35HXjjpWzj8weVo44K' ) }) + + it('should call signTransaction correctly for signAllTransactions', async () => { + await walletConnectProvider.connect() + const transactions = [mockLegacyTransaction(), mockVersionedTransaction()] + await walletConnectProvider.signAllTransactions(transactions) + + expect(provider.request).toHaveBeenNthCalledWith( + 1, + { + method: 'solana_signTransaction', + params: { + feePayer: '2VqKhjZ766ZN3uBtBpb7Ls3cN4HrocP1rzxzekhVEgoP', + instructions: [ + { + data: '3Bxs4NN8M2Yn4TLb', + keys: [ + { + isSigner: true, + isWritable: true, + pubkey: '2VqKhjZ766ZN3uBtBpb7Ls3cN4HrocP1rzxzekhVEgoP' + }, + { + isSigner: false, + isWritable: true, + pubkey: '2VqKhjZ766ZN3uBtBpb7Ls3cN4HrocP1rzxzekhVEgoP' + } + ], + programId: '11111111111111111111111111111111' + } + ], + recentBlockhash: 'EZySCpmzXRuUtM95P2JGv9SitqYph6Nv6HaYBK7a8PKJ', + transaction: + 'AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAECFj6WhBP/eepC4T4bDgYuJMiSVXNh9IvPWv1ZDUV52gYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMmaU6FiJxS/swxct+H8Iree7FERP/8vrGuAdF90ANelAQECAAAMAgAAAICWmAAAAAAA', + pubkey: TestConstants.accounts[0].address + } + }, + 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp' + ) + expect(provider.request).toHaveBeenNthCalledWith( + 2, + { + method: 'solana_signTransaction', + params: { + feePayer: '2VqKhjZ766ZN3uBtBpb7Ls3cN4HrocP1rzxzekhVEgoP', + instructions: [ + { + data: '3Bxs4NN8M2Yn4TLb', + keys: [ + { + isSigner: true, + isWritable: true, + pubkey: '2VqKhjZ766ZN3uBtBpb7Ls3cN4HrocP1rzxzekhVEgoP' + }, + { + isSigner: true, + isWritable: true, + pubkey: '2VqKhjZ766ZN3uBtBpb7Ls3cN4HrocP1rzxzekhVEgoP' + } + ], + programId: '11111111111111111111111111111111' + } + ], + recentBlockhash: 'EZySCpmzXRuUtM95P2JGv9SitqYph6Nv6HaYBK7a8PKJ', + transaction: + 'AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQABAhY+loQT/3nqQuE+Gw4GLiTIklVzYfSLz1r9WQ1FedoGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADJmlOhYicUv7MMXLfh/CK3nuxRET//L6xrgHRfdADXpQEBAgAADAIAAACAlpgAAAAAAAA=', + pubkey: TestConstants.accounts[0].address + } + }, + 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp' + ) + }) }) diff --git a/packages/base/adapters/solana/web3js/tests/WalletStandardProvider.test.ts b/packages/base/adapters/solana/web3js/tests/WalletStandardProvider.test.ts index 6209ca47ea..3fce944bb5 100644 --- a/packages/base/adapters/solana/web3js/tests/WalletStandardProvider.test.ts +++ b/packages/base/adapters/solana/web3js/tests/WalletStandardProvider.test.ts @@ -125,4 +125,17 @@ describe('WalletStandardProvider specific tests', () => { walletStandardProvider.signAndSendTransaction(mockLegacyTransaction()) ).rejects.toThrowError(WalletStandardFeatureNotSupportedError) }) + + it('should call signTransaction with correct params for multiple transactions over singAllTransactions method', async () => { + const transactions = [mockLegacyTransaction(), mockVersionedTransaction()] + await walletStandardProvider.signAllTransactions(transactions) + + expect(wallet.features[SolanaSignTransaction].signTransaction).toHaveBeenCalledWith( + ...transactions.map(transaction => ({ + transaction: transaction.serialize({ verifySignatures: false }), + account: wallet.accounts[0], + chain: 'solana:mainnet' + })) + ) + }) }) diff --git a/packages/base/adapters/solana/web3js/tests/mocks/WalletStandard.ts b/packages/base/adapters/solana/web3js/tests/mocks/WalletStandard.ts index 13fa9a91fa..89da7f495e 100644 --- a/packages/base/adapters/solana/web3js/tests/mocks/WalletStandard.ts +++ b/packages/base/adapters/solana/web3js/tests/mocks/WalletStandard.ts @@ -43,9 +43,9 @@ export function mockWalletStandard() { 'solana:signTransaction': { version: '1.0.0', supportedTransactionVersions: [0, 'legacy'], - signTransaction: vi.fn(() => - Promise.resolve([ - { + signTransaction: vi.fn((...transactions: unknown[]) => + Promise.resolve( + Array.from({ length: transactions.length }, () => ({ signedTransaction: new Uint8Array([ 1, 195, 86, 227, 117, 63, 116, 76, 21, 3, 236, 37, 188, 235, 178, 151, 68, 192, 248, 193, 10, 232, 44, 63, 138, 193, 225, 213, 179, 76, 95, 250, 42, 74, 225, 195, 254, @@ -62,8 +62,8 @@ export function mockWalletStandard() { 0, 0, 0, 0, 3, 0, 5, 2, 64, 13, 3, 0, 2, 2, 0, 1, 12, 2, 0, 0, 0, 128, 150, 152, 0, 0, 0, 0, 0 ]) - } - ]) + })) + ) ) } satisfies SolanaSignTransactionFeature['solana:signTransaction'], From 011098348b80b36b7b37d43d9a3444ce5e389246 Mon Sep 17 00:00:00 2001 From: Felipe Mendes Date: Fri, 30 Aug 2024 11:22:42 -0300 Subject: [PATCH 08/11] feat: add solana_signAllTransactions wallet rpc method --- .../solana/web3js/providers/AuthProvider.ts | 23 ++++++++++++++++++- .../solana/web3js/tests/AuthProvider.test.ts | 17 +++++--------- .../web3js/tests/mocks/W3mFrameProvider.ts | 12 ++++++++-- packages/wallet/src/W3mFrameSchema.ts | 8 +++++++ packages/wallet/src/W3mFrameTypes.ts | 4 +++- 5 files changed, 49 insertions(+), 15 deletions(-) diff --git a/packages/base/adapters/solana/web3js/providers/AuthProvider.ts b/packages/base/adapters/solana/web3js/providers/AuthProvider.ts index c165cfd73a..2578e0b45b 100644 --- a/packages/base/adapters/solana/web3js/providers/AuthProvider.ts +++ b/packages/base/adapters/solana/web3js/providers/AuthProvider.ts @@ -132,7 +132,28 @@ export class AuthProvider extends ProviderEventEmitter implements Provider, Prov } public async signAllTransactions(transactions: AnyTransaction[]) { - return Promise.all(transactions.map(transaction => this.signTransaction(transaction))) + const result = await this.provider.request({ + method: 'solana_signAllTransactions', + params: { + transactions: transactions.map(transaction => this.serializeTransaction(transaction)) + } + }) + + return (result.transactions as string[]).map((encodedTransaction, index) => { + const transaction = transactions[index] + + if (!transaction) { + throw new Error('Invalid solana_signAllTransactions response') + } + + const decodedTransaction = base58.decode(encodedTransaction) + + if (isVersionedTransaction(transaction)) { + return VersionedTransaction.deserialize(decodedTransaction) + } + + return Transaction.from(decodedTransaction) + }) } // -- W3mFrameProvider methods ------------------------------------------- // diff --git a/packages/base/adapters/solana/web3js/tests/AuthProvider.test.ts b/packages/base/adapters/solana/web3js/tests/AuthProvider.test.ts index 348342ac28..9da09a661f 100644 --- a/packages/base/adapters/solana/web3js/tests/AuthProvider.test.ts +++ b/packages/base/adapters/solana/web3js/tests/AuthProvider.test.ts @@ -135,23 +135,18 @@ describe('AuthProvider specific tests', () => { expect(listener).toHaveBeenCalledWith(newChain.chainId) }) - it('should call signTransaction correctly for signAllTransactions', async () => { + it('should call signAllTransactions with correct params', async () => { await authProvider.connect() const transactions = [mockLegacyTransaction(), mockVersionedTransaction()] await authProvider.signAllTransactions(transactions) - expect(provider.request).toHaveBeenNthCalledWith(1, { - method: 'solana_signTransaction', - params: { - transaction: - 'AKhoybLLJS1deDJDyjELDNhfkBBX3k4dt4bBfmppjfPVVimhQdFEfDo8AiFcCBCC9VkYWV2r3jkh9n1DAXEhnJPwMmnsrx6huAVrhHAbmRUqfUuWZ9aWMGmdEWaeroCnPR6jkEnjJcn14a59TZhkiTXMygMqu4KaqD1TqzE8vNHSw3YgbW24cfqWfQczGysuy4ugxj4TGSpqRtNmf5D7zRRa76eJTeZEaBcBQGkqxb31vBRXDMdQzGEbq' - } - }) - expect(provider.request).toHaveBeenNthCalledWith(2, { - method: 'solana_signTransaction', + expect(provider.request).toHaveBeenCalledWith({ + method: 'solana_signAllTransactions', params: { - transaction: + transactions: [ + 'AKhoybLLJS1deDJDyjELDNhfkBBX3k4dt4bBfmppjfPVVimhQdFEfDo8AiFcCBCC9VkYWV2r3jkh9n1DAXEhnJPwMmnsrx6huAVrhHAbmRUqfUuWZ9aWMGmdEWaeroCnPR6jkEnjJcn14a59TZhkiTXMygMqu4KaqD1TqzE8vNHSw3YgbW24cfqWfQczGysuy4ugxj4TGSpqRtNmf5D7zRRa76eJTeZEaBcBQGkqxb31vBRXDMdQzGEbq', '48ckoQL1HhH5aqU1ifKqpQkwq3WPDgMnsHHQkVfddisxYcapwAVXr8hejTi2jeJpMPkZMsF72SwmJFDByyfRtaknz4ytCYNAcdHrxtrHa9hTjMKckVQrFFqS8zG63Wj5mJ6wPfj8dv1wKu2XkU6GSXSGdQmuvfRv3K6LUSMbK5XSP3yBGb1SDZKCuoFX4qDKcKhCG7Awn3ssAWB1yRaXMd6mS6HQHKSF11FTp3jTH2HKUNbKyyuGh4tYtq8b' + ] } }) }) diff --git a/packages/base/adapters/solana/web3js/tests/mocks/W3mFrameProvider.ts b/packages/base/adapters/solana/web3js/tests/mocks/W3mFrameProvider.ts index 586d0e38df..08b0e55626 100644 --- a/packages/base/adapters/solana/web3js/tests/mocks/W3mFrameProvider.ts +++ b/packages/base/adapters/solana/web3js/tests/mocks/W3mFrameProvider.ts @@ -8,8 +8,8 @@ export function mockW3mFrameProvider() { w3mFrame.connect = vi.fn(() => Promise.resolve(mockSession())) w3mFrame.disconnect = vi.fn(() => Promise.resolve(undefined)) - w3mFrame.request = vi.fn(({ method }: W3mFrameTypes.RPCRequest) => { - switch (method) { + w3mFrame.request = vi.fn((request: W3mFrameTypes.RPCRequest) => { + switch (request.method) { case 'solana_signMessage': return Promise.resolve({ signature: @@ -25,6 +25,14 @@ export function mockW3mFrameProvider() { signature: '2Lb1KQHWfbV3pWMqXZveFWqneSyhH95YsgCENRWnArSkLydjN1M42oB82zSd6BBdGkM9pE6sQLQf1gyBh8KWM2c4' }) + case 'solana_signAllTransactions': + return Promise.resolve({ + transactions: Array.from({ length: request.params.transactions.length }).map( + () => + '4zZMC2ddAFY1YHcA2uFCqbuTHmD1xvB5QLzgNnT3dMb4aQT98md8jVm1YRGUsKJkYkLPYarnkobvESUpjqEUnDmoG76e9cgNJzLuFXBW1i6njs2Sy1Lnr9TZmLnhif5CYjh1agVJEvjfYpTq1QbTnLS3rBt4yKVjQ6FcV3x22Vm3XBPqodTXz17o1YcHMcvYQbHZfVUyikQ3Nmv6ktZzWe36D6ceKCVBV88VvYkkFhwWUWkA5ErPvsHWQU64VvbtENaJXFUUnuqTFSX4q3ccHuHdmtnhWQ7Mv8Xkb' + ) + }) + default: return Promise.reject(new Error('not implemented')) } diff --git a/packages/wallet/src/W3mFrameSchema.ts b/packages/wallet/src/W3mFrameSchema.ts index 776cbd4773..dd119b7cf0 100644 --- a/packages/wallet/src/W3mFrameSchema.ts +++ b/packages/wallet/src/W3mFrameSchema.ts @@ -326,6 +326,13 @@ export const RpcSolanaSignTransactionRequest = z.object({ }) }) +export const RpcSolanaSignAllTransactionsRequest = z.object({ + method: z.literal('solana_signAllTransactions'), + params: z.object({ + transactions: z.array(z.string()) + }) +}) + export const RpcSolanaSignAndSendTransactionRequest = z.object({ method: z.literal('solana_signAndSendTransaction'), params: z.object({ @@ -503,6 +510,7 @@ export const W3mFrameSchema = { .or(RpcEthSendTransactionRequest) .or(RpcSolanaSignMessageRequest) .or(RpcSolanaSignTransactionRequest) + .or(RpcSolanaSignAllTransactionsRequest) .or(RpcSolanaSignAndSendTransactionRequest) .or(WalletGetCallsReceiptRequest) .or(WalletSendCallsRequest) diff --git a/packages/wallet/src/W3mFrameTypes.ts b/packages/wallet/src/W3mFrameTypes.ts index 2fef971b06..fa43cee93e 100644 --- a/packages/wallet/src/W3mFrameTypes.ts +++ b/packages/wallet/src/W3mFrameTypes.ts @@ -71,7 +71,8 @@ import { WalletGrantPermissionsRequest, RpcSolanaSignMessageRequest, RpcSolanaSignTransactionRequest, - RpcSolanaSignAndSendTransactionRequest + RpcSolanaSignAndSendTransactionRequest, + RpcSolanaSignAllTransactionsRequest } from './W3mFrameSchema.js' import type { W3mFrameRpcConstants } from './W3mFrameConstants.js' import type { CaipNetworkId } from '@web3modal/common' @@ -174,6 +175,7 @@ export namespace W3mFrameTypes { | z.infer | z.infer | z.infer + | z.infer | z.infer | z.infer | z.infer From 4fede87f29b1517d4f27da059de4cc1c24f0bc37 Mon Sep 17 00:00:00 2001 From: Felipe Mendes Date: Fri, 30 Aug 2024 11:27:15 -0300 Subject: [PATCH 09/11] refactor: generic type for signAllTransactions --- .../solana/web3js/providers/WalletConnectProvider.ts | 6 ++++-- .../solana/web3js/providers/WalletStandardProvider.ts | 8 ++++---- .../solana/web3js/tests/GenericProvider.test.ts | 10 ++++++++-- packages/scaffold-utils/src/solana/SolanaTypesUtil.ts | 2 +- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/packages/base/adapters/solana/web3js/providers/WalletConnectProvider.ts b/packages/base/adapters/solana/web3js/providers/WalletConnectProvider.ts index 209fe481ce..cc12dc8b78 100644 --- a/packages/base/adapters/solana/web3js/providers/WalletConnectProvider.ts +++ b/packages/base/adapters/solana/web3js/providers/WalletConnectProvider.ts @@ -177,8 +177,10 @@ export class WalletConnectProvider extends ProviderEventEmitter implements Provi return signature } - public async signAllTransactions(transactions: AnyTransaction[]) { - return Promise.all(transactions.map(transaction => this.signTransaction(transaction))) + public async signAllTransactions(transactions: T): Promise { + return (await Promise.all( + transactions.map(transaction => this.signTransaction(transaction)) + )) as T } // -- Private ------------------------------------------ // diff --git a/packages/base/adapters/solana/web3js/providers/WalletStandardProvider.ts b/packages/base/adapters/solana/web3js/providers/WalletStandardProvider.ts index 2a9af94e4c..bba28eef2f 100644 --- a/packages/base/adapters/solana/web3js/providers/WalletStandardProvider.ts +++ b/packages/base/adapters/solana/web3js/providers/WalletStandardProvider.ts @@ -183,7 +183,7 @@ export class WalletStandardProvider extends ProviderEventEmitter implements Prov return signature } - public async signAllTransactions(transactions: AnyTransaction[]) { + public async signAllTransactions(transactions: T): Promise { const feature = this.getWalletFeature(SolanaSignTransaction) const account = this.getAccount(true) @@ -205,11 +205,11 @@ export class WalletStandardProvider extends ProviderEventEmitter implements Prov } if (isVersionedTransaction(transaction)) { - return VersionedTransaction.deserialize(signedTransaction) as AnyTransaction + return VersionedTransaction.deserialize(signedTransaction) } - return Transaction.from(signedTransaction) as AnyTransaction - }) + return Transaction.from(signedTransaction) + }) as T } // -- Private ------------------------------------------- // diff --git a/packages/base/adapters/solana/web3js/tests/GenericProvider.test.ts b/packages/base/adapters/solana/web3js/tests/GenericProvider.test.ts index 209983b048..bdb619fa6f 100644 --- a/packages/base/adapters/solana/web3js/tests/GenericProvider.test.ts +++ b/packages/base/adapters/solana/web3js/tests/GenericProvider.test.ts @@ -9,6 +9,7 @@ import { Transaction, VersionedTransaction } from '@solana/web3.js' import { mockLegacyTransaction, mockVersionedTransaction } from './mocks/Transaction.js' import { AuthProvider } from '../providers/AuthProvider.js' import { mockW3mFrameProvider } from './mocks/W3mFrameProvider.js' +import { isVersionedTransaction } from '@solana/wallet-adapter-base' const getActiveChain = vi.fn(() => TestConstants.chains[0]) @@ -110,8 +111,13 @@ describe.each(providers)('Generic provider tests for $name', ({ provider }) => { const result = await provider.signAllTransactions(transactions) expect(result).toHaveLength(transactions.length) - result.forEach(transaction => { - expect(transaction.signatures).toHaveLength(1) + + transactions.forEach((transaction, index) => { + if (isVersionedTransaction(transaction)) { + expect(result[index]).toBeInstanceOf(VersionedTransaction) + } else { + expect(result[index]).toBeInstanceOf(Transaction) + } }) }) }) diff --git a/packages/scaffold-utils/src/solana/SolanaTypesUtil.ts b/packages/scaffold-utils/src/solana/SolanaTypesUtil.ts index c7373cea99..91df22a5de 100644 --- a/packages/scaffold-utils/src/solana/SolanaTypesUtil.ts +++ b/packages/scaffold-utils/src/solana/SolanaTypesUtil.ts @@ -56,7 +56,7 @@ export interface Provider extends ProviderEventEmitterMethods { connection: Connection, options?: SendTransactionOptions ) => Promise - signAllTransactions: (transactions: AnyTransaction[]) => Promise + signAllTransactions: (transactions: T) => Promise } export interface ProviderEventEmitterMethods { From 49f6f4ffed88ae295327d046dd46b5b81c0adf73 Mon Sep 17 00:00:00 2001 From: Felipe Mendes Date: Fri, 30 Aug 2024 11:29:01 -0300 Subject: [PATCH 10/11] fix: formating issue --- .../Solana/SolanaSignAllTransactionsTest.tsx | 272 +++++++++--------- .../solana/web3js/providers/AuthProvider.ts | 4 +- 2 files changed, 138 insertions(+), 138 deletions(-) diff --git a/apps/laboratory/src/components/Solana/SolanaSignAllTransactionsTest.tsx b/apps/laboratory/src/components/Solana/SolanaSignAllTransactionsTest.tsx index bf704e9ce6..e271cd0a32 100644 --- a/apps/laboratory/src/components/Solana/SolanaSignAllTransactionsTest.tsx +++ b/apps/laboratory/src/components/Solana/SolanaSignAllTransactionsTest.tsx @@ -1,136 +1,136 @@ -import { useState } from 'react' -import { Button, Stack, Text, Spacer } from '@chakra-ui/react' -import { - PublicKey, - Transaction, - TransactionMessage, - VersionedTransaction, - SystemProgram -} from '@solana/web3.js' - -import { useWeb3ModalAccount, useWeb3ModalProvider, type Provider } from '@web3modal/solana/react' - -import { solana } from '../../utils/ChainsUtil' -import { useChakraToast } from '../Toast' -import type { Connection } from '@web3modal/base/adapters/solana/web3js' -import bs58 from 'bs58' - -const PHANTOM_DEVNET_ADDRESS = '8vCyX7oB6Pc3pbWMGYYZF5pbSnAdQ7Gyr32JqxqCy8ZR' -const recipientAddress = new PublicKey(PHANTOM_DEVNET_ADDRESS) -const amountInLamports = 1_000_000 - -export function SolanaSignAllTransactionsTest() { - const toast = useChakraToast() - const { chainId } = useWeb3ModalAccount() - const { walletProvider, connection } = useWeb3ModalProvider() - const [loading, setLoading] = useState(false) - - async function onSignTransaction(type: 'legacy' | 'versioned') { - try { - setLoading(true) - if (!walletProvider?.publicKey) { - throw Error('user is disconnected') - } - - if (!connection) { - throw Error('no connection set') - } - - const transactions = await Promise.all( - Array.from({ length: 5 }, () => createTransaction(walletProvider, connection, type)) - ) - const response = await walletProvider.signAllTransactions(transactions) - - const description = response - .map(transaction => { - const signature = - transaction.signatures[0] instanceof Uint8Array - ? transaction.signatures[0] - : transaction.signatures[0]?.signature - - if (!signature) { - throw Error('Empty signature') - } - - return bs58.encode(signature) - }) - .join('\n\n') - - toast({ - title: 'Success', - description, - type: 'success' - }) - } catch (err) { - toast({ - title: 'Error', - description: (err as Error).message, - type: 'error' - }) - } finally { - setLoading(false) - } - } - - if (chainId === solana.chainId) { - return ( - - Switch to Solana Devnet or Testnet to test this feature - - ) - } - - return ( - - - - - - ) -} - -async function createTransaction( - provider: Provider, - connection: Connection, - type: 'legacy' | 'versioned' -) { - if (!provider.publicKey) { - throw Error('No public key found') - } - - const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash() - - const instructions = [ - SystemProgram.transfer({ - fromPubkey: provider.publicKey, - toPubkey: recipientAddress, - lamports: amountInLamports - }) - ] - - if (type === 'legacy') { - return new Transaction({ feePayer: provider.publicKey, blockhash, lastValidBlockHeight }).add( - ...instructions - ) - } - - return new VersionedTransaction( - new TransactionMessage({ - payerKey: provider.publicKey, - recentBlockhash: blockhash, - instructions - }).compileToV0Message() - ) -} +import { useState } from 'react' +import { Button, Stack, Text, Spacer } from '@chakra-ui/react' +import { + PublicKey, + Transaction, + TransactionMessage, + VersionedTransaction, + SystemProgram +} from '@solana/web3.js' + +import { useWeb3ModalAccount, useWeb3ModalProvider, type Provider } from '@web3modal/solana/react' + +import { solana } from '../../utils/ChainsUtil' +import { useChakraToast } from '../Toast' +import type { Connection } from '@web3modal/base/adapters/solana/web3js' +import bs58 from 'bs58' + +const PHANTOM_DEVNET_ADDRESS = '8vCyX7oB6Pc3pbWMGYYZF5pbSnAdQ7Gyr32JqxqCy8ZR' +const recipientAddress = new PublicKey(PHANTOM_DEVNET_ADDRESS) +const amountInLamports = 1_000_000 + +export function SolanaSignAllTransactionsTest() { + const toast = useChakraToast() + const { chainId } = useWeb3ModalAccount() + const { walletProvider, connection } = useWeb3ModalProvider() + const [loading, setLoading] = useState(false) + + async function onSignTransaction(type: 'legacy' | 'versioned') { + try { + setLoading(true) + if (!walletProvider?.publicKey) { + throw Error('user is disconnected') + } + + if (!connection) { + throw Error('no connection set') + } + + const transactions = await Promise.all( + Array.from({ length: 5 }, () => createTransaction(walletProvider, connection, type)) + ) + const response = await walletProvider.signAllTransactions(transactions) + + const description = response + .map(transaction => { + const signature = + transaction.signatures[0] instanceof Uint8Array + ? transaction.signatures[0] + : transaction.signatures[0]?.signature + + if (!signature) { + throw Error('Empty signature') + } + + return bs58.encode(signature) + }) + .join('\n\n') + + toast({ + title: 'Success', + description, + type: 'success' + }) + } catch (err) { + toast({ + title: 'Error', + description: (err as Error).message, + type: 'error' + }) + } finally { + setLoading(false) + } + } + + if (chainId === solana.chainId) { + return ( + + Switch to Solana Devnet or Testnet to test this feature + + ) + } + + return ( + + + + + + ) +} + +async function createTransaction( + provider: Provider, + connection: Connection, + type: 'legacy' | 'versioned' +) { + if (!provider.publicKey) { + throw Error('No public key found') + } + + const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash() + + const instructions = [ + SystemProgram.transfer({ + fromPubkey: provider.publicKey, + toPubkey: recipientAddress, + lamports: amountInLamports + }) + ] + + if (type === 'legacy') { + return new Transaction({ feePayer: provider.publicKey, blockhash, lastValidBlockHeight }).add( + ...instructions + ) + } + + return new VersionedTransaction( + new TransactionMessage({ + payerKey: provider.publicKey, + recentBlockhash: blockhash, + instructions + }).compileToV0Message() + ) +} diff --git a/packages/base/adapters/solana/web3js/providers/AuthProvider.ts b/packages/base/adapters/solana/web3js/providers/AuthProvider.ts index 2578e0b45b..682859159d 100644 --- a/packages/base/adapters/solana/web3js/providers/AuthProvider.ts +++ b/packages/base/adapters/solana/web3js/providers/AuthProvider.ts @@ -131,7 +131,7 @@ export class AuthProvider extends ProviderEventEmitter implements Provider, Prov return signature } - public async signAllTransactions(transactions: AnyTransaction[]) { + public async signAllTransactions(transactions: T): Promise { const result = await this.provider.request({ method: 'solana_signAllTransactions', params: { @@ -153,7 +153,7 @@ export class AuthProvider extends ProviderEventEmitter implements Provider, Prov } return Transaction.from(decodedTransaction) - }) + }) as T } // -- W3mFrameProvider methods ------------------------------------------- // From 0fbe4a7536f8c58c19e08f22804b9748d81bed37 Mon Sep 17 00:00:00 2001 From: Felipe Mendes Date: Fri, 30 Aug 2024 12:18:10 -0300 Subject: [PATCH 11/11] chore: add solana_signAllTransactions as a not safe rpc method for wallet --- packages/wallet/src/W3mFrameConstants.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/wallet/src/W3mFrameConstants.ts b/packages/wallet/src/W3mFrameConstants.ts index 7185425f72..1a5323c9de 100644 --- a/packages/wallet/src/W3mFrameConstants.ts +++ b/packages/wallet/src/W3mFrameConstants.ts @@ -142,6 +142,7 @@ export const W3mFrameRpcConstants = { 'eth_sendTransaction', 'solana_signMessage', 'solana_signTransaction', + 'solana_signAllTransactions', 'solana_signAndSendTransaction', 'wallet_sendCalls', 'wallet_grantPermissions'