Skip to main content

How to Swap Assets Between EVM Chains and Solana Using Bungee

Bungee now supports swap assets from any EVM chain to Solana and from Solana to any EVM chain.

This guide walks you through swapping assets between EVM chains and Solana using Bungee. It covers supported chains, transaction options, and current limitations.

Quick Start​

info

Not all current API endpoints are supported on Solana. Please see the limitations below.

Solana Chain ID on Bungee API​

The chain ID for Solana queries is 89999.

Key differences​

Here's what's different when integrating Solana Bungee API compared to standard EVM chains:

Transaction Flow

  • No token approvals are needed on Solana
  • No multi-transaction routes supported

API Behavior

  • Both userAddress and recipient required for quotes
  • /tx-receipt endpoint not supported (use Solana RPC directly)
  • **/**approvals endpoint not applicable
  • Fee collection only works for EVM → Solana direction

Token Support

  • Any supported token can be sent TO Solana
  • Only specific tokens (ETH, USDC, USDT) can be received when bridging TO EVM
  • Same-chain Solana swaps not supported yet

Supported chains​

Currently, these chains are supported to bridge between EVM and Solana:

  • Ethereum
  • Optimism
  • Arbitrum
  • Polygon
  • Base
  • Avalanche
  • Binance Smart Chain
info

This chain list is for reference only. To ensure the chain is supported, please use the /to-token-list with the singleTxOnly flag set to true.

Single Transaction Bridging​

Bungee enables cross-chain swaps in a single transaction between Solana and EVM chains. But the direction of swaps and support determine which assets are supported:

  • EVM → Solana: Swap any supported token to Solana
  • Solana → EVM: Swap any supported token to specific EVM tokens only:
    • ETH on Ethereum, Optimism, Arbitrum and Base
    • USDC on all supported chains
    • USDT on Binance Smart Chain (BSC)

Integration Steps​

The "Single Transaction Bridging" guide from Bungee Docs outlines how to integrate a bridging process that allows users to swap and bridge tokens across chains in a single transaction on the source chain, eliminating the need for any transactions on the destination chain. This same process should be implemented when bridging to Solana.

  1. Select Chains: Users choose the source and destination chains, which determine the tokens available for bridging.

  2. Populate Token Lists: Fetch and display tokens that can be sent from the source chain and received on the destination chain using the /from-token-list and /to-token-list endpoints, respectively. Set the singleTxOnly flag to true on both endpoints to ensure only single-transaction routes are considered.

  3. Fetch Routes: After selecting the tokens, retrieve possible bridging routes using the /quote endpoint with the singleTxOnly flag set to true. This provides routes that involve only one user transaction for bridging.

    info

    For Solana quotes, please ensure both userAddress and recipient are defined.

  4. Token Approval: For ERC-20 tokens, users must grant permission for the protocol to spend their tokens. Check the token allowance via the check-allowance endpoint using the approvalData from the quote response. If insufficient, prompt the user to approve token spending.

    info

    Solana does not require token approvals - /check-allowance and approvalData should not be used when fromChain is 89999 (Solana).

  5. Build Transaction: Obtain the transaction data from the /server/build-tx endpoint. Use this data to execute the bridging transaction on the source chain.

  6. Track Transaction Status: Monitor the transaction's progress by polling the /bridge-status endpoint until the bridging process is complete.

Solana API Support and unavailable endpoints​

Supported Endpoints for Solana​

The following endpoints support Solana queries:

  • /token-price
  • /build-tx
  • /bridge-status
  • /quote
  • /supported/token-supported
  • /balances
  • /balances/token-balance
  • /token-lists/from-token-list
  • /token-lists/to-token-list
  • /token-lists/all
  • /token-lists/chain

Unavailable Endpoints for Solana​

The following endpoints currently return a 500 error for Solana queries:

  • /tx-receipt and /getTransactionReceipt

    info

    Querying the Solana RPC directly is recommended.

  • /approvals

    info

    Solana does not require token approvals - check-allowance and approvalData should not be used when fromChain is 89999 (Solana).

  • /route

    info

    The multi-transaction flow is not supported.

Examples​

Queries​

Quote from Base BRETT to Solana TRUMP

https://api.socket.tech/v2/quote?fromChainId=8453&fromTokenAddress=0xB4fDe59a779991bfB6a52253B51947828b982be3&toChainId=89999&toTokenAddress=6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN&fromAmount=10000000000000000000000000&userAddress=0x3e8cB4bd04d81498aB4b94a392c334F5328b237b&recipient=7BchahMyqpBZYmQS3QnbY3kRweDLgPCRpWdo1rxWmJ3g&uniqueRoutesPerBridge=true&sort=output&feePercent=1&feeTakerAddress=0x3e8cB4bd04d81498aB4b94a392c334F5328b237b

Check if MELANIA token on Solana is supported

https://api.socket.tech/v2/supported/token-support?chainId=89999&address=FUAfBo2jgks6gB4Z4LfZkqSZgzNucisEHqnNebaRxM1P

Check user balance on Solana

Querying the Solana RPC directly is recommended, notheless the current API supports: https://api.socket.tech/v2/balances?userAddress=7BchahMyqpBZYmQS3QnbY3kRweDLgPCRpWdo1rxWmJ3g

Check all supported tokens on Solana

https://api.socket.tech/v2/token-lists/chain?chainId=89999&isShortList=false

Scripts​

Quote and swap from Polygon USDC to Solana SOL (Ethers)
// @ts-nocheck
import axios from 'axios';
import { ethers } from 'ethers';

// Environment variables
const SOLANA_WALLET_ADDRESS = process.env.SOLANA_WALLET_ADDRESS;
const EVM_WALLET_ADDRESS = process.env.EVM_WALLET_ADDRESS;
const EVM_PRIVATE_KEY = process.env.EVM_PRIVATE_KEY;

// API information
const SOCKET_API_BASE_URL = 'https://api.socket.tech/v2';
const SOCKET_API_KEY = process.env.SOCKET_API_KEY || '72a5b4b0-e727-48be-8aa1-5da9d62fe635'; // Bungee public API key
const POLYGON_RPC_URL = 'https://polygon-rpc.com';

// Contract Addresses
const SOLANA_TOKEN_ADDRESS = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'; // native
const POLYGON_USDC = '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359';
const SOCKET_GATEWAY_CONTRACT = '0x3a23F943181408EAC424116Af7b7790c94Cb97a5';

// Transaction details
const TX_GAS_LIMIT = 420000;
const SWAP_AMOUNT = '6000000';
const DEFAULT_SWAP_SLIPPAGE = '0.5';
const FROM_CHAIN_ID = '137'; // Polygon chain ID
const TO_CHAIN_ID = '89999'; // Bungee Solana chain address

async function getQuote() {
const config = {
method: 'get',
maxBodyLength: Infinity,
url:
new URL(`${SOCKET_API_BASE_URL}/quote`).toString() +
'?' +
new URLSearchParams({
userAddress: EVM_WALLET_ADDRESS,
singleTxOnly: 'true',
bridgeWithGas: 'false',
sort: 'output',
defaultSwapSlippage: DEFAULT_SWAP_SLIPPAGE,
isContractCall: 'false',
fromChainId: FROM_CHAIN_ID,
showAutoRoutes: 'false',
toChainId: TO_CHAIN_ID,
fromTokenAddress: POLYGON_USDC,
toTokenAddress: SOLANA_TOKEN_ADDRESS,
fromAmount: SWAP_AMOUNT,
recipient: SOLANA_WALLET_ADDRESS,
}).toString(),
headers: {
'accept-language': 'en-GB,en;q=0.7',
'api-key': SOCKET_API_KEY,
},
};

const resp = await axios.request(config);

const quoteResp = JSON.stringify(resp.data);
const quotes = JSON.parse(quoteResp).result.routes;

await getbuildtx(quotes[0]);
}

async function getbuildtx(data: string): Promise<void> {
const config = {
method: 'post',
maxBodyLength: Infinity,
url: `${SOCKET_API_BASE_URL}/build-tx`,
headers: {
'api-key': SOCKET_API_KEY,
'Content-Type': 'application/json',
},
data: { route: data },
};

const resp = await axios.request(config);

const buildtxResp = JSON.stringify(resp?.data);
const buildtxData = JSON.parse(buildtxResp).result;
const txData = JSON.parse(buildtxResp).result.txData;

const provider = new ethers.providers.StaticJsonRpcProvider(POLYGON_RPC_URL);
const signer = new ethers.Wallet(EVM_PRIVATE_KEY, provider);
const gasPrice = await provider.getGasPrice();

console.log({ buildtxData });

const erc20Abi = ['function approve(address spender, uint256 amount) public returns (bool)'];

const tokenContract = new ethers.Contract(
buildtxData.approvalData.approvalTokenAddress,
erc20Abi,
signer
);

try {
// Send the approval transaction
const tx = await tokenContract.approve(
buildtxData.txTarget,
buildtxData.approvalData.minimumApprovalAmount,
{
gasPrice,
gasLimit: TX_GAS_LIMIT,
}
);

console.log('Transaction sent! Waiting for confirmation...', tx);

// Wait for the transaction to be confirmed
const receipt = await tx.wait();
console.log('Approval successful!');
console.log(`Transaction hash: ${receipt.transactionHash}`);
} catch (error) {
console.error('Error during approval:', error);
return;
}

const tx = {
to: SOCKET_GATEWAY_CONTRACT,
data: txData,
gasLimit: ethers.BigNumber.from(TX_GAS_LIMIT),
gasPrice,
};

const transactionResponse = await signer.sendTransaction(tx);

console.log('Transaction sent. Hash:', transactionResponse.hash);

const receipt = await transactionResponse.wait();
console.log('Transaction confirmed. Receipt:', receipt);
}

(async () => {
await getQuote();
})();
Quote and swap from Polygon USDC to Solana SOL (Viem)
import axios from 'axios';
import { createPublicClient, createWalletClient, http, parseUnits } from 'viem';
import { polygon } from 'viem/chains';
import { privateKeyToAccount } from 'viem/accounts';

// Environment variables
const SOLANA_WALLET_ADDRESS = process.env.SOLANA_WALLET_ADDRESS;
const EVM_WALLET_ADDRESS = process.env.EVM_WALLET_ADDRESS;
const EVM_PRIVATE_KEY = process.env.EVM_PRIVATE_KEY;

// API information
const SOCKET_API_BASE_URL = 'https://api.socket.tech/v2';
const SOCKET_API_KEY = process.env.SOCKET_API_KEY || '72a5b4b0-e727-48be-8aa1-5da9d62fe635'; // Bungee public API key
const POLYGON_RPC_URL = 'https://polygon-rpc.com';

// Contract Addresses
const SOLANA_TOKEN_ADDRESS = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'; // native
const POLYGON_USDC = '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359';
const SOCKET_GATEWAY_CONTRACT = '0x3a23F943181408EAC424116Af7b7790c94Cb97a5';

// Transaction details
const TX_GAS_LIMIT = 420000n;
const SWAP_AMOUNT = 6000000n;
const DEFAULT_SWAP_SLIPPAGE = 0.5;
const FROM_CHAIN_ID = 137n; // Polygon chain ID
const TO_CHAIN_ID = 89999n; // Bungee Solana chain address

// ERC-20 Approval ABI
const erc20Abi = [
{
name: 'approve',
type: 'function',
inputs: [
{ name: 'spender', type: 'address' },
{ name: 'amount', type: 'uint256' }
],
outputs: [{ type: 'bool' }]
}
] as const;

async function getQuote() {
const config = {
method: 'get',
maxBodyLength: Infinity,
url:
new URL(`${SOCKET_API_BASE_URL}/quote`).toString() +
'?' +
new URLSearchParams({
userAddress: EVM_WALLET_ADDRESS,
singleTxOnly: 'true',
bridgeWithGas: 'false',
sort: 'output',
defaultSwapSlippage: DEFAULT_SWAP_SLIPPAGE.toString(),
isContractCall: 'false',
fromChainId: FROM_CHAIN_ID.toString(),
showAutoRoutes: 'false',
toChainId: TO_CHAIN_ID.toString(),
fromTokenAddress: POLYGON_USDC,
toTokenAddress: SOLANA_TOKEN_ADDRESS,
fromAmount: SWAP_AMOUNT.toString(),
recipient: SOLANA_WALLET_ADDRESS,
}).toString(),
headers: {
'accept-language': 'en-GB,en;q=0.7',
'api-key': SOCKET_API_KEY,
},
};

const resp = await axios.request(config);

const quoteResp = JSON.stringify(resp.data);
const quotes = JSON.parse(quoteResp).result.routes;

await buildAndSendTx(quotes[0]);
}

async function buildAndSendTx(data: string): Promise<void> {
const config = {
method: 'post',
maxBodyLength: Infinity,
url: `${SOCKET_API_BASE_URL}/build-tx`,
headers: {
'api-key': SOCKET_API_KEY,
'Content-Type': 'application/json',
},
data: { route: data },
};

const resp = await axios.request(config);

const buildtxResp = JSON.stringify(resp?.data);
const buildtxData = JSON.parse(buildtxResp).result;
const txData = JSON.parse(buildtxResp).result.txData;

// Create Viem clients
const publicClient = createPublicClient({
chain: polygon,
transport: http(POLYGON_RPC_URL)
});

const account = privateKeyToAccount(`0x${EVM_PRIVATE_KEY}`);

const walletClient = createWalletClient({
account,
chain: polygon,
transport: http(POLYGON_RPC_URL)
});

// Get gas price
const gasPrice = await publicClient.getGasPrice();

console.log({ buildtxData });

// Perform token approval
try {
const approveTx = await walletClient.writeContract({
address: buildtxData.approvalData.approvalTokenAddress as `0x${string}`,
abi: erc20Abi,
functionName: 'approve',
args: [
buildtxData.txTarget as `0x${string}`,
BigInt(buildtxData.approvalData.minimumApprovalAmount)
],
gasPrice,
gas: TX_GAS_LIMIT
});

console.log('Approval Transaction sent! Hash:', approveTx);

// Wait for transaction confirmation
const approvalReceipt = await publicClient.waitForTransactionReceipt({
hash: approveTx
});
console.log('Approval successful!', approvalReceipt);
} catch (error) {
console.error('Error during approval:', error);
return;
}

// Send bridge transaction
try {
const bridgeTx = await walletClient.sendTransaction({
to: SOCKET_GATEWAY_CONTRACT as `0x${string}`,
data: txData as `0x${string}`,
gasPrice,
gas: TX_GAS_LIMIT
});

console.log('Bridge Transaction sent. Hash:', bridgeTx);

// Wait for transaction confirmation
const receipt = await publicClient.waitForTransactionReceipt({
hash: bridgeTx
});
console.log('Bridge Transaction confirmed:', receipt);
} catch (error) {
console.error('Error during bridge transaction:', error);
}
}

(async () => {
await getQuote();
})();
Quote and swap from Solana SOL to Polygon USDC (Solana/web3.js)
// @ts-nocheck
import axios from 'axios';
import * as solanaWeb3 from '@solana/web3.js';
import { getAddressLookupTableAccounts } from '@mayanfinance/swap-sdk';
import KEY from '/keypath/.config/solana/id.json'; // SOLANA PRIVATE KEY

// Environment variables
const SOLANA_WALLET_ADDRESS = process.env.SOLANA_WALLET_ADDRESS;
const EVM_WALLET_ADDRESS = process.env.EVM_WALLET_ADDRESS;

// API Information
const SOCKET_API_BASE_URL = 'https://api.socket.tech/v2';
const SOCKET_API_KEY = process.env.SOCKET_API_KEY || '72a5b4b0-e727-48be-8aa1-5da9d62fe635'; // Bungee public API key

// Token Addresses
const SOLANA_TOKEN_ADDRESS = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'; // native
const POLYGON_USDC = '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359';

// Transaction Parameters
const SWAP_AMOUNT = '50000000';
const DEFAULT_SWAP_SLIPPAGE = '0.5';
const FROM_CHAIN_ID = '89999'; // SOCKET Solana chain address
const TO_CHAIN_ID = '137'; // Polygon chain ID

async function getQuote() {
const config = {
method: 'get',
maxBodyLength: Infinity,
url:
new URL(`${SOCKET_API_BASE_URL}/quote`).toString() +
'?' +
new URLSearchParams({
userAddress: SOLANA_WALLET_ADDRESS,
singleTxOnly: 'true',
bridgeWithGas: 'false',
sort: 'output',
defaultSwapSlippage: DEFAULT_SWAP_SLIPPAGE,
isContractCall: 'false',
fromChainId: FROM_CHAIN_ID,
showAutoRoutes: 'false',
toChainId: TO_CHAIN_ID,
fromTokenAddress: SOLANA_TOKEN_ADDRESS,
toTokenAddress: POLYGON_USDC,
fromAmount: SWAP_AMOUNT,
recipient: EVM_WALLET_ADDRESS,
}).toString(),
headers: {
'accept-language': 'en-GB,en;q=0.7',
'api-key': SOCKET_API_KEY,
},
};

const resp = await axios.request(config);

const quoteResp = JSON.stringify(resp.data);
const quotes = JSON.parse(quoteResp).result.routes;

await getbuildtx(quotes[0]);
}

async function getbuildtx(data: string) {
const config = {
method: 'post',
maxBodyLength: Infinity,
url: `${SOCKET_API_BASE_URL}/build-tx`,
headers: {
'api-key': SOCKET_API_KEY,
'Content-Type': 'application/json',
},
data: { route: data },
};

const resp = await axios.request(config);

const buildtxResp = JSON.stringify(resp?.data);
const txData = JSON.parse(buildtxResp).result.txData;

const clientInstructions = txData.instructions.map(
(instruction) =>
new solanaWeb3.TransactionInstruction({
programId: new solanaWeb3.PublicKey(instruction.programId),
keys: instruction.keys.map((key) => ({
pubkey: new solanaWeb3.PublicKey(key.pubkey),
isSigner: key.isSigner,
isWritable: key.isWritable,
})),
data: Buffer.from(instruction.data),
}),
);

const solanaConnection = new solanaWeb3.Connection(
solanaWeb3.clusterApiUrl('mainnet-beta'),
);

const clientLookupTables = await getAddressLookupTableAccounts(
txData.lookupTables,
solanaConnection,
);

const _signers = txData.signers.map((signer) =>
solanaWeb3.Keypair.fromSecretKey(Uint8Array.from(signer)),
);

const feePayer = new solanaWeb3.PublicKey(SOLANA_WALLET_ADDRESS);
const { blockhash, lastValidBlockHeight } =
await solanaConnection.getLatestBlockhash('finalized');

const message = solanaWeb3.MessageV0.compile({
instructions: clientInstructions,
payerKey: feePayer,
recentBlockhash: blockhash,
addressLookupTableAccounts: clientLookupTables,
});
const transaction = new solanaWeb3.VersionedTransaction(message);
transaction.sign(_signers);

const privateKey = Uint8Array.from(KEY);
const keypair = solanaWeb3.Keypair.fromSecretKey(privateKey);
transaction.recentBlockhash = blockhash;
transaction.feePayer = keypair.publicKey;
transaction.sign([keypair]);

const fee = await transaction.getEstimatedFee(solanaConnection);

// Convert lamports to SOL
const feeInSol = fee / solanaWeb3.LAMPORTS_PER_SOL;

const signature = await solanaConnection.sendRawTransaction(
transaction.serialize(),
{
skipPreflight: false,
preflightCommitment: 'confirmed',
},
);

// Optional: Wait for confirmation
const confirmation = await solanaConnection.confirmTransaction({
signature,
blockhash: blockhash,
lastValidBlockHeight: lastValidBlockHeight,
});

if (confirmation.value.err) {
throw new Error(`Transaction failed: ${confirmation.value.err}`);
}
console.log({ signature });

return signature;
}

(async () => {
await getQuote();
})();