# Send transaction

Solana wallets use [`@solana/kit`](https://www.npmjs.com/package/@solana/kit) for transaction construction and signing. Openfort's embedded wallet signs transaction message bytes directly using Ed25519, so you create a `TransactionSigner` adapter that delegates to the wallet provider.

## Sign and send a SOL transfer

First, create a `TransactionSigner` adapter that wraps the provider's `signMessage` for compatibility with `@solana/kit`. Then use it in a hook to build and send transactions.

:::info
The Solana embedded wallet provider uses Ed25519 for signing, which signs raw message bytes directly (no keccak256 hashing). This is handled automatically by the provider.
:::

:::code-group
```tsx [useSendSol.ts]
import { useMemo } from 'react'
import { useEmbeddedSolanaWallet } from '@openfort/react-native'
import {
  type Address,
  address,
  createSolanaRpc,
  createSolanaRpcSubscriptions,
  createTransactionMessage,
  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 = useEmbeddedSolanaWallet()

  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, (message) =>
        solana.provider.signMessage(message),
      )

      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' })

      return Object.keys(signedTransaction.signatures)[0]
    }
  }, [solana])
}
```

```tsx [createOpenfortSigner.ts]
import {
  type Address,
  type SignatureBytes,
  type SignatureDictionary,
  type TransactionSigner,
} from '@solana/kit'
import { Base58 } from 'ox'

export function createOpenfortSigner(
  signerAddress: Address,
  signMessage: (message: Uint8Array) => Promise<string>,
): TransactionSigner {
  return {
    address: signerAddress,
    signTransactions: async (transactions): Promise<readonly SignatureDictionary[]> => {
      return Promise.all(
        transactions.map(async (transaction) => {
          const signatureBase58 = await signMessage(
            new Uint8Array(transaction.messageBytes),
          )
          let signatureBytes = Base58.toBytes(signatureBase58)

          // 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,
          })
        }),
      )
    },
  }
}
```
:::

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

## Gasless Solana transactions

Keep transactions SOL-free for users by sponsoring fees through the [Openfort Solana Paymaster](/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).
:::

Build the transfer with the standard `@solana-program/token` library, set Kora as the fee payer, sign the user's part with the same `createOpenfortSigner` adapter from above, then submit through Kora for co-signing and broadcast:

```tsx [useGaslessSol.ts]
import { KoraClient } from '@solana/kora'
import { useEmbeddedSolanaWallet } from '@openfort/react-native'
import {
  address,
  createNoopSigner,
  getBase64EncodedWireTransaction,
  partiallySignTransactionMessageWithSigners,
  pipe,
  createTransactionMessage,
  setTransactionMessageFeePayerSigner,
  setTransactionMessageLifetimeUsingBlockhash,
  appendTransactionMessageInstruction,
  type Blockhash,
} from '@solana/kit'
import {
  findAssociatedTokenPda,
  getTransferInstruction,
  TOKEN_PROGRAM_ADDRESS,
} from '@solana-program/token'
import { createOpenfortSigner } from './createOpenfortSigner'

const client = new KoraClient({
  rpcUrl: 'https://api.openfort.io/rpc/solana/devnet',
  apiKey: 'Bearer YOUR_OPENFORT_PUBLISHABLE_KEY',
})

export function useGaslessSol() {
  const solana = useEmbeddedSolanaWallet()

  return async (destination: string) => {
    if (solana.status !== 'connected') throw new Error('Wallet not connected')

    const from = address(solana.activeWallet.address)
    const userSigner = createOpenfortSigner(from, (message) =>
      solana.provider.signMessage(message),
    )

    // 1. Kora's fee-payer signer address
    const { signer_address } = await client.getPayerSigner()
    const feePayer = createNoopSigner(address(signer_address))

    // 2. The SPL token to transfer
    const { allowed_spl_paid_tokens } = (await client.getConfig()).validation_config
    const mint = address(allowed_spl_paid_tokens[0])

    // 3. Build the transfer with the standard SPL Token program
    const [source] = await findAssociatedTokenPda({ owner: from, tokenProgram: TOKEN_PROGRAM_ADDRESS, mint })
    const [dest] = await findAssociatedTokenPda({ owner: address(destination), tokenProgram: TOKEN_PROGRAM_ADDRESS, mint })

    const { blockhash } = await client.getBlockhash()

    const transactionMessage = pipe(
      createTransactionMessage({ version: 0 }),
      (tx) => setTransactionMessageFeePayerSigner(feePayer, tx),
      (tx) =>
        setTransactionMessageLifetimeUsingBlockhash(
          { blockhash: blockhash as Blockhash, lastValidBlockHeight: 0n },
          tx,
        ),
      (tx) =>
        appendTransactionMessageInstruction(
          getTransferInstruction({
            source,
            destination: dest,
            authority: userSigner,
            amount: 100_000n, // 0.10 USDC (6 decimals)
          }),
          tx,
        ),
    )

    // 4. Sign the user's part, then submit for Kora co-signing + broadcast
    const userSigned = await partiallySignTransactionMessageWithSigners(transactionMessage)
    const { signature } = await client.signAndSendTransaction({
      transaction: getBase64EncodedWireTransaction(userSigned),
      signer_key: signer_address,
    })

    return signature
  }
}
```

:::tip
For the complete copy-pasteable flow (compute-budget instructions, payment instruction, and confirmation), see the [Solana Paymaster](/docs/products/infrastructure/paymaster/solana) docs and the [Solana sample app](https://github.com/openfort-xyz/openfort-js/tree/main/examples/apps/solana-sample).
:::
