Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: solana sign and send transaction #2646

Merged
merged 9 commits into from
Aug 7, 2024
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>
Comment on lines 51 to +61
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the difference between Dapp and Wallet?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dapp: the dapp request to wallet sign transaction and sends to blockchain
Wallet: the dapp request to wallet sign and send the transaction

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm I feel this is confusing, so you are doing basically

Request wallet to sign transaction => Manually send it from lab
Request wallet to sign and send transaction directly

Why send the tx from lab in the first place? isn't just requesting the signing enough?

Just a nit tho

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is the two ways of doing it and both are valid: the first case is testing the signTransaction and sendTransaction functions, the second is testing signAndSendTransaction which sends for wallet handle the blockchain connection.

{(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
Loading