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​
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
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.
-
Select Chains: Users choose the source and destination chains, which determine the tokens available for bridging.
-
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 thesingleTxOnly
flag totrue
on both endpoints to ensure only single-transaction routes are considered. -
Fetch Routes: After selecting the tokens, retrieve possible bridging routes using the
/quote
endpoint with thesingleTxOnly
flag set totrue
. This provides routes that involve only one user transaction for bridging.infoFor Solana quotes, please ensure both
userAddress
andrecipient
are defined. -
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 theapprovalData
from the quote response. If insufficient, prompt the user to approve token spending.infoSolana does not require token approvals -
/check-allowance
andapprovalData
should not be used whenfromChain
is89999
(Solana). -
Build Transaction: Obtain the transaction data from the
/server/build-tx
endpoint. Use this data to execute the bridging transaction on the source chain. -
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
infoQuerying the Solana RPC directly is recommended.
-
/approvals
infoSolana does not require token approvals -
check-allowance
andapprovalData
should not be used whenfromChain
is89999
(Solana). -
/route
infoThe 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();
})();