> **Can't find what you're looking for?** Use `search_docs` on the docs MCP server at `https://www.openfort.io/api/mcp` to find what you need.
>
> **Have feedback?** Use `submit_feedback` on the same MCP server.

# Send transaction

Solana wallets use [`@solana/kit`](https://www.npmjs.com/package/@solana/kit) 6 to build, sign, and submit transactions. Wrap the wallet's `signTransaction` in a Kit `TransactionSigner`, build the transaction message, sign it, then broadcast and confirm.

```bash
pnpm add @solana/kit@^6 @solana-program/system
```

:::tip[Built-in Send UI]
If you render [`<OpenfortButton />`](https://www.openfort.io/docs/products/embedded-wallet/react/ui), your users already get a **Send** action for native SOL in the wallet modal — with an optional **gasless** toggle that sponsors the fee through the [Solana Paymaster](https://www.openfort.io/docs/products/infrastructure/paymaster/solana). On an empty wallet, Send routes to **Add funds** instead. The steps below are for building a custom send flow.
:::

## How signing works

Two details are specific to Solana:

* **Ed25519 signatures.** Transaction message bytes go to `provider.signTransaction({ messageBytes })`.
* **A signer adapter.** `@solana/kit` signs through a `TransactionSigner`; the adapter decodes the provider's base58 signature into 64-byte signature bytes.

## 1. Create the signer adapter

`createOpenfortSigner` turns the embedded provider into a reusable `@solana/kit` `TransactionSigner`:

```tsx [createOpenfortSigner.ts]
import {
  getBase58Encoder,
  type Address,
  type SignatureBytes,
  type SignatureDictionary,
  type TransactionSigner,
} from '@solana/kit'
import type { OpenfortEmbeddedSolanaWalletProvider } from '@openfort/react/solana'

export function createOpenfortSigner(
  signerAddress: Address,
  provider: OpenfortEmbeddedSolanaWalletProvider,
): TransactionSigner {
  return {
    address: signerAddress,
    signTransactions: async (transactions): Promise<readonly SignatureDictionary[]> => {
      return Promise.all(
        transactions.map(async (transaction) => {
          const { signature } = await provider.signTransaction({
            messageBytes: new Uint8Array(transaction.messageBytes),
          })
          let signatureBytes = new Uint8Array(getBase58Encoder().encode(signature))

          // Trim recovery byte if present (65 → 64 bytes for Ed25519)
          if (signatureBytes.length === 65) {
            signatureBytes = signatureBytes.slice(0, 64)
          }

          return Object.freeze({
            [signerAddress]: signatureBytes as SignatureBytes,
          })
        }),
      )
    },
  }
}
```

## 2. Build, sign, and send a transfer

Build the transfer with the signer as the source, sign it with `signTransactionMessageWithSigners`, then broadcast and confirm with `sendAndConfirmTransactionFactory`:

```tsx [useSendSol.ts]
import { useMemo } from 'react'
import { useSolanaEmbeddedWallet } from '@openfort/react/solana'
import {
  type Address,
  address,
  createSolanaRpc,
  createSolanaRpcSubscriptions,
  createTransactionMessage,
  getSignatureFromTransaction,
  lamports,
  pipe,
  sendAndConfirmTransactionFactory,
  setTransactionMessageFeePayer,
  setTransactionMessageLifetimeUsingBlockhash,
  signTransactionMessageWithSigners,
  appendTransactionMessageInstruction,
  assertIsTransactionWithBlockhashLifetime,
} from '@solana/kit'
import { getTransferSolInstruction } from '@solana-program/system'
import { createOpenfortSigner } from './createOpenfortSigner'

const rpc = createSolanaRpc('https://api.devnet.solana.com')
const rpcSubscriptions = createSolanaRpcSubscriptions('wss://api.devnet.solana.com')

export function useSendSol() {
  const solana = useSolanaEmbeddedWallet()

  return useMemo(() => {
    if (solana.status !== 'connected') return null

    return async (to: string, amountInSol: number) => {
      const from = solana.activeWallet.address as Address
      const openfortSigner = createOpenfortSigner(from, solana.provider)

      const { value: latestBlockhash } = await rpc.getLatestBlockhash().send()

      const transactionMessage = pipe(
        createTransactionMessage({ version: 0 }),
        (tx) => setTransactionMessageFeePayer(from, tx),
        (tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
        (tx) =>
          appendTransactionMessageInstruction(
            getTransferSolInstruction({
              source: openfortSigner,
              destination: address(to),
              amount: lamports(BigInt(Math.floor(amountInSol * 1_000_000_000))),
            }),
            tx,
          ),
      )

      const signedTransaction = await signTransactionMessageWithSigners(transactionMessage)
      assertIsTransactionWithBlockhashLifetime(signedTransaction)

      const sendAndConfirm = sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })
      await sendAndConfirm(signedTransaction, {
        commitment: 'confirmed',
        abortSignal: AbortSignal.timeout(60_000),
      })

      return getSignatureFromTransaction(signedTransaction)
    }
  }, [solana])
}
```

:::tip
To sign arbitrary messages on Solana, see [Sign message (Solana)](https://www.openfort.io/docs/products/embedded-wallet/react/wallet/actions/sign-message-solana).
:::

## Gasless Solana transactions

Keep transactions SOL-free for users by sponsoring fees through the [Openfort Solana Paymaster](https://www.openfort.io/docs/products/infrastructure/paymaster/solana), which integrates with [Kora](https://www.npmjs.com/package/@solana/kora). Openfort acts as the transaction fee payer, so users never need to hold SOL.

1. In [**Dashboard → Gas sponsorship**](https://dashboard.openfort.io/policies), add a gas sponsorship with a `sponsorSolTransaction` rule, then pick a **Sponsorship mode**:
   * **App pays** — your project covers the SOL fees so users transact for free.
   * **User pays** — users pay fees in a supported SPL token; Kora computes the exchange rate at request time.
2. Point a `KoraClient` at the Openfort RPC for your cluster (`https://api.openfort.io/rpc/solana/{cluster}`), authenticated with your publishable key.

:::info
Solana gas sponsorship is **project-scoped** — matching policies apply automatically, with no per-request `policyId` to pass (unlike Ethereum).
:::

Kora must supply the fee payer and payment instructions before the user signs. Follow the [Solana Paymaster](https://www.openfort.io/docs/products/infrastructure/paymaster/solana) guide for the complete flow, including compute-budget instructions, co-signing, submission, and confirmation. A maintained implementation is also available in the [Solana quickstart](https://github.com/openfort-xyz/openfort-react/tree/main/examples/quickstarts/solana-headless).

:::tip
Do not take a normally signed transaction and swap its fee payer afterward; changing the message invalidates the user's signature.
:::
