Skip to content

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

  1. Solana Addresses: Must be base58 format (e.g., 9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM)
  2. Contact IDs: Use CAIP-10 format:
    • Solana: caip10:solana:*:${solanaAddress}
    • EVM: caip10:eip155:${chainId}:${evmAddress}

CAIP-19 Asset Identifiers

  • Native SOL: solana:<chainId>/slip44:501
  • SPL Tokens: solana:<chainId>/erc20:<tokenAddress> (base58 token mint address)

Important Notes

  1. 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
  2. Bridge Provider: Solana swaps automatically use relay.link for bridging to Avalanche

  3. Recipient Address Validation:

    • For Solana destinations, the contact field must contain a valid base58 Solana address
    • The SDK validates this automatically and will throw an error if invalid
  4. 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