Prerequisites
- Ensure you have a wallet created.
- Signer: The signing wallet must be registered as a signer on the wallet. You can do this at wallet creation by passing
signers, or afterward by adding a signer.
- API Key: Ensure you have an API key with the scopes:
wallets:transactions.create.
Solana wallets do not support device signers. You must use an explicit signer type (e.g., email, phone, or external-wallet) both as the recovery signer and for signing transactions.
What is sending a custom transaction?
Sending a custom transaction lets you interact with any smart contract on the blockchain beyond simple transfers. Common use cases include minting free tokens, claiming rewards, or registering for allowlists—all without needing to manage private keys yourself.
Sending a Transaction
React
Node.js
React Native
Swift
REST
import { useWallet, SolanaWallet } from '@crossmint/client-sdk-react-ui';
const { wallet } = useWallet();
const solanaWallet = SolanaWallet.from(wallet);
const { hash, explorerLink } = await solanaWallet.sendTransaction({
transaction: versionedTransaction,
additionalSigners: additionalSigners,
});
See the React SDK reference for more details.import { CrossmintWallets, createCrossmint, SolanaWallet } from "@crossmint/wallets-sdk";
const SERVER_API_KEY = process.env.SERVER_API_KEY!;
const CROSSMINT_SIGNER_SECRET = process.env.CROSSMINT_SIGNER_SECRET!;
// Replace with your wallet address
const WALLET_ADDRESS = "YOUR_WALLET_ADDRESS";
const crossmint = createCrossmint({
apiKey: SERVER_API_KEY,
});
const crossmintWallets = CrossmintWallets.from(crossmint);
const wallet = await crossmintWallets.getWallet(
WALLET_ADDRESS,
{ chain: "solana" }
);
await wallet.useSigner({ type: "server", secret: CROSSMINT_SIGNER_SECRET });
const solanaWallet = SolanaWallet.from(wallet);
try {
const { hash, explorerLink } = await solanaWallet.sendTransaction({
transaction: versionedTransaction, // your VersionedTransaction instance
});
console.log("Transaction sent:", hash, explorerLink);
} catch (error) {
console.error("Failed to send transaction:", error);
throw error;
}
See the SDK reference for all parameters and return types.import { useWallet, SolanaWallet } from '@crossmint/client-sdk-react-native-ui';
const { wallet } = useWallet();
const solanaWallet = SolanaWallet.from(wallet);
const { hash, explorerLink } = await solanaWallet.sendTransaction({
transaction: versionedTransaction,
additionalSigners: additionalSigners,
});
See the React Native SDK reference for more details.import CrossmintClient
import Wallet
let sdk = CrossmintSDK.shared
let wallet = try await sdk.crossmintWallets.getWallet(
chain: .solana
)
let solanaWallet = try SolanaWallet.from(wallet: wallet)
let result = try await solanaWallet.sendTransaction(transaction)
Parameters
transaction
VersionedTransaction
required
The transaction to send.
Returns
The hash of the transaction.
The explorer link of the transaction.
Transactions must be approved by one of the wallet’s signers.
The SDK handles this automatically, but with the REST API you must approve the transaction to complete it.Create the transaction
Call the create transaction endpoint.curl --request POST \
--url https://staging.crossmint.com/api/2025-06-09/wallets/<wallet-locator>/transactions \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: YOUR_API_KEY' \
--data '{
"params": {
"transaction": "AQAAAAAAAAAAAAA...",
"signer": "server:YOUR_SERVER_SIGNER_ADDRESS"
}
}'
See the API reference for more details. Choose your signer type
The next steps depend on which signer type you specified in the previous step. API Key
External Wallet
Email & Phone
Passkey
API Key signers require no additional steps. Crossmint approves transactions automatically when using API key authentication, so the remaining steps in this guide do not apply. For External Wallet signers, you must manually sign the approval message and submit it via the API. The response from Step 1 includes a pending approval with a message field that must be signed exactly as returned.From the previous step’s response, extract:
id - The transaction ID (used in the next step)
approvals.pending[0].message - The hex message to sign
Sign the message using your external wallet. The message is a raw hex string and must be signed exactly as returned. Here’s an example using an EVM wallet with Viem:import { privateKeyToAccount } from "viem/accounts";
// The message from tx.approvals.pending[0].message
const messageToSign = "<messageFromResponse>";
// Sign the message exactly as returned (raw hex)
const account = privateKeyToAccount(`0x${"<privateKey>"}`);
const signature = await account.signMessage({
message: { raw: messageToSign },
});
Email and phone signers require client-side OTP verification and key derivation, which the Crossmint SDK handles automatically. While REST API signing is technically possible, Crossmint does not recommend it because you would still need client-side SDK integration for the signing step.Crossmint recommends using the React, React Native, Swift, or Node.js SDK examples instead. The SDK handles the full signing flow for email and phone signers.
Passkey signers use WebAuthn for biometric or password manager authentication, which requires browser interaction. While REST API signing is technically possible, Crossmint does not recommend it because you would still need client-side SDK integration for the WebAuthn signing step.Crossmint recommends using the React, React Native, Swift, or Node.js SDK examples instead. The SDK handles the full passkey signing flow automatically.
Submit the approval
Skip this step if using an api-key signer.
Call the approve transaction endpoint with the signature from Step 2 and the transaction ID from Step 1.curl --request POST \
--url https://staging.crossmint.com/api/2025-06-09/wallets/<walletAddress>/transactions/<txId>/approvals \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: <x-api-key>' \
--data '{
"approvals": [{
"signer": "external-wallet:<externalWalletAddress>",
"signature": "<signature>"
}]
}'
See the API reference for more details.