Announcing our $20m venture round co-led by cyber•Fund and Blockchain Capital. Read more.
Get your first Solana cross-chain swap working in 5 minutes with this complete working example
pnpm install @solana/web3.js bs58 dotenv viem
SOLANA_PRIVATE_KEY=your_base58_private_key_here
ONEBALANCE_API_KEY=your_api_key_here
ARBITRUM_RECIPIENT=0x895Cf62399bF1F8b88195E741b64278b41EB7F09
import { Keypair, MessageV0, VersionedTransaction, PublicKey } from '@solana/web3.js';
import { formatUnits, parseUnits } from 'viem';
import bs58 from 'bs58';
import dotenv from 'dotenv';
// Load environment variables
dotenv.config();
// Configuration from environment
const SOLANA_PRIVATE_KEY = process.env.SOLANA_PRIVATE_KEY!;
const ARBITRUM_RECIPIENT = process.env.ARBITRUM_RECIPIENT!;
const API_KEY = process.env.ONEBALANCE_API_KEY || "42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11";
// Create Solana keypair from private key
const keypair = Keypair.fromSecretKey(bs58.decode(SOLANA_PRIVATE_KEY));
const SOLANA_ACCOUNT = keypair.publicKey.toString();
// Solana asset IDs
const SOL_ASSET_ID = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501";
const USDC_ARB_ASSET_ID = "eip155:42161/erc20:0xaf88d065e77c8cC2239327C5EDb3A432268e5831";
console.log(`Using Solana account: ${SOLANA_ACCOUNT}`);
console.log(`Arbitrum recipient: ${ARBITRUM_RECIPIENT}`);
/**
* Check SOL balance before swapping
*/
async function checkSOLBalance() {
try {
console.log('Checking SOL balance...');
const response = await fetch(
`https://be.onebalance.io/api/v3/balances/aggregated-balance?account=solana:${SOLANA_ACCOUNT}&aggregatedAssetId=ds:sol`,
{ headers: { 'x-api-key': API_KEY } }
);
if (!response.ok) {
throw new Error('Failed to fetch balance');
}
const balanceData = await response.json();
const solBalance = balanceData.balanceByAggregatedAsset?.find(
(asset: any) => asset.aggregatedAssetId === 'ds:sol'
);
if (!solBalance) {
throw new Error('No SOL balance found');
}
const balanceInSOL = parseFloat(formatUnits(BigInt(solBalance.balance), 9));
console.log(`Available SOL: ${balanceInSOL.toFixed(4)} SOL`);
return balanceInSOL;
} catch (error) {
console.error('Balance check failed:', error);
throw error;
}
}
/**
* Swap SOL to USDC on Arbitrum (cross-chain)
*/
async function swapSOLtoUSDC() {
try {
console.log('Starting SOL → USDC (Arbitrum) swap...\n');
// Validate Solana address format
try {
new PublicKey(SOLANA_ACCOUNT);
} catch (error) {
throw new Error('Invalid Solana address format');
}
// Check balance
const balance = await checkSOLBalance();
const swapAmount = 0.01; // 0.01 SOL
if (balance < swapAmount) {
throw new Error(`Insufficient balance. Need ${swapAmount} SOL, have ${balance.toFixed(4)} SOL`);
}
console.log(`Swapping ${swapAmount} SOL to USDC on Arbitrum...`);
// Step 1: Get quote
console.log('Getting quote...');
const quoteRequest = {
from: {
accounts: [{
type: "solana",
accountAddress: SOLANA_ACCOUNT
}],
asset: {
assetId: SOL_ASSET_ID
},
amount: parseUnits(swapAmount.toString(), 9).toString() // SOL has 9 decimals
},
to: {
asset: {
assetId: USDC_ARB_ASSET_ID // USDC on Arbitrum
},
account: `eip155:42161:${ARBITRUM_RECIPIENT}`
}
};
const quoteResponse = await fetch('https://be.onebalance.io/api/v3/quote', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': API_KEY
},
body: JSON.stringify(quoteRequest)
});
if (!quoteResponse.ok) {
const error = await quoteResponse.json();
throw new Error(`Quote failed: ${error.message}`);
}
const quote = await quoteResponse.json();
console.log('Quote received:', {
id: quote.id,
willReceive: `${formatUnits(BigInt(quote.destinationToken.amount), 6)} USDC`,
fiatValue: `$${quote.destinationToken.fiatValue}`,
fees: `$${quote.fees.cumulativeUSD}`
});
// Step 2: Sign the Solana operation
console.log('Signing Solana transaction...');
const solanaOperation = quote.originChainsOperations.find((op: any) => op.type === 'solana');
if (!solanaOperation) {
throw new Error('No Solana operation found in quote');
}
const signedOperation = signSolanaOperation(solanaOperation);
console.log('Transaction signed successfully');
const signedQuote = {
...quote,
originChainsOperations: quote.originChainsOperations.map((op: any) =>
op.type === 'solana' ? signedOperation : op
)
};
// Step 3: Execute the swap
console.log('Executing cross-chain swap...');
const executeResponse = await fetch('https://be.onebalance.io/api/v3/quote/execute', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': API_KEY
},
body: JSON.stringify(signedQuote)
});
if (!executeResponse.ok) {
const error = await executeResponse.json();
throw new Error(`Execution failed: ${error.message}`);
}
const result = await executeResponse.json();
console.log('Swap submitted successfully!');
// Step 4: Monitor transaction completion
console.log('Monitoring transaction...');
await monitorCompletion(quote.id);
console.log('Cross-chain swap completed successfully!');
console.log(`USDC delivered to Arbitrum address: ${ARBITRUM_RECIPIENT}`);
return result;
} catch (error) {
console.error('Swap failed:', (error as Error).message);
throw error;
}
}
// Real Solana signing implementation
function signSolanaOperation(chainOp: any): any {
try {
const msgBuffer = Buffer.from(chainOp.dataToSign, 'base64');
const message = MessageV0.deserialize(msgBuffer);
const transaction = new VersionedTransaction(message);
// Sign with keypair
transaction.sign([keypair]);
// Extract signature
const signature = bs58.encode(Buffer.from(transaction.signatures[transaction.signatures.length - 1]));
return {
...chainOp,
signature,
};
} catch (error) {
console.error('Signing failed:', error);
throw new Error(`Failed to sign Solana transaction: ${(error as Error).message}`);
}
}
// Monitor transaction completion
async function monitorCompletion(quoteId: string): Promise<void> {
const timeout = 60_000; // 60 seconds
const startTime = Date.now();
while (Date.now() - startTime < timeout) {
try {
const statusResponse = await fetch(
`https://be.onebalance.io/api/v3/status/get-execution-status?quoteId=${quoteId}`,
{ headers: { 'x-api-key': API_KEY } }
);
if (statusResponse.ok) {
const status = await statusResponse.json();
console.log(`Status: ${status.status.status}`);
if (status.status.status === 'COMPLETED') {
console.log('Transaction completed successfully!');
return;
} else if (status.status.status === 'FAILED') {
throw new Error('Transaction failed');
}
}
} catch (error) {
console.log('Status check error (retrying):', (error as Error).message);
}
// Wait 3 seconds before next check
await new Promise(resolve => setTimeout(resolve, 3000));
}
throw new Error('Transaction monitoring timeout');
}
// Run the swap
async function main() {
try {
await swapSOLtoUSDC();
} catch (error) {
console.error('Failed:', error);
process.exit(1);
}
}
main();
npx tsx swap.ts