Sign and send on Solana

Once you’ve created a smart wallet on Solana, send transactions and sign messages using the useSolanaTransaction and useSolanaSignMessage hooks. You can also add mutliplie signers to your Solana wallet.

Sending Solana transactions

To send a transaction from an embedded Solana wallet, use the sendTransaction method from the useSolanaTransaction hook.

  • This method supports signing both single and batched transactions and integrates seamlessly with popular web3 libraries like @solana/web3js.
  • You’ll need to use a web3 library to construct and submit transactions.

Want to sponsor gas for Solana transactions? Skip to this guide for sending sponsored transactions.

Send a single transaction

single-solana-transaction.tsx
1import React from "react";
2import { useSolanaTransaction } from "@account-kit/react";
3
4function MyComponent() {
5 const {
6 sendTransaction,
7 isPending,
8 signer,
9 reset,
10 data: { hash: txHash = null } = {},
11 } = useSolanaTransaction({});
12
13 if (!signer) {
14 return <div>Loading alchemy signer...</div>;
15 }
16
17 return (
18 <div>
19 Solana Address {signer.address}
20 <button
21 onClick={() =>
22 sendTransaction({
23 transfer: {
24 amount: 1000000,
25 toAddress: "<ToSolanaAddress>",
26 },
27 })
28 }
29 >
30 Go make Transfer
31 </button>
32 {!!txHash && <button onClick={() => reset()}> Reset </button>}
33 {!!txHash && (
34 <a
35 href={`https://explorer.solana.com/tx/${txHash}?cluster=devnet`}
36 target="_blank"
37 >
38 View transaction
39 </a>
40 )}
41 </div>
42 );
43}

Send a batched transaction

Batch multiple Solana instructions into a single transaction.

batched-solana-transaction.tsx
1import React from "react";
2import { useSolanaTransaction } from "@account-kit/react";
3import { PublicKey, SystemProgram } from "@solana/web3.js";
4
5function MyComponent() {
6 const {
7 sendTransaction,
8 isPending,
9 signer,
10 reset,
11 data: { hash: txHash = null } = {},
12 } = useSolanaTransaction({});
13
14 if (!signer) {
15 return <div>Loading alchemy signer...</div>;
16 }
17
18 return (
19 <div>
20 Solana Address {signer.address}
21 <button
22 onClick={() =>
23 sendTransaction({
24 instructions: [
25 SystemProgram.transfer({
26 fromPubkey: new PublicKey(signer.address),
27 toPubkey: new PublicKey("<ToAddress>"),
28 lamports: someValue, // ex: lamports: 50000000 - 0.05 SOL
29 }),
30 SystemProgram.transfer({
31 fromPubkey: new PublicKey(signer.address),
32 toPubkey: new PublicKey("<ToAddress>"),
33 lamports: someValue2,
34 }),
35 ],
36 })
37 }
38 >
39 Go make Transfer
40 </button>
41 {!!txHash && <button onClick={() => reset()}> Reset </button>}
42 {!!txHash && (
43 <a
44 href={`https://explorer.solana.com/tx/${txHash}?cluster=devnet`}
45 target="_blank"
46 >
47 Go To Tracker
48 </a>
49 )}
50 </div>
51 );
52}

How does sendTransaction work?

The sendTransaction method will construct and send a Solana transaction using the connection you provide. You can either construct a transaction using simple parameters or instructions for finer grained control.

Using simple transfer parameters:

1sendTransaction({
2 transfer: {
3 // Amount in the atomic values of solana (Sol)
4 amount: 123,
5 toAddress: "<AddressToSendTo>",
6 },
7});

Using instructions:

If you want finer grained control like transfer and nonceAdvance you can pass instructions by using the exposed web3 SystemProgram.

1sendTransaction({
2 instructions: [
3 SystemProgram.transfer({
4 fromPubkey: new PublicKey("<FromAddress>"),
5 toPubkey: new PublicKey("<ToAddress>"),
6 lamports: params.transfer.amount,
7 }),
8 ],
9});

Sign messages

Sign messages (either a string or a byte array) with your Solana wallet by using the useSolanaSignMessage hook.

sign-solana-message.tsx
1import { useSolanaSignMessage } from "@account-kit/react";
2import { SolanaSigner } from "@account-kit/signer";
3
4function MyComponent() {
5 const {
6 signer,
7 data,
8 signMessage,
9 data: signedMessage,
10 reset,
11 } = useSolanaSignMessage({});
12
13 if (!signer) {
14 return <div>Loading signer...</div>;
15 }
16
17 return (
18 <div>
19 <button onClick={() => signMessage({ message: "Here is my message" })}>
20 {" Sign "}
21 </button>
22 Message Signed: {signedMessage}
23 </div>
24 );
25}

Multi-signature support using custom signers

You can implement multi-signature behavior using the preSign feature to authorize multiple signers on a transaction before execution.

Example use case: adding additional signers

A common pattern involves supporting multiple signers for a single transaction. For example, in our demo environment, we generate a temporary (or “throwaway”) account that can hold various tokens. However, this account alone isn’t sufficient for transaction security or coordination.

To enable multi-signature functionality, we attach additional signers to the transaction using preSign. This allows the transaction to be authorized by multiple parties before being broadcast, ensuring that:

  • Multiple stakeholders can approve a transaction.
  • Tokens can exist at a shared account address with controlled access.
  • Coordination between signers happens off-chain via pre-signed approvals.

Example flow:

  1. Create a transaction from a temporary account.
  2. Add one or more preSign signatures from required co-signers.
  3. Submit the fully signed transaction.

Learn more about Solana multisig.

add-solana-signer.ts
1import {
2 Transaction,
3 VersionedTransaction,
4 Keypair,
5 PublicKey,
6 SystemProgram,
7} from "@solana/web3.js";
8
9import {
10 ExtensionType,
11 LENGTH_SIZE,
12 TOKEN_2022_PROGRAM_ID,
13 TYPE_SIZE,
14 createInitializeMetadataPointerInstruction,
15 getMintLen,
16} from "@solana/spl-token";
17import { pack, TokenMetadata } from "@solana/spl-token-metadata";
18const stakeAccount = Keypair.generate();
19const publicKey = new PublicKey(signer.address);
20const metaData: (readonly [string, string])[] = [];
21const tokenMetadata: TokenMetadata = {
22 updateAuthority: publicKey,
23 mint: stakeAccount.publicKey,
24 name: "Alchemy Duck",
25 symbol: "ALCHDUCK",
26 uri: "https://bafybeigtvzjqalevyw67xdhr7am5r3jxe5kjbg4pi2jv3nxvhelptwksoe.ipfs.dweb.link?filename=duckImage.png",
27 additionalMetadata: metaData,
28};
29const mintLen = getMintLen([ExtensionType.MetadataPointer]);
30const metadataLen = TYPE_SIZE + LENGTH_SIZE + pack(tokenMetadata).length;
31const mintLamports = await connection.getMinimumBalanceForRentExemption(
32 mintLen + metadataLen,
33);
34
35const mint = stakeAccount.publicKey;
36const instructions = [
37 SystemProgram.createAccount({
38 fromPubkey: publicKey,
39 newAccountPubkey: mint,
40 space: mintLen,
41 lamports: mintLamports,
42 programId: TOKEN_2022_PROGRAM_ID,
43 }),
44 createInitializeMetadataPointerInstruction(
45 mint,
46 publicKey,
47 mint,
48 TOKEN_2022_PROGRAM_ID,
49 ),
50];
51/**
52 * Send a transaction with a custom `preSend` hook to add an extra signer.
53 */
54const tx = await sendTransactionAsync({
55 instructions, // Standard transaction instructions
56 transactionComponents: {
57 preSend: getAddStakeSignatureFn(stakeAccount), // Add secondary signer before sending
58 },
59});
60
61/**
62 * Returns a preSend hook that appends a signature from the provided `stakeAccount`.
63 *
64 * @param stakeAccount - The Keypair used to sign the transaction
65 * @returns A function that accepts a transaction and returns a signed transaction
66 */
67function getAddStakeSignatureFn(stakeAccount: Keypair) {
68 return async (transaction: Transaction | VersionedTransaction) => {
69 if ("version" in transaction) {
70 transaction.sign([stakeAccount]); // Add stake account's signature for v0 transactions
71 }
72 return transaction;
73 };
74}