Skip to content

Commit

Permalink
refactor: solana sign and send transaction (#2646)
Browse files Browse the repository at this point in the history
  • Loading branch information
zoruka authored Aug 7, 2024
1 parent d4eb59e commit 2bd1578
Show file tree
Hide file tree
Showing 8 changed files with 413 additions and 279 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { useState } from 'react'
import { Button, Stack, Text, Spacer, Link } from '@chakra-ui/react'
import { useWeb3ModalAccount, useWeb3ModalProvider } from '@web3modal/solana/react'
import { PublicKey, Transaction, SystemProgram } from '@solana/web3.js'

import { solana } from '../../utils/ChainsUtil'
import { useChakraToast } from '../Toast'

const PHANTOM_TESTNET_ADDRESS = '8vCyX7oB6Pc3pbWMGYYZF5pbSnAdQ7Gyr32JqxqCy8ZR'
const recipientAddress = new PublicKey(PHANTOM_TESTNET_ADDRESS)
const amountInLamports = 50000000

export function SolanaSignAndSendTransaction() {
const toast = useChakraToast()
const { address, chainId } = useWeb3ModalAccount()
const { walletProvider, connection } = useWeb3ModalProvider()
const [loading, setLoading] = useState(false)

async function onSendTransaction() {
try {
setLoading(true)
if (!walletProvider || !address) {
throw Error('user is disconnected')
}

if (!connection) {
throw Error('no connection set')
}

const balance = await connection.getBalance(walletProvider.publicKey)
if (balance < amountInLamports) {
throw Error('Not enough SOL in wallet')
}

// Create a new transaction
const transaction = new Transaction().add(
SystemProgram.transfer({
fromPubkey: walletProvider.publicKey,
toPubkey: recipientAddress,
lamports: amountInLamports
})
)
transaction.feePayer = walletProvider.publicKey
const signature = await walletProvider.signAndSendTransaction(transaction)

toast({
title: 'Success',
description: signature,
type: 'success'
})
} catch (err) {
toast({
title: 'Error',
description: (err as Error).message,
type: 'error'
})
} finally {
setLoading(false)
}
}

if (!address) {
return null
}

if (chainId === solana.chainId) {
return (
<Text fontSize="md" color="yellow">
Switch to Solana Devnet or Testnet to test this feature
</Text>
)
}

return (
<Stack direction={['column', 'column', 'row']}>
<Button
data-test-id="sign-transaction-button"
onClick={onSendTransaction}
isDisabled={loading}
>
Sign and Send Transaction
</Button>
<Spacer />

<Link isExternal href="https://solfaucet.com/">
<Button variant="outline" colorScheme="blue">
Solana Faucet
</Button>
</Link>
</Stack>
)
}
9 changes: 8 additions & 1 deletion apps/laboratory/src/components/Solana/SolanaTests.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { SolanaSendTransactionTest } from './SolanaSendTransactionTest'
import { SolanaSignMessageTest } from './SolanaSignMessageTest'
import { SolanaWriteContractTest } from './SolanaWriteContractTest'
import { solana, solanaDevnet, solanaTestnet } from '../../utils/ChainsUtil'
import { SolanaSignAndSendTransaction } from './SolanaSignAndSendTransactionTest'

export function SolanaTests() {
const { isConnected, currentChain } = useWeb3ModalAccount()
Expand Down Expand Up @@ -48,10 +49,16 @@ export function SolanaTests() {
</Box>
<Box>
<Heading size="xs" textTransform="uppercase" pb="2">
Sign and Send Transaction
Sign and Send Transaction (dApp)
</Heading>
<SolanaSendTransactionTest />
</Box>
<Box>
<Heading size="xs" textTransform="uppercase" pb="2">
Sign and Send Transaction (Wallet)
</Heading>
<SolanaSignAndSendTransaction />
</Box>
{(currentChain?.chainId === solanaTestnet.chainId ||
currentChain?.chainId === solanaDevnet.chainId) && (
<Stack divider={<StackDivider />} spacing="4">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,8 @@ export function SolanaWriteContractTest() {
const tx = new Transaction().add(allocIx).add(incrementIx)

tx.feePayer = walletProvider.publicKey
tx.recentBlockhash = (await connection.getLatestBlockhash('confirmed')).blockhash

await walletProvider.signAndSendTransaction(tx, [counterKeypair])
await walletProvider.signAndSendTransaction(tx)

const counterAccountInfo = await connection.getAccountInfo(counter, {
commitment: 'confirmed'
Expand Down
5 changes: 2 additions & 3 deletions packages/solana/src/connectors/baseConnector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { SolConstantsUtil, SolStoreUtil } from '../utils/scaffold/index.js'
import { getHashedName, getNameAccountKey } from '../utils/hash.js'
import { NameRegistry } from '../utils/nameService.js'

import type { ConfirmOptions, Signer, TransactionSignature } from '@solana/web3.js'
import type { SendOptions, TransactionSignature } from '@solana/web3.js'

import type {
BlockResult,
Expand All @@ -43,8 +43,7 @@ export interface Connector {
sendTransaction: (transaction: Transaction | VersionedTransaction) => Promise<string>
signAndSendTransaction: (
transaction: Transaction | VersionedTransaction,
signers: Signer[],
confirmOptions?: ConfirmOptions
options?: SendOptions
) => Promise<TransactionSignature>
getAccount: (
requestedAddress?: string,
Expand Down
46 changes: 20 additions & 26 deletions packages/solana/src/connectors/walletConnectConnector.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import base58 from 'bs58'
import { PublicKey, Transaction, VersionedTransaction } from '@solana/web3.js'
import { PublicKey, Transaction, VersionedTransaction, type SendOptions } from '@solana/web3.js'
import { OptionsController } from '@web3modal/core'

import { SolStoreUtil } from '../utils/scaffold/index.js'
import { UniversalProviderFactory } from './universalProvider.js'
import { BaseConnector } from './baseConnector.js'

import type { Signer } from '@solana/web3.js'
import type UniversalProvider from '@walletconnect/universal-provider'

import type { Connector } from './baseConnector.js'
Expand Down Expand Up @@ -164,34 +163,29 @@ export class WalletConnectConnector extends BaseConnector implements Connector {
return signature
}

public async signAndSendTransaction(
transactionParam: Transaction | VersionedTransaction,
signers: Signer[]
public async signAndSendTransaction<T extends Transaction | VersionedTransaction>(
transaction: T,
options?: SendOptions
) {
if (transactionParam instanceof VersionedTransaction) {
if (transaction instanceof VersionedTransaction) {
throw Error('Versioned transactions are not supported')
}

if (signers.length) {
transactionParam.partialSign(...signers)
}

const { tx } = await this._sendTransaction(transactionParam)

if (tx) {
const latestBlockHash = await SolStoreUtil.state.connection?.getLatestBlockhash()
if (latestBlockHash?.blockhash) {
await SolStoreUtil.state.connection?.confirmTransaction({
blockhash: latestBlockHash.blockhash,
lastValidBlockHeight: latestBlockHash.lastValidBlockHeight,
signature: tx
})

return tx
}
}
const { signature } = await this.request('solana_signAndSendTransaction', {
feePayer: transaction.feePayer?.toBase58() ?? '',
instructions: transaction.instructions.map(instruction => ({
data: base58.encode(instruction.data),
keys: instruction.keys.map(key => ({
isWritable: key.isWritable,
isSigner: key.isSigner,
pubkey: key.pubkey.toBase58()
})),
programId: instruction.programId.toBase58()
})),
options
})

throw Error('Transaction Failed')
return signature
}

/**
Expand All @@ -218,7 +212,7 @@ export class WalletConnectConnector extends BaseConnector implements Connector {
return {
solana: {
chains: getChainsFromChainId(`solana:${chainId}` as ChainIDType),
methods: ['solana_signMessage', 'solana_signTransaction'],
methods: ['solana_signMessage', 'solana_signTransaction', 'solana_signAndSendTransaction'],
events: [],
rpcMap
}
Expand Down
24 changes: 17 additions & 7 deletions packages/solana/src/utils/scaffold/SolanaTypesUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import type {
Transaction as SolanaWeb3Transaction,
TransactionSignature,
VersionedTransaction,
ConfirmOptions,
Signer
SendOptions
} from '@solana/web3.js'

import type { SendTransactionOptions } from '@solana/wallet-adapter-base'

export type Connection = SolanaConnection
Expand Down Expand Up @@ -49,8 +49,7 @@ export interface Provider {
) => Promise<TransactionSignature[]>
signAndSendTransaction: (
transaction: SolanaWeb3Transaction | VersionedTransaction,
signers: Signer[],
confirmOptions?: ConfirmOptions
options?: SendOptions
) => Promise<TransactionSignature>
signMessage: (message: Uint8Array) => Promise<Uint8Array> | Promise<{ signature: Uint8Array }>
signTransaction: (transaction: SolanaWeb3Transaction | VersionedTransaction) => Promise<{
Expand Down Expand Up @@ -187,7 +186,7 @@ export type FilterObject =
}
| { dataSize: number }

export interface TransactionInstructionRq {
export interface TransactionInstructionRequest {
programId: string
data: string
keys: {
Expand All @@ -197,7 +196,7 @@ export interface TransactionInstructionRq {
}[]
}

interface VersionedInstractionRequest {
interface VersionedInstructionRequest {
data: string
programIdIndex: number
accountKeyIndexes: number[]
Expand All @@ -220,7 +219,7 @@ export interface RequestMethods {
solana_signTransaction: {
params: {
feePayer: string
instructions: TransactionInstructionRq[] | VersionedInstractionRequest[]
instructions: TransactionInstructionRequest[] | VersionedInstructionRequest[]
recentBlockhash: string
signatures?: {
pubkey: string
Expand All @@ -231,6 +230,17 @@ export interface RequestMethods {
signature: string
}
}
solana_signAndSendTransaction: {
params: {
feePayer: string
instructions: TransactionInstructionRequest[]
options?: SendOptions
}
returns: {
signature: string
}
}

signMessage: {
params: {
message: Uint8Array
Expand Down
Loading

0 comments on commit 2bd1578

Please sign in to comment.