Complete Code Repository
Get the full working example with setup instructions. 100% free and ready to run.
Setup
Install packages and create environment file:Copy
Ask AI
pnpm install @solana/web3.js bs58 dotenv viem
.env
Copy
Ask AI
SOLANA_PRIVATE_KEY=your_base58_private_key_here
ONEBALANCE_API_KEY=your_api_key_here
ARBITRUM_RECIPIENT=0x895Cf62399bF1F8b88195E741b64278b41EB7F09
Example
swap.ts
Copy
Ask AI
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=ob: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 === 'ob: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();
How It Works
The script performs a complete cross-chain swap with validation and monitoring:- Balance Check: Verify you have sufficient SOL for the swap
- Request Quote: Get pricing for 0.01 SOL → USDC on Arbitrum
- Sign Transaction: Sign the Solana operation with your private key
- Execute Swap: Submit the signed transaction to OneBalance
- Monitor Progress: Track the transaction until completion
Expected Result: ~0.015 USDC delivered to your Arbitrum address with full monitoring
Run the Example
Set your environment variables and run:Copy
Ask AI
npx tsx swap.ts
Make sure your Solana account has at least 0.01 SOL for the swap.
Next Steps
More Examples
Explore additional swap patterns and advanced operations
Contract Calls
Use Solana assets to fund smart contract calls on EVM chains
Got stuck? Check the Solana Troubleshooting Guide for common issues and solutions.