OneBalance Dashboard
Account Information
Chain-Abstracted Swap
Error
{error}
Success!
Your chain-abstracted swap has been initiated. {isPolling && ' Monitoring transaction status...'}
# Building with LLMs
Source: https://docs.onebalance.io/ai/building-with-llms
Learn how to use OneBalance with LLMs like ChatGPT or Claude. Our docs are AI-ready, making it easy to integrate into your models and dev tools.
## Using AI to integrate OneBalance
OneBalance documentation is AI-enhanced. Whether you're using ChatGPT, Claude, or a custom LLM integration, we've made it easy to feed OneBalance docs directly into your models and dev tools.
## LLM Feed Files
We expose two continuously updated files for AI ingestion:
* [`llms.txt`](https://docs.onebalance.io/llms.txt) - Concise list of top-level pages for quick context
* [`llms-full.txt`](https://docs.onebalance.io/llms-full.txt) - Complete documentation for full-context indexing
Use these URLs in your custom GPTs or LLM apps to ensure accurate OneBalance-specific answers.
## Plain Text Docs
Access any documentation page as markdown by adding `.md` to the URL. For example: [/ai/building-with-llms.md](/ai/building-with-llms.md)
This helps AI tools consume content with:
* Fewer formatting tokens
* Complete content (including hidden tabs)
* Proper markdown hierarchy
## Code Editor Integration
### Cursor Setup
1. Navigate to **Cursor Settings** > **Indexing & Docs**
2. Select "Add Doc" and paste:
```
https://docs.onebalance.io/llms-full.txt
```
3. Use `@docs -> OneBalance` to reference docs in your code
## MCP Server Integration
Connect to OneBalance directly through our hosted MCP server at:
```
https://docs.onebalance.io/mcp
```
Simply add this URL to your AI tool (Claude, Cursor, etc.) for instant access to OneBalance documentation search and API functions directly in your AI tools.
• Simplest in use and UI/UX
• Easy to integrate, if already in use within the application | • Contract upgrades, if required, are cumbersome |
| Role-Based | • Secure in emergency cases related to WaaS/TEE | • Consumes slightly more gas |
| EIP-7702 | • Preserves existing EOA addresses
• Gas efficient delegation model
• Built on ERC-4337 infrastructure
• Can act as both EOA and Smart Account | • Recently introduced by the Ethereum account standard, not yet widely battle-proven
• Not yet supported on all EVMs |
## Getting Started with Account Models
Follow the [Getting Started guide](/getting-started/introduction), which uses Role-Based configuration.
## Signer Compatibility
The OneBalance Toolkit is designed to work with various signer providers:
| Signer | Account | Standard Path (no RL) | Enabling Resource Lock |
| :------------ | :------------- | :---------------------- | :---------------------- |
| `turnkey` | `Basic` | 🟢 Available | 🟢 Available |
| | `EIP-7702` | 🟢 Available | 🟢 Available |
| | `Role-Based` | RL can't be disabled | 🟢 Available |
| | `solana` | 🟢 Available | In design |
| `privy` | `Basic` | 🟢 Available | 🟢 Available |
| | `EIP-7702` | 🟢 Available | In design |
| | `Role-Based` | RL can't be disabled | 🟢 Available |
| | `solana` | 🟢 Available | In design |
| Other signers | Other accounts | On demand, case by case | On demand, case by case |
## Account Components Explanation
The OneBalance account system consists of several key components that can be combined in different ways:
Critical Path:
Source chain finality + Destination chain inclusion
Typical Duration:
3 minutes or more
Critical Path:
Source chain inclusion + Routing + Destination chain inclusion
Typical Duration:
15-45 seconds
Critical Path:
Lock verification + Routing + Destination chain inclusion
Typical Duration:
5-10 seconds
(null); const [error, setError] = useState(null); const [success, setSuccess] = useState (false); const statusPollingRef = useRef (null); const [isPolling, setIsPolling] = useState(false); const [swapAmount, setSwapAmount] = useState('5.00'); const [estimatedAmount, setEstimatedAmount] = useState (null); const [fetchingQuote, setFetchingQuote] = useState(false); const [swapDirection, setSwapDirection] = useState<'USDC_TO_ETH' | 'ETH_TO_USDC'>('USDC_TO_ETH'); const [showUsdcChainDetails, setShowUsdcChainDetails] = useState(false); const [showEthChainDetails, setShowEthChainDetails] = useState(false); // Helper function to get chain name from chain ID const getChainName = (chainId: string): string => { const chainMap: Record = { '1': 'Ethereum', '137': 'Polygon', '42161': 'Arbitrum', '10': 'Optimism', '8453': 'Base', '59144': 'Linea', '43114': 'Avalanche' }; return chainMap[chainId] || `Chain ${chainId}`; }; const embeddedWallet = wallets.find(wallet => wallet.walletClientType === 'privy'); // Handle logout and redirect to home page const handleLogout = async () => { await logout(); router.push('/'); }; // Get OneBalance account address based on Privy wallet useEffect(() => { async function setupAccount() { if (embeddedWallet && embeddedWallet.address) { try { // For this demo, we'll use the same address as both session and admin const predictedAddress = await predictAccountAddress( embeddedWallet.address, embeddedWallet.address ); setAccountAddress(predictedAddress); // Get aggregated balance for USDC and ETH fetchBalances(predictedAddress); } catch (err) { console.error('Error setting up account:', err); setError('Failed to set up OneBalance account'); } } } if (ready && authenticated) { setupAccount(); } // Clean up polling interval on unmount return () => { if (statusPollingRef.current) { clearInterval(statusPollingRef.current); } }; }, [embeddedWallet, ready, authenticated]); // Handle swap direction toggle const toggleSwapDirection = () => { // Reset current quote and estimated amount setQuote(null); setEstimatedAmount(null); // Toggle direction setSwapDirection(prevDirection => prevDirection === 'USDC_TO_ETH' ? 'ETH_TO_USDC' : 'USDC_TO_ETH' ); // Reset swap amount to a sensible default based on direction if (swapDirection === 'USDC_TO_ETH') { // Switching to ETH -> USDC, set a default ETH amount (0.001 ETH) setSwapAmount('0.001'); } else { // Switching to USDC -> ETH, set a default USDC amount (5 USDC) setSwapAmount('5.00'); } // Fetch a new quote after a brief delay to ensure state is updated setTimeout(() => { if (accountAddress && embeddedWallet) { fetchEstimatedQuote(swapDirection === 'USDC_TO_ETH' ? '0.001' : '5.00'); } }, 300); }; // Handle swap amount change const handleSwapAmountChange = async (e: ChangeEvent ) => { const value = e.target.value; if (isNaN(Number(value)) || Number(value) <= 0) { return; } setSwapAmount(value); // Reset estimated amount when input changes setEstimatedAmount(null); // If we have a valid amount and an account, fetch a new quote if (Number(value) > 0 && accountAddress && embeddedWallet) { fetchEstimatedQuote(value); } }; // Fetch a quote for estimation purposes const fetchEstimatedQuote = async (amountStr: string) => { if (!accountAddress || !embeddedWallet) return; try { setFetchingQuote(true); // Convert to smallest unit based on direction // USDC has 6 decimals, ETH has 18 decimals const amount = swapDirection === 'USDC_TO_ETH' ? (parseFloat(amountStr) * 1_000_000).toString() // USDC -> ETH (6 decimals) : (parseFloat(amountStr) * 1e18).toString(); // ETH -> USDC (18 decimals) const quoteRequest = { from: { account: { sessionAddress: embeddedWallet.address, adminAddress: embeddedWallet.address, accountAddress: accountAddress, }, asset: { assetId: swapDirection === 'USDC_TO_ETH' ? 'ob:usdc' : 'ob:eth', }, amount, }, to: { asset: { assetId: swapDirection === 'USDC_TO_ETH' ? 'ob:eth' : 'ob:usdc', }, }, }; const quoteResponse = await getQuote(quoteRequest); setQuote(quoteResponse); // Extract estimated amount from the quote if (quoteResponse.destinationToken && quoteResponse.destinationToken.amount) { // Format based on direction if (swapDirection === 'USDC_TO_ETH') { // USDC -> ETH (ETH has 18 decimals) const ethAmount = parseFloat(formatUnits(BigInt(quoteResponse.destinationToken.amount), 18)).toFixed(6); setEstimatedAmount(ethAmount); } else { // ETH -> USDC (USDC has 6 decimals) const usdcAmount = parseFloat(formatUnits(BigInt(quoteResponse.destinationToken.amount), 6)).toFixed(2); setEstimatedAmount(usdcAmount); } } } catch (err) { console.error('Error fetching quote for estimation:', err); setEstimatedAmount(null); } finally { setFetchingQuote(false); } }; // Fetch USDC and ETH balances const fetchBalances = async (address: string) => { try { const balanceData = await getAggregatedBalance(address); // Find USDC in the balance data const usdcAsset = balanceData.balanceByAggregatedAsset.find( (asset: any) => asset.aggregatedAssetId === 'ob:usdc' ); // Find ETH in the balance data const ethAsset = balanceData.balanceByAggregatedAsset.find( (asset: any) => asset.aggregatedAssetId === 'ob:eth' ); if (usdcAsset) { // Format the balance (USDC has 6 decimals) const formattedBalance = parseFloat(formatUnits(BigInt(usdcAsset.balance), 6)).toFixed(2); setUsdcBalance(formattedBalance); // Extract individual chain balances for USDC if (usdcAsset.individualAssetBalances && usdcAsset.individualAssetBalances.length > 0) { const chainBalances = usdcAsset.individualAssetBalances .map((chainBalance: any) => { // Extract chain ID from assetType (format: eip155:CHAIN_ID/...) const chainId = chainBalance.assetType.split(':')[1].split('/')[0]; const formattedBalance = parseFloat(formatUnits(BigInt(chainBalance.balance), 6)).toFixed(2); return { chainId, balance: formattedBalance, numericBalance: parseFloat(formattedBalance), // For sorting assetType: chainBalance.assetType }; }) // Filter out zero balances .filter((chainBalance: {numericBalance: number}) => chainBalance.numericBalance > 0) // Sort by balance in descending order .sort((a: {numericBalance: number}, b: {numericBalance: number}) => b.numericBalance - a.numericBalance); setUsdcChainBalances(chainBalances); } } else { setUsdcBalance('0.00'); setUsdcChainBalances([]); } if (ethAsset) { // Format the balance (ETH has 18 decimals) const formattedBalance = parseFloat(formatUnits(BigInt(ethAsset.balance), 18)).toFixed(6); setEthBalance(formattedBalance); // Extract individual chain balances for ETH if (ethAsset.individualAssetBalances && ethAsset.individualAssetBalances.length > 0) { const chainBalances = ethAsset.individualAssetBalances .map((chainBalance: any) => { // Extract chain ID from assetType (format: eip155:CHAIN_ID/...) const chainId = chainBalance.assetType.split(':')[1].split('/')[0]; const formattedBalance = parseFloat(formatUnits(BigInt(chainBalance.balance), 18)).toFixed(6); return { chainId, balance: formattedBalance, numericBalance: parseFloat(formattedBalance), // For sorting assetType: chainBalance.assetType }; }) // Filter out zero balances .filter((chainBalance: {numericBalance: number}) => chainBalance.numericBalance > 0) // Sort by balance in descending order .sort((a: {numericBalance: number}, b: {numericBalance: number}) => b.numericBalance - a.numericBalance); setEthChainBalances(chainBalances); } } else { setEthBalance('0.000000'); setEthChainBalances([]); } // After getting balances, fetch an initial quote estimate using the default amount if (address && embeddedWallet) { fetchEstimatedQuote(swapAmount); } } catch (err) { console.error('Error fetching balances:', err); setUsdcBalance('0.00'); setEthBalance('0.000000'); setUsdcChainBalances([]); setEthChainBalances([]); } }; // Poll for transaction status const startStatusPolling = (quoteId: string) => { if (statusPollingRef.current) { clearInterval(statusPollingRef.current); } setIsPolling(true); statusPollingRef.current = setInterval(async () => { try { const statusData = await checkTransactionStatus(quoteId); setStatus(statusData); // If the transaction is completed or failed, stop polling if (statusData.status === 'COMPLETED' || statusData.status === 'FAILED') { if (statusPollingRef.current) { clearInterval(statusPollingRef.current); setIsPolling(false); } // Refresh balances after transaction is completed if (accountAddress && statusData.status === 'COMPLETED') { fetchBalances(accountAddress); } } } catch (err) { console.error('Error polling transaction status:', err); if (statusPollingRef.current) { clearInterval(statusPollingRef.current); setIsPolling(false); } } }, 1000); // Poll every 1 second }; // Request and execute a chain-abstracted swap const handleSwap = async () => { if (!accountAddress || !embeddedWallet) { setError('Wallet not connected or OneBalance account not set up'); return; } setLoading(true); setSwapping(true); setError(null); setSuccess(false); try { // Use already fetched quote if available let quoteResponse = quote; // If no quote available or it's stale, fetch a new one if (!quoteResponse) { // Convert to smallest unit based on direction const amount = swapDirection === 'USDC_TO_ETH' ? (parseFloat(swapAmount) * 1_000_000).toString() // USDC -> ETH (6 decimals) : (parseFloat(swapAmount) * 1e18).toString(); // ETH -> USDC (18 decimals) const quoteRequest = { from: { account: { sessionAddress: embeddedWallet.address, adminAddress: embeddedWallet.address, accountAddress: accountAddress, }, asset: { assetId: swapDirection === 'USDC_TO_ETH' ? 'ob:usdc' : 'ob:eth', }, amount, }, to: { asset: { assetId: swapDirection === 'USDC_TO_ETH' ? 'ob:eth' : 'ob:usdc', }, }, }; quoteResponse = await getQuote(quoteRequest); setQuote(quoteResponse); } if (!quoteResponse) { throw new Error('Failed to get a quote for the swap'); } // Step 2: Sign the quote const signedQuote = await signQuote(quoteResponse, embeddedWallet); // Step 3: Execute the quote setLoading(true); const executionResponse = await executeQuote(signedQuote); // Step 4: Start polling for transaction status startStatusPolling(quoteResponse.id); setSuccess(true); setLoading(false); } catch (err: any) { console.error('Error in swap process:', err); setError(err.message || 'Failed to complete swap'); setLoading(false); } finally { setSwapping(false); } }; if (!ready || !authenticated) { return ( ); } return (); } ``` When the swap is successfully completed, users can see the status of the transaction:  ## Understanding the Chain-Abstracted Swap Flow This improved implementation offers several key enhancements over the basic version: 1. **Bidirectional Swapping** * Users can swap from USDC → ETH or ETH → USDC * The UI dynamically updates based on the selected direction 2. **Real-Time Quote Estimation** * When users input an amount, a quote is fetched to show the estimated output * This provides transparency about the expected exchange rate 3. **Balance Display** * Shows users their current USDC and ETH balances from across all chains * Updates balances automatically after a successful swap 4. **Robust Transaction Polling** * Implements a proper polling mechanism to track transaction status * Handles different transaction states (pending, completed, failed) * Automatically stops polling when the transaction completes or fails 5. **Error Handling & Recovery** * Provides clear error messages when issues occur * Implements clean-up logic to prevent resource leaks (clearing intervals) 6. **Enhanced User Experience** * Shows loading indicators during swap operations * Provides visual feedback for transaction states * Includes links to block explorers to view transactions ## Multichain Asset Visibility One of the most powerful aspects of this implementation is how it handles multichain asset visibility. When users interact with the application: * They see their USDC and ETH balances as a **single aggregated total** across all supported chains * They can select "from" and "to" assets without needing to specify which chain they're on * The underlying complexity of potentially interacting with multiple blockchains is completely hidden This creates an abstraction layer where users don't need to think about chains at all - they simply work with tokens as if they existed in a unified space, which dramatically simplifies the user experience. ## Key Technical Implementations ### 1. Transaction Status Polling ```typescript status-polling.ts theme={null} // Poll for transaction status const startStatusPolling = (quoteId: string) => { if (statusPollingRef.current) { clearInterval(statusPollingRef.current); } setIsPolling(true); statusPollingRef.current = setInterval(async () => { try { const statusData = await checkTransactionStatus(quoteId); setStatus(statusData); // If the transaction is completed or failed, stop polling if (statusData.status.status === 'COMPLETED' || statusData.status.status === 'FAILED') { if (statusPollingRef.current) { clearInterval(statusPollingRef.current); setIsPolling(false); } // Refresh balances after transaction is completed if (accountAddress && statusData.status.status === 'COMPLETED') { fetchBalances(accountAddress); } } } catch (err) { console.error('Error polling transaction status:', err); if (statusPollingRef.current) { clearInterval(statusPollingRef.current); setIsPolling(false); } } }, 1000); // Poll every 1 second }; ``` OneBalance Dashboard
{/* Account Info Section */}Connected as{user?.email?.address || embeddedWallet?.address || 'Anonymous'}Account Information
OneBalance Smart Account{accountAddress || }USDC Balance{usdcBalance ? `${usdcBalance} USDC` : }setShowUsdcChainDetails(!showUsdcChainDetails)}> {showUsdcChainDetails ? "Hide chain details" : "Show chain details"}{showUsdcChainDetails && ({usdcChainBalances.length > 0 ? ( usdcChainBalances.map((chainBalance, idx) => ()}{getChainName(chainBalance.chainId)} {chainBalance.balance} USDC)) ) : (No chain data available)}ETH Balance{ethBalance ? `${ethBalance} ETH` : }setShowEthChainDetails(!showEthChainDetails)}> {showEthChainDetails ? "Hide chain details" : "Show chain details"}{showEthChainDetails && ({ethChainBalances.length > 0 ? ( ethChainBalances.map((chainBalance, idx) => ()}{getChainName(chainBalance.chainId)} {chainBalance.balance} ETH)) ) : (No chain data available)}{error && (Chain-Abstracted Swap
From{swapDirection === 'USDC_TO_ETH' ? 'USDC' : 'ETH'}on any chain↔To{fetchingQuote ? () : estimatedAmount ? ( `${estimatedAmount} ${swapDirection === 'USDC_TO_ETH' ? 'ETH' : 'USDC'}` ) : ( swapDirection === 'USDC_TO_ETH' ? 'ETH' : 'USDC' )}on any chainNo gas tokens needed - pay fees with your {swapDirection === 'USDC_TO_ETH' ? 'USDC' : 'ETH'} balance!)} {success && (Error
{error}
)} {status && (Success!
Your chain-abstracted swap has been initiated. {isPolling && ' Monitoring transaction status...'}
)}Transaction Status
Status: {status.status || 'Pending'}{status.originChainOperations?.length > 0 && (Origin Chain: View Transaction)} {status.destinationChainOperations?.length > 0 && (Destination Chain: View Transaction)}This polling mechanism uses the [Get Execution Status](/api-reference/status/get-quote-status) endpoint to track transaction progress in real-time. ### 2. Signing with Privy A critical part of the implementation is securely signing operations using the Privy wallet. The signing process handles: * Converting the typed data format required by EIP-712 * Managing the signature process using the embedded wallet * Ensuring all operations in a quote are properly signed * Handling both origin and destination chain operations ```typescript swap-flow.ts theme={null} // Example of the signing flow const handleSwap = async () => { // ... other code // Get the quote from OneBalance const quoteResponse = await getQuote(quoteRequest); // Sign the quote using the Privy wallet const signedQuote = await signQuote(quoteResponse, embeddedWallet); // Execute the signed quote await executeQuote(signedQuote); // ... other code }; ``` ### 3. Balance Checking Across Chains ```typescript balance-checker.ts theme={null} // Fetch USDC and ETH balances const fetchBalances = async (address: string) => { try { const balanceData = await getAggregatedBalance(address); // Find USDC in the balance data const usdcAsset = balanceData.balanceByAggregatedAsset.find( (asset: any) => asset.aggregatedAssetId === 'ob:usdc' ); // Find ETH in the balance data const ethAsset = balanceData.balanceByAggregatedAsset.find( (asset: any) => asset.aggregatedAssetId === 'ob:eth' ); if (usdcAsset) { // Format the balance (USDC has 6 decimals) const formattedBalance = parseFloat(formatUnits(BigInt(usdcAsset.balance), 6)).toFixed(2); setUsdcBalance(formattedBalance); } if (ethAsset) { // Format the balance (ETH has 18 decimals) const formattedBalance = parseFloat(formatUnits(BigInt(ethAsset.balance), 18)).toFixed(6); setEthBalance(formattedBalance); } } catch (err) { console.error('Error fetching balances:', err); } }; ```This function leverages the [Aggregated Balance](/api-reference/balances/aggregated-balance) endpoint to retrieve all token balances across multiple chains with a single API call. ## What Makes This Implementation Powerful? 1. **Chain Abstraction** * Users can swap tokens across chains without even knowing which chains are involved * The UI treats USDC and ETH as unified assets regardless of their underlying chains 2. **Robust Error Handling** * Handles API errors gracefully * Provides clear feedback to users * Implements proper cleanup of resources 3. **Real-Time Transaction Tracking** * Users can see the status of their transactions as they progress * Provides links to block explorers for deeper inspection * Automatically refreshes balances when transactions complete 4. **Bidirectional Swapping** * Users can swap in either direction (USDC → ETH or ETH → USDC) * The UI adapts dynamically to the selected direction 5. **Gas Abstraction** * Users can pay transaction fees with the token they're swapping * No need to hold native gas tokens on each chain ## Next Steps Now that you've seen how to create a chain-abstracted swap interface with OneBalance, you can explore the complete working implementation:# Your First API Call Source: https://docs.onebalance.io/getting-started/first-api-call Learn to make your first OneBalance API call by setting up a smart contract account for chain-abstracted, gasless, cross-chain transactions. Before you can execute chain-abstracted operations with OneBalance, you need to set up a smart contract account (SCA). This smart contract account will hold your assets and enable gas abstraction across chains. Get the full working code example to start building immediately Discover all available endpoints in our API documentation **Key Concept**: OneBalance uses smart contract accounts (SCAs) to enable chain and gas abstraction. Your SCA is your actual deposit address where funds are stored, and your Privy wallet acts as a **signer** for this account. ## Predicting Your Account Address Now that we've set up our project with proper CORS handling, let's use the OneBalance API to predict a smart contract account (SCA) address based on Privy's embedded wallet addresses: First, create a component that will handle the account setup: ```tsx AccountSetup.tsx theme={null} // src/components/AccountSetup.tsx 'use client'; // Example usage in a component import { useEffect, useState } from 'react'; import { useWallets } from '@privy-io/react-auth'; import { predictAccountAddress } from '@/lib/onebalance'; export function AccountSetup() { const { wallets } = useWallets(); const [accountAddress, setAccountAddress] = useState(null); // Find the Privy-managed embedded wallet const embeddedWallet = wallets.find(wallet => wallet.walletClientType === 'privy'); useEffect(() => { async function setupAccount() { if (embeddedWallet && embeddedWallet.address) { try { // Using the same address as both session and admin for simplicity const predictedAddress = await predictAccountAddress( embeddedWallet.address, embeddedWallet.address ); setAccountAddress(predictedAddress); console.log(`Smart Contract Account Address: ${predictedAddress}`); } catch (err) { console.error('Error setting up account:', err); } } } setupAccount(); }, [embeddedWallet]); return ( {accountAddress ? (); } ```Your OneBalance Smart Account: {accountAddress}
) : (Loading account address...
)}This component uses the [Predict Address](/api-reference/account/predict-address) endpoint to determine your smart contract account address before it's deployed. Next, create a dashboard page that will use this component: ```tsx page.tsx theme={null} // src/app/dashboard/page.tsx import { AccountSetup } from '@/components/AccountSetup'; export default function Dashboard() { return (); } ``` When a user logs in with Privy on the homepage, they'll be redirected to this dashboard page where their predicted smart contract account address will be displayed. This is your first successful API call to OneBalance! The `predictAccountAddress` function makes an API call to OneBalance's `/account/predict-address` endpoint with the Privy wallet address as both the session and admin address. This deterministically calculates the smart contract account address that will be used for all your OneBalance operations. OneBalance Dashboard
When a user logs in with Privy via email (as shown in the previous section), they receive a verification code to complete the authentication process. Once verified, Privy creates an embedded wallet for them automatically, which becomes available through the `useWallets()` hook shown above. ## Understanding Smart Contract Accounts (SCAs) OneBalance uses smart contract accounts to enable key features like: 1. **Gas Abstraction**: Pay for transactions using any token in your balance, not just the chain's native gas token 2. **Resource Locks**: Secure validation of transaction intents without requiring on-chain confirmations for every step 3. **Fast Path**: Near-instant transactions without waiting for block confirmations**Important Account Relationship**: - The **SCA address** (predicted by `/account/predict-address`) is your deposit address where funds are stored - The **Privy-managed EOA wallet** acts as a **signer** for your SCA, authorizing transactions - Always send funds to the SCA address, not to the Privy wallet address Smart contract accounts are **counterfactual**, meaning they don't need to be deployed until they're actually used in a transaction. This saves gas costs and simplifies the user experience.The account setup in this quickstart uses a common configuration for ease of use. OneBalance offers more advanced modular account options for specialized use cases, which you can explore in our [Core Concepts: Account Models](/concepts/accounts) section. ## Checking Account Balances Before performing operations, you'll want to check if your account has sufficient funds. The OneBalance API allows you to fetch aggregated balances across all chains: ```typescript balanceChecker.ts theme={null} // Example of checking for USDC and ETH balances import { getAggregatedBalance } from '@/lib/onebalance'; import { formatUnits } from 'viem'; async function checkBalances(accountAddress: string) { try { const balanceData = await getAggregatedBalance(accountAddress); // Find USDC in the balance data const usdcAsset = balanceData.balanceByAggregatedAsset.find( (asset: any) => asset.aggregatedAssetId === 'ob:usdc' ); // Find ETH in the balance data const ethAsset = balanceData.balanceByAggregatedAsset.find( (asset: any) => asset.aggregatedAssetId === 'ob:eth' ); if (usdcAsset) { // Format the balance (USDC has 6 decimals) const formattedBalance = parseFloat(formatUnits(BigInt(usdcAsset.balance), 6)).toFixed(2); console.log(`USDC Balance: ${formattedBalance} USDC`); } if (ethAsset) { // Format the balance (ETH has 18 decimals) const formattedBalance = parseFloat(formatUnits(BigInt(ethAsset.balance), 18)).toFixed(6); console.log(`ETH Balance: ${formattedBalance} ETH`); } } catch (err) { console.error('Error fetching balances:', err); } } ```**Asset ID Format**: OneBalance uses the "ob:" prefix for aggregated asset identifiers (e.g., `ob:usdc`, `ob:eth`). This prefix indicates that these are aggregated assets that represent the same token across multiple chains. For example, `ob:usdc` represents USDC from Ethereum, Polygon, Arbitrum, and other supported networks as a single unified asset. Learn more about how aggregated assets work in [Aggregated Assets](/concepts/aggregated-assets). This example uses the [Get Aggregated Balance](/api-reference/balances/aggregated-balance) endpoint to fetch your token balances across all supported chains with a single API call. This function fetches the aggregated balance for an account and formats the values correctly: * USDC has 6 decimals, so we divide by 10^6 * ETH has 18 decimals, so we divide by 10^18 The balances shown represent the total amount available across all supported chains, providing users with a unified view of their assets.  Your dashboard will show aggregated balances across multiple chains as a single, unified total. For example, if you have 10 USDC on Ethereum, 5 USDC on Polygon, and 5 USDC on Arbitrum, your dashboard will display a total of 20 USDC. This multichain aggregation is a key benefit of using OneBalance, as it eliminates the need to track assets separately across different networks.Learn how OneBalance unifies tokens across multiple chains into single aggregated assets In the full implementation (next section), we'll enhance the dashboard to display both the aggregated balance and a breakdown of assets per chain. Users can toggle to see exactly how their funds are distributed across networks like Ethereum, Arbitrum, Optimism, and others. **Cross-Chain Visibility**: OneBalance automatically tracks your assets across all supported chains, so you don't need to switch networks or use separate wallets to view your complete holdings. All tokens of the same type (e.g., USDC) are presented as a single aggregated balance, even if they're distributed across multiple networks. ## Funding Your Account Before you can perform any operations with your smart contract account, you'll need to fund it. Send tokens to the `predictedAddress` returned from the API. This is your SCA's deposit address.Remember to send funds to the SCA address (predictedAddress), not to your Privy wallet addresses. The predictedAddress is where your funds will be stored and managed across all chains. ## Next Steps Now that you've learned how to set up a smart contract account and check balances, you're ready to execute chain-abstracted operations. In the next section, we'll use the predicted account address to create a quote for swapping tokens across chains, with no bridging required.Learn how to execute seamless chain-abstracted swaps using your new smart contract account # Introduction Source: https://docs.onebalance.io/getting-started/introduction Learn how to get started with the OneBalance Toolkit to begin removing onchain complexity like chains, bridges and gas in your app. OneBalance lets you create seamless chain-abstracted experiences with a single API. Instead of integrating with multiple blockchains individually, you can use OneBalance to access tokens across all chains with one simple integration. This guide uses the Role-Based account configuration. See more on configurations [here](/concepts/accounts).All examples in this guide are 100% free and open-source. Clone the repository to quickly get started, or try the [live demo app](https://onebalance-privy-demo.vercel.app). ## What You'll BuildBy the end of this quickstart, you'll create a simple application that lets users interact with tokens across multiple chains using OneBalance's chain abstraction capabilities. Follow our quickstart to make your first chain-abstracted transaction in under 5 minutes Access tokens across chains as if they were one token ## Key APIs Used in This Guide As you follow this guide, you'll interact with several core OneBalance APIs: * [Predict Account Address](/api-reference/account/predict-address) - Get your smart contract account address before deployment * [Get Aggregated Balance](/api-reference/balances/aggregated-balance) - View your token balances across all chains * [Get Quote](/api-reference/quotes/get-quote) - Request quotes for cross-chain swaps or transfers * [Execute Quote](/api-reference/quotes/execute-quote) - Execute your signed quotes * [Check Status](/api-reference/status/get-quote-status) - Track your transaction status Get API keys and configure your project with [Privy](https://www.privy.io) for social login Predict your account address and view aggregated balances across multiple chains in one place Swap tokens across chains with no bridging required Monitor the status of your operations across multiple chains You can learn more about these and other APIs in our [API Reference](/api-reference/introduction). ## Key BenefitsNo chains to manage - treat multichain interactions as single-chain operations No native gas tokens needed - pay fees with any token in your balance Seamlessly interact across chains without manual bridging Ready to get started? Let's begin with [setting up your environment](/getting-started/setup). # Set Up Your Environment Source: https://docs.onebalance.io/getting-started/setup Set up your development environment with OneBalance and Privy by configuring keys and settings for an enhanced embedded wallet experience. To create a truly seamless chain-abstracted experience, we'll combine OneBalance's chain abstraction with Privy's wallet infrastructure. This integration gives your users social login options and a smooth onboarding experience. Complete transactions up to 10x faster for supported routes Choose your preferred account model and wallet provider View all your assets in one place across chains **Skip the Setup?** If you want to skip the manual setup process, you can [clone the complete repository](https://github.com/OneBalance-io/onebalance-privy-demo) and jump straight to exploring the code. ```bash clone-and-run.sh theme={null} # Clone the repository git clone https://github.com/OneBalance-io/onebalance-privy-demo.git cd onebalance-privy-demo # Install dependencies pnpm install # Copy environment variables cp .env.example .env # Start the development server pnpm dev ```**Complete Integration**: This guide combines Privy for wallet management with OneBalance for chain-abstracted functionality, providing the best experience for your users. ## Prerequisites Before you begin, make sure you have: * [Node.js](https://nodejs.org/) version 20 or higher * We recommend using [nvm](https://github.com/nvm-sh/nvm) to manage your Node.js versions * [pnpm](https://pnpm.io/installation) package manager installed * [Privy Dashboard](https://console.privy.io) account for your application * For a faster quickstart, you can use our pre-configured Privy App ID: `cmb94tjcj001xle0mdeerz9vp` (recommended) * Alternatively, you can set up your own Privy application * OneBalance API Key - we'll use the public test key for this example: `42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11`The pre-configured keys and IDs provided in this guide are strictly for learning purposes. For any production application, you must use your own Privy App ID and OneBalance API Key. ## Understanding Smart Contract Accounts (SCAs) When you set up OneBalance with Privy, it's important to understand how the account structure works: 1. **Smart Contract Account (SCA)**: * This is your actual deposit address where funds are stored * The SCA enables **gas abstraction**, allowing you to pay for transactions using any token in your balance, not just the chain's native gas token * The SCA is controlled by signers (e.g., your Privy-managed key) 2. **Privy-Managed Key**: * The key created via Privy acts as a **signer** for your SCA * It allows you to authorize transactions from your SCA * It is NOT where your funds are stored - the SCA is 3. **Account Funding**: * When funding your OneBalance account, send assets to your SCA address (not the Privy EOA address) * These funds will then appear in your aggregated balance across chains If you don't have Node.js installed or need to update, we recommend using nvm: ```bash terminal-commands.sh theme={null} # Install nvm (if not already installed) curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash # Install and use Node.js 20 nvm install 20 nvm use 20 # Install pnpm globally # Using npm to install pnpm as a one-time step npm install -g pnpm ``` Set up a new React project using Next.js: ```bash project-setup.sh theme={null} pnpm create next-app onebalance-privy-demo cd onebalance-privy-demo ``` When prompted, select the following options: * Would you like to use TypeScript? › Yes * Would you like to use ESLint? › Yes * Would you like to use Tailwind CSS? › Yes * Would you like your code inside a `src/` directory? › Yes * Would you like to use App Router? (recommended) › Yes * Would you like to use Turbopack for `next dev`? › Yes * Would you like to customize the import alias (`@/*` by default)? › No  Install the required packages: ```bash install-deps.sh theme={null} pnpm add @privy-io/react-auth viem @tanstack/react-query dotenv axios ``` Create a `.env` file in the project root with your API keys: ```toml .env theme={null} # .env NEXT_PUBLIC_PRIVY_APP_ID=cmb94tjcj001xle0mdeerz9vp # For quickstart only; use your own in production NEXT_PUBLIC_ONEBALANCE_API_KEY=42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11 # For quickstart only ``` Alternatively, you can create the `.env` file using command line: ```bash create-env.sh theme={null} # Create .env file touch .env # Add environment variables echo "NEXT_PUBLIC_PRIVY_APP_ID=cmb94tjcj001xle0mdeerz9vp" >> .env echo "NEXT_PUBLIC_ONEBALANCE_API_KEY=42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11" >> .env ``` When working with the OneBalance API from a browser, you'll need to handle CORS restrictions. The recommended approach is to create a server-side proxy using Next.js API routes. Create a new file at `src/app/api/[...path]/route.ts`: ```typescript route.ts theme={null} // src/app/api/[...path]/route.ts import { NextRequest, NextResponse } from 'next/server'; // OneBalance API base URL and API key const API_BASE_URL = 'https://be.onebalance.io'; const API_KEY = process.env.NEXT_PUBLIC_ONEBALANCE_API_KEY || ''; export async function GET( request: NextRequest, { params }: { params: Promise<{ path: string[] }> } ) { const { path } = await params; const pathString = path.join('/'); const searchParams = request.nextUrl.searchParams; try { // Build the API URL with any query parameters const apiUrl = new URL(`/api/${pathString}`, API_BASE_URL); searchParams.forEach((value, key) => { apiUrl.searchParams.append(key, value); }); const response = await fetch(apiUrl.toString(), { headers: { 'Content-Type': 'application/json', 'x-api-key': API_KEY, }, }); const data = await response.json(); return NextResponse.json(data); } catch (error) { return NextResponse.json({ message: 'Failed to fetch data', error }, { status: 400 }); } } export async function POST( request: NextRequest, { params }: { params: Promise<{ path: string[] }> } ) { const { path } = await params; const pathString = path.join('/'); try { const body = await request.json(); const response = await fetch(`${API_BASE_URL}/api/${pathString}`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': API_KEY, }, body: JSON.stringify(body), }); const data = await response.json(); return NextResponse.json(data); } catch (error) { return NextResponse.json({ message: 'Failed to fetch data', error }, { status: 400 }); } } ``` This proxy implementation ensures proper [authentication](/api-reference/authentication) and CORS handling for all API calls. Learn more about [CORS handling](/api-reference/cors) in our documentation. This proxy will allow your frontend to make requests to the OneBalance API without CORS issues.Now that we have the CORS handling in place, let's create a client to interact with the OneBalance API through our proxy. Create a file at `src/lib/onebalance.ts`: ```typescript onebalance.ts theme={null} // src/lib/onebalance.ts import axios from 'axios'; // Create an axios client that points to our proxy export const apiClient = axios.create({ baseURL: '/api', headers: { 'Content-Type': 'application/json', }, }); // OneBalance API base URL and API key (for reference) export const API_BASE_URL = 'https://be.onebalance.io/api'; export const API_KEY = process.env.NEXT_PUBLIC_ONEBALANCE_API_KEY; // Predict account address for a user based on their Privy wallet export async function predictAccountAddress(sessionAddress: string, adminAddress: string) { try { const response = await apiClient.post('/account/predict-address', { sessionAddress, adminAddress }); return response.data?.predictedAddress; } catch (error) { console.error('Error predicting account address:', error); throw error; } } // Get aggregated balance for a smart account export async function getAggregatedBalance(address: string) { try { const response = await apiClient.get(`/v2/balances/aggregated-balance?address=${address}`); return response.data; } catch (error) { console.error('Error fetching aggregated balance:', error); throw error; } } // Get a quote for swapping or transferring tokens export async function getQuote(params: { from: { account: { sessionAddress: string; adminAddress: string; accountAddress: string; }; asset: { assetId: string; }; amount: string; }; to: { asset: { assetId: string; }; }; }) { try { const response = await apiClient.post('/v1/quote', params); return response.data; } catch (error) { console.error('Error getting quote:', error); throw error; } } // Execute a quote after getting user signature export async function executeQuote(signedQuote: any) { try { const response = await apiClient.post('/quotes/execute-quote', signedQuote); return response.data; } catch (error) { console.error('Error executing quote:', error); throw error; } } // Check transaction status export async function checkTransactionStatus(quoteId: string) { try { const response = await apiClient.get(`/status/get-execution-status?quoteId=${quoteId}`); return response.data; } catch (error) { console.error('Error checking transaction status:', error); throw error; } } ``` This client implements several core OneBalance API endpoints: * [Predict Account Address](/api-reference/account/predict-address) - Get a smart contract account address before deployment * [Get Aggregated Balance](/api-reference/balances/aggregated-balance) - View multichain token balances * [Get Quote](/api-reference/quotes/get-quote) - Request a quote for token swaps or transfers * [Execute Quote](/api-reference/quotes/execute-quote) - Execute a signed swap or transfer * [Check Transaction Status](/api-reference/status/get-quote-status) - Monitor transaction progress Create a Privy provider component to wrap your application. Create a new file at `src/components/PrivyProvider.tsx`: ```tsx PrivyProvider.tsx theme={null} // src/components/PrivyProvider.tsx 'use client'; import { PrivyProvider } from '@privy-io/react-auth'; export function PrivyClientProvider({ children }: { children: React.ReactNode }) { const privyAppId = process.env.NEXT_PUBLIC_PRIVY_APP_ID as string; // Configure Privy with your app ID return ( {children} ); } ```Update your app's layout to include the Privy provider: ```tsx layout.tsx theme={null} // src/app/layout.tsx import type { Metadata } from 'next'; import { Inter } from 'next/font/google'; import './globals.css'; import { PrivyClientProvider } from '@/components/PrivyProvider'; const inter = Inter({ subsets: ['latin'] }); export const metadata: Metadata = { title: 'OneBalance + Privy Demo', description: 'Seamless chain-abstracted transfers with OneBalance and Privy', }; export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( {children} ); } ```Let's create a simple login page that uses Privy's login UI: ```tsx page.tsx theme={null} // src/app/page.tsx 'use client'; import { usePrivy } from '@privy-io/react-auth'; import { useRouter } from 'next/navigation'; import { useEffect } from 'react'; export default function Home() { const { login, ready, authenticated } = usePrivy(); const router = useRouter(); // Redirect to dashboard if already authenticated useEffect(() => { if (ready && authenticated) { router.push('/dashboard'); } }, [ready, authenticated, router]); return ( ); } ``` When users click the login button, they'll see Privy's login UI with options for email login, social login, or connecting an existing wallet:  When users choose email login, they'll receive a verification code to complete the sign-in process. Once verified, Privy will create a secure embedded wallet for them that can be used to sign OneBalance transactions.  OneBalance Demo
Experience seamless chain-abstracted transactions with OneBalance and Privy
Login with email, social, or connect your wallet to get started
Let's create a simple dashboard page that we'll enhance in the next section. Create a new directory and file at `src/app/dashboard/page.tsx`: ```tsx page.tsx theme={null} // src/app/dashboard/page.tsx 'use client'; import { usePrivy } from '@privy-io/react-auth'; import { useRouter } from 'next/navigation'; export default function Dashboard() { const { user, logout } = usePrivy(); const router = useRouter(); const handleLogout = async () => { await logout(); router.push('/'); }; return ( ); } ``` OneBalance Dashboard
Welcome!{user?.email?.address || 'Anonymous User'}🎉 Setup Complete!
You've successfully logged in with Privy. In the next section, we'll enhance this dashboard to:
- Set up your OneBalance smart contract account
- Display your aggregated token balances
- Enable chain-abstracted swaps
Start the development server: ```bash start-dev.sh theme={null} pnpm dev ``` Your app should now be running at [http://localhost:3000](http://localhost:3000). Open this URL in your browser to see it in action. For this quickstart guide, OneBalance sponsors the underlying network fees for some operations to provide a smoother demonstration experience. In a production environment, fees are handled according to the method outlined in our [Fees documentation](/concepts/fees). **Important**: Always send funds to your SCA address (which you'll create in the next step), not to the Privy EOA address. ## The User Journey With Privy integration, users can experience a seamless onboarding flow: 1. **Simple Authentication**: Users can login via email, social accounts, or connecting an existing wallet 2. **Embedded Wallet Creation**: For users without a crypto wallet, Privy automatically creates one 3. **Smart Contract Account**: The Privy wallet acts as a signer for the user's OneBalance SCA 4. **Chain-Abstracted Transactions**: Users can swap and transfer assets across chains without switching networks This approach eliminates many barriers to entry for new users while providing a powerful experience for crypto-native users as well. ## Understanding the Integration Our setup combines: 1. **Privy for Wallet Infrastructure**: * Social login options (email, passkey) * Embedded wallet creation and management * Simplified transaction signing 2. **OneBalance for Chain Abstraction**: * Unified access to assets across chains * Chain-abstracted swaps and transfers with no bridging * Gas abstraction (pay fees with any token) * Aggregated balances for a unified view of your assets 3. **CORS Handling with Next.js API Routes**: * Server-side proxy to avoid browser CORS restrictions * Secure API key management * Consistent error handling ## What's Next? Now that your environment is set up, we'll create a dashboard for chain-abstracted operations in the [next section](/getting-started/first-api-call). # Building cross-chain swaps with OneBalance and Privy Tutorial Source: https://docs.onebalance.io/guides/chain-abstracted-swap-with-privy Learn the key concepts for building a production-ready chain-abstracted token swap interface using OneBalance API and Privy embedded wallets.This guide covers the essential concepts and patterns for building a swap interface that abstracts away blockchain complexity. Complete demo available at [onebalance-chain-abstracted-swap.vercel.app](https://onebalance-chain-abstracted-swap.vercel.app). All examples in this guide are 100% free and open-source. Clone the repository to quickly get started. ## Core Architecture Overview A successful chain-abstracted swap application requires understanding these key components: * **Smart Account Prediction**: Determine account addresses before deployment * **Quote Management**: Real-time quote fetching with expiration handling * **Transaction Signing**: EIP-712 typed data signing with Privy * **Status Monitoring**: Real-time transaction status polling * **Balance Validation**: Ensure sufficient funds before execution ## Prerequisites Before diving into building the cross-chain swap interface, you'll need to have the following prerequisites in place: ### Development Environment * **Node.js 20+**: Our project uses modern TypeScript features. Install using [nvm](https://github.com/nvm-sh/nvm) or download from [nodejs.org](https://nodejs.org/). * **pnpm**: We'll use pnpm as our package manager for its speed and efficiency. Install it by running `npm install -g pnpm` or follow the [official installation guide](https://pnpm.io/installation). ### Technical Knowledge * **Next.js App Router**: Understanding of Next.js 15's App Router architecture is essential for this project. * **React Hooks**: Familiarity with React's useState, useEffect, useCallback, and custom hooks. * **TypeScript**: Basic knowledge of TypeScript types and interfaces. * **Tailwind CSS & shadcn/ui**: We'll use these for styling and components. ### Privy Setup To integrate Privy into your codebase, follow [Privy's Quickstart documentation](https://docs.privy.io/guide/react/quickstart). Once Privy is set up, ensure you have the following: * **Privy App ID** * **PrivyProvider** configured on your client * User login workflow organized and tested on the client side ## Setting Up the Project We will use [Next.js 15](https://nextjs.org) with app router & TypeScript for this project, along with [Tailwind CSS](https://tailwindcss.com) for styling and [shadcn/ui](https://ui.shadcn.com) for components. Let's initialize the project: ```bash terminal theme={null} pnpm dlx shadcn@latest init ``` For wallet connection and transaction signing, we will use [Privy](https://www.privy.io): ```bash terminal theme={null} pnpm add @tanstack/react-query wagmi viem @privy-io/react-auth ``` ### Environment Setup Make sure to copy `.env.example` into `.env`: ```jsx .env theme={null} NEXT_PUBLIC_API_URL=https://be.onebalance.io NEXT_PUBLIC_API_KEY= NEXT_PUBLIC_PRIVY_APP_ID= ```Get your OneBalance API key from [www.onebalance.io](https://www.onebalance.io) and Privy App ID from [console.privy.io](https://console.privy.io) ## 1. Smart Account Integration ### Understanding OneBalance Smart Accounts OneBalance uses Smart Contract Accounts (SCAs) that can be **predicted** before deployment. This enables: * Receiving funds before the account exists on-chain * Seamless transaction execution across chains * Gas sponsorship and batched operations ### Implementation Pattern ```typescript lib/api/account.ts theme={null} export const accountApi = { predictAddress: async (sessionAddress: string, adminAddress: string) => { const response = await apiClient.post('/account/predict-address', { sessionAddress, adminAddress, }); return response.data?.predictedAddress; }, }; ``` **Key Concept**: Use the same wallet address for both `sessionAddress` and `adminAddress` for simplicity. The predicted address becomes your user's primary account identifier. ## 2. Quote Management & Lifecycle ### Quote Request Structure OneBalance quotes follow a specific request format that defines the swap parameters: ```typescript theme={null} interface QuoteRequest { from: { account: { sessionAddress: string; // Privy wallet address adminAddress: string; // Same as session for simplicity accountAddress: string; // Predicted smart account }; asset: { assetId: string }; // e.g., "ob:usdc" amount: string; // Token amount in wei }; to: { asset: { assetId: string }; // e.g., "ob:eth" account?: string; // Optional: for transfers to other addresses }; } ``` ### Quote Lifecycle Management The quote lifecycle involves several critical stages: 1. **Validation**: Check user balance before requesting quotes 2. **Expiration Handling**: Quotes expire in 30 seconds - implement countdown timers 3. **Auto-refresh**: Automatically fetch new quotes when current ones expire 4. **Debouncing**: Prevent excessive API calls during user input **Implementation Pattern** (from `useQuotes.ts`): ```typescript theme={null} // Debounced quote fetching const debouncedGetQuote = useCallback( debounce(async (request) => { await getQuote(request); }, 1000), [getQuote] ); // Balance validation before quote request const hasSufficientBalance = (amount: string) => { if (!sourceBalance || !selectedAsset || !amount) return false; const parsedAmount = parseTokenAmount(amount, selectedAsset.decimals); return BigInt(sourceBalance.balance) >= BigInt(parsedAmount); }; ``` ## 3. Transaction Signing with Privy ### EIP-712 Typed Data Signing OneBalance transactions require signing structured data (EIP-712) for each chain operation. The signing process handles multiple operations sequentially. **Key Implementation** (from `privySigningUtils.ts`): ```typescript theme={null} import { Address, createWalletClient, custom, Hash } from 'viem'; import { ConnectedWallet } from '@privy-io/react-auth'; import { ChainOperation, Quote } from '@/lib/types/quote'; export const signTypedDataWithPrivy = (embeddedWallet: ConnectedWallet) => async (typedData: any): Promise=> { const provider = await embeddedWallet.getEthereumProvider(); const walletClient = createWalletClient({ transport: custom(provider), account: embeddedWallet.address as Address, }); return walletClient.signTypedData(typedData); }; export const signOperation = (embeddedWallet: ConnectedWallet) => (operation: ChainOperation): (() => Promise ) => async () => { const signature = await signTypedDataWithPrivy(embeddedWallet)(operation.typedDataToSign); return { ...operation, userOp: { ...operation.userOp, signature }, }; }; export const signQuote = async (quote: Quote, embeddedWallet: ConnectedWallet) => { const signWithEmbeddedWallet = signOperation(embeddedWallet); const signedQuote = { ...quote, }; signedQuote.originChainsOperations = await sequentialPromises( quote.originChainsOperations.map(signWithEmbeddedWallet) ); if (quote.destinationChainOperation) { signedQuote.destinationChainOperation = await signWithEmbeddedWallet( quote.destinationChainOperation )(); } return signedQuote; }; // Helper to run an array of lazy promises in sequence export const sequentialPromises = (promises: (() => Promise )[]): Promise => { return promises.reduce >( (acc, curr) => acc.then(results => curr().then(result => [...results, result])), Promise.resolve([]) ); }; ``` **Critical Points**: * Each quote contains multiple `ChainOperation` objects that need individual signatures * Operations must be signed **sequentially** to avoid nonce conflicts * The `typedDataToSign` field contains the EIP-712 structure for each operation ## 4. Transaction Execution Flow ### Complete Transaction Workflow 1. **Quote Validation**: Check expiration before execution 2. **Signing**: Sign all required operations using Privy 3. **Execution**: Submit signed quote to OneBalance 4. **Monitoring**: Poll transaction status until completion **Implementation Pattern**: ```typescript theme={null} const executeQuote = async () => { // 1. Validate quote expiration const expirationTime = parseInt(quote.expirationTimestamp) * 1000; if (Date.now() > expirationTime) { throw new Error('Quote has expired'); } // 2. Sign quote with Privy const signedQuote = await signQuote(quote, embeddedWallet); // 3. Execute signed quote await quotesApi.executeQuote(signedQuote); // 4. Start status polling startStatusPolling(quote.id); }; ``` ## 5. Real-Time Status Monitoring ### Status Polling Implementation After execution, implement real-time polling to track transaction progress: ```typescript theme={null} // Polling pattern from useQuotes.ts const pollTransactionStatus = (quoteId: string) => { const interval = setInterval(async () => { try { const status = await quotesApi.getQuoteStatus(quoteId); if (status.status === 'COMPLETED' || status.status === 'FAILED') { clearInterval(interval); handleTransactionComplete(status); } } catch (error) { console.error('Polling error:', error); clearInterval(interval); } }, 1000); // Poll every second }; ``` **Status Types**: * `PENDING`: Transaction submitted to blockchain * `IN_PROGRESS`: Being processed across chains * `COMPLETED`: Successfully completed * `FAILED`: Transaction failed * `REFUNDED`: Funds returned to user ## 6. User Experience Enhancements ### Quote Countdown Timer Show users when quotes will expire: ```typescript theme={null} // From QuoteCountdown component import { useState, useEffect, useCallback } from 'react'; export function useCountdown(timestamp: number, onExpire?: () => void) { const [timeLeft, setTimeLeft] = useState (0); useEffect(() => { const calculateTimeLeft = () => { const difference = timestamp * 1000 - Date.now(); return Math.max(0, Math.floor(difference / 1000)); }; setTimeLeft(calculateTimeLeft()); const timer = setInterval(() => { const newTimeLeft = calculateTimeLeft(); setTimeLeft(newTimeLeft); if (newTimeLeft < 1) { clearInterval(timer); onExpire?.(); } }, 1000); return () => clearInterval(timer); }, [timestamp, onExpire]); const formatTime = useCallback((seconds: number) => { if (seconds === 0) { return 'Expired'; } const secs = seconds % 60; return `${secs.toString()}`; }, []); return { timeLeft, formattedTime: formatTime(timeLeft), isExpired: timeLeft === 0, }; } ```  ## 7. Error Handling & Recovery ### Graceful Error Management Implement error handling for all failure scenarios: ```typescript theme={null} // Quote error handling if (quote?.error) { return ( ); } // Transaction failure recovery const handleTransactionFailure = (error) => { // Clear state resetQuote(); // Show user-friendly error setError(error.message || 'Transaction failed'); // Refresh balances to ensure accuracy fetchBalances(); }; ``` ## 8. API Integration Patterns ### Proxy Pattern for CORS Use Next.js API routes to handle CORS and secure API keys: ```typescript app/api/[...path]/route.ts theme={null} import { NextRequest, NextResponse } from 'next/server'; import { API_BASE_URL, API_KEY } from '@/lib/constants'; export async function GET( request: NextRequest, { params }: { params: Promise<{ path: string[] }> } ) { const { path } = await params; const pathString = path.join('/'); const searchParams = request.nextUrl.searchParams; try { // Build the API URL with any query parameters const apiUrl = new URL(`/api/${pathString}`, API_BASE_URL); searchParams.forEach((value, key) => { apiUrl.searchParams.append(key, value); }); const response = await fetch(apiUrl.toString(), { headers: { 'Content-Type': 'application/json', 'x-api-key': API_KEY, }, }); const data = await response.json(); return NextResponse.json(data); } catch (error) { return NextResponse.json({ message: 'Failed to fetch data', error }, { status: 400 }); } } export async function POST( request: NextRequest, { params }: { params: Promise<{ path: string[] }> } ) { const { path } = await params; const pathString = path.join('/'); try { const body = await request.json(); const response = await fetch(`${API_BASE_URL}/api/${pathString}`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': API_KEY, }, body: JSON.stringify(body), }); const data = await response.json(); return NextResponse.json(data); } catch (error) { return NextResponse.json({ message: 'Failed to fetch data', error }, { status: 400 }); } } ``` ## Key Integration Concepts ### Chain Abstraction Benefits * **Unified Token Balances**: Users see aggregated balances across all chains * **Automatic Routing**: OneBalance finds optimal paths for swaps * **Gas Sponsorship**: No need for users to hold native tokens for gas * **Network Abstraction**: Users never need to think about which chain they're using ### Production Considerations 1. **Error Boundaries**: Implement React error boundaries for graceful failures 2. **Rate Limiting**: Implement client-side rate limiting for API calls 3. **Cache Management**: Cache asset data and balance information appropriately 4. **Security**: Never expose API keys in client-side code Quote Error {quote.message} The complete implementation demonstrates these patterns in production. Check the [GitHub repository](https://github.com/dzimiks/onebalance-chain-abstracted-swap) for detailed examples and best practices. # Advanced Contract Call Patterns Source: https://docs.onebalance.io/guides/contract-calls/advanced-patterns Explore advanced cross-chain contract call patterns in OneBalance, including multi-step flows and production optimizations. Implement sophisticated contract call patterns for production applications. Learn advanced techniques for complex workflows and robust error handling. ## Transaction Batching ### Aggregate, Swap, and Stake Pattern This pattern combines multiple DeFi operations into a single transaction, using aggregated funds to open leveraged positions across protocols. ```typescript cross-chain-leverage.ts theme={null} async function openLeveragedPosition( marginAmount: bigint, leverage: number, marginAsset: string, isLong: boolean ) { const calls = []; // 1. Swap aggregated USDC to margin asset if needed if (marginAsset !== USDC_ADDRESS) { calls.push({ to: DEX_ROUTER, data: encodeFunctionData({ abi: parseAbi(['function swap(address,address,uint256,uint256,address)']), functionName: 'swap', args: [USDC_ADDRESS, marginAsset, marginAmount, minOut, account.accountAddress] }), value: '0' }); } // 2. Deposit margin to perpetual protocol calls.push({ to: PERP_CONTRACT, data: encodeFunctionData({ abi: parseAbi(['function depositMargin(address,uint256)']), functionName: 'depositMargin', args: [marginAsset, marginAmount] }), value: '0' }); // 3. Open leveraged position calls.push({ to: PERP_CONTRACT, data: encodeFunctionData({ abi: parseAbi(['function openPosition(uint256,bool)']), functionName: 'openPosition', args: [marginAmount * BigInt(leverage), isLong] }), value: '0' }); return await executeCall({ targetChain: 'eip155:42161', // Arbitrum calls, tokensRequired: [{ assetType: `eip155:42161/erc20:${marginAsset}`, amount: marginAmount.toString() }], allowanceRequirements: [ { assetType: `eip155:42161/erc20:${USDC_ADDRESS}`, amount: marginAmount.toString(), spender: DEX_ROUTER }, { assetType: `eip155:42161/erc20:${marginAsset}`, amount: marginAmount.toString(), spender: PERP_CONTRACT } ], fromAggregatedAssetId: 'ob:usdc' // Pull from any chain }); } ``` ### Cross-Chain Swap and Stake Use USDC from multiple chains to swap and stake in a single operation. This pattern optimizes for users who want to participate in staking protocols using their distributed token holdings. ```typescript cross-chain-staking.ts theme={null} const crossChainStake = async (stakeAmount: bigint) => { const WETH = '0x4200000000000000000000000000000000000006'; const STAKING_CONTRACT = '0x796f1793599D7b6acA6A87516546DdF8E5F3aA9d'; const UNISWAP_ROUTER = '0x2626664c2603336E57B271c5C0b26F421741e481'; // 1. Swap USDC to WETH on Base const swapData = encodeFunctionData({ abi: parseAbi([ 'function exactInputSingle((address,address,uint24,address,uint256,uint256,uint256,uint160)) returns (uint256)' ]), functionName: 'exactInputSingle', args: [{ tokenIn: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC tokenOut: WETH, fee: 3000, recipient: account.accountAddress, deadline: Math.floor(Date.now() / 1000) + 1800, amountIn: stakeAmount, amountOutMinimum: 0n, sqrtPriceLimitX96: 0n }] }); // 2. Stake WETH (approval handled by allowanceRequirements) const stakeData = encodeFunctionData({ abi: parseAbi(['function stake(uint256 amount)']), functionName: 'stake', args: [stakeAmount] // Will use actual WETH received from swap }); return await executeCall({ targetChain: 'eip155:8453', // Base calls: [ { to: UNISWAP_ROUTER, data: swapData, value: '0' }, { to: STAKING_CONTRACT, data: stakeData, value: '0' } ], tokensRequired: [{ assetType: 'eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', amount: stakeAmount.toString() }], allowanceRequirements: [ { assetType: 'eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', amount: stakeAmount.toString(), spender: UNISWAP_ROUTER }, { assetType: `eip155:8453/erc20:${WETH}`, amount: stakeAmount.toString(), // Approximate amount for approval spender: STAKING_CONTRACT } ], fromAggregatedAssetId: 'ob:usdc' }); }; ``` ## Batch Operations ### Multi-Recipient Payment System Process multiple payments efficiently by grouping them by token type and using multicall patterns to reduce gas costs. ```typescript batch-payments.ts theme={null} interface Payment { recipient: string; amount: bigint; token: string; } async function batchPayout(payments: Payment[]) { const calls = []; const tokensRequired = []; // Group payments by token to optimize gas usage const paymentsByToken = payments.reduce((acc, payment) => { if (!acc[payment.token]) acc[payment.token] = []; acc[payment.token].push(payment); return acc; }, {} as Record); for (const [token, tokenPayments] of Object.entries(paymentsByToken)) { // Use multicall for same-token transfers const multicallData = encodeFunctionData({ abi: parseAbi(['function multicall(bytes[] calldata data)']), functionName: 'multicall', args: [ tokenPayments.map(p => encodeFunctionData({ abi: parseAbi(['function transfer(address,uint256)']), functionName: 'transfer', args: [p.recipient, p.amount] }) ) ] }); calls.push({ to: token, data: multicallData, value: '0' }); const totalAmount = tokenPayments.reduce( (sum, p) => sum + p.amount, 0n ); tokensRequired.push({ assetType: `eip155:8453/erc20:${token}`, amount: totalAmount.toString() }); } return await executeCall({ targetChain: 'eip155:8453', calls, tokensRequired, fromAggregatedAssetId: 'ob:usdc' }); } ``` ## Gas Optimization Patterns ### Conditional Execution Pattern Skip unnecessary operations by checking on-chain state before execution, reducing gas costs for operations that may not be needed. ```typescript conditional-execution.ts theme={null} async function optimizedDeFiOperation(amount: bigint) { const calls = []; const AAVE_POOL = '0xA238Dd80C259a72e81d7e4664a9801593F98d1c5'; const USDC_ADDRESS = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'; // Check if we need to withdraw from lending protocol first const currentDeposit = await checkAaveDeposit(account.accountAddress); if (currentDeposit > 0n) { calls.push({ to: AAVE_POOL, data: encodeFunctionData({ abi: parseAbi(['function withdraw(address,uint256,address)']), functionName: 'withdraw', args: [USDC_ADDRESS, currentDeposit, account.accountAddress] }), value: '0' }); } // Main operation (e.g., swap, stake, etc.) calls.push({ to: TARGET_CONTRACT, data: mainOperationData, value: '0' }); // Only re-deposit excess if amount justifies gas cost const minDepositAmount = parseUnits('100', 6); // $100 minimum if (amount > minDepositAmount) { calls.push({ to: AAVE_POOL, data: encodeFunctionData({ abi: parseAbi(['function supply(address,uint256,address,uint16)']), functionName: 'supply', args: [USDC_ADDRESS, amount, account.accountAddress, 0] }), value: '0' }); } return await executeCall({ targetChain: 'eip155:8453', calls, tokensRequired: [{ assetType: 'eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', amount: amount.toString() }], allowanceRequirements: [{ assetType: 'eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', amount: amount.toString(), spender: AAVE_POOL }] }); } ``` ## Error Recovery Patterns ### Safe Execution with Fallbacks Implement fallback mechanisms for DEX operations when primary routes fail due to liquidity or slippage issues. ```typescript error-recovery.ts theme={null} interface CallData { to: string; data: string; value: string; } async function safeExecuteWithFallback( primaryOperation: CallData[], fallbackOperation: CallData[] ) { try { // Attempt primary operation first return await executeCall({ targetChain: 'eip155:8453', calls: primaryOperation, tokensRequired: calculateTokensRequired(primaryOperation) }); } catch (error: any) { // Handle specific errors with fallback strategies if (error.message.includes('INSUFFICIENT_LIQUIDITY') || error.message.includes('EXCESSIVE_SLIPPAGE')) { console.log('Primary route failed, attempting fallback...'); return await executeCall({ targetChain: 'eip155:8453', calls: fallbackOperation, tokensRequired: calculateTokensRequired(fallbackOperation) }); } // Re-throw unexpected errors throw error; } } function calculateTokensRequired(calls: CallData[]) { // Implementation to analyze calls and determine token requirements return []; } ``` ## State Management Patterns ### Transaction Lifecycle Tracking Track and monitor complex transactions with automatic retry and status monitoring capabilities. ```typescript transaction-tracker.ts theme={null} interface OperationStatus { status: 'PENDING' | 'COMPLETED' | 'FAILED' | 'IN_PROGRESS' | 'REFUNDED'; } interface OperationDetails { hash: string; chainId: number; explorerUrl: string; } interface OriginAssetUsed { aggregatedAssetId: string; amount: string; assetType: string[]; fiatValue: Array<{ assetType: string; fiatValue: string; }>; } interface DestinationAssetUsed { aggregatedAssetId: string; amount: string; assetType: string; fiatValue: string; minimumAmount?: string; minimumFiatValue?: string; } interface Transaction { quoteId: string; status: OperationStatus; user: string; recipientAccountId: string; originChainOperations: OperationDetails[]; destinationChainOperations: OperationDetails[]; type: string; originToken: OriginAssetUsed; destinationToken: DestinationAssetUsed; timestamp: string; } interface LocalTransactionState { id: string; request: any; localStatus: 'preparing' | 'executing' | 'monitoring' | 'completed' | 'failed'; preparedAt: number; transaction?: Transaction; error?: any; } class TransactionTracker { private pending = new Map (); async executeAndTrack(request: any): Promise { const txId = this.generateId(); // Store initial transaction state this.pending.set(txId, { id: txId, request, localStatus: 'preparing', preparedAt: Date.now() }); try { // Step 1: Prepare and sign const quote = await this.prepareAndSign(request); this.updateLocalStatus(txId, 'executing'); // Step 2: Execute const result = await this.executeQuote(quote); this.updateLocalStatus(txId, 'monitoring'); // Step 3: Monitor with exponential backoff const finalResult = await this.monitorWithBackoff(quote.id); this.updateLocalStatus(txId, 'completed', finalResult); return finalResult; } catch (error) { this.updateLocalStatus(txId, 'failed', undefined, error); throw error; } finally { // Cleanup after 24 hours setTimeout(() => this.pending.delete(txId), 86400000); } } private async monitorWithBackoff(quoteId: string): Promise { const delays = [1000, 2000, 5000, 10000, 15000]; // Progressive delays const maxAttempts = 30; for (let attempt = 0; attempt < maxAttempts; attempt++) { const status = await getQuoteStatus(quoteId); if (['COMPLETED', 'FAILED', 'REFUNDED'].includes(status.status.status)) { return status; } const delay = delays[Math.min(attempt, delays.length - 1)]; await new Promise(resolve => setTimeout(resolve, delay)); } throw new Error('Transaction monitoring timeout'); } private generateId(): string { return `tx_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } private updateLocalStatus( id: string, localStatus: LocalTransactionState['localStatus'], transaction?: Transaction, error?: any ) { const tx = this.pending.get(id); if (tx) { tx.localStatus = localStatus; if (transaction) tx.transaction = transaction; if (error) tx.error = error; } } // Get current transaction status getTransactionStatus(id: string): LocalTransactionState | undefined { return this.pending.get(id); } // Get all pending transactions getPendingTransactions(): LocalTransactionState[] { return Array.from(this.pending.values()); } } ``` ## Integration Patterns ### Wallet-Agnostic Implementation Create universal interfaces that work with any wallet provider, making your integration flexible across different wallet ecosystems. ```typescript wallet-integration.ts theme={null} interface WalletAdapter { signTypedData(data: any): Promise ; getAddress(): Promise ; isConnected(): Promise ; } class UniversalOneBalance { constructor( private apiKey: string, private wallet: WalletAdapter ) {} async executeCall(params: any) { // Ensure wallet is connected const isConnected = await this.wallet.isConnected(); if (!isConnected) { throw new Error('Wallet not connected'); } // Prepare operation const account = await this.getAccount(); const preparedQuote = await this.prepare({ ...params, account }); // Sign with wallet adapter const signature = await this.wallet.signTypedData( preparedQuote.chainOperation.typedDataToSign ); preparedQuote.chainOperation.userOp.signature = signature; // Execute operation return await this.execute(preparedQuote); } private async getAccount() { const address = await this.wallet.getAddress(); return { sessionAddress: address, adminAddress: address, // Simplified for basic account accountAddress: await this.predictAccountAddress(address) }; } } // MetaMask adapter example const metamaskAdapter: WalletAdapter = { signTypedData: (data) => window.ethereum.request({ method: 'eth_signTypedData_v4', params: [address, JSON.stringify(data)] }), getAddress: async () => { const accounts = await window.ethereum.request({ method: 'eth_accounts' }); return accounts[0]; }, isConnected: async () => { const accounts = await window.ethereum.request({ method: 'eth_accounts' }); return accounts.length > 0; } }; // Usage const oneBalance = new UniversalOneBalance(apiKey, metamaskAdapter); ``` ## Performance Optimization Tips ### Implementation Best Practices Group similar operations to reduce transaction overhead and gas costs Combine multiple calls to the same contract in a single transaction Store prepared quotes for retry scenarios (valid for \~30 seconds) Don't block UI while monitoring transaction status - use background polling Design your call sequence to minimize gas usage and maximize success probability Implement fallbacks for common failure scenarios like insufficient liquidity Track transaction status and provide users with real-time updates Validate complex patterns with small amounts before production deployment **Advanced Features**: For even more sophisticated patterns, consider combining these techniques with OneBalance's resource locks and time-based permissions for enhanced security and user experience. # Allowances & Approvals Source: https://docs.onebalance.io/guides/contract-calls/allowances-and-approvals Learn how OneBalance manages token allowances and approvals, ensuring secure and gas-efficient transactions automatically. Learn how OneBalance automatically handles token approvals for DeFi operations while maintaining security best practices.**Important**: Never manually call `approve()` functions when using OneBalance. The platform automatically manages all required approvals as part of the contract call preparation process. ## How Approval Management Works When you specify `allowanceRequirements`, OneBalance automatically: 1. **Checks existing allowances** - Verifies current approval amounts 2. **Adds approval transactions** - Only when needed, for exact amounts 3. **Executes your operations** - Runs your contract calls 4. **Removes excess approvals** - Cleans up remaining allowances for security This prevents front-running attacks and ensures optimal gas usage.OneBalance bundles approvals, your calls, and approval cleanup into a single user operation for atomic execution. ## Basic Example Here's how to properly handle approvals for a DEX swap: ```typescript swap-with-approval.ts theme={null} import { encodeFunctionData, parseAbi, parseUnits } from 'viem'; const USDC_BASE = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'; const UNISWAP_ROUTER = '0x2626664c2603336E57B271c5C0b26F421741e481'; // ✅ Correct - Let OneBalance handle approvals const prepareRequest = { account: { sessionAddress: '0x...', adminAddress: '0x...', accountAddress: '0x...' }, targetChain: 'eip155:8453', // Base calls: [{ to: UNISWAP_ROUTER, data: encodeFunctionData({ abi: parseAbi(['function exactInputSingle((address tokenIn, address tokenOut, uint24 fee, address recipient, uint256 deadline, uint256 amountIn, uint256 amountOutMinimum, uint160 sqrtPriceLimitX96))']), functionName: 'exactInputSingle', args: [{ tokenIn: USDC_BASE, tokenOut: '0x...', fee: 3000, recipient: account.accountAddress, deadline: Math.floor(Date.now() / 1000) + 3600, amountIn: parseUnits('100', 6), // 100 USDC amountOutMinimum: 0, sqrtPriceLimitX96: 0 }] }), value: '0' }], allowanceRequirements: [{ assetType: `eip155:8453/erc20:${USDC_BASE}`, amount: parseUnits('100', 6).toString(), // Exact amount needed spender: UNISWAP_ROUTER }], tokensRequired: [{ assetType: `eip155:8453/erc20:${USDC_BASE}`, amount: parseUnits('100', 6).toString() }] }; ``` ## What NOT to Do**Common Mistake**: Including approval calls manually will cause transaction failures. ```typescript wrong-approach.ts theme={null} // ❌ WRONG - Don't do this! const badRequest = { calls: [ { // This will cause the transaction to fail to: USDC_BASE, data: encodeFunctionData({ abi: parseAbi(['function approve(address spender, uint256 amount)']), functionName: 'approve', args: [UNISWAP_ROUTER, parseUnits('100', 6)] }), value: '0' }, { to: UNISWAP_ROUTER, data: swapCallData, value: '0' } ] // No allowanceRequirements - this is the problem! }; ``` ## Common Patterns ### DEX Swaps ```typescript dex-swap.ts theme={null} // Uniswap V3 swap requiring token approval allowanceRequirements: [{ assetType: `eip155:8453/erc20:${inputToken}`, amount: inputAmount.toString(), spender: UNISWAP_V3_ROUTER }] ``` ### Lending Protocols ```typescript aave-deposit.ts theme={null} // Aave deposit requiring pool approval allowanceRequirements: [{ assetType: `eip155:1/erc20:${asset}`, amount: depositAmount.toString(), spender: AAVE_POOL_ADDRESS }] ``` ### Multiple Token Operations ```typescript multi-token-defi.ts theme={null} // Complex DeFi operation requiring multiple approvals allowanceRequirements: [ { assetType: `eip155:1/erc20:${tokenA}`, amount: amountA.toString(), spender: DEX_ROUTER }, { assetType: `eip155:1/erc20:${tokenB}`, amount: amountB.toString(), spender: LENDING_POOL } ] ``` ### NFT Purchases ```typescript nft-purchase.ts theme={null} // NFT marketplace purchase with USDC allowanceRequirements: [{ assetType: 'eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', amount: nftPrice.toString(), spender: NFT_MARKETPLACE_ADDRESS }] ``` ## Special Cases ### Native Tokens (ETH, MATIC, etc.) Native tokens don't require approvals - they're sent directly: ```typescript native-token.ts theme={null} // No allowanceRequirements needed for ETH calls: [{ to: CONTRACT_ADDRESS, data: contractCallData, value: ethAmount.toString() // Send ETH directly }], tokensRequired: [{ assetType: 'eip155:1/slip44:60', // ETH amount: ethAmount.toString() }] // No allowanceRequirements array needed ``` ### Existing Allowances OneBalance optimizes gas by checking existing allowances:## Security Features OneBalance's approval management includes several security measures: * **Exact Amount Approvals** - Never approves more than needed * **Automatic Cleanup** - Removes remaining allowances after execution * **Front-running Protection** - Atomic bundling prevents manipulation * **Approval Validation** - Verifies spender addresses match your calls ## Best Practices If `current allowance ≥ required amount`, no approval transaction is added Only approves the exact amount needed, not unlimited approvals ## TypeScript Interface ```typescript interfaces.ts theme={null} interface AllowanceRequirement { assetType: string; // Format: "eip155:chainId/erc20:tokenAddress" amount: string; // Amount in smallest unit (e.g., wei) spender: string; // Contract address that needs approval } interface PrepareCallRequest { account: Account; targetChain: string; calls: CallData[]; allowanceRequirements: AllowanceRequirement[]; tokensRequired: TokenRequirement[]; } ``` ## Troubleshooting * **Transaction fails with 'Approval not found'** - Make sure you're using `allowanceRequirements` instead of manual approval calls. * **Gas costs seem high** - OneBalance only adds approvals when needed. High gas might indicate multiple tokens or complex operations. * **Spender address mismatch** - Verify that the spender in `allowanceRequirements` matches the contract address in your `calls`. * **Amount calculation errors** - Ensure you're using the correct decimals for the token (e.g., 6 for USDC, 18 for most ERC20s). Always specify token approvals in the `allowanceRequirements` array, never in `calls` Use precise amounts to minimize approval exposure Double-check that spender addresses match your contract calls Start with small transactions to verify your integration Need help? Check the [troubleshooting guide](/guides/contract-calls/troubleshooting) for common issues and solutions. # Contract Call Examples Source: https://docs.onebalance.io/guides/contract-calls/examples Explore OneBalance Toolkit contract call examples for DeFi, NFT, and payments, showing how cross-chain smart contract operations work. Real-world examples of smart contract calls using OneBalance APIs. Each example shows the complete flow from preparing quotes to execution with actual API payloads. ## Overview These examples demonstrate the three-step flow for contract calls: 1. **Prepare** - Generate user operation and typed data 2. **Sign** - Sign the typed data (handled by your wallet/signer) 3. **Execute** - Submit the signed quote for executionFor interactive testing, use the [API Reference playground](/api-reference/quotes/prepare-call-quote) or copy the cURL commands directly. ## DeFi Examples ### Uniswap V3 Swap Swap USDC for WETH on Base using Uniswap V3 router.```json uniswap-prepare.json theme={null} { "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "targetChain": "eip155:8453", "calls": [ { "to": "0x2626664c2603336E57B271c5C0b26F421741e481", "data": "0x414bf389000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda029130000000000000000000000004200000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000bb8000000000000000000000000e20295ec513def805d9c3083b0c8eab64692d764000000000000000000000000000000000000000000000000000000006748d28800000000000000000000000000000000000000000000000000000000000f42400000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000000", "value": "0x0" } ], "allowanceRequirements": [ { "assetType": "eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "amount": "1000000", "spender": "0x2626664c2603336E57B271c5C0b26F421741e481" } ], "tokensRequired": [ { "assetType": "eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "amount": "1000000" } ] } ``` ```bash prepare-uniswap-swap.sh theme={null} curl --request POST \ --url https://be.onebalance.io/api/quotes/prepare-call-quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "targetChain": "eip155:8453", "calls": [ { "to": "0x2626664c2603336E57B271c5C0b26F421741e481", "data": "0x414bf389000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda029130000000000000000000000004200000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000bb8000000000000000000000000e20295ec513def805d9c3083b0c8eab64692d764000000000000000000000000000000000000000000000000000000006748d28800000000000000000000000000000000000000000000000000000000000f42400000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000000", "value": "0x0" } ], "allowanceRequirements": [ { "assetType": "eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "amount": "1000000", "spender": "0x2626664c2603336E57B271c5C0b26F421741e481" } ], "tokensRequired": [ { "assetType": "eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "amount": "1000000" } ] }' ``` ### Aave V3 Supply Supply USDC to Aave V3 on Base to earn yield.```json aave-supply-prepare.json theme={null} { "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "targetChain": "eip155:8453", "calls": [ { "to": "0xA238Dd80C259a72e81d7e4664a9801593F98d1c5", "data": "0x617ba037000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000000000000000000000000000000000000098968000000000000000000000000e20295ec513def805d9c3083b0c8eab64692d7640000000000000000000000000000000000000000000000000000000000000000", "value": "0x0" } ], "allowanceRequirements": [ { "assetType": "eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "amount": "10000000", "spender": "0xA238Dd80C259a72e81d7e4664a9801593F98d1c5" } ], "tokensRequired": [ { "assetType": "eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "amount": "10000000" } ] } ``` ```bash prepare-aave-supply.sh theme={null} curl --request POST \ --url https://be.onebalance.io/api/quotes/prepare-call-quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "targetChain": "eip155:8453", "calls": [ { "to": "0xA238Dd80C259a72e81d7e4664a9801593F98d1c5", "data": "0x617ba037000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000000000000000000000000000000000000098968000000000000000000000000e20295ec513def805d9c3083b0c8eab64692d7640000000000000000000000000000000000000000000000000000000000000000", "value": "0x0" } ], "allowanceRequirements": [ { "assetType": "eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "amount": "10000000", "spender": "0xA238Dd80C259a72e81d7e4664a9801593F98d1c5" } ], "tokensRequired": [ { "assetType": "eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "amount": "10000000" } ] }' ``` ## NFT Examples ### Mint NFT Collection Mint 2 NFTs from a collection on Base with ETH payment.```json nft-mint-prepare.json theme={null} { "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "targetChain": "eip155:8453", "calls": [ { "to": "0x1234567890123456789012345678901234567890", "data": "0xa0712d680000000000000000000000000000000000000000000000000000000000000002", "value": "0x1bc16d674ec80000" } ], "allowanceRequirements": [], "tokensRequired": [ { "assetType": "eip155:8453/slip44:60", "amount": "2000000000000000000" } ] } ``` ```bash prepare-nft-mint.sh theme={null} curl --request POST \ --url https://be.onebalance.io/api/quotes/prepare-call-quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "targetChain": "eip155:8453", "calls": [ { "to": "0x1234567890123456789012345678901234567890", "data": "0xa0712d680000000000000000000000000000000000000000000000000000000000000002", "value": "0x1bc16d674ec80000" } ], "allowanceRequirements": [], "tokensRequired": [ { "assetType": "eip155:8453/slip44:60", "amount": "2000000000000000000" } ] }' ``` ## Gaming Examples ### In-Game Purchase Purchase game item #1 using USDC as payment token.```json game-purchase-prepare.json theme={null} { "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "targetChain": "eip155:8453", "calls": [ { "to": "0x1234567890123456789012345678901234567890", "data": "0x96a81f59000000000000000000000000000000000000000000000000000000000000000100000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000000000000000000000000000000000000098968", "value": "0x0" } ], "allowanceRequirements": [ { "assetType": "eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "amount": "10000000", "spender": "0x1234567890123456789012345678901234567890" } ], "tokensRequired": [ { "assetType": "eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "amount": "10000000" } ] } ``` ```bash prepare-game-purchase.sh theme={null} curl --request POST \ --url https://be.onebalance.io/api/quotes/prepare-call-quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "targetChain": "eip155:8453", "calls": [ { "to": "0x1234567890123456789012345678901234567890", "data": "0x96a81f59000000000000000000000000000000000000000000000000000000000000000100000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000000000000000000000000000000000000098968", "value": "0x0" } ], "allowanceRequirements": [ { "assetType": "eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "amount": "10000000", "spender": "0x1234567890123456789012345678901234567890" } ], "tokensRequired": [ { "assetType": "eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "amount": "10000000" } ] }' ``` ## Basic Account Examples Basic accounts use a simpler structure with just signer and account addresses. ### Token Transfer with Basic Account Transfer USDC tokens using a basic account configuration.```json basic-transfer-prepare.json theme={null} { "account": { "type": "kernel-v3.1-ecdsa", "signerAddress": "0x5d6fb4eb211a6a2e406a1111b54d26c534753c8e", "accountAddress": "0xb7bc0d7baf6761c302ff6772dfd8f9e22ec706e7" }, "targetChain": "eip155:8453", "calls": [ { "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "data": "0xa9059cbb000000000000000000000000742d35cc6634c0532925a3b844bc454e4438f44e0000000000000000000000000000000000000000000000000000000000989680", "value": "0x0" } ], "allowanceRequirements": [], "tokensRequired": [ { "assetType": "eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "amount": "10000000" } ] } ``` ```bash prepare-basic-transfer.sh theme={null} curl --request POST \ --url https://be.onebalance.io/api/quotes/prepare-call-quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "account": { "type": "kernel-v3.1-ecdsa", "signerAddress": "0x5d6fb4eb211a6a2e406a1111b54d26c534753c8e", "accountAddress": "0xb7bc0d7baf6761c302ff6772dfd8f9e22ec706e7" }, "targetChain": "eip155:8453", "calls": [ { "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "data": "0xa9059cbb000000000000000000000000742d35cc6634c0532925a3b844bc454e4438f44e0000000000000000000000000000000000000000000000000000000000989680", "value": "0x0" } ], "allowanceRequirements": [], "tokensRequired": [ { "assetType": "eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "amount": "10000000" } ] }' ``` ## Multi-Step Operations ### Batch Token Transfers Send tokens to multiple recipients in a single transaction.```json batch-transfer-prepare.json theme={null} { "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "targetChain": "eip155:8453", "calls": [ { "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "data": "0xa9059cbb000000000000000000000000742d35cc6634c0532925a3b844bc454e4438f44e0000000000000000000000000000000000000000000000000000000000989680", "value": "0x0" }, { "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "data": "0xa9059cbb000000000000000000000000456d35cc6634c0532925a3b844bc454e4438f44e0000000000000000000000000000000000000000000000000000000000989680", "value": "0x0" }, { "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "data": "0xa9059cbb000000000000000000000000789d35cc6634c0532925a3b844bc454e4438f44e0000000000000000000000000000000000000000000000000000000000989680", "value": "0x0" } ], "allowanceRequirements": [], "tokensRequired": [ { "assetType": "eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "amount": "30000000" } ] } ``` ```bash prepare-batch-transfer.sh theme={null} curl --request POST \ --url https://be.onebalance.io/api/quotes/prepare-call-quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "targetChain": "eip155:8453", "calls": [ { "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "data": "0xa9059cbb000000000000000000000000742d35cc6634c0532925a3b844bc454e4438f44e0000000000000000000000000000000000000000000000000000000000989680", "value": "0x0" }, { "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "data": "0xa9059cbb000000000000000000000000456d35cc6634c0532925a3b844bc454e4438f44e0000000000000000000000000000000000000000000000000000000000989680", "value": "0x0" }, { "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "data": "0xa9059cbb000000000000000000000000789d35cc6634c0532925a3b844bc454e4438f44e0000000000000000000000000000000000000000000000000000000000989680", "value": "0x0" } ], "allowanceRequirements": [], "tokensRequired": [ { "assetType": "eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "amount": "30000000" } ] }' ``` ## Integration Helpers ### TypeScript Helper Functions Utility functions to simplify contract call integration. ```typescript helper-functions.ts theme={null} import { encodeFunctionData, parseAbi } from 'viem'; // Generate transfer call data export function createTransferCall( tokenAddress: string, recipient: string, amount: bigint ) { return { to: tokenAddress, data: encodeFunctionData({ abi: parseAbi(['function transfer(address to, uint256 amount)']), functionName: 'transfer', args: [recipient, amount] }), value: '0x0' }; } // Generate approve call data export function createApprovalRequirement( tokenAddress: string, spender: string, amount: bigint, chainId: number ) { return { assetType: `eip155:${chainId}/erc20:${tokenAddress}`, amount: amount.toString(), spender }; } // Generate Uniswap V3 swap call data export function createUniswapV3Swap( tokenIn: string, tokenOut: string, fee: number, recipient: string, amountIn: bigint, amountOutMinimum: bigint, deadline: number ) { const swapRouter = '0x2626664c2603336E57B271c5C0b26F421741e481'; return { to: swapRouter, data: encodeFunctionData({ abi: parseAbi([ 'function exactInputSingle((address,address,uint24,address,uint256,uint256,uint256,uint160)) returns (uint256)' ]), functionName: 'exactInputSingle', args: [{ tokenIn, tokenOut, fee, recipient, deadline, amountIn, amountOutMinimum, sqrtPriceLimitX96: 0n }] }), value: '0x0' }; } ``` ## Production Best PracticesAlways validate addresses, amounts, and parameters before preparing quotes. Use checksummed addresses. Implement proper error handling for failed preparations, rejections, and execution failures. OneBalance handles gas estimation, but monitor network congestion and adjust timeouts accordingly. Test all operations with small amounts before production deployment. **Next Steps**: Learn about [troubleshooting common issues](/guides/contract-calls/troubleshooting) or explore [advanced patterns](/guides/contract-calls/advanced-patterns) for complex use cases. # How to Execute Smart Contract Calls Source: https://docs.onebalance.io/guides/contract-calls/getting-started Learn to execute smart contract calls with OneBalance by preparing call data, signing, and submitting transactions across chains. This guide walks you through implementing smart contract calls in your application. We'll cover the complete three-step flow with production-ready code examples. ## Quickstart Execute any smart contract function across multiple blockchains with a single integration. This guide shows you how to transfer ERC20 tokens on Arbitrum using OneBalance's calldata API. ## What You'll Build## Quick Example Here's what you'll accomplish - a complete working script: ```typescript index.ts theme={null} // Complete working example - see full code below async function main() { // Step 1: Predict account address from session/admin keys const accountAddress = await predictAddress(sessionKey.address, adminKey.address); console.log('Predicted Address:', accountAddress); // Step 2: Check USDC balance const usdcBalances = await fetchUSDCBalance(accountAddress); if (!usdcBalances) throw new Error('No USDC balance found'); // Step 3-5: Execute ERC20 transfer on Arbitrum await transferErc20OnChain({ accountAddress, sessionAddress: sessionKey.address, adminAddress: adminKey.address, }, usdcBalances); } ``` Now let's build this step by step. ## Project Setup Set up your development environment: ```bash theme={null} mkdir onebalance-calldata && cd onebalance-calldata pnpm init ``` Install dependencies: ```bash theme={null} pnpm add viem axios pnpm add -D typescript @types/node ts-node tslib ``` Now, let's create and configure `tsconfig.json`: ```bash theme={null} touch tsconfig.json ``` ```json tsconfig.json theme={null} { "compilerOptions": { "target": "ES2020", "module": "commonjs", "lib": ["ES2020"], "outDir": "./dist", "rootDir": "./", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "declaration": true, "declarationMap": true, "sourceMap": true, "moduleResolution": "node", "allowSyntheticDefaultImports": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, "typeRoots": ["./node_modules/@types"], "types": ["node"] }, "include": ["**/*.ts", "**/*.tsx"], "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"], "ts-node": { "esm": false, "experimentalSpecifierResolution": "node" } } ``` Initialize a TypeScript project with OneBalance dependencies Send arbitrary smart contract calls across any supported chain Monitor transaction execution in real-time Implement proper error handling This guide uses a public API key for testing. For production, get your API key from [sales@onebalance.io](mailto:sales@onebalance.io). ## Calldata Flow ### Step 1: Generate Keys & Predict Account Create your main file with key generation and account prediction: ```bash theme={null} touch index.ts ``` ```typescript index.ts theme={null} import { existsSync, readFileSync, writeFileSync } from 'node:fs'; import axios, { AxiosResponse } from 'axios'; import { HashTypedDataParameters, encodeFunctionData, parseAbi } from 'viem'; import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'; const BASE_URL = 'https://be.onebalance.io'; // Note: Using the production API endpoint will produce a different predicted address const PUBLIC_API_KEY = '42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11'; // Helper function to create authenticated headers function createAuthHeaders(): Record{ return { 'x-api-key': PUBLIC_API_KEY, }; } async function apiRequest ( method: 'get' | 'post', endpoint: string, data: RequestData, isParams = false, ): Promise { try { const config = { headers: createAuthHeaders(), ...(isParams ? { params: data } : {}), }; const url = `${BASE_URL}${endpoint}`; const response: AxiosResponse = method === 'post' ? await axios.post(url, data, config) : await axios.get(url, { ...config, params: data }); return response.data; } catch (error) { if (axios.isAxiosError(error) && error.response) { throw new Error(JSON.stringify(error.response.data)); } throw error; } } // API methods async function apiPost (endpoint: string, data: RequestData): Promise { return apiRequest ('post', endpoint, data); } async function apiGet (endpoint: string, params: RequestData): Promise { return apiRequest ('get', endpoint, params, true); } // Generate session key pair function generateEOAKey() { const privateKey = generatePrivateKey(); const account = privateKeyToAccount(privateKey); return { privateKey, address: account.address, }; } function readOrCacheEOAKey(key: string) { if (existsSync(`${key}-key.json`)) { const cachedKeys = readFileSync(`${key}-key.json`, 'utf8'); return JSON.parse(cachedKeys); } const keys = generateEOAKey(); writeFileSync(`${key}-key.json`, JSON.stringify(keys, null, 2)); return keys; } // Usage example const sessionKey = readOrCacheEOAKey('session'); console.log('Session Address:', sessionKey.address); const adminKey = readOrCacheEOAKey('admin'); console.log('Admin Address:', adminKey.address); async function predictAddress(sessionAddress: string, adminAddress: string): Promise { const response = await apiPost<{ sessionAddress: string; adminAddress: string }, { predictedAddress: string }>( '/api/account/predict-address', { sessionAddress, adminAddress, }, ); return response.predictedAddress; } ``` ### Step 2: Check Balance Add balance checking functionality with proper TypeScript interfaces: ```typescript index.ts theme={null} async function fetchBalances(address: string) { const response = await apiGet< { address: string }, { balanceByAggregatedAsset: { aggregatedAssetId: string; balance: string; individualAssetBalances: { assetType: string; balance: string; fiatValue: number }[]; fiatValue: number; }[]; balanceBySpecificAsset: { assetType: string; balance: string; fiatValue: number; }[]; totalBalance: { fiatValue: number; }; } >('/api/v2/balances/aggregated-balance', { address }); return response; } async function fetchUSDCBalance(address: string) { const response = await fetchBalances(address); return response.balanceByAggregatedAsset.find((asset) => asset.aggregatedAssetId === 'ob:usdc'); } ``` ### Step 3: Add TypeScript Interfaces Add TypeScript interfaces for type safety: ```typescript index.ts theme={null} type Hex = `0x${string}`; interface EvmAccount { accountAddress: Hex; sessionAddress: Hex; adminAddress: Hex; } interface EvmCall { to: Hex; value?: Hex; data?: Hex; } interface TokenRequirement { assetType: string; amount: string; } interface TokenAllowanceRequirement extends TokenRequirement { spender: Hex; } type StateMapping = { [slot: Hex]: Hex; }; type StateDiff = { stateDiff?: StateMapping; code?: Hex; balance?: Hex; }; type Override = StateDiff & { address: Hex; }; interface PrepareCallRequest { account: EvmAccount; targetChain: string; // CAIP-2 calls: EvmCall[]; tokensRequired: TokenRequirement[]; allowanceRequirements?: TokenAllowanceRequirement[]; overrides?: Override[]; // permits validAfter?: string; validUntil?: string; } interface SerializedUserOperation { sender: Hex; nonce: string; factory?: Hex; factoryData?: Hex; callData: Hex; callGasLimit: string; verificationGasLimit: string; preVerificationGas: string; maxFeePerGas: string; maxPriorityFeePerGas: string; paymaster?: Hex; paymasterVerificationGasLimit?: string; paymasterPostOpGasLimit?: string; paymasterData?: Hex; signature: Hex; initCode?: Hex; paymasterAndData?: Hex; } interface ChainOperationBasic { userOp: SerializedUserOperation; typedDataToSign: HashTypedDataParameters; } interface ChainOperation extends ChainOperationBasic { assetType: string; amount: string; } interface TargetCallQuote { account: EvmAccount; chainOperation: ChainOperation; tamperProofSignature: string; } interface CallRequest { account: EvmAccount; chainOperation: ChainOperation; tamperProofSignature: string; fromAggregatedAssetId: string; } interface AssetUsed { aggregatedAssetId: string; assetType: string[] | string; amount: string; minimumAmount?: string; } interface FiatValue { fiatValue: string; amount: string; } interface OriginAssetUsed extends AssetUsed { assetType: string[]; fiatValue: FiatValue[]; } interface DestinationAssetUsed extends AssetUsed { assetType: string; fiatValue: string; minimumAmount?: string; minimumFiatValue?: string; } interface Quote { id: string; account: EvmAccount; originChainsOperations: ChainOperation[]; destinationChainOperation?: ChainOperation; originToken?: OriginAssetUsed; destinationToken?: DestinationAssetUsed; validUntil?: string; // block number, if empty the valid until will be MAX_UINT256 validAfter?: string; // block number, if empty the valid after will be 0 expirationTimestamp: string; tamperProofSignature: string; } interface OpGuarantees { non_equivocation: boolean; reorg_protection: boolean; valid_until?: number; valid_after?: number; } type BundleGuarantees = Record ; interface BundleResponse { success: boolean; guarantees: BundleGuarantees | null; error: string | null; } type TransactionType = 'SWAP' | 'TRANSFER' | 'CALL'; type OperationStatus = | 'PENDING' // not yet begun processing but has been submitted | 'IN_PROGRESS' // processing the execution steps of the operation | 'COMPLETED' // all steps completed with success | 'REFUNDED' // none or some steps completed, some required step failed causing the whole operation to be refunded | 'FAILED'; // all steps failed interface OperationDetails { hash?: Hex; chainId?: number; explorerUrl?: string; } interface HistoryTransaction { quoteId: string; type: TransactionType; originToken?: OriginAssetUsed; destinationToken?: DestinationAssetUsed; status: OperationStatus; user: Hex; recipientAccountId: string; // the caip-10 address of the recipient // if type is SWAP or TRANSFER originChainOperations?: OperationDetails[]; // the asset(s) that were sent from the source destinationChainOperations?: OperationDetails[]; // the asset that was received to the final destination } interface HistoryResponse { transactions: HistoryTransaction[]; continuation?: string; } ``` ### Step 4: Prepare Quote Add the quote preparation logic with proper type safety: ```typescript index.ts theme={null} async function prepareCallQuote(quoteRequest: PrepareCallRequest): Promise { return apiPost ('/api/quotes/prepare-call-quote', quoteRequest); } async function fetchCallQuote(callRequest: CallRequest): Promise { return apiPost('/api/quotes/call-quote', callRequest); } async function executeQuote(quote: Quote): Promise { return apiPost ('/api/quotes/execute-quote', quote); } ```See all quote options in our [Quotes API reference](/api-reference/quotes/prepare-call-quote). ### Step 5: Sign Operations Add signing functionality: ```typescript index.ts theme={null} async function signOperation(operation: ChainOperation, key: Hex): Promise{ return { ...operation, userOp: { ...operation.userOp, signature: await privateKeyToAccount(key).signTypedData(operation.typedDataToSign) }, }; } ``` ### Step 6: Track Status Add transaction monitoring with proper error handling: ```typescript index.ts theme={null} async function fetchTransactionHistory(address: string): Promise { return apiGet<{ user: string; limit: number; sortBy: string }, HistoryResponse>('/api/status/get-tx-history', { user: address, limit: 1, sortBy: 'createdAt', }); } ``` Track all transaction states in our [Status API reference](/api-reference/status/get-transaction-history). ## Put It Together Add the main transfer function and execution: ```typescript index.ts theme={null} async function transferErc20OnChain( account: EvmAccount, usdcBalances: { aggregatedAssetId: string; balance: string; individualAssetBalances: { assetType: string; balance: string; fiatValue: number }[]; }, ) { const largestUsdcBalanceEntry = usdcBalances.individualAssetBalances.reduce((max, current) => { return Number(current.balance) > Number(max.balance) ? current : max; }); const chain = 'eip155:42161'; // Arbitrum const usdcAddress = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831'; // Arbitrum USDC address if (largestUsdcBalanceEntry.balance === '0') { throw new Error('No USDC balance found'); } const transferDefinition = parseAbi(['function transfer(address to, uint256 amount) returns (bool)']); const transferCallData = encodeFunctionData({ abi: transferDefinition, functionName: 'transfer', args: [adminKey.address, 1n], }); const quoteRequest: PrepareCallRequest = { account, targetChain: chain, calls: [ { to: usdcAddress as Hex, data: transferCallData, value: '0x0', }, ], tokensRequired: [ { assetType: `${chain}/erc20:${usdcAddress}`, amount: '100000', }, ], }; console.log(quoteRequest); const preparedQuote = await prepareCallQuote(quoteRequest); const signedChainOp = await signOperation(preparedQuote.chainOperation, sessionKey.privateKey); const callRequest: CallRequest = { fromAggregatedAssetId: 'ob:usdc', account, tamperProofSignature: preparedQuote.tamperProofSignature, chainOperation: signedChainOp, }; console.log('callRequest', callRequest); const quote = await fetchCallQuote(callRequest); for (let i = 0; i < quote.originChainsOperations.length; i++) { const callQuoteSignedChainOperation = await signOperation(quote.originChainsOperations[i], sessionKey.privateKey); quote.originChainsOperations[i] = callQuoteSignedChainOperation; } console.log('quote', quote); const bundle = await executeQuote(quote); if (bundle.success) { console.log('Bundle executed'); const timeout = 60_000; let completed = false; const startTime = Date.now(); while (!completed) { try { console.log('fetching transaction history...'); const transactionHistory = await fetchTransactionHistory(quote.account.accountAddress); console.log('transactionHistory', transactionHistory); if (transactionHistory.transactions.length > 0) { const [tx] = transactionHistory.transactions; if (tx.quoteId === quote.id) { if (tx.status === 'COMPLETED') { console.log('Transaction completed and operation executed'); completed = true; break; } console.log('Transaction status: ', tx.status); } } } catch {} if (Date.now() - startTime > timeout) { throw new Error('Transaction not completed in time'); } await new Promise((resolve) => setTimeout(resolve, 1_000)); } } else { console.log('Bundle execution failed'); } } async function main() { const predictedAddress = await predictAddress(sessionKey.address, adminKey.address); console.log('Predicted Address:', predictedAddress); const usdcBalances = await fetchUSDCBalance(predictedAddress); console.log('USDC Balances:', usdcBalances); if (!usdcBalances) { throw new Error('No USDC balance found'); } await transferErc20OnChain( { accountAddress: predictedAddress as Hex, sessionAddress: sessionKey.address as Hex, adminAddress: adminKey.address as Hex, }, usdcBalances, ); } main(); ``` Add scripts to your `package.json` to run the `index.ts` file: ```json package.json theme={null} { "name": "onebalance-calldata", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "calldata": "ts-node index.ts", "build": "tsc", "clean": "rm -rf dist" }, "keywords": [], "author": "", "license": "ISC", "packageManager": "pnpm@10.9.0", "dependencies": { "axios": "^1.9.0", "viem": "^2.30.5" }, "devDependencies": { "@types/node": "^22.15.28", "ts-node": "^10.9.2", "tslib": "^2.8.1", "typescript": "^5.8.3" } } ``` Run your calldata execution: ```bash theme={null} pnpm run calldata ```After running the script for the first time, you'll see a predicted address printed in the console. You need to fund this address with USDC on any supported chain before the transaction will succeed. Transfer some USDC to the predicted address, then run the script again. **Checkpoint:** You should see session/admin addresses printed, account prediction, balance check, and transaction execution in your console. ## Next Steps## Common Issues * **No USDC balance found** - User needs to fund their `predicted address` with USDC. * **Balance Too Low** - Ensure your account has sufficient USDC. The API requires tokens for both gas and the transfer amount. * **Transaction Timeout** - Network congestion can delay execution. Increase timeout or check status manually using the quote ID. * **Invalid Calldata** - Verify your ABI encoding matches the target contract's interface exactly. ## Complete Example The complete implementation is available in our [OneBalance Examples repository](https://github.com/OneBalance-io/onebalance-examples/tree/main/calldata). ```typescript theme={null} import { existsSync, readFileSync, writeFileSync } from 'node:fs'; import axios, { AxiosResponse } from 'axios'; import { HashTypedDataParameters, encodeFunctionData, parseAbi } from 'viem'; import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'; const BASE_URL = 'https://be.onebalance.io'; // Note: Using the production API endpoint will produce a different predicted address const PUBLIC_API_KEY = '42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11'; // Helper function to create authenticated headers function createAuthHeaders(): Record Understanding token allowances and approval management Complex scenarios, batch operations, and optimization techniques Production-ready examples for DeFi, NFT, and gaming use cases Handle common errors and implement retry logic { return { 'x-api-key': PUBLIC_API_KEY, }; } async function apiRequest ( method: 'get' | 'post', endpoint: string, data: RequestData, isParams = false, ): Promise { try { const config = { headers: createAuthHeaders(), ...(isParams ? { params: data } : {}), }; const url = `${BASE_URL}${endpoint}`; const response: AxiosResponse = method === 'post' ? await axios.post(url, data, config) : await axios.get(url, { ...config, params: data }); return response.data; } catch (error) { if (axios.isAxiosError(error) && error.response) { throw new Error(JSON.stringify(error.response.data)); } throw error; } } // API methods async function apiPost (endpoint: string, data: RequestData): Promise { return apiRequest ('post', endpoint, data); } async function apiGet (endpoint: string, params: RequestData): Promise { return apiRequest ('get', endpoint, params, true); } // Generate session key pair function generateEOAKey() { const privateKey = generatePrivateKey(); const account = privateKeyToAccount(privateKey); return { privateKey, address: account.address, }; } function readOrCacheEOAKey(key: string) { if (existsSync(`${key}-key.json`)) { const cachedKeys = readFileSync(`${key}-key.json`, 'utf8'); return JSON.parse(cachedKeys); } const keys = generateEOAKey(); writeFileSync(`${key}-key.json`, JSON.stringify(keys, null, 2)); return keys; } // Usage example const sessionKey = readOrCacheEOAKey('session'); console.log('Session Address:', sessionKey.address); const adminKey = readOrCacheEOAKey('admin'); console.log('Admin Address:', adminKey.address); async function predictAddress(sessionAddress: string, adminAddress: string): Promise { const response = await apiPost<{ sessionAddress: string; adminAddress: string }, { predictedAddress: string }>( '/api/account/predict-address', { sessionAddress, adminAddress, }, ); return response.predictedAddress; } async function fetchBalances(address: string) { const response = await apiGet< { address: string }, { balanceByAggregatedAsset: { aggregatedAssetId: string; balance: string; individualAssetBalances: { assetType: string; balance: string; fiatValue: number }[]; fiatValue: number; }[]; balanceBySpecificAsset: { assetType: string; balance: string; fiatValue: number; }[]; totalBalance: { fiatValue: number; }; } >('/api/v2/balances/aggregated-balance', { address }); return response; } async function fetchUSDCBalance(address: string) { const response = await fetchBalances(address); return response.balanceByAggregatedAsset.find((asset) => asset.aggregatedAssetId === 'ob:usdc'); } type Hex = `0x${string}`; interface EvmAccount { accountAddress: Hex; sessionAddress: Hex; adminAddress: Hex; } interface EvmCall { to: Hex; value?: Hex; data?: Hex; } interface TokenRequirement { assetType: string; amount: string; } interface TokenAllowanceRequirement extends TokenRequirement { spender: Hex; } type StateMapping = { [slot: Hex]: Hex; }; type StateDiff = { stateDiff?: StateMapping; code?: Hex; balance?: Hex; }; type Override = StateDiff & { address: Hex; }; interface PrepareCallRequest { account: EvmAccount; targetChain: string; // CAIP-2 calls: EvmCall[]; tokensRequired: TokenRequirement[]; allowanceRequirements?: TokenAllowanceRequirement[]; overrides?: Override[]; // permits validAfter?: string; validUntil?: string; } interface SerializedUserOperation { sender: Hex; nonce: string; factory?: Hex; factoryData?: Hex; callData: Hex; callGasLimit: string; verificationGasLimit: string; preVerificationGas: string; maxFeePerGas: string; maxPriorityFeePerGas: string; paymaster?: Hex; paymasterVerificationGasLimit?: string; paymasterPostOpGasLimit?: string; paymasterData?: Hex; signature: Hex; initCode?: Hex; paymasterAndData?: Hex; } interface ChainOperationBasic { userOp: SerializedUserOperation; typedDataToSign: HashTypedDataParameters; } interface ChainOperation extends ChainOperationBasic { assetType: string; amount: string; } interface TargetCallQuote { account: EvmAccount; chainOperation: ChainOperation; tamperProofSignature: string; } interface CallRequest { account: EvmAccount; chainOperation: ChainOperation; tamperProofSignature: string; fromAggregatedAssetId: string; } interface AssetUsed { aggregatedAssetId: string; assetType: string[] | string; amount: string; minimumAmount?: string; } interface FiatValue { fiatValue: string; amount: string; } interface OriginAssetUsed extends AssetUsed { assetType: string[]; fiatValue: FiatValue[]; } interface DestinationAssetUsed extends AssetUsed { assetType: string; fiatValue: string; minimumAmount?: string; minimumFiatValue?: string; } interface Quote { id: string; account: EvmAccount; originChainsOperations: ChainOperation[]; destinationChainOperation?: ChainOperation; originToken?: OriginAssetUsed; destinationToken?: DestinationAssetUsed; validUntil?: string; // block number, if empty the valid until will be MAX_UINT256 validAfter?: string; // block number, if empty the valid after will be 0 expirationTimestamp: string; tamperProofSignature: string; } interface OpGuarantees { non_equivocation: boolean; reorg_protection: boolean; valid_until?: number; valid_after?: number; } type BundleGuarantees = Record ; interface BundleResponse { success: boolean; guarantees: BundleGuarantees | null; error: string | null; } type TransactionType = 'SWAP' | 'TRANSFER' | 'CALL'; type OperationStatus = | 'PENDING' // not yet begun processing but has been submitted | 'IN_PROGRESS' // processing the execution steps of the operation | 'COMPLETED' // all steps completed with success | 'REFUNDED' // none or some steps completed, some required step failed causing the whole operation to be refunded | 'FAILED'; // all steps failed interface OperationDetails { hash?: Hex; chainId?: number; explorerUrl?: string; } interface HistoryTransaction { quoteId: string; type: TransactionType; originToken?: OriginAssetUsed; destinationToken?: DestinationAssetUsed; status: OperationStatus; user: Hex; recipientAccountId: string; // the caip-10 address of the recipient // if type is SWAP or TRANSFER originChainOperations?: OperationDetails[]; // the asset(s) that were sent from the source destinationChainOperations?: OperationDetails[]; // the asset that was received to the final destination } interface HistoryResponse { transactions: HistoryTransaction[]; continuation?: string; } async function prepareCallQuote(quoteRequest: PrepareCallRequest): Promise { return apiPost ('/api/quotes/prepare-call-quote', quoteRequest); } async function fetchCallQuote(callRequest: CallRequest): Promise { return apiPost('/api/quotes/call-quote', callRequest); } async function executeQuote(quote: Quote): Promise { return apiPost ('/api/quotes/execute-quote', quote); } async function fetchTransactionHistory(address: string): Promise{ return apiGet<{ user: string; limit: number; sortBy: string }, HistoryResponse>('/api/status/get-tx-history', { user: address, limit: 1, sortBy: 'createdAt', }); } async function signOperation(operation: ChainOperation, key: Hex): Promise { return { ...operation, userOp: { ...operation.userOp, signature: await privateKeyToAccount(key).signTypedData(operation.typedDataToSign) }, }; } async function transferErc20OnChain( account: EvmAccount, usdcBalances: { aggregatedAssetId: string; balance: string; individualAssetBalances: { assetType: string; balance: string; fiatValue: number }[]; }, ) { const largestUsdcBalanceEntry = usdcBalances.individualAssetBalances.reduce((max, current) => { return Number(current.balance) > Number(max.balance) ? current : max; }); const chain = 'eip155:42161'; // Arbitrum const usdcAddress = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831'; // Arbitrum USDC address if (largestUsdcBalanceEntry.balance === '0') { throw new Error('No USDC balance found'); } const transferDefinition = parseAbi(['function transfer(address to, uint256 amount) returns (bool)']); const transferCallData = encodeFunctionData({ abi: transferDefinition, functionName: 'transfer', args: [adminKey.address, 1n], }); const quoteRequest: PrepareCallRequest = { account, targetChain: chain, calls: [ { to: usdcAddress as Hex, data: transferCallData, value: '0x0', }, ], tokensRequired: [ { assetType: `${chain}/erc20:${usdcAddress}`, amount: '100000', }, ], }; console.log(quoteRequest); const preparedQuote = await prepareCallQuote(quoteRequest); const signedChainOp = await signOperation(preparedQuote.chainOperation, sessionKey.privateKey); const callRequest: CallRequest = { fromAggregatedAssetId: 'ob:usdc', account, tamperProofSignature: preparedQuote.tamperProofSignature, chainOperation: signedChainOp, }; console.log('callRequest', callRequest); const quote = await fetchCallQuote(callRequest); for (let i = 0; i < quote.originChainsOperations.length; i++) { const callQuoteSignedChainOperation = await signOperation(quote.originChainsOperations[i], sessionKey.privateKey); quote.originChainsOperations[i] = callQuoteSignedChainOperation; } console.log('quote', quote); const bundle = await executeQuote(quote); if (bundle.success) { console.log('Bundle executed'); const timeout = 60_000; let completed = false; const startTime = Date.now(); while (!completed) { try { console.log('fetching transaction history...'); const transactionHistory = await fetchTransactionHistory(quote.account.accountAddress); console.log('transactionHistory', transactionHistory); if (transactionHistory.transactions.length > 0) { const [tx] = transactionHistory.transactions; if (tx.quoteId === quote.id) { if (tx.status === 'COMPLETED') { console.log('Transaction completed and operation executed'); completed = true; break; } console.log('Transaction status: ', tx.status); } } } catch {} if (Date.now() - startTime > timeout) { throw new Error('Transaction not completed in time'); } await new Promise((resolve) => setTimeout(resolve, 1_000)); } } else { console.log('Bundle execution failed'); } } async function main() { const predictedAddress = await predictAddress(sessionKey.address, adminKey.address); console.log('Predicted Address:', predictedAddress); const usdcBalances = await fetchUSDCBalance(predictedAddress); console.log('USDC Balances:', usdcBalances); if (!usdcBalances) { throw new Error('No USDC balance found'); } await transferErc20OnChain( { accountAddress: predictedAddress as Hex, sessionAddress: sessionKey.address as Hex, adminAddress: adminKey.address as Hex, }, usdcBalances, ); } main(); ``` ## Integration Checklist Before going to production, ensure you have: * Stored API keys securely (environment variables) * Implemented proper error handling * Added transaction monitoring * Tested with small amounts first * Implemented retry logic for network failures * Added logging for debugging * Validated all addresses and amounts * Handled signature rejection cases # Contract Calls Overview Source: https://docs.onebalance.io/guides/contract-calls/overview Overview of OneBalance smart contract calls, enabling cross-chain execution with gas abstraction and unified asset management. Contract calls enable you to execute any smart contract operation using your aggregated balance. This powerful feature allows for complex DeFi operations, NFT interactions, gaming actions, and custom business logic - all while benefiting from unified asset management. ## What Are Contract Calls? Contract calls let you interact with smart contracts on any supported chain while OneBalance handles: * **Cross-chain token routing** - Use tokens from any chain to pay for operations * **Automatic gas abstraction** - No need to hold native tokens on target chains * **Token approvals** - Automatic ERC20 approvals when needed * **Transaction bundling** - Multiple operations in a single user experience **Account Configuration Required**: Contract calls require proper account setup. Choose your account model based on your security and operational needs. See [Account Models](/concepts/accounts) for setup instructions. ## Quick Example Here's how to transfer USDC on Arbitrum using funds from your aggregated balance: ```bash Terminal theme={null} curl -X POST "https://be.onebalance.io/api/quotes/prepare-call-quote" \ -H "x-api-key: ONEBALANCE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "account": { "sessionAddress": "0x1cBF...", "adminAddress": "0xc162...", "accountAddress": "0xE202..." }, "targetChain": "eip155:42161", "calls": [{ "to": "0xaf88d065e77c8cc2239327c5edb3a432268e5831", "data": "0xa9059cbb000000000000000000000000742d35cc6634c0532925a3b844bc454e4438f44e0000000000000000000000000000000000000000000000000000000005f5e100", "value": "0x0" }], "tokensRequired": [{ "assetType": "eip155:42161/erc20:0xaf88d065e77c8cc2239327c5edb3a432268e5831", "amount": "100000000" }], "allowanceRequirements": [] }' ``` ## Cross-Chain Funding with Solana You can fund EVM contract calls using assets from Solana for cost efficiency and liquidity access:Complete examples for funding EVM contract calls with SOL and USDC from Solana Use Solana assets with EIP-7702 delegated accounts for atomic cross-chain execution Solana funding enables you to leverage cheaper transaction fees and access SOL/USDC liquidity for EVM contract execution. ## How It Works OneBalance contract calls are executed through a three-phase process that handles all the complexity behind the scenes. ### Phase Breakdown 1. **Prepare Quote** - Submit your contract calls and requirements to get executable operations. OneBalance analyzes your requirements, generates user operations, and calculates optimal routing across chains. 2. **Sign Target Operation** - Sign the generated target chain operation with your wallet. This happens client-side using standard EIP-712 typed data signatures. See [Signing Guide](/concepts/signing) for complete implementation details. 3. **Get Full Quote** - Submit the signed target operation to get the complete cross-chain quote including all origin chain operations required. 4. **Sign & Execute** - Sign all origin chain operations, then submit the fully signed quote for execution across all chains. ```mermaid theme={null} flowchart LR A[Prepare Quote] --> B[Sign Target] --> C[Get Full Quote] --> D[Sign & Execute] ``` ### Detailed Flow For those interested in the technical details, here's the complete execution flow: ```mermaid theme={null} sequenceDiagram participant User participant OneBalance API participant Origin Chain participant Target Chain Note over User, Target Chain: Phase 1: Preparation User->>OneBalance API: 1. Prepare call quote Note right of OneBalance API: • Analyze requirements
• Generate target operation
• Calculate routing OneBalance API-->>User: Target operation + typed data Note over User, Target Chain: Phase 2: Sign Target User->>User: 2. Sign target operation Note right of User: • Sign with session key
• EIP-712 signature User->>OneBalance API: 3. Get full quote Note over User, Target Chain: Phase 3: Get Full Quote OneBalance API-->>User: Complete quote with origin operations User->>User: 4. Sign all origin operations Note right of User: • Sign each origin operation
• Multiple signatures Note over User, Target Chain: Phase 4: Execution User->>OneBalance API: 5. Execute quote OneBalance API->>Origin Chain: Execute origin operations Note right of Origin Chain: • Token transfers
• Cross-chain bridging OneBalance API->>Target Chain: Execute target operation Note right of Target Chain: • Smart contract calls
• Token settlements OneBalance API-->>User: Transaction confirmations ```Each phase uses specific API endpoints: * **Phase 1: Prepare** - [POST /quotes/prepare-call-quote](/api-reference/quotes/prepare-call-quote) - Analyzes your calls and generates target operation * **Phase 2: Sign Target** - Client-side signing with your wallet or library * **Phase 3: Get Full Quote** - [POST /quotes/call-quote](/api-reference/quotes/get-call-quote) - Returns complete cross-chain quote * **Phase 4: Execute** - [POST /quotes/execute-quote](/api-reference/quotes/execute-quote) - Submits fully signed quote for execution ## Common Use Cases### Real-World Examples * **Cross-chain DEX Trading** - Use USDC from Polygon to trade on Uniswap on Base * **Multi-chain Staking** - Stake ETH on Ethereum using tokens from your Arbitrum balance * **NFT Minting** - Mint NFTs on any chain using your unified token balance * **DAO Participation** - Vote on proposals using tokens from multiple chains ## Benefits Cross-chain transfers, swaps, and token management Lending, borrowing, staking, and yield farming Minting, trading, and marketplace operations Proposal voting and treasury management One API for all chains and protocols Optimized routing and batched operations No chain switching or token bridging required Simple integration with existing smart contracts **Getting Started**: All examples in this section use **Role-Based** accounts. For complete setup instructions and to choose the right account model for your needs, see [Account Models](/concepts/accounts). ## Next Steps# Troubleshooting Contract Calls Source: https://docs.onebalance.io/guides/contract-calls/troubleshooting Solutions to common issues in cross-chain contract calls with OneBalance, including debugging and error-handling best practices. This guide covers the most common issues developers encounter when implementing OneBalance contract calls and their solutions. Build your first contract call integration step-by-step Copy-paste examples for common use cases and protocols Complex scenarios, batch operations, and optimization techniques Handle token approvals and permission management Debug common issues and error handling strategies **Quick Debug Tip**: Most contract call failures happen during the prepare phase. Always validate your inputs before calling the API. ## Common Errors ### Including Manual Approval Calls**Most Common Error**: Adding `approve()` calls to your transaction array will cause failures. OneBalance automatically handles all token approvals through the `allowanceRequirements` field. Including manual `approve()` calls in your transaction array conflicts with this system and will cause the request to fail. ```typescript wrong-approval-approach.ts theme={null} // ❌ WRONG - This will fail const badRequest = { calls: [ { to: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC data: encodeFunctionData({ abi: parseAbi(['function approve(address spender, uint256 amount)']), functionName: 'approve', args: [spenderAddress, amount] }), value: '0' }, { to: spenderAddress, data: actualCallData, value: '0' } ] // This causes "Invalid call data" errors }; ``` ```typescript correct-approval-approach.ts theme={null} // ✅ CORRECT - Use allowanceRequirements const correctRequest = { calls: [{ to: spenderAddress, data: actualCallData, value: '0' }], allowanceRequirements: [{ assetType: 'eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', amount: amount.toString(), spender: spenderAddress }] }; ``` ### Invalid Tamper Proof Signature This error occurs when the quote structure is modified after preparation. The tamper-proof signature validates the exact quote structure, so any changes will invalidate it. **Common Causes:** * JSON key ordering changed during serialization * Extra fields added to the quote object * Quote passed through JSON.stringify/parse incorrectly ```typescript preserve-quote-structure.ts theme={null} // ❌ Wrong - modifies structure const modifiedQuote = { ...preparedQuote, extraField: 'value' }; // ❌ Wrong - changes key order const reordered = JSON.parse(JSON.stringify(preparedQuote)); // ✅ Correct - preserve exact structure preparedQuote.chainOperation.userOp.signature = signature; ``` ### Account Configuration Errors Account validation ensures all required addresses are present and properly formatted. Missing or invalid addresses will cause authentication failures during quote preparation. ```typescript account-validation.ts theme={null} // ✅ Validate account before using function validateAccount(account: Account) { if (!account.sessionAddress || !account.adminAddress || !account.accountAddress) { throw new Error('Missing required account addresses'); } if (!isAddress(account.sessionAddress)) { throw new Error('Invalid session address format'); } if (account.sessionAddress === account.adminAddress) { throw new Error('Session and admin addresses must be different'); } return true; } ``` ## API Response Errors## Balance & Amount Issues ### Insufficient Balance Always verify the account has sufficient balance before attempting operations. This prevents failed transactions and provides better user feedback. ```typescript balance-validation.ts theme={null} // Always check balance before operations async function validateSufficientBalance( accountAddress: string, assetId: string, requiredAmount: bigint ) { const response = await apiGet('/v2/balances/aggregated-balance', { address: accountAddress, assetId }); const assetBalance = response.balanceByAggregatedAsset.find( asset => asset.aggregatedAssetId === assetId ); if (!assetBalance) { throw new Error(`No balance found for asset ${assetId}`); } const availableBalance = BigInt(assetBalance.balance); if (availableBalance < requiredAmount) { throw new Error( `Insufficient balance. Required: ${requiredAmount}, Available: ${availableBalance}` ); } return true; } ``` ### Decimal Calculation Errors Token amounts must be calculated using the correct decimal places. Using wrong decimals is a common cause of "insufficient balance" or "amount too small" errors. ### 400 Bad Request These errors occur when request data doesn't match expected formats. Always validate input data before sending API requests. **Asset ID format errors:** ```typescript asset-id-validation.ts theme={null} // ❌ Wrong formats const wrong = [ 'usdc', 'USDC', 'erc20:0x...', 'ob:USDC' ]; // ✅ Correct format const correct = 'ob:usdc'; ``` **Chain ID format errors:** ```typescript chain-validation.ts theme={null} // ❌ Wrong formats const wrongChains = ['base', '8453', 'Base']; // ✅ Correct format const correctChain = 'eip155:8453'; ``` ### 401 Unauthorized This error indicates missing or invalid API key configuration. Verify your environment variables are set correctly. ```typescript api-key-check.ts theme={null} // Check your API key configuration const headers = { 'x-api-key': process.env.ONEBALANCE_API_KEY }; if (!headers['x-api-key']) { throw new Error('OneBalance API key not configured'); } ``` ### 422 Unprocessable Entity Usually indicates business logic errors: * Insufficient balance * Amount below minimum (\$0.50) * Unsupported token/chain combination ### 503 Service Unavailable Server errors are typically temporary. Implement retry logic with exponential backoff to handle these gracefully. ```typescript retry-logic.ts theme={null} async function retryableApiCall ( apiCall: () => Promise , maxRetries = 3 ): Promise { for (let attempt = 0; attempt < maxRetries; attempt++) { try { return await apiCall(); } catch (error: any) { const isRetryable = error.response?.status >= 500 || error.response?.status === 503; if (!isRetryable || attempt === maxRetries - 1) { throw error; } // Exponential backoff: 1s, 2s, 4s const delay = Math.pow(2, attempt) * 1000; await new Promise(resolve => setTimeout(resolve, delay)); } } throw new Error('Max retries exceeded'); } ``` ```typescript decimal-handling.ts theme={null} import { parseUnits, formatUnits } from 'viem'; // ✅ Correct decimal handling function formatTokenAmount(amount: string, decimals: number) { // Convert user input to smallest unit const parsedAmount = parseUnits(amount, decimals); // Validate minimum amount ($0.50 equivalent) const minimumUSD = parseUnits('0.50', 6); // $0.50 in USDC terms return { amount: parsedAmount.toString(), formatted: formatUnits(parsedAmount, decimals) }; } ``` ## Signing & Execution Issues ### Signature Validation Verify signatures locally before sending to prevent failed transactions. This helps debug signing issues early in the development process. ```typescript signature-validation.ts theme={null} import { verifyTypedData } from 'viem'; async function validateSignature( account: Account, typedData: any, signature: string ) { const isValid = await verifyTypedData({ address: account.sessionAddress as `0x${string}`, domain: typedData.domain, types: typedData.types, primaryType: typedData.primaryType, message: typedData.message, signature: signature as `0x${string}` }); if (!isValid) { throw new Error('Invalid signature'); } return true; } ``` ### Transaction Status Monitoring Implement proper status polling to track transaction progress and handle different completion states. This provides users with real-time feedback on their transactions. ```typescript status-monitoring.ts theme={null} async function waitForTransactionCompletion( quoteId: string, timeoutMs = 60000 ): Promise * **USDC**: 6 decimals * **ETH/WETH**: 18 decimals * **WBTC**: 8 decimals * **DAI**: 18 decimals Always use `parseUnits()` and verify the token's actual decimals before calculations. { const startTime = Date.now(); while (Date.now() - startTime < timeoutMs) { const status = await apiGet('/status/get-execution-status', { quoteId }); switch (status.status.status) { case 'COMPLETED': return status; case 'FAILED': throw new Error(`Transaction failed: ${status.quoteId}`); case 'REFUNDED': throw new Error(`Transaction refunded - likely amount too small or gas costs too high`); case 'PENDING': // Continue polling await new Promise(resolve => setTimeout(resolve, 2000)); break; default: throw new Error(`Unknown status: ${status.status.status}`); } } throw new Error('Transaction timeout'); } ``` ## Contract-Specific Issues ### DEX Integration Problems DEX protocols have specific parameter requirements that must be validated before execution. Common issues include expired deadlines and invalid fee tiers. ```typescript dex-troubleshooting.ts theme={null} // Common Uniswap V3 issues function validateUniswapV3Call(params: any) { // Check deadline is in future const now = Math.floor(Date.now() / 1000); if (params.deadline <= now) { throw new Error('Deadline must be in the future'); } // Validate fee tier const validFees = [100, 500, 3000, 10000]; if (!validFees.includes(params.fee)) { throw new Error(`Invalid fee tier: ${params.fee}`); } // Check recipient matches account if (params.recipient !== account.accountAddress) { console.warn('Recipient does not match account address'); } } ``` ### NFT Marketplace Issues NFT purchases require additional validation for contract addresses and reasonable price limits. This helps prevent accidental overpayments or invalid transactions. ```typescript nft-troubleshooting.ts theme={null} // Common NFT purchase validation function validateNFTPurchase(contractAddress: string, tokenId: string, price: bigint) { if (!isAddress(contractAddress)) { throw new Error('Invalid NFT contract address'); } // Check if price seems reasonable (basic sanity check) const maxReasonablePrice = parseUnits('1000', 6); // $1000 USDC if (price > maxReasonablePrice) { console.warn('Price seems unusually high - please verify'); } return true; } ``` ## Debugging Tools ### Enhanced Logging The logging helps identify issues quickly during development. This debug function provides structured output for all request parameters. ```typescript debug-helpers.ts theme={null} // Request logging function debugPrepareRequest(request: PrepareCallRequest) { console.group('🔍 OneBalance Prepare Request Debug'); console.log('📋 Account:', { session: request.account.sessionAddress, admin: request.account.adminAddress, smart: request.account.accountAddress }); console.log('🌐 Target Chain:', request.targetChain); console.log(`📞 Calls (${request.calls.length}):`); request.calls.forEach((call, i) => { console.log(` Call ${i + 1}:`, { to: call.to, value: call.value, dataLength: call.data.length, dataPreview: call.data.slice(0, 10) + '...' }); }); console.log(`💰 Tokens Required (${request.tokensRequired.length}):`); request.tokensRequired.forEach((token, i) => { console.log(` Token ${i + 1}:`, token); }); console.log(`🔐 Allowances Required (${request.allowanceRequirements.length}):`); request.allowanceRequirements.forEach((allowance, i) => { console.log(` Allowance ${i + 1}:`, allowance); }); console.groupEnd(); } ``` ### Health Check Function Run this function before implementing your main logic to verify all components are working correctly. It tests API connectivity, account prediction, and balance retrieval. ```typescript health-check.ts theme={null} async function performHealthCheck(account: Account) { const checks = []; try { // 1. Check API connectivity await apiGet('/chains/supported-list', {}); checks.push('✅ API connectivity'); } catch { checks.push('❌ API connectivity failed'); } try { // 2. Check account prediction const prediction = await apiPost('/account/predict-address', { sessionAddress: account.sessionAddress, adminAddress: account.adminAddress }); checks.push(`✅ Account prediction: ${prediction.predictedAddress}`); } catch { checks.push('❌ Account prediction failed'); } try { // 3. Check balance retrieval const balance = await apiGet('/v2/balances/aggregated-balance', { address: account.accountAddress }); checks.push(`✅ Balance check: ${balance.balanceByAggregatedAsset.length} assets`); } catch { checks.push('❌ Balance check failed'); } console.log('🏥 OneBalance Health Check Results:'); checks.forEach(check => console.log(check)); return checks.every(check => check.startsWith('✅')); } ``` ## Common Pitfalls **Top 5 Mistakes to Avoid:** 1. **Manual Approvals** - Never include `approve()` in your calls array 2. **Wrong Decimals** - Always verify token decimals (USDC = 6, not 18) 3. **Invalid Chain Format** - Use `eip155:chainId`, not just the chain ID 4. **Modifying Quotes** - Never alter the quote structure after preparation 5. **Insufficient Balance** - Always check balance before attempting operations ## Error Code Reference | Error Code | Meaning | Solution | | ---------- | -------------------- | --------------------------------------- | | `400` | Bad Request | Check input format and validation | | `401` | Unauthorized | Verify API key configuration | | `422` | Unprocessable Entity | Check business logic (balance, amounts) | | `503` | Service Unavailable | Implement retry logic | ## Getting HelpReview the specific error section above for your issue Run the health check and debug logging functions Check [working examples](/guides/contract-calls/examples) for reference implementations Use the **Intercom chat widget** in the bottom right corner for instant help, or email [support@onebalance.io](mailto:support@onebalance.io). Provide your quote ID, error message, and debug logs for faster resolution. **Pro Tip**: Most issues can be prevented by running the health check function before implementing your main logic. # Getting Started with EIP-7702 Source: https://docs.onebalance.io/guides/eip-7702/getting-started Implement cross-chain contract calls using EIP-7702 delegation This guide shows you how to implement EIP-7702 with OneBalance using a complete, runnable example. You'll build a cross-chain USDC transfer that automatically handles delegation.**Working Code Examples**: Complete, production-ready EIP-7702 examples are available in our open-source repository: [OneBalance Examples - EIP-7702](https://github.com/OneBalance-io/onebalance-examples/tree/main/eip-7702). These examples include proper signature handling, multi-chain operations, and error recovery patterns. ## Prerequisites * OneBalance API key ([Get one here](/api-reference/authentication)) * Wallet library with `signAuthorization` support (viem) * TypeScript/JavaScript environment ## Account Configuration EIP-7702 accounts use your existing EOA address: ```typescript theme={null} const account = { type: "kernel-v3.3-ecdsa", deploymentType: "EIP7702", signerAddress: "0x5Cb2369421F8a00Ef556d662D6E97C1419B1d37c", // Your EOA accountAddress: "0x5Cb2369421F8a00Ef556d662D6E97C1419B1d37c" // Same address }; ```Unlike [ERC-4337 accounts](/concepts/accounts), you don't need to predict a new address. Your EOA address becomes your smart account address through delegation. ## Step 1: Prepare Quote Call [prepare-call-quote](/api-reference/quotes/prepare-call-quote) to analyze your requirements. If your EOA needs delegation, you'll receive delegation objects to sign:```typescript TypeScript theme={null} const prepareResponse = await fetch('https://be.onebalance.io/api/quotes/prepare-call-quote', { method: 'POST', headers: { 'x-api-key': 'YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ account: { type: "kernel-v3.3-ecdsa", deploymentType: "EIP7702", signerAddress: "0x5Cb2369421F8a00Ef556d662D6E97C1419B1d37c", accountAddress: "0x5Cb2369421F8a00Ef556d662D6E97C1419B1d37c" }, targetChain: "eip155:42161", // Arbitrum calls: [{ to: "0xaf88d065e77c8cc2239327c5edb3a432268e5831", // USDC on Arbitrum data: "0xa9059cbb000000000000000000000000742d35cc6634c0532925a3b844bc454e4438f44e0000000000000000000000000000000000000000000000000000000000989680", // transfer(address,uint256) value: "0x0" }], tokensRequired: [{ assetType: "eip155:42161/erc20:0xaf88d065e77c8cc2239327c5edb3a432268e5831", amount: "10000000" // 10 USDC }] }) }); const prepareData = await prepareResponse.json(); ``` ```bash cURL theme={null} curl -X POST 'https://be.onebalance.io/api/quotes/prepare-call-quote' \ -H 'x-api-key: YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "account": { "type": "kernel-v3.3-ecdsa", "deploymentType": "EIP7702", "signerAddress": "0x5Cb2369421F8a00Ef556d662D6E97C1419B1d37c", "accountAddress": "0x5Cb2369421F8a00Ef556d662D6E97C1419B1d37c" }, "targetChain": "eip155:42161", "calls": [{ "to": "0xaf88d065e77c8cc2239327c5edb3a432268e5831", "data": "0xa9059cbb000000000000000000000000742d35cc6634c0532925a3b844bc454e4438f44e0000000000000000000000000000000000000000000000000000000000989680", "value": "0x0" }], "tokensRequired": [{ "assetType": "eip155:42161/erc20:0xaf88d065e77c8cc2239327c5edb3a432268e5831", "amount": "10000000" }] }' ``` ## Step 2: Sign Delegations If your EOA isn't delegated yet, sign the delegation objects using viem's [`signAuthorization`](https://viem.sh/docs/eip7702/signAuthorization) method: EIP-7702 enables EOAs to delegate execution to smart contracts. Delegation is required on **source chains** (for spending) and **destination chains** (only for contract calls). ### Delegation Signature Structure When your EOA needs delegation, OneBalance returns a delegation object that you need to sign: ```typescript theme={null} interface DelegationSignature { chainId: number; contractAddress: Hex; // Kernel v3.3 smart contract address nonce: number; // Current delegation nonce for the EOA r: Hex; s: Hex; v: Hex; yParity: number; type: 'Signed' | 'Unsigned'; } ``` ### Signing Process**Kernel Account Signing**: EIP-7702 accounts using Kernel v3.3 require special signing patterns. Unlike role-based accounts that sign typed data, Kernel accounts sign the UserOperation hash directly. For complete signing documentation including error handling and troubleshooting, see the [Signing Guide](/concepts/signing). ```typescript theme={null} import { privateKeyToAccount } from 'viem/accounts'; import { entryPoint07Address, getUserOperationHash } from 'viem/account-abstraction'; const signerAccount = privateKeyToAccount('0x...' as `0x${string}`); const operation = prepareData.chainOperation; const chainId = Number(operation.typedDataToSign.domain.chainId); // Step 1: Sign delegation if needed if (operation.delegation) { const authTuple = { contractAddress: operation.delegation.contractAddress, nonce: operation.delegation.nonce, chainId: chainId, }; const signedTuple = await signerAccount.signAuthorization(authTuple); if (signedTuple.yParity == null) { throw new Error('Y parity is required for EIP-7702 delegation'); } // Add signature to delegation object operation.delegation.signature = { chainId: chainId, contractAddress: signedTuple.address, nonce: signedTuple.nonce, r: signedTuple.r, s: signedTuple.s, v: `0x${Number(signedTuple.v).toString(16).padStart(2, '0')}`, yParity: signedTuple.yParity, type: 'Signed', }; } // Step 2: Sign UserOperation hash (Kernel v3.3 specific) // Deserialize UserOp with proper BigInt conversions const deserializedUserOp = { sender: operation.userOp.sender, nonce: BigInt(operation.userOp.nonce), factory: operation.userOp.factory, factoryData: operation.userOp.factoryData, callData: operation.userOp.callData, callGasLimit: BigInt(operation.userOp.callGasLimit), verificationGasLimit: BigInt(operation.userOp.verificationGasLimit), preVerificationGas: BigInt(operation.userOp.preVerificationGas), maxFeePerGas: BigInt(operation.userOp.maxFeePerGas), maxPriorityFeePerGas: BigInt(operation.userOp.maxPriorityFeePerGas), paymaster: operation.userOp.paymaster, paymasterVerificationGasLimit: operation.userOp.paymasterVerificationGasLimit ? BigInt(operation.userOp.paymasterVerificationGasLimit) : undefined, paymasterPostOpGasLimit: operation.userOp.paymasterPostOpGasLimit ? BigInt(operation.userOp.paymasterPostOpGasLimit) : undefined, paymasterData: operation.userOp.paymasterData, signature: operation.userOp.signature, }; // Get UserOperation hash for EntryPoint 0.7 const userOpHash = getUserOperationHash({ userOperation: deserializedUserOp, entryPointAddress: entryPoint07Address, entryPointVersion: '0.7', chainId: chainId, }); // Sign the hash directly (not typed data) const signature = await signerAccount.signMessage({ message: { raw: userOpHash } }); operation.userOp.signature = signature; ```The [`signAuthorization`](https://viem.sh/docs/eip7702/signAuthorization) method is part of viem's EIP-7702 support. It signs an authorization that allows your EOA to delegate execution to the Kernel v3.3 smart contract. Other wallet providers like [Privy](https://docs.privy.io/wallets/using-wallets/ethereum/sign-7702-authorization) also support this method. You must sign delegation objects for ALL chain operations returned by `prepare-call-quote` - both source and destination chains. ## Step 3: Get Quote Submit the signed delegation to [call-quote](/api-reference/quotes/get-call-quote) to get the executable quote:```typescript TypeScript theme={null} const quoteResponse = await fetch('https://be.onebalance.io/api/quotes/call-quote', { method: 'POST', headers: { 'x-api-key': 'YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ account: prepareData.account, chainOperation: operation, // Contains signed delegation tamperProofSignature: prepareData.tamperProofSignature, fromAggregatedAssetId: 'ob:usdc' // Specify which aggregated asset to use }) }); const quote = await quoteResponse.json(); ``` ```bash cURL theme={null} curl -X POST 'https://be.onebalance.io/api/quotes/call-quote' \ -H 'x-api-key: YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "account": { "type": "kernel-v3.3-ecdsa", "deploymentType": "EIP7702", "signerAddress": "0x5Cb2369421F8a00Ef556d662D6E97C1419B1d37c", "accountAddress": "0x5Cb2369421F8a00Ef556d662D6E97C1419B1d37c" }, "chainOperation": { "delegation": { "contractAddress": "0xd6CEDDe84be40893d153Be9d467CD6aD37875b28", "nonce": 0, "signature": { "chainId": 42161, "contractAddress": "0xd6CEDDe84be40893d153Be9d467CD6aD37875b28", "nonce": 0, "r": "0x...", "s": "0x...", "v": "0x1c", "yParity": 1, "type": "Signed" } }, "userOp": { "signature": "0x..." } }, "tamperProofSignature": "0x...", "fromAggregatedAssetId": "ob:usdc" }' ``` ## Step 4: Execute Execute the quote using [execute-quote](/api-reference/quotes/execute-quote). **Important**: You must also sign any origin chain operations: ```typescript theme={null} // Sign origin chain operations using the same pattern as Step 2 for (let i = 0; i < quote.originChainsOperations.length; i++) { const originOperation = quote.originChainsOperations[i]; // Use same signing logic as Step 2 for each origin chain operation // (delegation + UserOperation hash signing) quote.originChainsOperations[i] = await signOperation(originOperation); } ``````typescript TypeScript theme={null} const executeResponse = await fetch('https://be.onebalance.io/api/quotes/execute-quote', { method: 'POST', headers: { 'x-api-key': 'YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify(quote) }); const result = await executeResponse.json(); console.log('Transaction executed:', result); ``` ```bash cURL theme={null} curl -X POST 'https://be.onebalance.io/api/quotes/execute-quote' \ -H 'x-api-key: YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "quoteId": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", "account": { "type": "kernel-v3.3-ecdsa", "deploymentType": "EIP7702", "signerAddress": "0x5Cb2369421F8a00Ef556d662D6E97C1419B1d37c", "accountAddress": "0x5Cb2369421F8a00Ef556d662D6E97C1419B1d37c" }, "chainOperations": [ { "chainId": "eip155:42161", "userOp": { "sender": "0x5Cb2369421F8a00Ef556d662D6E97C1419B1d37c", "nonce": "0x0", "signature": "0x..." }, "delegation": { "contractAddress": "0xd6CEDDe84be40893d153Be9d467CD6aD37875b28", "signature": { "chainId": 42161, "r": "0x...", "s": "0x...", "yParity": 1, "type": "Signed" } } } ] }' ``` The operation will: 1. **Delegate** your EOA on chains that need it (automatically) 2. **Bridge** 10 USDC from source chains to the destination 3. **Execute** your contract call on the destination chain All in a single user interaction. ## Implementation Notes ### Critical Requirements * **Account type**: Must use `type: "kernel-v3.3-ecdsa"` and `deploymentType: "EIP7702"` * **Address configuration**: Both `accountAddress` and `signerAddress` must be the same EOA address * **Signing method**: Kernel accounts sign UserOperation hash (`signMessage()`) not typed data * **Y parity validation**: Always check `signedTuple.yParity` is not null * **Multi-chain signing**: Sign both destination operation (Step 2) and origin operations (Step 4) ### Key Differences from Role-Based Accounts | Aspect | Kernel v3.3 (EIP-7702) | Role-Based | | ------------------ | -------------------------------------- | -------------------------------- | | **Signing method** | UserOperation hash via `signMessage()` | Typed data via `signTypedData()` | | **Address** | Same as EOA | Predicted new address | | **EntryPoint** | Requires 0.7 | Not applicable | | **Delegation** | Required on spending chains | Not applicable | ## Next StepsHandle common errors and multi-input scenarios Complete production-ready code examples Your EOA now has smart account capabilities whenever needed, without changing addresses or migrating funds. # EIP-7702 Overview Source: https://docs.onebalance.io/guides/eip-7702/overview Enable EOAs to delegate execution to smart contracts for cross-chain operations ## What is EIP-7702? [EIP-7702](https://eip7702.io) allows Externally Owned Accounts (EOAs) to delegate their execution logic to smart contracts. When an EOA signs an authorization, it temporarily gains smart account capabilities without changing its address. ## Supported Operations EIP-7702 accounts work with all OneBalance transaction types: * **Swaps**: Cross-chain token exchanges with automatic bridging * **Transfers**: Send assets across chains to any address * **Contract Calls**: Execute smart contract functions with call data All operations benefit from gas abstraction and single-transaction UX. ### Key Concept: Delegation vs Deployment **EIP-7702 Accounts (Delegation)**: * EOA signs authorization to delegate to smart contract implementation * Address remains unchanged - no migration needed * Delegation happens when first transaction is needed **ERC-4337 Accounts (Deployment)**: * New smart contract deployed with predicted address * Requires address migration from existing EOA * Deployment happens when account is created ## Integration Flow OneBalance implements EIP-7702 through a clear process:Call [prepare-call-quote](/api-reference/quotes/prepare-call-quote) to analyze your requirements and get delegation objects if needed Sign any delegation objects using viem's [`signAuthorization`](https://viem.sh/docs/eip7702/signAuthorization) method Submit signed delegations to [call-quote](/api-reference/quotes/get-call-quote) to get the executable quote Execute the quote using [execute-quote](/api-reference/quotes/execute-quote) to perform the cross-chain operation OneBalance handles delegation automatically. If your EOA is already delegated on a chain, no additional signing is required. ## How EIP-7702 Works When you configure an EIP-7702 account with OneBalance: 1. **Account Configuration**: Set `deploymentType: "EIP7702"` and use your EOA address for both `signerAddress` and `accountAddress` 2. **Smart Contract Implementation**: OneBalance uses Kernel 3.3 as the delegation target 3. **Automatic Delegation**: When needed, OneBalance generates authorization objects for you to sign 4. **Execution**: Your EOA temporarily gains smart account capabilities during the transaction ## Example: Account Configuration ```typescript theme={null} const account = { type: "kernel-v3.3-ecdsa", deploymentType: "EIP7702", signerAddress: "0x5Cb2369421F8a00Ef556d662D6E97C1419B1d37c", // Your EOA accountAddress: "0x5Cb2369421F8a00Ef556d662D6E97C1419B1d37c" // Same address }; ``` When delegation is needed: * **Source chain**: Always required for spending operations (swaps, transfers, contract calls) * **Destination chain**: Only required for contract calls (not for swaps or transfers) ## When to Use EIP-7702 Choose EIP-7702 when you want to preserve existing EOA addresses: * **Existing EOA users**: No address migration needed * **Embedded wallets**: Upgrade EOAs to smart account capabilities * **Address preservation**: Keep on-chain history and familiar addresses **Choose ERC-4337 when you need:** * [Resource Locks](/concepts/resource-locks) (requires managed key infrastructure) * Custom account implementations beyond Kernel v3.3EIP-7702 uses the same ERC-4337 infrastructure (bundlers, paymasters, UserOps) but extends it to work with existing EOA addresses. Learn more about [Account Types](/concepts/accounts). ## Current Limitations OneBalance's EIP-7702 implementation works for most scenarios but has one constraint: **Multi-Input Limitation**: When you have assets on more than 1 source chain different from the destination chain, you'll need to use a [manual delegation workaround](/guides/eip-7702/troubleshooting#multi-input-limitation). | Example Assets | Destination | Result | | --------------------------- | ----------- | -------------------- | | USDC on Optimism | Arbitrum | ✅ Single transaction | | USDC on Optimism + Arbitrum | Arbitrum | ✅ Single transaction | | USDC on Optimism + Base | Arbitrum | ⚠️ Needs workaround | ## Cross-Chain Funding with Solana EIP-7702 accounts can be funded using Solana assets for cost efficiency and expanded liquidity access:Fund EIP-7702 contract calls using SOL or USDC from Solana Complete working examples of cross-chain contract execution Using Solana funding with EIP-7702 enables atomic operations: SOL → USDC swap → bridge → delegation → contract execution. ## Next StepsBuild your first EIP-7702 swap with delegation Handle multi-input limitations and edge cases All existing OneBalance API endpoints work with EIP-7702. Just update your account configuration to use `type: "kernel-v3.3-ecdsa"` and `deploymentType: "EIP7702"`. # EIP-7702 Troubleshooting Source: https://docs.onebalance.io/guides/eip-7702/troubleshooting Handle multi-input limitations and edge cases ## Common Implementation Issues Quick reference for resolving EIP-7702 implementation questions: ### Signature-Related Errors | Issue | Cause | Solution | | --------------------------------- | -------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | **"Y parity is required"** | Wallet doesn't return yParity in signature | Use [viem](https://viem.sh) or [compatible wallet library](https://docs.privy.io/wallets/using-wallets/ethereum/sign-7702-authorization) | | **"Invalid signature"** | Using `signTypedData()` instead of `signMessage()` | Use UserOperation hash signing for Kernel accounts | | **"UserOperation hash mismatch"** | Incorrect BigInt conversion or EntryPoint version | Ensure all numeric fields are `BigInt` and use EntryPoint 0.7 | ### Multi-Chain Operation Errors | Issue | Cause | Solution | | ---------------------------------------- | --------------------------------------------------- | ---------------------------------------------------------------------- | | **"Missing origin chain signatures"** | Forgot to sign origin chain operations | Sign all operations in `quote.originChainsOperations[]` | | **"Transaction failed on origin chain"** | Origin chain delegation or UserOp signature missing | Check that both delegation and UserOp are signed for each origin chain | ### Delegation Errors | Issue | Cause | Solution | | ------------------------ | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | | **`DELEGATION-001`** | EOA has a pre-existing delegation to a non-Kernel contract | Revoke existing delegation or use a different EOA. See [error code reference](/api-reference/error-codes#delegation-001) | | **"Nonce too low"** | Outdated delegation nonce | OneBalance handles this automatically; retry the request | | **"Already delegated"** | EOA already delegated to this contract | Normal - no delegation object will be returned | | **"Contract not found"** | Using wrong Kernel v3.3 address | Use the contract address returned by OneBalance | ### Account Configuration Errors | Issue | Cause | Solution | | -------------------------------- | -------------------------------------- | ---------------------------------------------------------------------- | | **"Account type not supported"** | Wrong account type configuration | Must use `type: "kernel-v3.3-ecdsa"` and `deploymentType: "EIP7702"` | | **"Address mismatch"** | Different signer and account addresses | Both `signerAddress` and `accountAddress` must be the same EOA address |**Critical**: Kernel v3.3 accounts require different signing patterns than role-based accounts. Always use UserOperation hash signing (`signMessage()`) instead of typed data signing (`signTypedData()`). ## Multi-Input Limitation OneBalance's EIP-7702 implementation has one architectural limitation for all operations (swaps, transfers, contract calls): it can only handle single-transaction execution when you have assets on **≤1 source chain different from the destination chain**. This limitation exists because OneBalance relies on external routing providers that don't yet support EIP-7702 authorization tuples in their multi-input APIs. Learn more about [how OneBalance works](/overview/how-onebalance-works). ### When You Need the Workaround | Your Assets | Destination | Result | | --------------------------- | --------------- | -------------------- | | USDC on Optimism | Arbitrum | ✅ Single transaction | | USDC on Optimism + Arbitrum | Arbitrum | ✅ Single transaction | | USDC on Optimism + Base | Arbitrum | ⚠️ Needs workaround | | USDC on 3+ different chains | Any destination | ⚠️ Needs workaround |This affects approximately 20% of operations. Most users will use the single-transaction flow without issues. ## Manual Delegation Workaround When you encounter the multi-input limitation, you need to manually delegate your EOAs before using OneBalance:### Manual Delegation Process Submit EIP-7702 delegations directly to each chain where you have assets Once delegated, use the normal OneBalance integration flow If the process is interrupted, you may need to manually complete transactions This requires a wallet library that supports EIP-7702 [`signAuthorization`](https://viem.sh/docs/eip7702/signAuthorization) method, such as [viem](https://viem.sh). ```typescript theme={null} import { createWalletClient, http } from 'viem'; import { optimism, base } from 'viem/chains'; // Submit delegation manually on each required chain async function submitDelegation(chainId: number) { const walletClient = createWalletClient({ chain: chainId === 10 ? optimism : base, transport: http() }); const authorization = await walletClient.signAuthorization({ contractAddress: "0xd6CEDDe84be40893d153Be9d467CD6aD37875b28", // Kernel v3.3 nonce: 0 // Get current nonce from chain }); const hash = await walletClient.sendTransaction({ authorizationList: [authorization], data: "0x", to: walletClient.account.address, }); return hash; } ``` ## Best Practices * **Optimize for the 80%**: Most users will use the single-transaction flow without issues * **Detect automatically**: Route users based on their asset distribution * **Provide clear messaging**: Explain when and why the workaround is needed * **Handle errors gracefully**: Implement proper error handling and recovery * **Monitor success rates**: Track how often the workaround is neededConsider implementing a fallback that automatically detects when the single-transaction flow fails and offers the manual delegation alternative. Most errors are automatically handled by OneBalance. The main issue you'll encounter is the multi-input limitation, which affects about 20% of operations. Learn more about [transaction lifecycle](/concepts/transaction-lifecycle). ## Next Steps# Guides Overview Source: https://docs.onebalance.io/guides/overview Learn how to use the OneBalance Toolkit with guides, code snippets and examples to help you with your integration journey. ## What You'll Find Here Our guides are designed to get you building quickly with real-world examples that you can adapt for your own projects. ### Account Models & Advanced Features Learn the basic implementation flow Complete API documentation ### Integration Tutorials Enable single-transaction cross-chain execution using EOA delegation Get started with OneBalance quotes for asset transfers and swaps across multiple chains Execute smart contract interactions using your aggregated balance across blockchains ### Platform Integrations Build token swap interfaces that work across multiple blockchains Integrate OneBalance with Turnkey wallet infrastructure ### Error Handling & Troubleshooting Cross-chain operations between Solana and EVM chains Build AI-powered applications with OneBalance MCP integration ## Getting Started Complete reference of fail reason codes with troubleshooting steps Detailed explanations and handling strategies for quote execution failures Start with our API reference to understand core concepts and available endpoints. Jump into our most popular tutorial: building a chain-abstracted swap interface. All our guides include complete, working code examples that you can run immediately. ## Popular Examples## Need Help? * **[API Reference](/api-reference/introduction)**: Detailed documentation for all OneBalance endpoints and schemas * **[Error Codes](/api-reference/error-codes)**: Complete reference for troubleshooting quote execution failures * **[Contract Calls Troubleshooting](/guides/contract-calls/troubleshooting)**: Common issues and solutions for contract calls * **[Discord Community](https://discord.com/invite/vHkw7rpdT8)**: Join our Discord for questions, discussions, and community support * **[GitHub Examples](https://github.com/OneBalance-io/onebalance-examples)**: Complete source code for tutorial projects # Quote Examples Source: https://docs.onebalance.io/guides/quotes/examples Explore OneBalance API quote request examples for transfers and swaps, showing how to structure cross-chain API calls. The quote endpoint enables you to request quotes for various operations including token swaps, cross-chain bridges, and complex multichain transactions. Below are practical examples for different scenarios. ## Overview Each example below shows both the JSON request body and the complete cURL command. You can copy the cURL commands directly to test the API, or use the JSON format with your preferred HTTP client. Production-ready examples for DeFi, NFT, and gaming use cases Overview of quotes and how to use them for transfers and swaps Handle multi-input limitations and common implementation issues Cross-chain operations between Solana and EVM networks For interactive testing, use the [API Reference](/api-reference/quotes/get-quote) playground. **Account Types**: Learn more about the differences between Basic, EIP-7702, and Role-Based accounts in our [Account Models](/concepts/accounts) guide. Basic accounts provide simple setup, EIP-7702 preserves existing EOA addresses, and Role-Based accounts offer enhanced security features. ## Aggregated Token to Aggregated Token Swap Both source and destination chains are optimized by the Toolkit.## Aggregated Token to Chain-Specific Token with Custom Recipient The source chain (where to take the assets from) is optimized by the Toolkit, while the destination chain is explicitly specified. There is a different recipient on the destination chain. ```json JSON theme={null} { "from": { "account": { "type": "kernel-v3.1-ecdsa", "signerAddress": "0x5d6fb4eb211a6a2e406a1111b54d26c534753c8e", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "ob:eth" }, "amount": "1000000000000000" }, "to": { "asset": { "assetId": "ob:usdc" } } } ``` ```bash cURL theme={null} curl --request POST \ --url https://be.onebalance.io/api/v1/quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "from": { "account": { "type": "kernel-v3.1-ecdsa", "signerAddress": "0x5d6fb4eb211a6a2e406a1111b54d26c534753c8e", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "ob:eth" }, "amount": "1000000000000000" }, "to": { "asset": { "assetId": "ob:usdc" } } }' ``` ```json JSON theme={null} { "from": { "account": { "type": "kernel-v3.3-ecdsa", "deploymentType": "EIP7702", "signerAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "ob:eth" }, "amount": "1000000000000000" }, "to": { "asset": { "assetId": "ob:usdc" } } } ``` ```bash cURL theme={null} curl --request POST \ --url https://be.onebalance.io/api/v1/quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "from": { "account": { "type": "kernel-v3.3-ecdsa", "deploymentType": "EIP7702", "signerAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "ob:eth" }, "amount": "1000000000000000" }, "to": { "asset": { "assetId": "ob:usdc" } } }' ``` ```json JSON theme={null} { "from": { "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "ob:eth" }, "amount": "1000000000000000" }, "to": { "asset": { "assetId": "ob:usdc" } } } ``` ```bash cURL theme={null} curl --request POST \ --url https://be.onebalance.io/api/v1/quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "from": { "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "ob:eth" }, "amount": "1000000000000000" }, "to": { "asset": { "assetId": "ob:usdc" } } }' ``` ## Chain-Specific Token to Chain-Specific Token Swap Direct swap between tokens on specific chains without aggregation. ```json JSON theme={null} { "from": { "account": { "type": "kernel-v3.1-ecdsa", "signerAddress": "0x5d6fb4eb211a6a2e406a1111b54d26c534753c8e", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "ob:usdc" }, "amount": "1000000" }, "to": { "asset": { "assetId": "eip155:8453/erc20:0x532f27101965dd16442e59d40670faf5ebb142e4" }, "account": "eip155:8453:0x742d35Cc6634C0532925a3b844Bc454e4438f44e" } } ``` ```bash cURL theme={null} curl --request POST \ --url https://be.onebalance.io/api/v1/quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "from": { "account": { "type": "kernel-v3.1-ecdsa", "signerAddress": "0x5d6fb4eb211a6a2e406a1111b54d26c534753c8e", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "ob:usdc" }, "amount": "1000000" }, "to": { "asset": { "assetId": "eip155:8453/erc20:0x532f27101965dd16442e59d40670faf5ebb142e4" }, "account": "eip155:8453:0x742d35Cc6634C0532925a3b844Bc454e4438f44e" } }' ``` ```json JSON theme={null} { "from": { "account": { "type": "kernel-v3.3-ecdsa", "deploymentType": "EIP7702", "signerAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "ob:usdc" }, "amount": "1000000" }, "to": { "asset": { "assetId": "eip155:8453/erc20:0x532f27101965dd16442e59d40670faf5ebb142e4" }, "account": "eip155:8453:0x742d35Cc6634C0532925a3b844Bc454e4438f44e" } } ``` ```bash cURL theme={null} curl --request POST \ --url https://be.onebalance.io/api/v1/quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "from": { "account": { "type": "kernel-v3.3-ecdsa", "deploymentType": "EIP7702", "signerAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "ob:usdc" }, "amount": "1000000" }, "to": { "asset": { "assetId": "eip155:8453/erc20:0x532f27101965dd16442e59d40670faf5ebb142e4" }, "account": "eip155:8453:0x742d35Cc6634C0532925a3b844Bc454e4438f44e" } }' ``` ```json JSON theme={null} { "from": { "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "ob:usdc" }, "amount": "1000000" }, "to": { "asset": { "assetId": "eip155:8453/erc20:0x532f27101965dd16442e59d40670faf5ebb142e4" }, "account": "eip155:8453:0x742d35Cc6634C0532925a3b844Bc454e4438f44e" } } ``` ```bash cURL theme={null} curl --request POST \ --url https://be.onebalance.io/api/v1/quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "from": { "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "ob:usdc" }, "amount": "1000000" }, "to": { "asset": { "assetId": "eip155:8453/erc20:0x532f27101965dd16442e59d40670faf5ebb142e4" }, "account": "eip155:8453:0x742d35Cc6634C0532925a3b844Bc454e4438f44e" } }' ``` ## Chain-Specific Token to Aggregated Token Convert a chain-specific token to an aggregated token for broader liquidity access. ```json JSON theme={null} { "from": { "account": { "type": "kernel-v3.1-ecdsa", "signerAddress": "0x5d6fb4eb211a6a2e406a1111b54d26c534753c8e", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "eip155:8453/erc20:0x2da56acb9ea78330f947bd57c54119debda7af71" }, "amount": "1000000000000000000000000" }, "to": { "asset": { "assetId": "eip155:8453/erc20:0x9a26f5433671751c3276a065f57e5a02d2817973" } } } ``` ```bash cURL theme={null} curl --request POST \ --url https://be.onebalance.io/api/v1/quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "from": { "account": { "type": "kernel-v3.1-ecdsa", "signerAddress": "0x5d6fb4eb211a6a2e406a1111b54d26c534753c8e", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "eip155:8453/erc20:0x2da56acb9ea78330f947bd57c54119debda7af71" }, "amount": "1000000000000000000000000" }, "to": { "asset": { "assetId": "eip155:8453/erc20:0x9a26f5433671751c3276a065f57e5a02d2817973" } } }' ``` ```json JSON theme={null} { "from": { "account": { "type": "kernel-v3.3-ecdsa", "deploymentType": "EIP7702", "signerAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "eip155:8453/erc20:0x2da56acb9ea78330f947bd57c54119debda7af71" }, "amount": "1000000000000000000000000" }, "to": { "asset": { "assetId": "eip155:8453/erc20:0x9a26f5433671751c3276a065f57e5a02d2817973" } } } ``` ```bash cURL theme={null} curl --request POST \ --url https://be.onebalance.io/api/v1/quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "from": { "account": { "type": "kernel-v3.3-ecdsa", "deploymentType": "EIP7702", "signerAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "eip155:8453/erc20:0x2da56acb9ea78330f947bd57c54119debda7af71" }, "amount": "1000000000000000000000000" }, "to": { "asset": { "assetId": "eip155:8453/erc20:0x9a26f5433671751c3276a065f57e5a02d2817973" } } }' ``` ```json JSON theme={null} { "from": { "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "eip155:8453/erc20:0x2da56acb9ea78330f947bd57c54119debda7af71" }, "amount": "1000000000000000000000000" }, "to": { "asset": { "assetId": "eip155:8453/erc20:0x9a26f5433671751c3276a065f57e5a02d2817973" } } } ``` ```bash cURL theme={null} curl --request POST \ --url https://be.onebalance.io/api/v1/quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "from": { "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "eip155:8453/erc20:0x2da56acb9ea78330f947bd57c54119debda7af71" }, "amount": "1000000000000000000000000" }, "to": { "asset": { "assetId": "eip155:8453/erc20:0x9a26f5433671751c3276a065f57e5a02d2817973" } } }' ``` ## Cross-Chain Bridge Operation Bridge the same token between different chains (e.g., USDC from Arbitrum to Optimism). ```json JSON theme={null} { "from": { "account": { "type": "kernel-v3.1-ecdsa", "signerAddress": "0x5d6fb4eb211a6a2e406a1111b54d26c534753c8e", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "eip155:8453/erc20:0x2da56acb9ea78330f947bd57c54119debda7af71" }, "amount": "1000000000000000000000000" }, "to": { "asset": { "assetId": "ob:usdc" } } } ``` ```bash cURL theme={null} curl --request POST \ --url https://be.onebalance.io/api/v1/quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "from": { "account": { "type": "kernel-v3.1-ecdsa", "signerAddress": "0x5d6fb4eb211a6a2e406a1111b54d26c534753c8e", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "eip155:8453/erc20:0x2da56acb9ea78330f947bd57c54119debda7af71" }, "amount": "1000000000000000000000000" }, "to": { "asset": { "assetId": "ob:usdc" } } }' ``` ```json JSON theme={null} { "from": { "account": { "type": "kernel-v3.3-ecdsa", "deploymentType": "EIP7702", "signerAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "eip155:8453/erc20:0x2da56acb9ea78330f947bd57c54119debda7af71" }, "amount": "1000000000000000000000000" }, "to": { "asset": { "assetId": "ob:usdc" } } } ``` ```bash cURL theme={null} curl --request POST \ --url https://be.onebalance.io/api/v1/quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "from": { "account": { "type": "kernel-v3.3-ecdsa", "deploymentType": "EIP7702", "signerAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "eip155:8453/erc20:0x2da56acb9ea78330f947bd57c54119debda7af71" }, "amount": "1000000000000000000000000" }, "to": { "asset": { "assetId": "ob:usdc" } } }' ``` ```json JSON theme={null} { "from": { "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "eip155:8453/erc20:0x2da56acb9ea78330f947bd57c54119debda7af71" }, "amount": "1000000000000000000000000" }, "to": { "asset": { "assetId": "ob:usdc" } } } ``` ```bash cURL theme={null} curl --request POST \ --url https://be.onebalance.io/api/v1/quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "from": { "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "eip155:8453/erc20:0x2da56acb9ea78330f947bd57c54119debda7af71" }, "amount": "1000000000000000000000000" }, "to": { "asset": { "assetId": "ob:usdc" } } }' ``` # Quotes Overview Source: https://docs.onebalance.io/guides/quotes/overview Overview of OneBalance's quoting system: what quotes are, how they're generated, and how to use them in cross-chain operations. OneBalance quotes enable cross-chain operations including asset transfers, swaps, and arbitrary contract interactions. They facilitate the core transaction lifecycle by providing cost estimates and execution plans. ```json JSON theme={null} { "from": { "account": { "type": "kernel-v3.1-ecdsa", "signerAddress": "0x5d6fb4eb211a6a2e406a1111b54d26c534753c8e", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "eip155:42161/erc20:0xaf88d065e77c8cc2239327c5edb3a432268e5831" }, "amount": "1000000" }, "to": { "asset": { "assetId": "eip155:10/erc20:0x0b2c639c533813f4aa9d7837caf62653d097ff85" } } } ``` ```bash cURL theme={null} curl --request POST \ --url https://be.onebalance.io/api/v1/quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "from": { "account": { "type": "kernel-v3.1-ecdsa", "signerAddress": "0x5d6fb4eb211a6a2e406a1111b54d26c534753c8e", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "eip155:42161/erc20:0xaf88d065e77c8cc2239327c5edb3a432268e5831" }, "amount": "1000000" }, "to": { "asset": { "assetId": "eip155:10/erc20:0x0b2c639c533813f4aa9d7837caf62653d097ff85" } } }' ``` ```json JSON theme={null} { "from": { "account": { "type": "kernel-v3.3-ecdsa", "deploymentType": "EIP7702", "signerAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "eip155:42161/erc20:0xaf88d065e77c8cc2239327c5edb3a432268e5831" }, "amount": "1000000" }, "to": { "asset": { "assetId": "eip155:10/erc20:0x0b2c639c533813f4aa9d7837caf62653d097ff85" } } } ``` ```bash cURL theme={null} curl --request POST \ --url https://be.onebalance.io/api/v1/quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "from": { "account": { "type": "kernel-v3.3-ecdsa", "deploymentType": "EIP7702", "signerAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "eip155:42161/erc20:0xaf88d065e77c8cc2239327c5edb3a432268e5831" }, "amount": "1000000" }, "to": { "asset": { "assetId": "eip155:10/erc20:0x0b2c639c533813f4aa9d7837caf62653d097ff85" } } }' ``` ```json JSON theme={null} { "from": { "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "eip155:42161/erc20:0xaf88d065e77c8cc2239327c5edb3a432268e5831" }, "amount": "1000000" }, "to": { "asset": { "assetId": "eip155:10/erc20:0x0b2c639c533813f4aa9d7837caf62653d097ff85" } } } ``` ```bash cURL theme={null} curl --request POST \ --url https://be.onebalance.io/api/v1/quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "from": { "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "eip155:42161/erc20:0xaf88d065e77c8cc2239327c5edb3a432268e5831" }, "amount": "1000000" }, "to": { "asset": { "assetId": "eip155:10/erc20:0x0b2c639c533813f4aa9d7837caf62653d097ff85" } } }' ``` **Account Setup Required**: Quotes require proper account configuration. See [Account Models](/concepts/accounts) for setup instructions. ## Quote Types ### Unified Quotes For transfers and swaps between aggregated or chain-specific assets. ```bash Terminal theme={null} curl -X POST "https://be.onebalance.io/api/v1/quote" \ -H "x-api-key: ONEBALANCE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "from": { "account": { "sessionAddress": "0x1cBF...", "adminAddress": "0xc162...", "accountAddress": "0xE202..." }, "asset": { "assetId": "ob:eth" }, "amount": "1000000000000000000" }, "to": { "asset": { "assetId": "ob:usdc" } } }' ``` ### Contract Call Quotes For arbitrary smart contract interactions with automatic token routing. ```bash Terminal theme={null} curl -X POST "https://be.onebalance.io/api/quotes/prepare-call-quote" \ -H "x-api-key: ONEBALANCE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "account": { "sessionAddress": "0x1cBF...", "adminAddress": "0xc162...", "accountAddress": "0xE202..." }, "targetChain": "eip155:8453", "calls": [{ "to": "0x...", "data": "0x...", "value": "0x0" }], "tokensRequired": [{ "assetType": "eip155:8453/erc20:0x...", "amount": "1000000" }] }' ``` ## Quote Lifecycle## Key Features * **Cross-chain routing** - Use tokens from any supported chain * **Gas abstraction** - No need for native tokens on target chains * **Automatic approvals** - ERC20 approvals handled automatically * **Quote expiration** - Time-limited quotes ensure fresh pricing Generate a quote with pricing and execution plan Sign the typed data using your wallet or signer Submit the signed quote for on-chain execution Start with [quote examples](/guides/quotes/examples) for hands-on implementation guidance. # Understanding Quote Refund Reasons Source: https://docs.onebalance.io/guides/quotes/refund-reasons Learn about the different reasons why quotes get refunded and how to handle them in your application. When a quote execution fails, it gets marked as `REFUNDED` and includes a `failReason` field that explains why the refund occurred. Understanding these reasons helps you build better error handling and improve user experience.For a complete reference of all fail reason codes with detailed explanations, see [Error Codes](/api-reference/error-codes). ## How Refund Reasons Work When you check the status of a quote using the [Get Execution Status](/api-reference/status/get-quote-status) endpoint, refunded quotes include additional context: ```json theme={null} { "quoteId": "0xfa6094cd...", "status": "REFUNDED", "user": "0x9b747cC14...", "failReason": "SLIPPAGE", "originChainOperations": [], "destinationChainOperations": [] } ``` ## Implementation Patterns by Category ### Balance and Allowance Issues Handle insufficient funds and token approval problems:```typescript TypeScript theme={null} function handleBalanceAndAllowanceErrors(failReason: string) { switch (failReason) { case 'TRANSFER_AMOUNT_EXCEEDS_BALANCE': // Check current balance before retrying return handleInsufficientBalance(); case 'TRANSFER_AMOUNT_EXCEEDS_ALLOWANCE': // Request token approval return handleInsufficientAllowance(); case 'INSUFFICIENT_NATIVE_TOKENS_SUPPLIED': // Add gas tokens return handleInsufficientGas(); case 'TRANSFER_FROM_FAILED': case 'TRANSFER_FAILED': // Transfer operation failed return handleTransferFailure(); } } async function handleInsufficientBalance() { const balance = await getBalance(userAddress, tokenAddress); if (balance.gte(requiredAmount)) { // Balance recovered, safe to retry return { action: 'retry', message: 'Balance sufficient, retrying...' }; } else { // Show balance error to user return { action: 'error', message: 'Insufficient balance for this transaction' }; } } async function handleInsufficientAllowance() { // Request token approval return { action: 'approval_needed', message: 'Token approval required', nextStep: 'approve_token' }; } ``` ```javascript JavaScript theme={null} function handleBalanceAndAllowanceErrors(failReason) { switch (failReason) { case 'TRANSFER_AMOUNT_EXCEEDS_BALANCE': return handleInsufficientBalance(); case 'TRANSFER_AMOUNT_EXCEEDS_ALLOWANCE': return handleInsufficientAllowance(); case 'INSUFFICIENT_NATIVE_TOKENS_SUPPLIED': return handleInsufficientGas(); case 'TRANSFER_FROM_FAILED': case 'TRANSFER_FAILED': return handleTransferFailure(); } } async function handleInsufficientBalance() { const balance = await getBalance(userAddress, tokenAddress); if (balance >= requiredAmount) { return { action: 'retry', message: 'Balance sufficient, retrying...' }; } else { return { action: 'error', message: 'Insufficient balance for this transaction' }; } } ``` ### Price and Market Issues Handle slippage, expired orders, and market conditions: ```typescript theme={null} function handleMarketErrors(failReason: string, originalQuote: any) { switch (failReason) { case 'SLIPPAGE': // Retry with higher slippage tolerance return retryWithHigherSlippage(originalQuote); case 'ORDER_EXPIRED': // Generate fresh quote return generateFreshQuote(originalQuote); case 'ORDER_IS_CANCELLED': // Order was cancelled return { action: 'new_quote', message: 'Order cancelled, please create a new order' }; case 'ORDER_ALREADY_FILLED': // Order already completed return { action: 'error', message: 'Order already filled' }; case 'NO_QUOTES': // No liquidity available return handleNoLiquidity(originalQuote); case 'TOO_LITTLE_RECEIVED': // Output below minimum return handleLowOutput(originalQuote); case 'NO_INTERNAL_SWAP_ROUTES_FOUND': // No routing path available return { action: 'error', message: 'No swap route available for this pair' }; case 'SWAP_USES_TOO_MUCH_GAS': // Gas cost too high return { action: 'retry', message: 'Gas cost too high, try smaller amount' }; } } async function retryWithHigherSlippage(originalQuote: any) { const currentSlippage = originalQuote.slippageTolerance || 100; const newSlippage = Math.min(currentSlippage * 1.5, 500); // Cap at 5% return { action: 'retry', message: `Retrying with ${newSlippage / 100}% slippage tolerance`, newParams: { ...originalQuote, slippageTolerance: newSlippage } }; } async function generateFreshQuote(originalQuote: any) { return { action: 'new_quote', message: 'Order expired, generating new quote with current prices', newParams: originalQuote }; } ``` ### Signature and Authentication Issues Handle wallet and signing problems: ```typescript theme={null} function handleSignatureErrors(failReason: string) { switch (failReason) { case 'INVALID_SIGNATURE': return { action: 'resign', message: 'Invalid signature, please sign again' }; case 'SIGNATURE_EXPIRED': return { action: 'resign', message: 'Signature expired, please sign the new transaction' }; case 'INVALID_SENDER': return { action: 'reconnect', message: 'Please reconnect your wallet' }; case 'ACCOUNT_ABSTRACTION_INVALID_NONCE': case 'ACCOUNT_ABSTRACTION_SIGNATURE_ERROR': return { action: 'sync_wallet', message: 'Smart wallet sync required, please retry' }; } } ``` ### System and Protocol Issues Handle system capacity and protocol-specific errors: ```typescript theme={null} function handleSystemErrors(failReason: string) { switch (failReason) { case 'SOLVER_CAPACITY_EXCEEDED': return { action: 'retry', message: 'System at capacity, retrying in a moment', delay: 2000 }; case 'EXECUTION_REVERTED': case 'TRANSACTION_REVERTED': return { action: 'error', message: 'Transaction reverted, please check parameters' }; case 'GENERATE_SWAP_FAILED': case 'REVERSE_SWAP_FAILED': return { action: 'retry', message: 'Swap generation failed, retrying' }; case 'MISSING_REVERT_DATA': return { action: 'contact_support', message: 'Transaction failed without details, contact support' }; } } ``` ### Deposit and Validation Issues Handle deposit-related errors: ```typescript theme={null} function handleDepositErrors(failReason: string) { switch (failReason) { case 'DEPOSIT_ADDRESS_MISMATCH': case 'DEPOSIT_CHAIN_MISMATCH': case 'INCORRECT_DEPOSIT_CURRENCY': return { action: 'error', message: 'Deposit details incorrect, verify and retry' }; case 'AMOUNT_TOO_LOW_TO_REFUND': case 'DEPOSITED_AMOUNT_TOO_LOW_TO_FILL': return { action: 'error', message: 'Amount too low, increase amount' }; case 'NEGATIVE_NEW_AMOUNT_AFTER_FEES': return { action: 'error', message: 'Fees exceed amount, increase amount' }; case 'ORIGIN_CURRENCY_MISMATCH': return { action: 'error', message: 'Source currency mismatch, check token' }; } } ``` ### Token and Protocol Specific Issues Handle token-specific and protocol errors: ```typescript theme={null} function handleTokenErrors(failReason: string) { switch (failReason) { case 'TOKEN_NOT_TRANSFERABLE': return { action: 'error', message: 'Token has transfer restrictions' }; case 'ZERO_SELL_AMOUNT': return { action: 'error', message: 'Invalid amount, must be greater than zero' }; case 'SEAPORT_INEXACT_FRACTION': case 'SEAPORT_INVALID_FULFILLER': return { action: 'error', message: 'NFT order validation failed' }; case 'MINT_NOT_ACTIVE': return { action: 'wait', message: 'Minting not active, try later' }; case 'ERC_1155_TOO_MANY_REQUESTED': return { action: 'error', message: 'Reduce number of tokens requested' }; case 'INCORRECT_PAYMENT': return { action: 'error', message: 'Payment amount incorrect' }; case 'INVALID_GAS_PRICE': return { action: 'retry', message: 'Invalid gas price, retrying with updated price' }; case 'FLUID_DEX_ERROR': return { action: 'retry', message: 'DEX protocol error, retrying' }; case 'NEW_CALLDATA_INCLUDES_HIGHER_RENT_FEE': return { action: 'confirm', message: 'Higher fees required, confirm to proceed' }; } } ``` ### Solana-Specific Issues Handle Solana blockchain specific problems: ```typescript theme={null} function handleSolanaErrors(failReason: string) { switch (failReason) { case 'INSUFFICIENT_FUNDS_FOR_RENT': return { action: 'add_sol', message: 'Insufficient SOL for rent. Add SOL to your wallet.', minAmount: '0.002 SOL' }; case 'JUPITER_INVALID_TOKEN_ACCOUNT': return { action: 'init_account', message: 'Token account needs initialization. This will be handled automatically.', autoRetry: true }; } } ``` ## Automatic Retry Logic Implement smart retry mechanisms for recoverable errors: ```typescript theme={null} const RETRYABLE_REASONS = [ 'SLIPPAGE', 'ORDER_EXPIRED', 'SOLVER_CAPACITY_EXCEEDED', 'SWAP_USES_TOO_MUCH_GAS', 'EXECUTION_REVERTED', 'TRANSACTION_REVERTED', 'GENERATE_SWAP_FAILED', 'REVERSE_SWAP_FAILED', 'TOO_LITTLE_RECEIVED', 'INVALID_GAS_PRICE', 'FLUID_DEX_ERROR' ]; const USER_ACTION_REQUIRED = [ 'TRANSFER_AMOUNT_EXCEEDS_BALANCE', 'TRANSFER_AMOUNT_EXCEEDS_ALLOWANCE', 'INSUFFICIENT_NATIVE_TOKENS_SUPPLIED', 'INVALID_SIGNATURE', 'SIGNATURE_EXPIRED', 'INVALID_SENDER', 'ACCOUNT_ABSTRACTION_INVALID_NONCE', 'ACCOUNT_ABSTRACTION_SIGNATURE_ERROR', 'TRANSFER_FROM_FAILED', 'TRANSFER_FAILED', 'INSUFFICIENT_FUNDS_FOR_RENT', 'JUPITER_INVALID_TOKEN_ACCOUNT' ]; const NON_RETRYABLE_ERRORS = [ 'UNKNOWN', 'DOUBLE_SPEND', 'AMOUNT_TOO_LOW_TO_REFUND', 'DEPOSIT_ADDRESS_MISMATCH', 'DEPOSIT_CHAIN_MISMATCH', 'INCORRECT_DEPOSIT_CURRENCY', 'DEPOSITED_AMOUNT_TOO_LOW_TO_FILL', 'NEGATIVE_NEW_AMOUNT_AFTER_FEES', 'NO_QUOTES', 'NO_INTERNAL_SWAP_ROUTES_FOUND', 'ORDER_IS_CANCELLED', 'ORDER_ALREADY_FILLED', 'TOKEN_NOT_TRANSFERABLE', 'ZERO_SELL_AMOUNT', 'SEAPORT_INEXACT_FRACTION', 'SEAPORT_INVALID_FULFILLER', 'MINT_NOT_ACTIVE', 'ERC_1155_TOO_MANY_REQUESTED', 'INCORRECT_PAYMENT', 'ORIGIN_CURRENCY_MISMATCH', 'MISSING_REVERT_DATA' ]; async function executeWithSmartRetry(quoteParams: QuoteParams, maxRetries = 3) { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { const quote = await getQuote(quoteParams); const result = await executeQuote(quote); if (result.status === 'COMPLETED') { return { success: true, result }; } // Handle refunded quotes if (result.status === 'REFUNDED' && result.failReason) { const errorHandler = getErrorHandler(result.failReason); const handlerResult = errorHandler(result.failReason, quoteParams); if (handlerResult.action === 'retry' && attempt < maxRetries) { // Automatic retry with adjusted parameters quoteParams = { ...quoteParams, ...handlerResult.newParams }; await delay(1000 * attempt); // Exponential backoff continue; } // Return error for user action or final attempt return { success: false, error: result.failReason, action: handlerResult.action, message: handlerResult.message, retryable: RETRYABLE_REASONS.includes(result.failReason) }; } } catch (error) { if (attempt === maxRetries) throw error; await delay(1000 * attempt); } } } function getErrorHandler(failReason: string) { // Balance and Allowance Issues if ([ 'TRANSFER_AMOUNT_EXCEEDS_BALANCE', 'TRANSFER_AMOUNT_EXCEEDS_ALLOWANCE', 'INSUFFICIENT_NATIVE_TOKENS_SUPPLIED', 'TRANSFER_FROM_FAILED', 'TRANSFER_FAILED' ].includes(failReason)) { return handleBalanceAndAllowanceErrors; } // Price and Market Issues if ([ 'SLIPPAGE', 'ORDER_EXPIRED', 'ORDER_IS_CANCELLED', 'ORDER_ALREADY_FILLED', 'NO_QUOTES', 'TOO_LITTLE_RECEIVED', 'NO_INTERNAL_SWAP_ROUTES_FOUND', 'SWAP_USES_TOO_MUCH_GAS' ].includes(failReason)) { return handleMarketErrors; } // Signature and Authentication Issues if ([ 'INVALID_SIGNATURE', 'SIGNATURE_EXPIRED', 'INVALID_SENDER', 'ACCOUNT_ABSTRACTION_INVALID_NONCE', 'ACCOUNT_ABSTRACTION_SIGNATURE_ERROR' ].includes(failReason)) { return handleSignatureErrors; } // System and Protocol Issues if ([ 'SOLVER_CAPACITY_EXCEEDED', 'EXECUTION_REVERTED', 'TRANSACTION_REVERTED', 'GENERATE_SWAP_FAILED', 'REVERSE_SWAP_FAILED', 'MISSING_REVERT_DATA' ].includes(failReason)) { return handleSystemErrors; } // Deposit and Validation Issues if ([ 'DEPOSIT_ADDRESS_MISMATCH', 'DEPOSIT_CHAIN_MISMATCH', 'INCORRECT_DEPOSIT_CURRENCY', 'AMOUNT_TOO_LOW_TO_REFUND', 'DEPOSITED_AMOUNT_TOO_LOW_TO_FILL', 'NEGATIVE_NEW_AMOUNT_AFTER_FEES', 'ORIGIN_CURRENCY_MISMATCH' ].includes(failReason)) { return handleDepositErrors; } // Token and Protocol Specific Issues if ([ 'TOKEN_NOT_TRANSFERABLE', 'ZERO_SELL_AMOUNT', 'SEAPORT_INEXACT_FRACTION', 'SEAPORT_INVALID_FULFILLER', 'MINT_NOT_ACTIVE', 'ERC_1155_TOO_MANY_REQUESTED', 'INCORRECT_PAYMENT', 'INVALID_GAS_PRICE', 'FLUID_DEX_ERROR', 'NEW_CALLDATA_INCLUDES_HIGHER_RENT_FEE' ].includes(failReason)) { return handleTokenErrors; } // Solana-Specific Issues if ([ 'INSUFFICIENT_FUNDS_FOR_RENT', 'JUPITER_INVALID_TOKEN_ACCOUNT' ].includes(failReason)) { return handleSolanaErrors; } // Default handler for unknown or unhandled errors return () => ({ action: 'manual', message: 'Unexpected error, please try again or contact support' }); } ``` ## User Experience Patterns ### Progress Indicators Show users what's happening during retry flows: ```typescript theme={null} interface RetryState { attempt: number; maxAttempts: number; currentAction: string; failReason?: string; } function ProgressIndicator({ retryState }: { retryState: RetryState }) { const progress = (retryState.attempt / retryState.maxAttempts) * 100; return (); } function getUserFriendlyMessage(failReason: string): string { const messages = { // Balance and Allowance TRANSFER_AMOUNT_EXCEEDS_BALANCE: 'Insufficient token balance', TRANSFER_AMOUNT_EXCEEDS_ALLOWANCE: 'Token approval needed', INSUFFICIENT_NATIVE_TOKENS_SUPPLIED: 'Insufficient gas tokens', TRANSFER_FROM_FAILED: 'Token transfer failed', TRANSFER_FAILED: 'Token transfer failed', // Price and Market SLIPPAGE: 'Price changed during execution', ORDER_EXPIRED: 'Quote expired', ORDER_IS_CANCELLED: 'Order was cancelled', ORDER_ALREADY_FILLED: 'Order already completed', NO_QUOTES: 'No liquidity available', TOO_LITTLE_RECEIVED: 'Output amount too low', NO_INTERNAL_SWAP_ROUTES_FOUND: 'No swap route found', SWAP_USES_TOO_MUCH_GAS: 'Gas cost too high', // Signature and Authentication INVALID_SIGNATURE: 'Invalid signature', SIGNATURE_EXPIRED: 'Signature expired', INVALID_SENDER: 'Invalid sender', ACCOUNT_ABSTRACTION_INVALID_NONCE: 'Wallet sync needed', ACCOUNT_ABSTRACTION_SIGNATURE_ERROR: 'Wallet signature error', // System and Protocol SOLVER_CAPACITY_EXCEEDED: 'System at capacity', EXECUTION_REVERTED: 'Transaction reverted', TRANSACTION_REVERTED: 'Transaction reverted', GENERATE_SWAP_FAILED: 'Swap generation failed', REVERSE_SWAP_FAILED: 'Reverse swap failed', MISSING_REVERT_DATA: 'Transaction failed', // Deposit and Validation DEPOSIT_ADDRESS_MISMATCH: 'Wrong deposit address', DEPOSIT_CHAIN_MISMATCH: 'Wrong blockchain', INCORRECT_DEPOSIT_CURRENCY: 'Wrong token deposited', AMOUNT_TOO_LOW_TO_REFUND: 'Amount too low', DEPOSITED_AMOUNT_TOO_LOW_TO_FILL: 'Amount too low', NEGATIVE_NEW_AMOUNT_AFTER_FEES: 'Fees exceed amount', ORIGIN_CURRENCY_MISMATCH: 'Source token mismatch', DOUBLE_SPEND: 'Duplicate transaction', // Token and Protocol Specific TOKEN_NOT_TRANSFERABLE: 'Token transfer restricted', ZERO_SELL_AMOUNT: 'Invalid amount', SEAPORT_INEXACT_FRACTION: 'NFT order error', SEAPORT_INVALID_FULFILLER: 'NFT fulfiller error', MINT_NOT_ACTIVE: 'Minting not active', ERC_1155_TOO_MANY_REQUESTED: 'Too many tokens requested', INCORRECT_PAYMENT: 'Payment amount incorrect', INVALID_GAS_PRICE: 'Invalid gas price', FLUID_DEX_ERROR: 'DEX protocol error', NEW_CALLDATA_INCLUDES_HIGHER_RENT_FEE: 'Higher fees required', // Solana-Specific INSUFFICIENT_FUNDS_FOR_RENT: 'Insufficient SOL for rent', JUPITER_INVALID_TOKEN_ACCOUNT: 'Solana account issue', // Unknown UNKNOWN: 'Unknown error' }; return messages[failReason] || 'Transaction failed'; } ``` ### Error Recovery UI Provide clear next steps for users: ```typescript theme={null} function ErrorRecoveryUI({ error, onRetry, onCancel }) { const getActionButton = (action: string) => { switch (action) { case 'retry': return ; case 'approval_needed': return ; case 'add_sol': return ; case 'reconnect': return ; default: return ; } }; return (Attempt {retryState.attempt} of {retryState.maxAttempts}: {retryState.currentAction}
{retryState.failReason && (Previous attempt failed: {getUserFriendlyMessage(retryState.failReason)}
)}); } ``` ## Analytics and Monitoring Track fail reason patterns to optimize your integration: ```typescript theme={null} interface FailReasonAnalytics { failReason: string; tokenPair: string; amount: string; attempt: number; userAgent: string; timestamp: string; } function trackFailReason(data: FailReasonAnalytics) { // Send to your analytics service analytics.track('quote_refunded', { fail_reason: data.failReason, token_pair: data.tokenPair, amount_usd: data.amount, retry_attempt: data.attempt, user_agent: data.userAgent, timestamp: data.timestamp }); } function analyzeFailReasonTrends(timeRange: string) { // Query your analytics to identify patterns return { mostCommonReasons: ['SLIPPAGE', 'TRANSFER_AMOUNT_EXCEEDS_BALANCE'], successRateByReason: { SLIPPAGE: 0.85, // 85% success after retry ORDER_EXPIRED: 0.92, NO_QUOTES: 0.45 }, recommendations: [ 'Increase default slippage tolerance to 1.5%', 'Implement balance checking before quote generation', 'Add liquidity warnings for low-liquidity pairs' ] }; } ``` ## Best Practices Summary 1. **Categorize Error Handling**: Group similar fail reasons and handle them with consistent patterns 2. **Implement Smart Retries**: Automatically retry recoverable errors with adjusted parameters 3. **Provide Clear UI**: Show users exactly what went wrong and what they need to do 4. **Monitor Patterns**: Track fail reasons to identify optimization opportunities 5. **Progressive Enhancement**: Start with basic error handling and add sophistication over time ## Related ResourcesTransaction Failed
{error.message}
{getActionButton(error.action)}{error.retryable && (This error can be automatically retried. Click "Try Again" to retry with adjusted parameters.
)}# Slippage Tolerance Examples Source: https://docs.onebalance.io/guides/slippage/examples Working code examples for implementing slippage tolerance across different OneBalance operations and asset types. Practical examples of implementing slippage tolerance in various scenarios. Each example includes complete, runnable code that you can adapt for your specific use case. ## Basic Slippage Implementation ### Stablecoin Swap (Low Slippage) Use minimal slippage for stable asset pairs: Complete reference of all fail reason codes with detailed explanations Understand the complete journey of a quote from creation to completion Working examples of quote generation and execution with proper error handling Deep dive into slippage tolerance and price impact management ```typescript TypeScript theme={null} const stablecoinSwap = await getQuote({ from: { account: { sessionAddress: "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", adminAddress: "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", accountAddress: "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, asset: { assetId: "ob:usdc" }, amount: "1000000" // 1 USDC }, to: { asset: { assetId: "ob:usdt" } }, slippageTolerance: 10 // 0.1% for stablecoins }); ``` ```bash cURL theme={null} curl --request POST \ --url https://be.onebalance.io/api/v1/quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "from": { "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "ob:usdc" }, "amount": "1000000" }, "to": { "asset": { "assetId": "ob:usdt" } }, "slippageTolerance": 10 }' ``` ### Major Token Swap (Medium Slippage) Standard slippage for major cryptocurrency pairs:```typescript TypeScript theme={null} const ethToUsdcSwap = await getQuote({ from: { account: { sessionAddress: "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", adminAddress: "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", accountAddress: "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, asset: { assetId: "ob:eth" }, amount: "100000000000000" // 1 ETH }, to: { asset: { assetId: "ob:usdc" } }, slippageTolerance: 100 // 1% for major tokens }); ``` ```bash cURL theme={null} curl --request POST \ --url https://be.onebalance.io/api/v1/quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "from": { "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "ob:eth" }, "amount": "100000000000000" }, "to": { "asset": { "assetId": "ob:usdc" } }, "slippageTolerance": 100 }' ``` ### Volatile Token Swap (High Slippage) Higher slippage tolerance for volatile or newer tokens:```typescript TypeScript theme={null} const volatileTokenSwap = await getQuote({ from: { account: { sessionAddress: "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", adminAddress: "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", accountAddress: "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, asset: { assetId: "eip155:1/erc20:0x1234567890123456789012345678901234567890" }, amount: "100000000000000" // 1000 tokens }, to: { asset: { assetId: "ob:eth" } }, slippageTolerance: 300 // 3% for volatile tokens }); ``` ```bash cURL theme={null} curl --request POST \ --url https://be.onebalance.io/api/v1/quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "from": { "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "eip155:1/erc20:0x1234567890123456789012345678901234567890" }, "amount": "100000000000000" }, "to": { "asset": { "assetId": "ob:eth" } }, "slippageTolerance": 300 }' ``` ## Contract Call Examples ### DeFi Protocol Interaction Smart contract calls with slippage tolerance for DEX operations:```typescript TypeScript theme={null} const uniswapSwapWithSlippage = await prepareCallQuote({ account: { sessionAddress: "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", adminAddress: "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", accountAddress: "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, targetChain: "eip155:8453", // Base calls: [{ to: "0x2626664c2603336E57B271c5C0b26F421741e481", // Uniswap V3 Router data: "0x414bf389000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda029130000000000000000000000004200000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000bb8000000000000000000000000e20295ec513def805d9c3083b0c8eab64692d764000000000000000000000000000000000000000000000000000000006748d28800000000000000000000000000000000000000000000000000000000000f42400000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000000", value: "0x0" }], allowanceRequirements: [{ assetType: "eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", amount: "1000000", spender: "0x2626664c2603336E57B271c5C0b26F421741e481" }], tokensRequired: [{ assetType: "eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", amount: "1000000" }], slippageTolerance: 100 // 1% slippage for DEX operations }); ``` ```bash cURL theme={null} curl --request POST \ --url https://be.onebalance.io/api/quotes/prepare-call-quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "targetChain": "eip155:8453", "calls": [{ "to": "0x2626664c2603336E57B271c5C0b26F421741e481", "data": "0x414bf389000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda029130000000000000000000000004200000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000bb8000000000000000000000000e20295ec513def805d9c3083b0c8eab64692d764000000000000000000000000000000000000000000000000000000006748d28800000000000000000000000000000000000000000000000000000000000f42400000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000000", "value": "0x0" }], "allowanceRequirements": [{ "assetType": "eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "amount": "1000000", "spender": "0x2626664c2603336E57B271c5C0b26F421741e481" }], "tokensRequired": [{ "assetType": "eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "amount": "1000000" }], "slippageTolerance": 100 }' ``` ## Advanced Implementation Patterns ### Adaptive Slippage Based on Market Conditions ```typescript Market-Adaptive Slippage theme={null} interface MarketConditions { volatility: 'low' | 'medium' | 'high'; liquidityDepth: 'shallow' | 'medium' | 'deep'; networkCongestion: 'low' | 'medium' | 'high'; } function calculateAdaptiveSlippage( baseSlippage: number, conditions: MarketConditions ): number { let multiplier = 1; // Adjust for volatility switch (conditions.volatility) { case 'high': multiplier *= 2; break; case 'medium': multiplier *= 1.5; break; case 'low': multiplier *= 1; break; } // Adjust for liquidity switch (conditions.liquidityDepth) { case 'shallow': multiplier *= 1.8; break; case 'medium': multiplier *= 1.3; break; case 'deep': multiplier *= 1; break; } // Adjust for network congestion switch (conditions.networkCongestion) { case 'high': multiplier *= 1.2; break; case 'medium': multiplier *= 1.1; break; case 'low': multiplier *= 1; break; } // Cap at 10% (1000 basis points) return Math.min(Math.round(baseSlippage * multiplier), 1000); } // Usage example const marketConditions: MarketConditions = { volatility: 'high', liquidityDepth: 'medium', networkCongestion: 'low' }; const adaptiveSlippage = calculateAdaptiveSlippage(100, marketConditions); const quote = await getQuote({ from: { account: yourAccount, asset: { assetId: "ob:eth" }, amount: "100000000000000" }, to: { asset: { assetId: "ob:usdc" } }, slippageTolerance: adaptiveSlippage // Dynamically calculated }); ``` ### Retry Logic with Progressive Slippage ```typescript Progressive Retry theme={null} async function executeWithProgressiveSlippage( baseRequest: QuoteRequest, options?: { maxRetries?: number; baseSlippage?: number; maxSlippage?: number; } ) { const { maxRetries = 3, baseSlippage = 50, // 0.5% maxSlippage = 500 // 5% } = options || {}; // Generate progressive slippage values const slippageSteps = []; for (let i = 0; i < maxRetries; i++) { const step = baseSlippage * Math.pow(2, i); // 50, 100, 200, 400... slippageSteps.push(Math.min(step, maxSlippage)); } let lastError; for (let attempt = 0; attempt < maxRetries; attempt++) { const currentSlippage = slippageSteps[attempt]; try { console.log(`Attempt ${attempt + 1}: Using ${currentSlippage / 100}% slippage`); const quote = await getQuote({ ...baseRequest, slippageTolerance: currentSlippage }); const result = await executeQuote(quote); console.log(`Success on attempt ${attempt + 1}`); return result; } catch (error: any) { lastError = error; if (error.code === 'QUOTE_REFUNDED' && attempt < maxRetries - 1) { console.log(`Attempt ${attempt + 1} failed, retrying with higher slippage...`); // Wait before retry to avoid rate limiting await new Promise(resolve => setTimeout(resolve, 1000 * (attempt + 1))); continue; } // Final attempt failed or non-retryable error break; } } throw new Error(`All ${maxRetries} attempts failed. Last error: ${lastError?.message}`); } // Usage try { const result = await executeWithProgressiveSlippage({ from: { account: yourAccount, asset: { assetId: "ob:eth" }, amount: "100000000000000" }, to: { asset: { assetId: "ob:usdc" } } }, { maxRetries: 4, baseSlippage: 75, // Start at 0.75% maxSlippage: 400 // Cap at 4% }); console.log('Transaction successful:', result); } catch (error) { console.error('All retry attempts failed:', error); } ``` ### User-Configurable Slippage Settings ```typescript User Settings theme={null} interface SlippagePreferences { mode: 'conservative' | 'balanced' | 'aggressive' | 'custom'; customValue?: number; autoRetry?: boolean; maxRetries?: number; } class SlippageManager { private presets = { conservative: { stablecoin: 5, // 0.05% major: 50, // 0.5% volatile: 150 // 1.5% }, balanced: { stablecoin: 10, // 0.1% major: 100, // 1% volatile: 300 // 3% }, aggressive: { stablecoin: 25, // 0.25% major: 200, // 2% volatile: 500 // 5% } }; getSlippageForAssets( fromAsset: string, toAsset: string, preferences: SlippagePreferences ): number { if (preferences.mode === 'custom' && preferences.customValue) { return preferences.customValue; } const assetType = this.categorizeAssetPair(fromAsset, toAsset); const preset = this.presets[preferences.mode as keyof typeof this.presets]; return preset?.[assetType] || this.presets.balanced[assetType]; } private categorizeAssetPair(fromAsset: string, toAsset: string): 'stablecoin' | 'major' | 'volatile' { const stablecoins = ['usdc', 'usdt', 'dai', 'busd']; const majorTokens = ['eth', 'btc', 'bnb', 'matic', 'avax']; const isStablecoin = (asset: string) => stablecoins.some(stable => asset.toLowerCase().includes(stable)); const isMajor = (asset: string) => majorTokens.some(major => asset.toLowerCase().includes(major)) || isStablecoin(asset); if (isStablecoin(fromAsset) && isStablecoin(toAsset)) { return 'stablecoin'; } if (isMajor(fromAsset) && isMajor(toAsset)) { return 'major'; } return 'volatile'; } async executeWithUserPreferences( request: QuoteRequest, preferences: SlippagePreferences ) { const slippage = this.getSlippageForAssets( request.from.asset.assetId, request.to.asset.assetId, preferences ); const enhancedRequest = { ...request, slippageTolerance: slippage }; if (preferences.autoRetry) { return await executeWithProgressiveSlippage(enhancedRequest, { maxRetries: preferences.maxRetries || 3, baseSlippage: slippage }); } const quote = await getQuote(enhancedRequest); return await executeQuote(quote); } } // Usage example const slippageManager = new SlippageManager(); // Conservative user const conservativeResult = await slippageManager.executeWithUserPreferences({ from: { account: yourAccount, asset: { assetId: "ob:eth" }, amount: "100000000000000" }, to: { asset: { assetId: "ob:usdc" } } }, { mode: 'conservative', autoRetry: true, maxRetries: 2 }); // Custom slippage user const customResult = await slippageManager.executeWithUserPreferences({ from: { account: yourAccount, asset: { assetId: "ob:usdc" }, amount: "1000000" }, to: { asset: { assetId: "ob:eth" } } }, { mode: 'custom', customValue: 75, // 0.75% autoRetry: false }); ``` ## Multi-Chain Examples ### Cross-Chain with Solana Assets ```typescript Solana + EVM theme={null} const crossChainQuote = await getQuote({ accounts: [ { type: "kernel-v3.1-ecdsa", signerAddress: "0x5d6fb4eb211a6a2e406a1111b54d26c534753c8e", accountAddress: "0xb7bc0d7baf6761c302ff6772dfd8f9e22ec706e7" }, { type: "solana", signerAddress: "5Cb2369421F8a00Ef556d662D6E97C1419B1d37c", accountAddress: "5Cb2369421F8a00Ef556d662D6E97C1419B1d37c" } ], from: { asset: { assetId: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/spl-token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" }, // USDC on Solana amount: "1000000" }, to: { asset: { assetId: "eip155:8453/erc20:0x4200000000000000000000000000000000000006" } // WETH on Base }, slippageTolerance: 150 // 1.5% for cross-chain operations }); ``` ## Error Handling Examples ### Complete Error Management ```typescript Error Handling theme={null} async function robustSlippageExecution(request: QuoteRequest) { try { const quote = await getQuote(request); const result = await executeQuote(quote); return { success: true, result }; } catch (error: any) { console.error('Quote execution failed:', error); // Handle specific slippage-related errors switch (error.code) { case 'QUOTE_REFUNDED': return { success: false, error: 'SLIPPAGE_EXCEEDED', message: 'Price moved beyond tolerance. Try again with higher slippage.', suggestedSlippage: (request.slippageTolerance || 100) * 1.5, retryable: true }; case 'SLIPPAGE_TOO_HIGH': return { success: false, error: 'SLIPPAGE_TOO_HIGH', message: 'Slippage tolerance too high. Please use a lower value.', maxRecommended: 500, // 5% retryable: true }; case 'INSUFFICIENT_BALANCE': return { success: false, error: 'INSUFFICIENT_BALANCE', message: 'Insufficient balance for this transaction.', retryable: false }; case 'RATE_LIMITED': return { success: false, error: 'RATE_LIMITED', message: 'Too many requests. Please wait before retrying.', retryAfter: error.retryAfter || 5000, retryable: true }; default: return { success: false, error: 'UNKNOWN_ERROR', message: error.message || 'An unexpected error occurred.', retryable: false }; } } } // Usage with error handling const result = await robustSlippageExecution({ from: { account: yourAccount, asset: { assetId: "ob:eth" }, amount: "100000000000000" }, to: { asset: { assetId: "ob:usdc" } }, slippageTolerance: 100 }); if (!result.success) { console.log('Error:', result.message); if (result.retryable) { console.log('This error is retryable'); if (result.suggestedSlippage) { console.log(`Suggested slippage: ${result.suggestedSlippage / 100}%`); } } } ``` ## Recommended Values by Scenario## Next Steps * **Stablecoins**: 10-25 basis points (0.1-0.25%) * **Major tokens**: 100 basis points (1%) * **Volatile tokens**: 300 basis points (3%) * **Cross-chain**: +50% of single-chain values * **Start conservative**: 50 basis points (0.5%) * **Auto-retry**: Enable with 2-3 attempts * **Max slippage**: 500 basis points (5%) * **Monitor success rates**: Adjust based on data Common issues and solutions for slippage tolerance implementation Step-by-step implementation guide for slippage tolerance Test your slippage implementation with small amounts first to ensure it works correctly before processing larger transactions. # Slippage Tolerance Overview Source: https://docs.onebalance.io/guides/slippage/overview Learn how slippage tolerance works in OneBalance to reduce quote refunds and improve transaction success rates. Slippage tolerance helps reduce quote refunds by allowing price movement during cross-chain execution. Configure tolerance levels to balance transaction success rates with price protection. ## What is Slippage Tolerance? Slippage tolerance defines the acceptable price movement between quote generation and execution. When prices move beyond your tolerance, the transaction will fail rather than execute at an unfavorable rate. **Key Benefits:** * **Fewer Refunds** - Transactions succeed despite minor price movements * **Better UX** - Users can retry with adjusted parameters instead of failed quotes * **Cost Efficiency** - Reduces wasted gas from failed transactionsSlippage tolerance is **optional** and maintains backward compatibility with existing integrations. ## How It Works OneBalance accepts slippage tolerance as a positive integer in basis points:## Quick Example Add slippage tolerance to any quote request: ```json Basic Usage theme={null} { "from": { "account": { "sessionAddress": "0x...", "adminAddress": "0x...", "accountAddress": "0x..." }, "asset": { "assetId": "ob:usdc" }, "amount": "1000000" }, "to": { "asset": { "assetId": "ob:eth" } }, "slippageTolerance": 100 } ``` ## Supported Endpoints Slippage tolerance works with all quote endpoints: * 50 = 0.5% * 100 = 1% * 1000 = 10% Transactions fail if price moves beyond tolerance rather than executing at unfavorable rates Failed transactions can retry with higher tolerance for better success rates ## When to Use Slippage Tolerance ### High-Volume Applications Applications processing many transactions should implement slippage tolerance to reduce operational overhead from failed quotes. ### Volatile Market Conditions During high volatility periods, slippage tolerance prevents unnecessary transaction failures from minor price movements. ### User-Facing Applications Give users control over their risk tolerance by allowing them to configure slippage levels. ## Common Use Cases Regular swaps and transfers between any assets Smart contract interactions with automatic token routing Multi-account operations including Solana V2 execute-quote and call-quote endpoints ## Integration Patterns ### Basic Implementation Simply add the parameter to existing quote requests: ```typescript theme={null} // Add to existing quote request const existingRequest = { // ... your existing quote parameters }; const withSlippage = { ...existingRequest, slippageTolerance: 100 // 1% }; ``` ### Dynamic Adjustment Implement retry logic with increasing slippage: ```typescript theme={null} async function executeWithRetry(baseRequest: QuoteRequest) { const slippageValues = [50, 100, 200]; // 0.5%, 1%, 2% for (const slippage of slippageValues) { try { const quote = await getQuote({ ...baseRequest, slippageTolerance: slippage }); return await executeQuote(quote); } catch (error) { if (error.code === 'QUOTE_REFUNDED' && slippage < 200) { continue; // Try next slippage level } throw error; } } } ``` ## Technical Details ### Validation * Must be a positive integer greater than 0 * Recommended maximum: 1000 basis points (10%) * No minimum enforced, but very low values may cause frequent failures ### Processing * Converted internally when sending to routing providers * Applied during execution, not quote generation * Maintains quote structure for tamper-proof signatures ## Next Steps ```typescript theme={null} // Low slippage for stable assets const request = { from: { asset: { assetId: 'ob:usdc' }, amount: '1000000' }, to: { asset: { assetId: 'ob:usdt' } }, slippageTolerance: 10 // 0.1% for stablecoins }; ``` ```typescript theme={null} // Medium slippage for major tokens const request = { from: { asset: { assetId: 'ob:eth' }, amount: '1000000000000000000' }, to: { asset: { assetId: 'ob:usdc' } }, slippageTolerance: 100 // 1% for ETH }; ``` ```typescript theme={null} // Higher slippage for volatile tokens const request = { from: { asset: { assetId: 'eip155:1/erc20:0x...' }, amount: '1000000' }, to: { asset: { assetId: 'ob:eth' } }, slippageTolerance: 300 // 3% for volatile tokens }; ``` Working code examples for different scenarios and asset types Common issues and solutions for slippage tolerance implementation Start with 100 basis points (1%) for most use cases. Adjust based on asset volatility and user preferences. # Slippage Tolerance Troubleshooting Source: https://docs.onebalance.io/guides/slippage/troubleshooting Common issues and solutions when implementing slippage tolerance in OneBalance integrations. This guide covers common issues when implementing slippage tolerance and provides solutions for optimal transaction success rates. ## Parameter Validation ### Invalid Value Type **Issue**: Getting validation errors when adding slippage tolerance parameter. ```bash Error theme={null} { "error": "Invalid slippageTolerance: must be a positive integer" } ``` **Cause**: Passing string, float, or negative values instead of positive integer. ```typescript ❌ Wrong theme={null} const request = { slippageTolerance: "100" // String slippageTolerance: 1.5 // Float slippageTolerance: -50 // Negative }; ``` ```typescript ✅ Correct theme={null} const request = { slippageTolerance: 100 // Positive integer in basis points }; ``` ### Value Too High **Issue**: Setting slippage tolerance above reasonable levels. ```typescript ❌ Wrong theme={null} const request = { slippageTolerance: 1500 // 15% - extremely high }; ``` ```typescript ✅ Correct theme={null} const request = { slippageTolerance: 500 // 5% - more reasonable maximum }; ``` ## Quote Still Failing **Issue**: Transactions still failing despite setting slippage tolerance. | Problem | Likely Cause | Solution | | ---------------------- | ----------------------------------- | --------------------------- | | **Slippage too low** | Market moving faster than tolerance | Increase slippage tolerance | | **Network congestion** | High gas costs affecting execution | Wait for lower congestion | | **Large trade size** | Price impact exceeding slippage | Break into smaller trades | | **Volatile market** | Rapid price movements | Use higher slippage | ### Simple Retry Pattern ```typescript theme={null} async function executeWithRetry(baseRequest: QuoteRequest) { const slippageValues = [50, 100, 200]; // 0.5%, 1%, 2% for (const slippage of slippageValues) { try { const quote = await getQuote({ ...baseRequest, slippageTolerance: slippage }); return await executeQuote(quote); } catch (error: any) { console.log(`Failed at ${slippage / 100}% slippage`); if (slippage === 200) { throw error; // Final attempt failed } } } } ``` ## Choosing Appropriate Values **Issue**: Unsure what slippage values to use for different scenarios. ### Recommended Values### Asset Classification Helper ```typescript theme={null} function getRecommendedSlippage(fromAsset: string, toAsset: string): number { const stablecoins = ['usdc', 'usdt', 'dai']; const majorTokens = ['eth', 'weth', 'btc', 'wbtc']; const isStablecoin = (asset: string) => stablecoins.some(stable => asset.toLowerCase().includes(stable)); const isMajor = (asset: string) => majorTokens.some(major => asset.toLowerCase().includes(major)); // Stablecoin to stablecoin if (isStablecoin(fromAsset) && isStablecoin(toAsset)) { return 25; // 0.25% } // Major token pairs if (isMajor(fromAsset) && isMajor(toAsset)) { return 100; // 1% } // Default for volatile tokens return 300; // 3% } ``` ## Integration Issues ### React State Management ```typescript theme={null} import { useState } from 'react'; function useSlippageTolerance(defaultSlippage = 100) { const [slippage, setSlippage] = useState(defaultSlippage); const executeWithSlippage = async (quoteRequest: QuoteRequest) => { const quote = await getQuote({ ...quoteRequest, slippageTolerance: slippage }); return await executeQuote(quote); }; return { slippage, setSlippage, executeWithSlippage }; } ``` ### Basic Error Handling ```typescript theme={null} async function handleSlippageExecution(request: QuoteRequest) { try { const quote = await getQuote(request); return await executeQuote(quote); } catch (error: any) { console.error('Transaction failed:', error.message); // Suggest increasing slippage if current value is low const currentSlippage = request.slippageTolerance || 0; if (currentSlippage < 100) { console.log('Consider increasing slippage tolerance to 1% (100 basis points)'); } throw error; } } ``` ## Best Practices **10-50 basis points** USDC ↔ USDT: `10` (0.1%) DAI ↔ USDC: `25` (0.25%) **50-100 basis points** ETH ↔ USDC: `100` (1%) BTC ↔ ETH: `100` (1%) **100-500 basis points** Altcoins: `300` (3%) New tokens: `500` (5%) ## Getting Help Begin with 1% (100 basis points) for most use cases Use lower slippage for stablecoins, higher for volatile tokens Add automatic retry with progressive slippage increases Track success rates and adjust defaults based on real data Use the **Intercom chat widget** in the bottom right corner for instant help, or email [support@onebalance.io](mailto:support@onebalance.io). Include your slippage values, error messages, and asset pair information for faster resolution. Most issues can be resolved by starting with 100 basis points (1%) and implementing simple retry logic with higher values. # Solana Contract Calls Source: https://docs.onebalance.io/guides/solana/contract-calls Execute smart contract calls on EVM chains using Solana assets as funding source Execute smart contract calls on EVM chains using Solana assets as the funding source. These examples use the V3 calldata endpoints that support cross-chain contract execution. ## Prerequisites Install the required dependencies:```bash npm theme={null} npm install @solana/web3.js bs58 ``` ```bash pnpm theme={null} pnpm add @solana/web3.js bs58 ``` ```bash yarn theme={null} yarn add @solana/web3.js bs58 ``` ## Signing Utility for Contract CallsFor complete signing documentation covering all account types (EVM, Solana, EIP-7702), see the [Signing Guide](/concepts/signing) with detailed explanations and troubleshooting. ```typescript theme={null} import { MessageV0, VersionedTransaction, PublicKey } from '@solana/web3.js'; import bs58 from 'bs58'; /** * Signs a Solana chain operation with a private key * @param accountAddress - The address of the account to sign the chain operation * @param privateKey - The private key in base58 format * @param chainOp - The chain operation object from quote response * @returns The signed chain operation with signature added */ export function signSolanaChainOperation( accountAddress: string, privateKey: string, chainOp: any, ): any { const msgBuffer = Buffer.from(chainOp.dataToSign, 'base64'); const message = MessageV0.deserialize(msgBuffer); const transaction = new VersionedTransaction(message); const decodedKey = bs58.decode(privateKey); transaction.sign([ { publicKey: new PublicKey(accountAddress), secretKey: Buffer.from(decodedKey), }, ]); const signature = bs58.encode(Buffer.from(transaction.signatures[transaction.signatures.length - 1])); return { ...chainOp, signature, }; } ``` ## Example 1: USDC (Solana) → EVM Contract Call Use USDC on Solana to fund and execute contract calls on Arbitrum:## Example 2: SOL → EVM Contract Call Use native SOL to fund contract calls on Arbitrum. This example shows more complex operations including Jupiter swaps: First, prepare the contract call by specifying the target chain operations and token requirements: ```bash cURL theme={null} curl -X POST 'https://be.onebalance.io/api/v3/quote/prepare-call-quote' \ -H 'Content-Type: application/json' \ -H 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ -d '{ "accounts": [ { "type": "solana", "accountAddress": "EB8Hi4LoqUVCGUPJ2y9MsHbEsJQJvmpQRUWrLpjEZxC6" }, { "type": "kernel-v3.3-ecdsa", "accountAddress": "0xDb69A4Ded06AaD92C69c42232b691CFD8bF347e8", "signerAddress": "0xDb69A4Ded06AaD92C69c42232b691CFD8bF347e8", "deploymentType": "EIP7702" } ], "targetChain": "eip155:42161", "calls": [ { "to": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", "data": "0xa9059cbb0000000000000000000000004EbcFae0C3e747C95504CA7c79c46f725Cb4c7520000000000000000000000000000000000000000000000000000000000000001", "value": "0x0" } ], "tokensRequired": [ { "assetType": "eip155:42161/erc20:0xaf88d065e77c8cC2239327C5EDb3A432268e5831", "amount": "10000" } ], "fromAssetId": "ob:usdc" }' ``` ```typescript TypeScript theme={null} const prepareRequest = { accounts: [ { type: "solana", accountAddress: "EB8Hi4LoqUVCGUPJ2y9MsHbEsJQJvmpQRUWrLpjEZxC6" }, { type: "kernel-v3.3-ecdsa", accountAddress: "0xDb69A4Ded06AaD92C69c42232b691CFD8bF347e8", signerAddress: "0xDb69A4Ded06AaD92C69c42232b691CFD8bF347e8", deploymentType: "EIP7702" } ], targetChain: "eip155:42161", // Arbitrum calls: [ { to: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", // USDC on Arbitrum data: "0xa9059cbb0000000000000000000000004EbcFae0C3e747C95504CA7c79c46f725Cb4c7520000000000000000000000000000000000000000000000000000000000000001", value: "0x0" } ], tokensRequired: [ { assetType: "eip155:42161/erc20:0xaf88d065e77c8cC2239327C5EDb3A432268e5831", amount: "10000" // 0.01 USDC needed for the contract call } ], fromAssetId: "ob:usdc" // Use aggregated USDC }; const response = await fetch('https://be.onebalance.io/api/v3/quote/prepare-call-quote', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': '42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' }, body: JSON.stringify(prepareRequest) }); const prepareData = await response.json(); ``` The `fromAssetId: "ob:usdc"` tells OneBalance to use USDC from your Solana account to fund the EVM contract call. Use the prepared data to get a signed quote for execution: ```bash cURL theme={null} curl -X POST 'https://be.onebalance.io/api/v3/quote/call-quote' \ -H 'Content-Type: application/json' \ -H 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ -d '{ "fromAggregatedAssetId": "ob:usdc", "accounts": [ { "type": "solana", "accountAddress": "EB8Hi4LoqUVCGUPJ2y9MsHbEsJQJvmpQRUWrLpjEZxC6" }, { "type": "kernel-v3.3-ecdsa", "accountAddress": "0xDb69A4Ded06AaD92C69c42232b691CFD8bF347e8", "signerAddress": "0xDb69A4Ded06AaD92C69c42232b691CFD8bF347e8", "deploymentType": "EIP7702" } ], "tamperProofSignature": "0x6316c2fe49d51af45b7bb7bb311395595fe29b7bd9de2e219cba0942fda2461c1c3f19108d6f391cd02c7cdc7d218ff0945d32ba784be1357b3c954ff7ae254e1c", "chainOperation": { "userOp": { "sender": "0xdb69a4ded06aad92c69c42232b691cfd8bf347e8", "nonce": "913479994650515257524606220465835134743662536739504622017003723935449089", "callData": "0xe9ae5c53010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e5831000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000004ebcfae0c3e747c95504ca7c79c46f725cb4c752000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000", "callGasLimit": "2831389", "verificationGasLimit": "46313", "preVerificationGas": "0", "maxFeePerGas": "0", "maxPriorityFeePerGas": "0", "paymaster": "0xa784e6482bd5edbfe5991b18cbd545ebd46e1cc4", "paymasterVerificationGasLimit": "13011", "paymasterPostOpGasLimit": "0", "paymasterData": "0x", "signature": "0x8ca6ea251b08d488919a3c8708c6c9cdc782190d57151a1e83c0b4627aa819fa6f668b46d894e8f68291338bdf05111bf98ad8dbc6f7d4b2aa484a2749a26ebc1b" }, "typedDataToSign": { "domain": { "name": "RoleBasedECDSAValidator", "version": "1.4.3", "chainId": 42161, "verifyingContract": "0xA24bD06230f3F54e5bf266AE7A41750eE3b789FA" }, "types": { "Approve": [ { "name": "callDataAndNonceHash", "type": "bytes32" } ] }, "primaryType": "Approve", "message": { "callDataAndNonceHash": "0x68752061ab5e1f42561b192a3ea5e70c4005b62a7ea4698b14a35ab0004f6687" } }, "assetType": "eip155:42161/erc20:0xaf88d065e77c8cC2239327C5EDb3A432268e5831", "amount": "10000" } } }' ``` ```typescript TypeScript theme={null} // Use the tamperProofSignature from step 1 response const callQuoteRequest = { fromAggregatedAssetId: "ob:usdc", accounts: [ { type: "solana", accountAddress: "EB8Hi4LoqUVCGUPJ2y9MsHbEsJQJvmpQRUWrLpjEZxC6" }, { type: "kernel-v3.3-ecdsa", accountAddress: "0xDb69A4Ded06AaD92C69c42232b691CFD8bF347e8", signerAddress: "0xDb69A4Ded06AaD92C69c42232b691CFD8bF347e8", deploymentType: "EIP7702" } ], tamperProofSignature: prepareData.tamperProofSignature, chainOperation: prepareData.chainOperation }; const callQuoteResponse = await fetch('https://be.onebalance.io/api/v3/quote/call-quote', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': '42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' }, body: JSON.stringify(callQuoteRequest) }); const callQuote = await callQuoteResponse.json(); ``` You must use the exact `tamperProofSignature` and `chainOperation` data from the prepare step response. Sign the Solana operation and execute the cross-chain contract call: ```typescript TypeScript theme={null} import { MessageV0, VersionedTransaction, PublicKey } from '@solana/web3.js'; import bs58 from 'bs58'; // Sign the Solana operation from the quote function signSolanaChainOperation( accountAddress: string, privateKey: string, chainOp: any ) { const msgBuffer = Buffer.from(chainOp.dataToSign, 'base64'); const message = MessageV0.deserialize(msgBuffer); const transaction = new VersionedTransaction(message); const decodedKey = bs58.decode(privateKey); transaction.sign([{ publicKey: new PublicKey(accountAddress), secretKey: Buffer.from(decodedKey), }]); const signature = bs58.encode(Buffer.from(transaction.signatures[transaction.signatures.length - 1])); return { ...chainOp, signature }; } // Sign and execute const solanaOp = callQuote.originChainsOperations[0]; const signedSolanaOp = signSolanaChainOperation( "EB8Hi4LoqUVCGUPJ2y9MsHbEsJQJvmpQRUWrLpjEZxC6", "your-private-key", solanaOp ); const executeRequest = { ...callQuote, originChainsOperations: [signedSolanaOp], tamperProofSignature: "0xb83221a24cf7df0ece73a97b9888ea2091bcc5478aa142ec77b5fe3ebe83f92b2ef121ace109ea79f3b032bb6186c8af3fdc0e9c3dd5e968bf05cabe9adcab421c" }; // Execute the quote const executeResponse = await fetch('https://be.onebalance.io/api/v3/quote/execute-quote', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': '42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' }, body: JSON.stringify(executeRequest) }); const result = await executeResponse.json(); console.log('Contract call executed:', result); ``` Successfully executed a USDC transfer contract call on Arbitrum using USDC from your Solana account as funding. ## EIP-7702 Integration OneBalance supports EIP-7702 delegated accounts that can be funded using Solana assets. This enables seamless cross-chain contract execution where Solana provides the funding and EIP-7702 provides the execution context. ### Multi-Account Setup (Solana + EIP-7702) When using Solana assets to fund EIP-7702 operations, you need both account types: ```bash cURL theme={null} curl -X POST 'https://be.onebalance.io/api/v3/quote/prepare-call-quote' \ -H 'Content-Type: application/json' \ -H 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ -d '{ "accounts": [ { "type": "solana", "accountAddress": "EB8Hi4LoqUVCGUPJ2y9MsHbEsJQJvmpQRUWrLpjEZxC6" }, { "type": "kernel-v3.3-ecdsa", "accountAddress": "0xDb69A4Ded06AaD92C69c42232b691CFD8bF347e8", "signerAddress": "0xDb69A4Ded06AaD92C69c42232b691CFD8bF347e8", "deploymentType": "EIP7702" } ], "targetChain": "eip155:42161", "calls": [ { "to": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", "data": "0xa9059cbb0000000000000000000000004EbcFae0C3e747C95504CA7c79c46f725Cb4c7520000000000000000000000000000000000000000000000000000000000000001", "value": "0x0" } ], "tokensRequired": [ { "assetType": "eip155:42161/erc20:0xaf88d065e77c8cC2239327C5EDb3A432268e5831", "amount": "10000" } ], "fromAssetId": "ob:sol" }' ``` ```typescript TypeScript theme={null} const prepareWithSOL = { accounts: [ { type: "solana", accountAddress: "EB8Hi4LoqUVCGUPJ2y9MsHbEsJQJvmpQRUWrLpjEZxC6" }, { type: "kernel-v3.3-ecdsa", accountAddress: "0xDb69A4Ded06AaD92C69c42232b691CFD8bF347e8", signerAddress: "0xDb69A4Ded06AaD92C69c42232b691CFD8bF347e8", deploymentType: "EIP7702" } ], targetChain: "eip155:42161", calls: [ { to: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", data: "0xa9059cbb0000000000000000000000004EbcFae0C3e747C95504CA7c79c46f725Cb4c7520000000000000000000000000000000000000000000000000000000000000001", value: "0x0" } ], tokensRequired: [ { assetType: "eip155:42161/erc20:0xaf88d065e77c8cC2239327C5EDb3A432268e5831", amount: "10000" } ], fromAssetId: "ob:sol" // Use SOL instead of USDC }; ``` When using `fromAssetId: "ob:sol"`, OneBalance will swap SOL to USDC on Solana via Jupiter, then bridge to Arbitrum. The response includes Jupiter swap instructions and address lookup tables: ```json theme={null} { "originChainsOperations": [ { "type": "solana", "instructions": [ { "keys": [ { "pubkey": "EB8Hi4LoqUVCGUPJ2y9MsHbEsJQJvmpQRUWrLpjEZxC6", "isSigner": true, "isWritable": true }, { "pubkey": "ATw6BVTaLgdDAJ9Deki3Zs1bvruoNfs99TWi7L3XhtFD", "isSigner": false, "isWritable": true } ], "programId": "11111111111111111111111111111111", "data": "02000000561b160000000000" }, { "programId": "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4", "data": "d033ef977b2bed5c010000001a640001c4d3030000000000d80d150000000000f40100" } ], "addressLookupTableAddresses": [ "D6XNrxMsDoABJVVY5YyHxJuAB6WGzYCXpZeKyNtqu2v4" ], "assetType": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501", "amount": "1448790" } ] } ``` SOL operations often use address lookup tables for efficiency. OneBalance handles this automatically. ```typescript theme={null} // The signing process is identical, but notice the complex swap operation const signedOperation = signSolanaChainOperation( "EB8Hi4LoqUVCGUPJ2y9MsHbEsJQJvmpQRUWrLpjEZxC6", "your-solana-private-key", solanaOperation ); // Execute with the complex operation including Jupiter swaps const result = await executeQuote(signedOperation); // Result shows: // - SOL swapped to USDC on Solana via Jupiter // - USDC bridged to Arbitrum // - Contract call executed on Arbitrum // - All in a single atomic operation ``` ```typescript Account Configuration theme={null} const multiAccountSetup = { accounts: [ { type: "solana", accountAddress: "EB8Hi4LoqUVCGUPJ2y9MsHbEsJQJvmpQRUWrLpjEZxC6" }, { type: "kernel-v3.3-ecdsa", accountAddress: "0xDb69A4Ded06AaD92C69c42232b691CFD8bF347e8", signerAddress: "0xDb69A4Ded06AaD92C69c42232b691CFD8bF347e8", deploymentType: "EIP7702" // This triggers delegation requirements } ] }; ``` ```bash Balance Check theme={null} # Check combined balance across both accounts curl -X GET 'https://be.onebalance.io/api/v3/balances/aggregated-balance?account=solana:EB8Hi4LoqUVCGUPJ2y9MsHbEsJQJvmpQRUWrLpjEZxC6,eip155:42161:0xDb69A4Ded06AaD92C69c42232b691CFD8bF347e8' \ -H 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' ``` ### Delegation Signature Requirements EIP-7702 accounts require delegation signatures before execution:**Delegation Required**: When using `deploymentType: "EIP7702"`, you must sign the delegation object from the `prepare-call-quote` response before proceeding to `call-quote`. ### Complete EIP-7702 Example Here's a complete example using SOL to fund an EIP-7702 contract call: The `prepare-call-quote` response includes delegation data that must be signed: ```typescript theme={null} const prepareResponse = await prepareCallQuote(multiAccountRequest); // EIP-7702 delegation object is included in response const delegationObject = prepareResponse.delegation; console.log('Delegation to sign:', delegationObject); ``` Sign the delegation using your EIP-7702 signer: ```typescript theme={null} // Sign the delegation object with your EIP-7702 signer const delegationSignature = await evmSigner.signTypedData(delegationObject); // Include the signature in your call-quote request const callQuoteRequest = { ...prepareResponse, delegationSignature: delegationSignature }; ``` The signed delegation enables atomic execution: ```typescript theme={null} // The operation will: // 1. Process delegation signature // 2. Use Solana assets for funding // 3. Execute contract calls via delegated account // 4. All in a single atomic transaction const result = await executeQuote(signedQuote); ``` ```typescript theme={null} import { signSolanaChainOperation } from './signing-utils'; async function solanaToEIP7702ContractCall() { // 1. Prepare the call with both accounts const prepareRequest = { accounts: [ { type: "solana", accountAddress: "EB8Hi4LoqUVCGUPJ2y9MsHbEsJQJvmpQRUWrLpjEZxC6" }, { type: "kernel-v3.3-ecdsa", accountAddress: "0xDb69A4Ded06AaD92C69c42232b691CFD8bF347e8", signerAddress: "0xDb69A4Ded06AaD92C69c42232b691CFD8bF347e8", deploymentType: "EIP7702" } ], targetChain: "eip155:42161", calls: [ { to: "0xA0b86a33E6Af8661A8B9e0A4B9b10b8Abc7e1234", // Your contract data: "0x...", // Your contract call data value: "0x0" } ], tokensRequired: [ { assetType: "eip155:42161/erc20:0xaf88d065e77c8cC2239327C5EDb3A432268e5831", amount: "1000000" // 1 USDC } ], fromAssetId: "ob:sol" // Fund with SOL }; // 2. Get preparation data including delegation const prepareResponse = await fetch('/api/v3/quote/prepare-call-quote', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(prepareRequest) }); const prepareData = await prepareResponse.json(); // 3. Sign the EIP-7702 delegation const delegationSignature = await evmSigner.signTypedData(prepareData.delegation); // 4. Get the call quote with delegation signature const callQuoteRequest = { ...prepareData, delegationSignature: delegationSignature }; const callQuoteResponse = await fetch('/api/v3/quote/call-quote', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(callQuoteRequest) }); const callQuote = await callQuoteResponse.json(); // 5. Sign the Solana operation const solanaOp = callQuote.originChainsOperations.find(op => op.type === 'solana'); const signedSolanaOp = signSolanaChainOperation( "EB8Hi4LoqUVCGUPJ2y9MsHbEsJQJvmpQRUWrLpjEZxC6", solanaPrivateKey, solanaOp ); // 6. Execute the quote const executeRequest = { ...callQuote, originChainsOperations: [signedSolanaOp] }; const result = await fetch('/api/v3/quote/execute-quote', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(executeRequest) }); return await result.json(); } ``` **Atomic Cross-Chain Execution:** * SOL swapped to USDC on Solana * USDC bridged to target EVM chain * EIP-7702 delegation processed * Contract call executed * All happens in coordinated atomic operations **Cost Efficiency:** * Use cheaper Solana transaction fees * Leverage SOL liquidity for EVM operations * Batch multiple operations together **Developer Experience:** * Single API call for complex operations * No manual bridging or swapping required * Built-in slippage protection EIP-7702 integration is supported on chains with EIP-7702 enabled. Check the [supported networks](/resources/supported-networks) for current availability. ## Error Handling Common issues with contract calls and their solutions: ### Insufficient Balance for Contract Execution ```typescript theme={null} // Always check aggregated balance before contract calls const balanceResponse = await fetch( 'https://be.onebalance.io/api/v3/balances/aggregated-balance?account=solana:YOUR_ACCOUNT', { headers: { 'x-api-key': 'YOUR_API_KEY' } } ); const balance = await balanceResponse.json(); const requiredAsset = balance.balanceByAggregatedAsset.find(b => b.aggregatedAssetId === 'ob:usdc'); if (parseInt(requiredAsset.balance) < requiredAmount) { throw new Error('Insufficient balance for contract call'); } ``` ### Missing EIP-7702 Delegation ```typescript theme={null} // EIP-7702 accounts must include delegation signature if (account.deploymentType === "EIP7702" && !callQuoteRequest.delegationSignature) { throw new Error('EIP-7702 requires delegation signature from prepare-call-quote'); } ``` ### Invalid Target Chain ```typescript theme={null} // Verify target chain supports the contract const supportedChains = ['eip155:1', 'eip155:42161', 'eip155:137']; if (!supportedChains.includes(targetChain)) { throw new Error(`Contract not available on chain: ${targetChain}`); } ``` ## Related GuidesBasic concepts and account setup for Solana integration Simple swap and transfer examples using Solana assets Learn about EIP-7702 delegated accounts and setup General contract call patterns and best practices For questions about Solana contract calls or integration support, use the **Intercom chat widget** in the bottom right corner, join our [Discord](https://discord.com/invite/vHkw7rpdT8), or reach out via [support](mailto:support@onebalance.io). # Solana Examples Source: https://docs.onebalance.io/guides/solana/examples Working code examples for Solana integration with OneBalance, including transaction signing and cross-chain operations This page provides complete, working code examples for integrating Solana with OneBalance. All examples use real data from our testing environment and are copy-paste ready. ## Prerequisites Install the required dependencies:```bash npm theme={null} npm install @solana/web3.js bs58 ``` ```bash pnpm theme={null} pnpm add @solana/web3.js bs58 ``` ```bash yarn theme={null} yarn add @solana/web3.js bs58 ``` ## Signing Utilities Here's the signing function you'll need for Solana operations:## Example 1: SOL → USDC (Same Chain) The simplest Solana operation - swap SOL to USDC within Solana: For browser wallets like Phantom or Solflare: ```typescript theme={null} import { MessageV0, VersionedTransaction } from '@solana/web3.js'; import bs58 from 'bs58'; /** * Signs a Solana operation using a browser wallet * @param dataToSign - Base64 encoded data from quote response * @param wallet - Connected Solana wallet (Phantom, Solflare, etc.) * @returns Base58 encoded signature */ async function signSolanaOperation(dataToSign: string, wallet: any): Promise { const msgBuffer = Buffer.from(dataToSign, 'base64'); const message = MessageV0.deserialize(msgBuffer); const transaction = new VersionedTransaction(message); const signedTx = await wallet.signTransaction(transaction); return bs58.encode(Buffer.from(signedTx.signatures[signedTx.signatures.length - 1])); } ``` For server-side operations or direct private key access: ```typescript theme={null} import { MessageV0, VersionedTransaction, PublicKey } from '@solana/web3.js'; import bs58 from 'bs58'; /** * Signs a Solana operation with a private key * @param accountAddress - Your Solana account address * @param privateKey - Your private key in base58 format * @param chainOp - Chain operation from quote response * @returns Signed chain operation */ function signSolanaChainOperation(accountAddress: string, privateKey: string, chainOp: any) { const msgBuffer = Buffer.from(chainOp.dataToSign, 'base64'); const message = MessageV0.deserialize(msgBuffer); const transaction = new VersionedTransaction(message); const decodedKey = bs58.decode(privateKey); transaction.sign([{ publicKey: new PublicKey(accountAddress), secretKey: Buffer.from(decodedKey), }]); const signature = bs58.encode(Buffer.from(transaction.signatures[transaction.signatures.length - 1])); return { ...chainOp, signature }; } ``` ## Example 2: SOL → USDC (Cross-Chain) Swap SOL on Solana to USDC on Arbitrum - the most common cross-chain pattern: ```bash cURL theme={null} curl -X POST 'https://be.onebalance.io/api/v3/quote' \ -H 'Content-Type: application/json' \ -H 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ -d '{ "from": { "accounts": [{ "type": "solana", "accountAddress": "J5CCzBULFax899tcirb6wMbenQUd8whbaetG7EfSick5" }], "asset": { "assetId": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501" }, "amount": "10000000" }, "to": { "asset": { "assetId": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" } } }' ``` ```typescript TypeScript theme={null} const quoteRequest = { from: { accounts: [{ type: "solana", accountAddress: "J5CCzBULFax899tcirb6wMbenQUd8whbaetG7EfSick5" }], asset: { assetId: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501" }, amount: "10000000" // 0.01 SOL (9 decimals) }, to: { asset: { assetId: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" } } }; const response = await fetch('https://be.onebalance.io/api/v3/quote', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': '42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' }, body: JSON.stringify(quoteRequest) }); const quote = await response.json(); ``` ```typescript TypeScript theme={null} // Sign the Solana operation const solanaOperation = quote.originChainsOperations.find(op => op.type === 'solana'); const signature = await signSolanaOperation(solanaOperation.dataToSign, wallet); // Add signature to the quote const signedQuote = { ...quote, originChainsOperations: quote.originChainsOperations.map(op => op.type === 'solana' ? { ...op, signature } : op ) }; // Execute the quote const executeResponse = await fetch('https://be.onebalance.io/api/v3/quote/execute', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': '42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' }, body: JSON.stringify(signedQuote) }); const result = await executeResponse.json(); console.log('Swap completed:', result); ``` Successfully swapped 0.01 SOL to \~1.63 USDC on Solana ## Example 3: USDC (Solana) → USDC (Arbitrum) Transfer USDC from Solana to Arbitrum: ```bash cURL theme={null} curl -X POST 'https://be.onebalance.io/api/v3/quote' \ -H 'Content-Type: application/json' \ -H 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ -d '{ "from": { "accounts": [{ "type": "solana", "accountAddress": "J5CCzBULFax899tcirb6wMbenQUd8whbaetG7EfSick5" }], "asset": { "assetId": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501" }, "amount": "10000000" }, "to": { "asset": { "assetId": "eip155:42161/erc20:0xaf88d065e77c8cC2239327C5EDb3A432268e5831" }, "account": "eip155:42161:0x895Cf62399bF1F8b88195E741b64278b41EB7F09" } }' ``` ```typescript TypeScript theme={null} const crossChainQuote = { from: { accounts: [{ type: "solana", accountAddress: "J5CCzBULFax899tcirb6wMbenQUd8whbaetG7EfSick5" }], asset: { assetId: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501" }, amount: "10000000" // 0.01 SOL }, to: { asset: { assetId: "eip155:42161/erc20:0xaf88d065e77c8cC2239327C5EDb3A432268e5831" }, account: "eip155:42161:0x895Cf62399bF1F8b88195E741b64278b41EB7F09" } }; const response = await fetch('https://be.onebalance.io/api/v3/quote', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': '42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' }, body: JSON.stringify(crossChainQuote) }); const quote = await response.json(); ``` ```json theme={null} { "id": "0xcd3a5cfe80d1b84db755bfb8ebe0a617ff153cc48ab6d5ab28436386f06ce100", "accounts": [{ "type": "solana", "accountAddress": "J5CCzBULFax899tcirb6wMbenQUd8whbaetG7EfSick5" }], "originToken": { "assetType": ["solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501"], "amount": "10000000", "fiatValue": "1.67" }, "destinationToken": { "assetType": "eip155:42161/erc20:0xaf88d065e77c8cC2239327C5EDb3A432268e5831", "amount": "1570450", // ~1.57 USDC on Arbitrum "minimumAmount": "1516171", "fiatValue": "1.57", "recipientAccount": "eip155:42161:0x895Cf62399bF1F8b88195E741b64278b41EB7F09" }, "fees": { "cumulativeUSD": "0.12" } } ``` ```typescript theme={null} // Sign and execute the cross-chain operation const solanaOp = quote.originChainsOperations.find(op => op.type === 'solana'); const signedOp = await signSolanaOperation(solanaOp.dataToSign, wallet); const executeRequest = { ...quote, originChainsOperations: [{ ...solanaOp, signature: signedOp }] }; const result = await fetch('https://be.onebalance.io/api/v3/quote/execute', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': '42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' }, body: JSON.stringify(executeRequest) }); const execution = await result.json(); console.log('Cross-chain swap completed:', execution); ``` Successfully swapped 0.01 SOL to 1.57 USDC and bridged to Arbitrum ```bash cURL theme={null} curl -X POST 'https://be.onebalance.io/api/v3/quote' \ -H 'Content-Type: application/json' \ -H 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ -d '{ "from": { "accounts": [{ "type": "solana", "accountAddress": "J5CCzBULFax899tcirb6wMbenQUd8whbaetG7EfSick5" }], "asset": { "assetId": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" }, "amount": "1000000" }, "to": { "asset": { "assetId": "eip155:42161/erc20:0xaf88d065e77c8cC2239327C5EDb3A432268e5831" }, "account": "eip155:42161:0x895Cf62399bF1F8b88195E741b64278b41EB7F09" } }' ``` ```typescript TypeScript theme={null} const usdcTransfer = { from: { accounts: [{ type: "solana", accountAddress: "J5CCzBULFax899tcirb6wMbenQUd8whbaetG7EfSick5" }], asset: { assetId: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" }, amount: "1000000" // 1 USDC (6 decimals) }, to: { asset: { assetId: "eip155:42161/erc20:0xaf88d065e77c8cC2239327C5EDb3A432268e5831" }, account: "eip155:42161:0x895Cf62399bF1F8b88195E741b64278b41EB7F09" } }; const response = await fetch('https://be.onebalance.io/api/v3/quote', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': '42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' }, body: JSON.stringify(usdcTransfer) }); const quote = await response.json(); ``` This operation only requires a Solana account since funds come entirely from Solana. The recipient is specified in `to.account`. ## Example 4: Aggregated USDC → SOL (Multi-Account) Use aggregated USDC balance from both EVM and Solana accounts to buy SOL:```bash cURL theme={null} curl -X POST 'https://be.onebalance.io/api/v3/quote' \ -H 'Content-Type: application/json' \ -H 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ -d '{ "from": { "accounts": [ { "type": "kernel-v3.1-ecdsa", "accountAddress": "0xd4f5A60c9b500f875ADf757BC3027A4424079E05", "deploymentType": "ERC4337", "signerAddress": "0x9b747cC14A5672a7166b4eccdc92d7F4003f8081" }, { "type": "solana", "accountAddress": "J5CCzBULFax899tcirb6wMbenQUd8whbaetG7EfSick5" } ], "asset": { "assetId": "ob:usdc" }, "amount": "10000000" }, "to": { "asset": { "assetId": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501" } } }' ``` ```typescript TypeScript theme={null} const aggregatedSwap = { from: { accounts: [ { type: "kernel-v3.1-ecdsa", accountAddress: "0xd4f5A60c9b500f875ADf757BC3027A4424079E05", deploymentType: "ERC4337", signerAddress: "0x9b747cC14A5672a7166b4eccdc92d7F4003f8081" }, { type: "solana", accountAddress: "J5CCzBULFax899tcirb6wMbenQUd8whbaetG7EfSick5" } ], asset: { assetId: "ob:usdc" // Aggregated USDC across chains }, amount: "10000000" // 10 USDC }, to: { asset: { assetId: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501" } } }; const response = await fetch('https://be.onebalance.io/api/v3/quote', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': '42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' }, body: JSON.stringify(aggregatedSwap) }); const quote = await response.json(); ``` This operation involves both EVM and Solana accounts: ```typescript theme={null} async function signMultiAccountQuote(quote: any, solanaWallet: any, evmSigner: any) { const signedOperations = await Promise.all( quote.originChainsOperations.map(async (operation: any) => { if (operation.type === 'solana') { const signature = await signSolanaOperation(operation.dataToSign, solanaWallet); return { ...operation, signature }; } else { // Sign EVM operation with typed data const signature = await evmSigner.signTypedData(operation.typedDataToSign); return { ...operation, userOp: { ...operation.userOp, signature } }; } }) ); return { ...quote, originChainsOperations: signedOperations }; } // Execute the multi-account operation const signedQuote = await signMultiAccountQuote(quote, solanaWallet, evmSigner); const result = await fetch('https://be.onebalance.io/api/v3/quote/execute', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': '42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' }, body: JSON.stringify(signedQuote) }); const execution = await result.json(); console.log('Aggregated swap completed:', execution); ``` Used aggregated USDC from multiple chains to purchase SOL This example shows OneBalance's power - using funds from multiple blockchains in a single operation. ## Check Balances Always verify your balances before creating quotes:```bash cURL - Single Account theme={null} curl -X GET 'https://be.onebalance.io/api/v3/balances/aggregated-balance?account=solana:J5CCzBULFax899tcirb6wMbenQUd8whbaetG7EfSick5' \ -H 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' ``` ```bash cURL - Multi-Account theme={null} curl -X GET 'https://be.onebalance.io/api/v3/balances/aggregated-balance?account=eip155:42161:0xfe52613d747E20F2f62e0A5cC36B0DFAe771C442,solana:J5CCzBULFax899tcirb6wMbenQUd8whbaetG7EfSick5' \ -H 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' ``` ```typescript TypeScript theme={null} // Check Solana account balance const balance = await fetch( 'https://be.onebalance.io/api/v3/balances/aggregated-balance?account=solana:J5CCzBULFax899tcirb6wMbenQUd8whbaetG7EfSick5', { headers: { 'x-api-key': '42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' } } ); const balanceData = await balance.json(); console.log('Available assets:', balanceData.balanceByAggregatedAsset); ``` ## Quick Error Handling```typescript Validate Account theme={null} import { PublicKey } from '@solana/web3.js'; // Ensure Solana address is valid before making requests try { new PublicKey(accountAddress); } catch (error) { throw new Error('Invalid Solana address format'); } ``` ```typescript Check Quote Status theme={null} // Monitor transaction progress const statusResponse = await fetch( `https://be.onebalance.io/api/v3/status/get-execution-status?quoteId=${quote.id}`, { headers: { 'x-api-key': 'YOUR_API_KEY' } } ); const status = await statusResponse.json(); console.log('Transaction status:', status.status.status); ``` ## Next StepsUse Solana assets to fund smart contract calls on EVM chains Common issues and solutions for Solana integration Complete API documentation for v3 endpoints Concepts and setup guide for Solana integration For additional support, use the **Intercom chat widget** in the bottom right corner, join our [Discord](https://discord.com/invite/vHkw7rpdT8), or reach out via [support](mailto:support@onebalance.io). # Solana Getting Started Source: https://docs.onebalance.io/guides/solana/getting-started Get your first Solana cross-chain swap working in 5 minutes with this complete working example Learn how to swap SOL to USDC on Arbitrum using OneBalance APIs.Get the full working example with setup instructions. 100% free and ready to run. ## Setup Install packages and create environment file: ```bash theme={null} pnpm install @solana/web3.js bs58 dotenv viem ``` ```bash .env theme={null} SOLANA_PRIVATE_KEY=your_base58_private_key_here ONEBALANCE_API_KEY=your_api_key_here ARBITRUM_RECIPIENT=0x895Cf62399bF1F8b88195E741b64278b41EB7F09 ``` ## Example ```typescript swap.ts theme={null} 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{ 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: 1. **Balance Check**: Verify you have sufficient SOL for the swap 2. **Request Quote**: Get pricing for 0.01 SOL → USDC on Arbitrum 3. **Sign Transaction**: Sign the Solana operation with your private key 4. **Execute Swap**: Submit the signed transaction to OneBalance 5. **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: ```bash theme={null} npx tsx swap.ts ```Make sure your Solana account has at least 0.01 SOL for the swap. ## Next StepsExplore additional swap patterns and advanced operations Use Solana assets to fund smart contract calls on EVM chains Got stuck? Check the [Solana Troubleshooting Guide](/guides/solana/troubleshooting) for common issues and solutions. # Solana Overview Source: https://docs.onebalance.io/guides/solana/overview Learn how to aggregate both EVM and Solana balances for chain abstraction, enabling unified operations across all supported blockchains. OneBalance now supports **Solana blockchain**, enabling you to aggregate balances and assets from both Solana and EVM chains into unified operations. Use your SOL, USDC, or other Solana assets alongside EVM tokens for optimized cross-chain transactions. ## What You Can Do## Key Differences from EVM ### v3 API Endpoints Required Solana operations require the **v3 API endpoints** which support multiple accounts: ```json theme={null} // v1 (EVM only) - Single account object { "from": { "account": { "sessionAddress": "0x...", ... } } } // v3 (Cross-chain) - Accounts array { "from": { "accounts": [ { "type": "role-based", "sessionAddress": "0x...", ... }, { "type": "solana", "accountAddress": "J5CC..." } ] } } ``` ### Solana Account Structure Solana accounts are simpler than EVM smart accounts: ```json theme={null} { "type": "solana", "accountAddress": "J5CCzBULFax899tcirb6wMbenQUd8whbaetG7EfSick5" } ``` Use EVM balances to buy tokens and pay fees on Solana, and vice versa - no manual bridging required Automatically find the best liquidity and lowest fees across Solana and EVM ecosystems Execute smart contract functions on EVM chains using combined EVM and Solana balance funding Execute programs on Solana using combined balances (coming soon) Solana uses **base58-encoded addresses** and doesn't require session/admin addresses like EVM smart accounts. ### Different Signing Process Solana transactions use a different signing mechanism than EVM:```typescript Browser Wallet theme={null} import { MessageV0, VersionedTransaction } from '@solana/web3.js'; import bs58 from 'bs58'; async function signSolanaOperation(dataToSign: string, wallet: any) { // 1. Convert base64 data to Message const message = MessageV0.deserialize(Buffer.from(dataToSign, 'base64')); // 2. Create versioned transaction const transaction = new VersionedTransaction(message); // 3. Sign with wallet const signedTx = await wallet.signTransaction(transaction); // 4. Extract signature in base58 format return bs58.encode(Buffer.from(signedTx.signatures[signedTx.signatures.length - 1])); } ``` ```typescript Private Key theme={null} import { MessageV0, VersionedTransaction, PublicKey } from '@solana/web3.js'; import bs58 from 'bs58'; /** * Signs a Solana chain operation with a private key * * @param accountAddress - The address of the account to sign the chain operation * @param privateKey - The private key to sign the chain operation * @param chainOp - The chain operation to sign * @returns The signed chain operation */ export function signSolanaOperation( accountAddress: string, privateKey: string, chainOp: any, ): any { const msgBuffer = Buffer.from(chainOp.dataToSign, 'base64'); const message = MessageV0.deserialize(msgBuffer); const transaction = new VersionedTransaction(message); const decodedKey = bs58.decode(privateKey); transaction.sign([ { publicKey: new PublicKey(accountAddress), secretKey: Buffer.from(decodedKey), }, ]); const signature = bs58.encode(Buffer.from(transaction.signatures[transaction.signatures.length - 1])); return { ...chainOp, signature, }; } ``` ## Prerequisites Before integrating Solana with OneBalance:**API Key Configuration**: Custom API keys need Solana explicitly enabled. Contact [support@onebalance.io](mailto:support@onebalance.io) or use the public test key `42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11` for development. ## Integration Steps## Account Setup Patterns You can configure accounts in different ways based on your needs: ### Single Account Operations ```typescript theme={null} // Solana-only operation const quote = await fetch('/api/v3/quote', { method: 'POST', body: JSON.stringify({ from: { accounts: [{ type: "solana", accountAddress: "J5CCzBULFax899tcirb6wMbenQUd8whbaetG7EfSick5" }], asset: { assetId: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501" }, amount: "10000000" // 0.01 SOL }, to: { asset: { assetId: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" } } }) }); ``` ### Cross-Chain Operations ```typescript theme={null} // Cross-chain: Solana → EVM const quote = await fetch('/api/v3/quote', { method: 'POST', body: JSON.stringify({ from: { accounts: [{ type: "solana", accountAddress: "J5CCzBULFax899tcirb6wMbenQUd8whbaetG7EfSick5" }], asset: { assetId: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501" }, amount: "10000000" }, to: { asset: { assetId: "eip155:42161/erc20:0xaf88d065e77c8C2239327C5EDb3A432268e5831" }, account: "eip155:42161:0x895Cf62399bF1F8b88195E741b64278b41EB7F09" } }) }); ``` ### Unified Balance Operations ```typescript theme={null} // Use aggregated balances from both EVM and Solana accounts const quote = await fetch('/api/v3/quote', { method: 'POST', body: JSON.stringify({ from: { accounts: [ // Your EVM smart account { type: "kernel-v3.1-ecdsa", accountAddress: "0xd4f5A60c9b500f875ADf757BC3027A4424079E05", deploymentType: "ERC4337", signerAddress: "0x9b747cC14A5672a7166b4eccdc92d7F4003f8081" }, // Your Solana account { type: "solana", accountAddress: "J5CCzBULFax899tcirb6wMbenQUd8whbaetG7EfSick5" } ], asset: { assetId: "ob:usdc" }, // Aggregated USDC across all chains amount: "10000000" }, to: { asset: { assetId: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501" } } }) }); ``` ## Next Steps Switch from v1 to v3 endpoints to support Solana accounts: * [Get Quote v3](/api-reference/quotes/get-quote-v3) - `POST /api/v3/quote` * [Execute Quote v3](/api-reference/quotes/execute-quote-v3) - `POST /api/v3/quote/execute` * [Aggregated Balance v3](/api-reference/balances/aggregated-balance-v3) - `GET /api/v3/balances/aggregated-balance` * [Prepare Call Quote v3](/api-reference/quotes/prepare-call-quote-v3) - `POST /api/v3/quote/prepare-call-quote` * [Get Call Quote v3](/api-reference/quotes/get-call-quote-v3) - `POST /api/v3/quote/call-quote` Integrate Solana wallet providers like Phantom, Solflare, or others in your frontend. Update your code to use the accounts array format to support both EVM and Solana accounts. Add Solana-specific transaction signing using `@solana/web3.js` and `bs58` libraries. Test your integration with the API Get your first Solana cross-chain swap working in 5 minutes Complete code examples for swaps and cross-chain operations Token discovery, API keys, limitations, and common questions Use Solana assets to fund smart contract calls on EVM chains Common issues, solutions, and debugging tips for Solana integration For questions or support with Solana integration, use the **Intercom chat widget** in the bottom right corner for instant help, join our [Discord](https://discord.com/invite/vHkw7rpdT8), or reach out via [support](mailto:support@onebalance.io). # Solana Troubleshooting Source: https://docs.onebalance.io/guides/solana/troubleshooting Common issues and solutions when integrating Solana with OneBalance This guide covers common issues you might encounter when working with Solana and OneBalance, along with their solutions.All TypeScript examples assume you have the required dependencies installed: ```bash theme={null} npm install @solana/web3.js bs58 ``` ## Wallet Connection Issues ### Wallet Not Connected Properly ```typescript theme={null} // Check if wallet is connected and supports signing if (!wallet.connected || !wallet.signTransaction) { throw new Error('Wallet not properly connected'); } ``` **Solution:** Ensure the wallet is connected and has the required methods before attempting to sign transactions. ## Asset Configuration Issues ### Invalid Asset IDs ```typescript theme={null} // Ensure you're using correct Solana asset IDs const SOL_ASSET_ID = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501"; const USDC_SOLANA_ASSET_ID = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"; ``` **Solution:** Use the correct CAIP-19 format for Solana asset identifiers. Check the [Aggregated Assets API](/api-reference/assets/list) for supported assets. ### Signature Encoding Issues ```typescript theme={null} // Always use base58 encoding for Solana signatures const signature = bs58.encode(Buffer.from(signedTransaction.signatures[signedTransaction.signatures.length - 1])); // NOT: signedTransaction.signatures[0].toString() ``` **Solution:** Solana signatures must be base58-encoded, not converted to strings. ## API Key Configuration Issues ### Custom API Key Not Working with Solana ```json theme={null} { "error": "Chain not supported", "message": "Solana operations not enabled for this API key", "statusCode": 400 } ``` **Root Cause:** Solana support must be explicitly enabled for custom API keys. **Solutions:** 1. **Contact support** to enable Solana: * Email: [support@onebalance.io](mailto:support@onebalance.io) * Include your API key prefix (first 6 characters) * Request "Solana enablement" 2. **Use public API key for testing**: ```bash theme={null} # Public test key (development only) x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11 ``` 3. **Verify enablement** with a simple balance check: ```bash theme={null} curl -X GET 'https://be.onebalance.io/api/v3/balances/aggregated-balance?account=solana:J5CCzBULFax899tcirb6wMbenQUd8whbaetG7EfSick5&aggregatedAssetId=ob:sol' \ -H 'x-api-key: YOUR_API_KEY' ```**Public Key Limitations**: The public API key has rate limits and should only be used for development and testing, never in production. ## Common Operation Issues ### Insufficient Balance ```typescript theme={null} // Always check balances before creating quotes const balances = await fetch('/api/v3/balances/aggregated-balance?account=solana:J5CCzBULFax899tcirb6wMbenQUd8whbaetG7EfSick5', { headers: { 'x-api-key': 'YOUR_API_KEY' } }); const balanceData = await balances.json(); const availableSOL = balanceData.balanceByAggregatedAsset .find(asset => asset.aggregatedAssetId === 'ob:sol')?.balance; if (!availableSOL || parseInt(availableSOL) < requiredAmount) { throw new Error('Insufficient SOL balance for operation'); } ``` **Solution:** Always verify balance before operations to avoid quote failures. ### Invalid Account Address ```typescript theme={null} // Ensure Solana addresses are valid base58 import { PublicKey } from '@solana/web3.js'; try { new PublicKey(accountAddress); console.log('Valid Solana address'); } catch (error) { throw new Error('Invalid Solana address format'); } ``` **Solution:** Validate Solana addresses using `PublicKey` constructor before making API calls. ### Signing Failures ```typescript theme={null} // Handle wallet connection and signing errors try { const signature = await signSolanaOperation(dataToSign, wallet); } catch (error) { if (error.code === 4001) { console.error('User rejected signing'); } else if (error.message?.includes('wallet')) { console.error('Wallet connection issue:', error.message); } throw error; } ``` **Solution:** Handle common wallet errors including user rejection and connection issues. ## Contract Call Operation Errors ### Missing Delegation Signature (EIP-7702) ```json theme={null} { "error": "Validation failed", "message": "Delegation signature required for EIP-7702 account. Sign delegation object from prepare-call-quote response.", "statusCode": 400 } ``` **Solution:** Always sign the delegation object from `prepare-call-quote` before calling `call-quote` when using EIP-7702 accounts. ### Insufficient Balance for Contract Call ```typescript theme={null} // Verify you have enough of the fromAssetId before preparing const balanceResponse = await fetch( 'https://be.onebalance.io/api/v3/balances/aggregated-balance?aggregatedAssetId=ob:usdc,ob:sol&account=solana:YOUR_ACCOUNT', { headers: { 'x-api-key': 'YOUR_API_KEY' } } ); const balance = await balanceResponse.json(); const requiredAsset = balance.balanceByAggregatedAsset.find(b => b.aggregatedAssetId === 'ob:usdc'); if (parseInt(requiredAsset.balance) < requiredAmount) { throw new Error('Insufficient balance for contract call'); } ``` **Solution:** Check aggregated balances before attempting contract calls to ensure sufficient funds. ### Invalid Target Chain Configuration ```typescript theme={null} // Ensure target chain supports the contract address const supportedChains = ['eip155:1', 'eip155:42161', 'eip155:137']; // etc. if (!supportedChains.includes(targetChain)) { throw new Error(`Unsupported target chain: ${targetChain}`); } ``` **Solution:** Verify the target chain is supported and the contract address exists on that chain. ### Address Lookup Table Issues (SOL Operations) ```typescript theme={null} // SOL operations may fail if lookup tables are unavailable // This is handled automatically by OneBalance, but can cause delays console.log('Address lookup tables:', operation.addressLookupTableAddresses); ``` **Solution:** SOL operations with complex swaps may require address lookup tables. OneBalance handles this automatically, but it can cause additional processing time. ## Multi-Account Operation Debugging ### Account Type Mismatch ```typescript theme={null} // Verify account types match your setup const expectedTypes = ['solana', 'kernel-v3.3-ecdsa']; const actualTypes = accounts.map(acc => acc.type); if (!expectedTypes.every(type => actualTypes.includes(type))) { throw new Error('Missing required account types'); } ``` **Solution:** Ensure all required account types are included in multi-account operations. ### Signature Order Issues ```typescript theme={null} // Sign operations in the correct order const signedOps = await Promise.all( quote.originChainsOperations.map(async (op, index) => { console.log(`Signing operation ${index + 1} of type: ${op.type}`); return await signOperation(op); }) ); ``` **Solution:** Sign operations in the order they appear in the quote response and handle each operation type appropriately. ## Common Debugging Steps ### Check Transaction Status ```typescript theme={null} // Check quote execution status const statusResponse = await fetch( `https://be.onebalance.io/api/v3/status/get-execution-status?quoteId=${quoteId}`, { headers: { 'x-api-key': 'YOUR_API_KEY' } } ); const status = await statusResponse.json(); console.log('Status:', status.status.status); ``` ### Validate Account Address ```typescript theme={null} import { PublicKey } from '@solana/web3.js'; // Ensure Solana address is valid try { new PublicKey(accountAddress); console.log('Valid Solana address'); } catch (error) { throw new Error('Invalid Solana address format'); } ``` ## Getting HelpToken discovery, API keys, limitations, and common questions Complete code examples for all Solana operations Full API documentation with endpoint details For additional support, use the **Intercom chat widget** in the bottom right corner, join our [Discord](https://discord.com/invite/vHkw7rpdT8), or reach out via [support](mailto:support@onebalance.io). # Turnkey Integration Source: https://docs.onebalance.io/guides/turnkey-integration Learn how to integrate Turnkey with OneBalance API We're building detailed documentation for Turnkey integration with OneBalance. In the meantime, you can explore our open-source implementation example.Complete Turnkey integration example with OneBalance API We're actively working on detailed integration guides and tutorials. These will be added to the documentation soon. ## Need Help? * **[Discord Community](https://discord.com/invite/vHkw7rpdT8)**: Join our Discord for integration questions and support * **[API Reference](/api-reference)**: Full API documentation for implementation details * **[Support](mailto:support@onebalance.io)**: Direct support for enterprise integrations # OneBalance Documentation Source: https://docs.onebalance.io/index A documentation for OneBalance - guides, API reference, tutorials, and resources to build chain-abstracted applications with ease# How OneBalance Works Source: https://docs.onebalance.io/overview/how-onebalance-works Learn how the OneBalance toolkit integrates with your application to enable simple one-click interactions on any blockchain, action, or token. ## High-Level Overview of Toolkit IntegrationOneBalance Documentation
Explore our guides and examples to integrate the OneBalance Toolkit
Get started in minutes
Everything you need to integrate OneBalance into your application
**5 minutes** - Make your first chain-abstracted transaction **Step-by-step** - Integration examples and tutorials **Complete docs** - Explore all endpoints with examples Popular guides
Step-by-step tutorials to get you building quickly
Connect OneBalance with Privy for seamless wallet management Choose the right account configuration for your application Deep dive into how RL Service enable fast cross-chain execution Set up dynamic fees that work towards your goals Developer resources
Tools and resources to accelerate your development
**GitHub** - Deployable apps built with OneBalance **10+ chains** - Complete list of supported networks **Get help** - Common questions and solutions **Join us** - Connect with other developers **LLM-powered** - Build with AI agents and automation **What's new** - Latest features and updates ## Routing Overview In most cases, when using the Toolkit, applications specify a high-level intent, which is then translated into the actual payload on the Toolkit side and provided to the client ready for signing. Routing in a chain-abstracted intent includes: 1. Which chains are optimal to spend from, given the user's balance is distributed across chains 2. Whether bridging is required to perform an intent, or if it can be executed on the same chain 3. What is the minimal bridging amount required to perform an intent 4. Which solver/bundler provides the best execution price 5. Whether a paymaster is needed or if the user pays for gas from their own account
Note that some optimizations from the list are work in progress and will be delivered soon. The application does not need to worry about any of these - they are optimized on the Toolkit side. Read more on how transactions work [in this section](/concepts/transaction-lifecycle). ## Account Overview The OneBalance Toolkit is compatible with embedded signer providers such as Turnkey and Privy, as well as with applications that have direct access to signer keys (such as Web3 wallets and centralized exchanges). The Toolkit utilizes accounts that are compatible with gas abstraction on all chains, so users never have to worry about paying gas. Read more on particular account options in [this section](/concepts/accounts). ## Resource Lock & Fast Path The [Resource Lock](/concepts/resource-locks) concept, introduced by OneBalance in early 2024, has significantly enhanced multichain transaction efficiency. More specifically, it allows asynchronous execution of a multichain intent, separating intent fulfillment from settlement, which makes multichain transactions feel like same-chain transactions for the user. Applications enable Resource Locks via the Toolkit. Once enabled, all user transactions are co-signed (sequenced) by OneBalance, preventing double-spending during asynchronous multichain executions.For a detailed explanation of how Resource Locks work and their impact on cross-chain bridging speed, see our [Resource Locks concept page](/concepts/resource-locks). You can also find definitions of key terms in our [Glossary](/resources/glossary). ## Monetization From inception, applications can define flexible user fees per transaction, configurable by chain and transaction type. Fees collected from users are intended to: * Offset application-subsidized gas fees * Generate robust and flexible revenue streams Fees are settled with every user transaction as a transfer to the application wallet. Read more on how to configure fees [in this section](/concepts/fees). # Onboarding Checklist Source: https://docs.onebalance.io/overview/onboarding-checklist Step-by-step onboarding checklist for building with OneBalance, from your first API call to deploying an app in production.**Minimum 2 weeks notice required** for production launches. Contact [support@onebalance.io](mailto:support@onebalance.io) early in your development process. ## Custom API Key * **Request custom API key**: Contact [support@onebalance.io](mailto:support@onebalance.io) with the following information (minimum 2 weeks notice): 1. Company details (name, website link, and social media links) 2. Use case description (what you're building, target users, and key features) 3. Expected launch ETA (approximate date when you expect to go live) 4. Volume estimates (expected transaction volume per day/month) 5. Chain/asset preferences (select chains from [supported networks](/resources/supported-networks) and specify assets like USDC, USDT, ETH, SOL, or request custom assets) 6. Fee requirements (choose your [fee structure](/concepts/fees): percentage-based, flat per-chain, or combination) * **Set up team communication**: OneBalance team will create a Telegram or Slack channel between your team and ours for faster support and coordination * **Implement authentication**: Set up proper [API authentication](/api-reference/authentication) and secure key storageRate limits are configured by OneBalance based on your expected transaction volume. Contact us if you need adjustments. ## Development Phase * **Complete getting started tutorials**: [First API Call](/getting-started/first-api-call) and [Chain-Abstracted Swap](/getting-started/chain-abstracted-swap) * **Understand core concepts**: [What is OneBalance](/overview/what-is-onebalance) and [How it Works](/overview/how-onebalance-works) ## Technical Setup * **Choose networks and assets**: Select from [supported networks](/resources/supported-networks) and [available assets](/api-reference/assets/list) * **Select account type**: Choose [EIP-7702 delegated accounts](/guides/eip-7702/overview) (recommended) or [ERC-4337 smart accounts](/concepts/accounts) * **Pick wallet provider**: Privy, Turnkey, or custom solution * **Determine advanced needs**: Contract calls, multi-input transactions ([see guides](/guides/overview)) ## Testing & Launch Prep * **Test with custom API key**: Verify transactions, fee collection (if applicable), and all supported chains * **Validate user flows**: Test wallet integration and complete user journeys end-to-end * **Notify OneBalance for production readiness**: Contact [support@onebalance.io](mailto:support@onebalance.io) when testing is complete to make your custom API key production-ready ## Monetization Setup * **Plan fee structure**: Choose fixed per chain, percentage, or combination approach ([see fee options](/concepts/fees)) * **Set up Pimlico**: Get API key and Policy ID for gas sponsorship (required for fee collection) * **Prepare beneficiary addresses**: Where collected fees will be sentFee configuration is handled by OneBalance during API key setup. Without Pimlico, you'll use our sponsored gas (no fee collection). ## Launch * **Confirm launch date** with OneBalance team and prepare rollback plan * **Verify first transactions** work successfully and monitor for any issues * **Monitor ongoing performance** and collect user feedback ## Support & Resources ### Getting Help### Documentation Resources Use the Intercom chat widget for instant help or email [support@onebalance.io](mailto:support@onebalance.io) Connect with other developers building on OneBalance ### Enterprise Solutions Complete API documentation and examples Step-by-step integration tutorials Complete Privy wallet integration guide DeFi, NFT, and gaming integration examples Need custom rate limits, dedicated support, or enterprise features? Contact our sales team for tailored solutions. ## Important Reminders* **Minimum 2 weeks notice** required for production launches * Early communication ensures smooth launch support * Complex integrations may require additional time * **Pimlico setup required** to collect user fees * **You're responsible** for paymaster account funding * **Test fee collection** before production launch * **Never expose production API keys** in client-side code * **Implement proper authentication** server-side * **Set up proper error handling** and logging This checklist covers the essential steps for a successful OneBalance launch. Click the links for detailed documentation on each topic. Contact our support team early and often - we're here to help. # Welcome to OneBalance Toolkit Source: https://docs.onebalance.io/overview/what-is-onebalance The OneBalance Toolkit is the simplest and most reliable way to bring your users onchain. We offer an all-in-one API platform that lets you build one-click crypto products. ## Who is the Toolkit for? The Toolkit is ideal for apps that need fast, highly reliable onchain execution: swaps, yield, transfers, and arbitrary calldata are all supported out of the box.## What does the Toolkit enable? Developers recognize that our Toolkit brings the following benefits. Apps, Wallets, and launchpads offering a one-click user experience Stablecoin-powered apps accessing 14M+ tokens and monetizing tx fees Services that need to execute reliably on any chain with a single API call ## Why choose OneBalance? Our unique architecture-built from the node up and powered by resource locks-delivers capabilities that competitors cannot replicate. One-click UX - no bridges, chain selection, or gas management Single API endpoint for all blockchain execution, built for modern AI coding workflows Built-in monetization via seamless transaction fee capture ## Sample Use Cases * Token swapping across multiple chains, including memecoins * Peer-to-peer crypto payments * Lending, borrowing, and yield-optimization products enabling one-click interactions * Perpetual trading across multiple chains from the same UI or user balance * NFT purchases across multiple chains * Interacting with any Web3 protocol on any network with a chain-spread balance * Stablecoin abstraction into a single USD balance ## Toolkit Integrations Unified account balance for execution on all chains 14M+ tokens across EVMs, Solana, and Bitcoin 40% lower latency and greater reliability ## Feature List | Category | Feature | Description | OneBalance Toolkit | Reference | | :------------------------- | :-------------------------------------------- | :--------------------------------------------------------------------- | :----------------------------------- | :------------------------------------------------------------------------------------------------------- | | **Account & Identity** | Smart-contract accounts (ERC-4337 & EIP-7702) | Gas abstraction lets users ignore gas tokens | ✓ | [Account Prediction](/api-reference/account/predict-address) | | | Account creation | Lazy deployment - instant onboarding with no upfront costs | ✓ | [Account Management](/concepts/accounts) | | | Key management / custody | Secure private key management via enterprise-grade providers | ✓ (via Privy, Turnkey, Fireblocks) | Partner Integration | | | Authentication | Social login & biometric auth for seamless onboarding | ✓ (via Auth0, Stytch, Passkeys) | Partner Integration | | **On / Off-Ramps** | Fiat ↔ crypto / stablecoin ramps | Direct fiat conversion - users fund accounts with traditional payments | ✓ (via Ramp Network, Stripe, Bridge) | Partner Integration | | **Reads & Analytics** | Aggregated balances & portfolio | View unified balances across all chains instantly | ✓ | [Aggregated Balance](/api-reference/balances/aggregated-balance) | | | Token pricing & metadata | Real-time prices for accurate portfolio values | ✓ | [Assets API](/api-reference/assets/list) | | | Execution status | Real-time tracking across multiple chains and solvers | ✓ | [Quote Status](/api-reference/status/get-quote-status) | | | Account & transaction history | Access a complete, auditable history | ✓ | [Transaction History](/api-reference/status/get-transaction-history) | | **Writes / Execution** | Swaps & transfers | Swap or transfer tokens across chains with one call | ✓ | [Quote API](/api-reference/quotes/get-quote) | | | Optimal multi-/same-chain routing | Intelligent routing for lowest cost & fastest execution | ✓ | [Quote API](/api-reference/quotes/get-quote) Connect your preferred key management service like Privy, Turnkey, and Fireblocks Use your preferred onramps to deposit into your user accounts
[Call Quote](/api-reference/quotes/prepare-call-quote) | | | Same-chain optimization | Direct DEX routing for faster & cheaper same-chain swaps | ✓ | [Quote API](/api-reference/quotes/get-quote)
[Call Quote](/api-reference/quotes/prepare-call-quote) | | | Contract calls | Interact with smart contracts on any supported chain | ✓ | [Call Quote](/api-reference/quotes/prepare-call-quote) | | | Multi-input spending | Spend from multiple chains automatically | ✓ | [Aggregated Balance](/api-reference/balances/aggregated-balance) | | | Solana integration | Swaps and transfers are available | ✓ | [Quote API](/api-reference/quotes/get-quote) | | | Fast cross-chain (resource locks) | 40% faster transactions for major tokens (USDC, USDT, WETH, WBTC) | ✓ | [Execute Quote](/api-reference/quotes/execute-quote) | | **Revenue & Monetization** | Flexible fee structure | Configurable fees by chain to optimize revenue | ✓ | [Fee Configuration](/concepts/fees) | | **Coming Soon** | Token / LP / DeFi positions | Track complex DeFi positions for complete portfolio visibility | ✗ | Coming Soon | | | Exact input/output | Specify send or receive amounts - perfect for payments & trading | ✗ | Coming Soon | | | Bitcoin support | Native Bitcoin integration for true multi-chain strategies | ✗ | Coming Soon | | | Admin dashboard | Self-service API management without contacting support | ✗ | Coming Soon | | | Client SDKs | TypeScript/JavaScript SDKs to reduce integration time | ✗ | Coming Soon |**Need a specific feature?** Use the Intercom chat widget in the bottom right corner or contact our team at [support@onebalance.io](mailto:support@onebalance.io) to discuss custom implementations or priority development. # Explore All Products Source: https://docs.onebalance.io/products Explore all the OneBalance products from our Toolkit API, consumer app and our innovative cross-chain technology Resource Locks. Browse our products and solutions for building seamless chain-abstracted experiences. ## Browse by Product# Benchmarks & Stress Tests Source: https://docs.onebalance.io/resources/benchmarks Explore our regularly updated OneBalance Toolkit performance benchmarks and stress test results, for reliable cross-chain transactions. ## Performance Data **Coming Soon**: Performance benchmarks and stress test results will be published here. We're preparing detailed performance data including: * **Transaction throughput** across different networks * **API response times** under various load conditions * **Cross-chain execution latency** for different transaction types * **System reliability** metrics and uptime statistics ## Community UpdatesJoin our community for real-time updates and performance discussions For performance discussions specific to your use case, use the **Intercom chat widget** in the bottom right corner or contact us at [support@onebalance.io](mailto:support@onebalance.io). # Glossary Source: https://docs.onebalance.io/resources/glossary A complete glossary of OneBalance-specific and chain abstraction topics, with clear definitions to support developers using our documentation. ## Core Platform Concepts 1. **Chain Abstraction** - A vision for Web3 that significantly improves user experience by hiding blockchain complexities such as chains, bridging, and gas payments from end users. 2. **Multichain Intent** - a declarative intent formed from the user's desired action, where the execution may touch any number of chains. We consider N->1 intent executions only, meaning there might be N input chains and a single output chain. 3. **Solver** - We combine both of the following definitions under the same name: * Liquidity-based bridging entity that accepts assets on the source chain and delivers assets or executes on the destination chain. The solver may use their own inventory or deliver assets through an on-chain swap. * The entity that finds the optimal route and provides quotes for execution on the same chain. Think of individual CoW Swap solvers or routing aggregators. 4. **OneBackend** - Service under the OneBalance API that receives requests, determines the optimal route, and, if needed, forwards the request to the solver. It also determines which solver or solver network to route to. 5. **RL Service** - service that issues Resource Lock guarantees to the solver for Fast Path cross-chain execution. 6. **Resource Lock** - An off-chain partial lock of user funds preventing double-spending. Similar to a bank account, spent but not yet settled funds are locked on the account even though they are still physically present in the same balance. Learn more in our [Resource Locks](/concepts/resource-locks) guide. 7. **Fast Path** - Multichain intent execution type where the intent executes at the speed of the destination chain. Not all multichain intents are eligible for Fast Path execution. See [Transaction Lifecycle](/concepts/transaction-lifecycle) for execution details. 8. **Standard Path** - Multichain intent execution type where the intent executes at the speed of escrow transaction confirmation on the source chain plus destination chain confirmation time. See [Transaction Lifecycle](/concepts/transaction-lifecycle) for execution details. ## Account Architecture **Account/Gas Abstraction** - A blockchain technology that allows smart contracts to act as user accounts, enabling features like gas sponsorship, batch transactions, and custom authentication methods. See also: EIP-4337, EIP-7702. Learn more about OneBalance's implementation in [Account Models](/concepts/accounts). **SCA (Smart Contract Account)** - An alternative to EOA, a blockchain account controlled by smart contract logic rather than a private key, enabling advanced features like multi-signature, spending limits, and account recovery. See [Account Models](/concepts/accounts) for supported configurations. **Credible Accounts** - A new account model that extends external accounts, smart contracts, and stateful accounts with the ability to issue Resource Locks. Details in [Account Models](/concepts/accounts). **Kernel** - The smart contract account implementation used by OneBalance, based on ZeroDev's open-source framework. Two versions are currently supported: * **Kernel 3.1** - Compatible with both ECDSAValidator and RoleBasedValidator * **Kernel 3.3** - Optimized for EIP-7702 deployments with ECDSAValidator **ECDSAValidator** - A validator module that uses the standard Elliptic Curve Digital Signature Algorithm for authentication, compatible with most existing wallet infrastructures. **RoleBasedValidator** - A validator module that enables sophisticated access control with different permission levels for various operations, currently supporting Fast Path execution only. ## Standards & Protocols [**EIP-4337**](https://eips.ethereum.org/EIPS/eip-4337) - The Ethereum Improvement Proposal that defines the Account Abstraction standard, enabling smart contract accounts across EVM chains without consensus-layer changes. [**EIP-7702**](https://eips.ethereum.org/EIPS/eip-7702) - A newer Ethereum Improvement Proposal that provides an optimized approach for account abstraction with potential gas savings through delegated smart contract authentication. ## Infrastructure & Services **Bundler** - A service that aggregates multiple user operations into a single transaction for submission to the blockchain, optimizing gas costs and execution efficiency. **Paymaster** - A service that sponsors on-chain gas fees on behalf of users, enabling gasless transactions and improving user experience. **WaaS (Wallet-as-a-Service)** - Cloud-based wallet infrastructure that enables applications to offer embedded wallets without requiring users to manage private keys directly. Examples include Privy and Turnkey. ## Research & Community **Frontier Research** - An independent research and advisory group that publishes research, incubates products, and organizes community events, including the Cake Working Group. **Fellowship of the OneBalance** - An industry group united under the common mission to implement the Credible Stack and bring Credible Accounts to Web3. # Community & Support Source: https://docs.onebalance.io/resources/support How to get support and connect with other builders using the OneBalance Toolkit building chain abstracted apps. ## Get Help**Quick Help**: Use the Intercom chat widget in the bottom right corner for instant support from our team. ## Community Click the chat widget in the bottom right for instant help Technical questions and integration help via email ## Resources Join our developer community for discussions and updates Follow us for announcements and ecosystem news ## Research & Learning Open source tools and examples Latest updates and technical insights Technical research and chain abstraction discussions Latest features and updates For urgent production issues, use the **Intercom chat widget** in the bottom right corner for fastest response, or email us directly at [support@onebalance.io](mailto:support@onebalance.io). # Supported Networks Source: https://docs.onebalance.io/resources/supported-networks A full list of blockchains supported by the OneBalance Toolkit, including all major EVM networks, Solana, and soon Bitcoin. ## Chain Support OneBalance is constantly expanding network support. If you need support for a network not listed here, please use the **Intercom chat widget** in the bottom right corner or [contact our team](mailto:support@onebalance.io). | Chain | ID | Status | | :------------------------------------------------ | --------------------------------------- | :---------------------- | || eip155:1 | ✅ | |Ethereum Mainnet
| eip155:10 | ✅ | |Optimism
| eip155:42161 | ✅ | |Arbitrum
| eip155:137 | ✅ | |Polygon
| eip155:8453 | ✅ | |Base
| eip155:59144 | ✅ | |Linea
| eip155:43114 | ✅ | |Avalanche
| eip155:56 | ✅ | |BNB Smart Chain (BSC)
| eip155:81457 | ✅ | |Blast
| eip155:80094 | ✅ | |Berachain
| eip155:130 | ✅ | |Unichain
| eip155:999 | ✅ | |HyperEVM
| solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp | ✅ Beta | |Solana
| - | TBD | | Other EVMs | - | On demand | | Other Chains | - | On demand, case by case | ## Network References Each network in OneBalance is identified using the [CAIP-2](https://chainagnostic.org/CAIPs/caip-2) chain ID format: * Format: `{namespace}:{chainId}` * Example: `eip155:1` for Ethereum Mainnet or `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp` for Solana Mainnet When using the OneBalance API, you'll reference these networks using their CAIP-2 identifiers. ```javascript CAIP-2 identifier theme={null} // Example of referencing Ethereum in the API const ethereumChain = 'eip155:1'; ```Bitcoin