Solana Swap Examples
This guide demonstrates how to execute Silent Swaps with Solana assets using the React SDK. Solana swaps require special handling for both source (Solana → EVM) and destination (EVM → Solana) scenarios.
Prerequisites
- Solana wallet adapter configured (e.g., Phantom, Solflare)
- EVM wallet connected (for facilitator operations)
- Both wallets must be connected simultaneously
Example 1: Solana Native SOL → EVM Token Swap
This example swaps native SOL on Solana to a token on an EVM chain (e.g., USDC on Ethereum).
'use client';
import React, { useState } from 'react';
import { useSilentSwap, useSwap } from '@silentswap/react';
import { useAccount, useWalletClient } from 'wagmi';
import { useSolanaAdapter } from '@silentswap/react';
import { useUserAddress } from '@/hooks/useUserAddress';
export function SolanaToEvmSwap() {
const { evmAddress, solAddress } = useUserAddress();
const { isConnected } = useAccount();
const { data: walletClient } = useWalletClient();
const { solanaConnector, solanaConnectionAdapter } = useSolanaAdapter();
const {
executeSwap,
isSwapping,
currentStep,
orderId,
orderComplete,
swapError,
} = useSilentSwap();
const { tokenIn, inputAmount, setInputAmount, destinations, setDestinations } = useSwap();
const [amount, setAmount] = useState('');
const handleSwap = async () => {
if (!solAddress || !evmAddress) {
alert('Both Solana and EVM wallets must be connected');
return;
}
if (!solanaConnector?.publicKey) {
alert('Solana wallet not connected');
return;
}
try {
// Set source asset to native SOL on Solana
// CAIP-19 format: solana:<chainId>/slip44:501
setInputAmount(amount);
// Set destination (e.g., USDC on Ethereum)
setDestinations([{
asset: 'eip155:1/erc20:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC on Ethereum
contact: `caip10:eip155:1:${evmAddress}`, // Recipient EVM address
amount: '',
}]);
// Execute swap - all parameters are required
const result = await executeSwap({
sourceAsset: 'solana:5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1/slip44:501', // Native SOL
sourceAmount: amount,
destinations: [{
asset: 'eip155:1/erc20:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
contact: `caip10:eip155:1:${evmAddress}`,
amount: '',
}],
splits: [1], // Split percentages (must sum to 1.0)
senderContactId: `caip10:solana:*:${solAddress}`, // Solana sender in CAIP-10 format
integratorId: process.env.NEXT_PUBLIC_INTEGRATOR_ID, // Optional: for tracking
});
if (result) {
console.log('Swap initiated:', result.orderId);
}
} catch (error) {
console.error('Swap failed:', error);
alert(`Swap failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
};
return (
<div className="flex flex-col gap-4 p-6 max-w-md mx-auto bg-zinc-900 rounded-2xl text-white">
<h2 className="text-2xl font-bold mb-4">Swap SOL → USDC</h2>
<div className="flex flex-col gap-2">
<label className="text-sm text-zinc-400">SOL Amount</label>
<input
type="text"
value={amount}
onChange={(e) => setAmount(e.target.value)}
placeholder="0.0"
className="p-4 bg-zinc-800 border-none rounded-xl text-xl outline-none"
disabled={isSwapping}
/>
<p className="text-xs text-zinc-500">
Solana Address: {solAddress?.slice(0, 8)}...{solAddress?.slice(-8)}
</p>
</div>
{isSwapping && (
<div className="p-4 bg-yellow/10 border border-yellow/20 rounded-xl">
<p className="font-semibold text-yellow">Status: {currentStep || 'Processing'}</p>
</div>
)}
{swapError && (
<div className="p-4 bg-red-500/20 border border-red-500/50 rounded-xl">
<p className="text-red-400">Error: {swapError.message}</p>
</div>
)}
{orderComplete && orderId && (
<div className="p-4 bg-green-500/20 border border-green-500/50 rounded-xl">
<p className="text-green-400">Swap Complete! Order ID: {orderId}</p>
</div>
)}
<button
onClick={handleSwap}
disabled={isSwapping || !amount || !solAddress || !evmAddress}
className="w-full py-4 bg-yellow text-black font-bold rounded-xl disabled:opacity-20 disabled:cursor-not-allowed"
>
{isSwapping ? 'Executing Swap...' : 'Swap SOL → USDC'}
</button>
</div>
);
}Example 2: EVM Token → Solana SPL Token Swap
This example swaps a token on an EVM chain (e.g., USDC on Avalanche) to an SPL token on Solana.
'use client';
import React, { useState } from 'react';
import { useSilentSwap, useSwap } from '@silentswap/react';
import { useAccount, useWalletClient } from 'wagmi';
import { useSolanaAdapter } from '@silentswap/react';
import { useUserAddress } from '@/hooks/useUserAddress';
export function EvmToSolanaSwap() {
const { evmAddress, solAddress } = useUserAddress();
const { isConnected } = useAccount();
const { data: walletClient } = useWalletClient();
const { solanaConnector, solanaConnectionAdapter } = useSolanaAdapter();
const {
executeSwap,
isSwapping,
currentStep,
orderId,
orderComplete,
swapError,
} = useSilentSwap();
const { tokenIn, inputAmount, setInputAmount, destinations, setDestinations } = useSwap();
const [amount, setAmount] = useState('');
const handleSwap = async () => {
if (!solAddress || !evmAddress) {
alert('Both Solana and EVM wallets must be connected');
return;
}
if (!solanaConnector?.publicKey) {
alert('Solana wallet not connected');
return;
}
try {
// Set source asset to USDC on Avalanche
setInputAmount(amount);
// Set destination to USDC SPL token on Solana
// CAIP-19 format: solana:<chainId>/erc20:<tokenAddress>
setDestinations([{
asset: 'solana:5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1/erc20:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', // USDC SPL
contact: `caip10:solana:*:${solAddress}`, // Recipient Solana address (base58)
amount: '',
}]);
// Execute swap - all parameters are required
const result = await executeSwap({
sourceAsset: 'eip155:43114/erc20:0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E', // USDC on Avalanche
sourceAmount: amount,
destinations: [{
asset: 'solana:5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1/erc20:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
contact: `caip10:solana:*:${solAddress}`, // Must be base58 Solana address in CAIP-10 format
amount: '',
}],
splits: [1], // Split percentages (must sum to 1.0)
senderContactId: `caip10:eip155:43114:${evmAddress}`, // EVM sender in CAIP-10 format
integratorId: process.env.NEXT_PUBLIC_INTEGRATOR_ID, // Optional: for tracking
});
if (result) {
console.log('Swap initiated:', result.orderId);
}
} catch (error) {
console.error('Swap failed:', error);
alert(`Swap failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
};
return (
<div className="flex flex-col gap-4 p-6 max-w-md mx-auto bg-zinc-900 rounded-2xl text-white">
<h2 className="text-2xl font-bold mb-4">Swap USDC → SOL USDC</h2>
<div className="flex flex-col gap-2">
<label className="text-sm text-zinc-400">USDC Amount (Avalanche)</label>
<input
type="text"
value={amount}
onChange={(e) => setAmount(e.target.value)}
placeholder="0.0"
className="p-4 bg-zinc-800 border-none rounded-xl text-xl outline-none"
disabled={isSwapping}
/>
<p className="text-xs text-zinc-500">
EVM Address: {evmAddress?.slice(0, 8)}...{evmAddress?.slice(-8)}
</p>
<p className="text-xs text-zinc-500">
Solana Address: {solAddress?.slice(0, 8)}...{solAddress?.slice(-8)}
</p>
</div>
{isSwapping && (
<div className="p-4 bg-yellow/10 border border-yellow/20 rounded-xl">
<p className="font-semibold text-yellow">Status: {currentStep || 'Processing'}</p>
</div>
)}
{swapError && (
<div className="p-4 bg-red-500/20 border border-red-500/50 rounded-xl">
<p className="text-red-400">Error: {swapError.message}</p>
</div>
)}
{orderComplete && orderId && (
<div className="p-4 bg-green-500/20 border border-green-500/50 rounded-xl">
<p className="text-green-400">Swap Complete! Order ID: {orderId}</p>
</div>
)}
<button
onClick={handleSwap}
disabled={isSwapping || !amount || !solAddress || !evmAddress}
className="w-full py-4 bg-yellow text-black font-bold rounded-xl disabled:opacity-20 disabled:cursor-not-allowed"
>
{isSwapping ? 'Executing Swap...' : 'Swap USDC → SOL USDC'}
</button>
</div>
);
}Key Points for Solana Swaps
Address Format Requirements
- Solana Addresses: Must be base58 format (e.g.,
9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM) - Contact IDs: Use CAIP-10 format:
- Solana:
caip10:solana:*:${solanaAddress} - EVM:
caip10:eip155:${chainId}:${evmAddress}
- Solana:
CAIP-19 Asset Identifiers
- Native SOL:
solana:<chainId>/slip44:501 - SPL Tokens:
solana:<chainId>/erc20:<tokenAddress>(base58 token mint address)
Important Notes
-
Dual Wallet Requirement: Both Solana and EVM wallets must be connected:
- Solana wallet: For signing Solana transactions (source swaps)
- EVM wallet: For facilitator operations and deposit calldata
-
Bridge Provider: Solana swaps automatically use relay.link for bridging to Avalanche
-
Recipient Address Validation:
- For Solana destinations, the
contactfield must contain a valid base58 Solana address - The SDK validates this automatically and will throw an error if invalid
- For Solana destinations, the
-
Deposit Calldata: For Solana source swaps, the deposit calldata uses the EVM signer address (not the Solana address), matching the quote request signer
Complete Provider Setup
// providers/provider.tsx
'use client';
import React from 'react';
import { SilentSwapProvider, useSolanaAdapter } from '@silentswap/react';
import { createSilentSwapClient, ENVIRONMENT } from '@silentswap/sdk';
import { useAccount, useWalletClient } from 'wagmi';
import { useUserAddress } from '@/hooks/useUserAddress';
const environment = ENVIRONMENT.STAGING;
const client = createSilentSwapClient({ environment });
export default function Provider({ children }: { children: React.ReactNode }) {
const { isConnected, connector } = useAccount();
const { data: walletClient } = useWalletClient();
const { evmAddress, solAddress } = useUserAddress();
// Solana adapter hook - required for Solana swaps
const { solanaConnector, solanaConnectionAdapter } = useSolanaAdapter();
return (
<SilentSwapProvider
client={client}
environment={environment}
evmAddress={evmAddress}
solAddress={solAddress}
isConnected={isConnected}
connector={connector}
walletClient={walletClient}
solanaConnector={solanaConnector}
solanaConnection={solanaConnectionAdapter}
solanaRpcUrl="https://api.mainnet-beta.solana.com" // Optional: custom RPC URL
>
{children}
</SilentSwapProvider>
);
}Error Handling
Common errors and solutions:
try {
await executeSwap({...});
} catch (error) {
if (error instanceof Error) {
if (error.message.includes('Solana address required')) {
// Solana wallet not connected
alert('Please connect your Solana wallet');
} else if (error.message.includes('EVM address required')) {
// EVM wallet not connected
alert('Please connect your EVM wallet');
} else if (error.message.includes('Invalid Solana recipient address')) {
// Invalid address format in destination contact field
alert('Invalid Solana address. Must be base58 format.');
} else if (error.message.includes('Price impact too high')) {
// Bridge price impact exceeded
alert('Price impact too high. Try a smaller amount.');
}
}
}Next Steps
- Review the Complete Example for general swap patterns
- Learn about Data & Utility Hooks for accessing balances and prices
- Check the Core SDK Solana Examples for backend implementations