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
Use LLMs in your OneBalance integration workflow.
You can use large language models (LLMs) to assist in the building of OneBalance integrations. We provide a set of tools and best practices if you use LLMs during development.
## Plain text docs
You can access all of our documentation as plain text markdown files by adding `.md` to the end of any URL. For example, you can find the plain text version of this page itself at [/ai/building-with-llms.md](/ai/building-with-llms.md).
This helps AI tools and agents consume our content and allows you to copy and paste the entire contents of a doc into an LLM. This format is preferable to scraping or copying from our HTML and JavaScript-rendered pages because:
* Plain text contains fewer formatting tokens.
* Content that isn't rendered in the default view (for example, it's hidden in a tab) of a given page is rendered in the plain text version.
* LLMs can parse and understand markdown hierarchy.
We also host the [/llms.txt](https://docs.onebalance.io/llms.txt) and [/llms-full.txt](https://docs.onebalance.io/llms-full.txt) files which instructs AI tools and agents how to retrieve the plain text versions of our pages. The `/llms.txt` file is an [emerging standard](https://llmstxt.org) for making websites and content more accessible to LLMs.
# MCP Server Setup
Source: https://docs.onebalance.io/ai/mcp-server-setup
Setting up a Model Context Protocol server for advanced agent interactions
## MCP Server Configuration
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' ? 'ds:usdc' : 'ds:eth', }, amount, }, to: { asset: { assetId: swapDirection === 'USDC_TO_ETH' ? 'ds:eth' : 'ds: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 === 'ds:usdc' ); // Find ETH in the balance data const ethAsset = balanceData.balanceByAggregatedAsset.find( (asset: any) => asset.aggregatedAssetId === 'ds: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' ? 'ds:usdc' : 'ds:eth', }, amount, }, to: { asset: { assetId: swapDirection === 'USDC_TO_ETH' ? 'ds:eth' : 'ds: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 // 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 // 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 // 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 === 'ds:usdc' ); // Find ETH in the balance data const ethAsset = balanceData.balanceByAggregatedAsset.find( (asset: any) => asset.aggregatedAssetId === 'ds: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. **Comprehensive 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 Set up a smart contract account for chain-abstracted 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 // 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 // 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 // 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 === 'ds:usdc' ); // Find ETH in the balance data const ethAsset = balanceData.balanceByAggregatedAsset.find( (asset: any) => asset.aggregatedAssetId === 'ds: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 "ds:" prefix for aggregated asset identifiers (e.g., `ds:usdc`, `ds:eth`). This prefix indicates that these are aggregated assets that represent the same token across multiple chains. For example, `ds: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 Build chain-abstracted experiences in minutes, not months export const LoomVideo = ({loomUrl}) => { return; }; 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](https://github.com/OneBalance-io/onebalance-privy-demo) to quickly get started, or try the [live demo app](https://onebalance-privy-demo.vercel.app). ## What You'll Build By 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 Integrate OneBalance with Privy for a seamless 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 # 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: `f9703eaqsbma20tmtphg2jirm0hk8z8v2hkodrfrvhfm6ziesi7p38u991bnih5f`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 # 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 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 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 # .env NEXT_PUBLIC_PRIVY_APP_ID=cmb94tjcj001xle0mdeerz9vp # For quickstart only; use your own in production NEXT_PUBLIC_ONEBALANCE_API_KEY=f9703eaqsbma20tmtphg2jirm0hk8z8v2hkodrfrvhfm6ziesi7p38u991bnih5f # For quickstart only ``` Alternatively, you can create the `.env` file using command line: ```bash create-env.sh # Create .env file touch .env # Add environment variables echo "NEXT_PUBLIC_PRIVY_APP_ID=cmb94tjcj001xle0mdeerz9vp" >> .env echo "NEXT_PUBLIC_ONEBALANCE_API_KEY=f9703eaqsbma20tmtphg2jirm0hk8z8v2hkodrfrvhfm6ziesi7p38u991bnih5f" >> .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/route.ts`: ```typescript route.ts // src/app/api/route.ts import { NextRequest, NextResponse } from 'next/server'; // OneBalance API base URL and API key const API_BASE_URL = 'https://be.onebalance.io/api'; const API_KEY = process.env.NEXT_PUBLIC_ONEBALANCE_API_KEY; export async function GET(request: NextRequest) { const searchParams = request.nextUrl.searchParams; const endpoint = searchParams.get('endpoint'); if (!endpoint) { return NextResponse.json({ error: 'Endpoint is required' }, { status: 400 }); } try { const response = await fetch(`${API_BASE_URL}${endpoint}`, { 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) { const searchParams = request.nextUrl.searchParams; const endpoint = searchParams.get('endpoint'); if (!endpoint) { return NextResponse.json({ error: 'Endpoint is required' }, { status: 400 }); } try { const body = await request.json(); const response = await fetch(`${API_BASE_URL}${endpoint}`, { 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 // 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('?endpoint=/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(`?endpoint=/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('?endpoint=/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('?endpoint=/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(`?endpoint=/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 // 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 // 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 // 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 // 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 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 a Chain-Abstracted Swap with OneBalance & Privy 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. ## 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 executionComplete demo available at [onebalance-chain-abstracted-swap.vercel.app](https://onebalance-chain-abstracted-swap.vercel.app) and source code on [GitHub](https://github.com/dzimiks/onebalance-chain-abstracted-swap). ## 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 pnpm dlx shadcn@latest init ``` For wallet connection and transaction signing, we will use [Privy](https://www.privy.io): ```bash terminal pnpm add @tanstack/react-query wagmi viem @privy-io/react-auth ``` ### Environment Setup Make sure to copy `.env.example` into `.env`: ```jsx .env NEXT_PUBLIC_API_URL=https://be.onebalance.io NEXT_PUBLIC_API_KEY= NEXT_PUBLIC_PRIVY_APP_ID= ```Get your OneBalance API key from [onebalance.io](https://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 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 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., "ds:usdc" amount: string; // Token amount in wei }; to: { asset: { assetId: string }; // e.g., "ds: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 // 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 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 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 // 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 // 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 // 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 ### Modular API Structure Organize API calls into logical modules: ```typescript // Organized API structure export const oneBalanceApi = { // Account management predictAddress: (sessionAddress, adminAddress) => {...}, // Quote operations getQuote: (quoteRequest) => {...}, executeQuote: (signedQuote) => {...}, getQuoteStatus: (quoteId) => {...}, // Asset & balance data getSupportedAssets: () => {...}, getAggregatedBalance: (address) => {...}, }; ``` ### Proxy Pattern for CORS Use Next.js API routes to handle CORS and secure API keys: ```typescript app/api/[...path]/route.ts 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 Complex multi-step operations, error recovery, and production optimization techniques 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 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: 'ds: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 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: 'ds: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 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: 'ds: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 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 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 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 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 How OneBalance automatically handles token approvals for secure, gas-efficient transactions 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 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 // β 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 // 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 // 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 // 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 // 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 // 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 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 Production-ready examples for common DeFi, NFT, and gaming use cases Real-world examples of 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 { "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 curl --request POST \ --url https://be.onebalance.io/api/quotes/prepare-call-quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: f9703eaqsbma20tmtphg2jirm0hk8z8v2hkodrfrvhfm6ziesi7p38u991bnih5f' \ --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" } ] }' ``` After signing the typed data from the prepare response, execute the quote:```bash execute-uniswap-swap.sh curl --request POST \ --url https://be.onebalance.io/api/quotes/call-quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: f9703eaqsbma20tmtphg2jirm0hk8z8v2hkodrfrvhfm6ziesi7p38u991bnih5f' \ --data '{ "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "chainOperation": { "userOp": { "sender": "0xe20295ec513def805d9c3083b0c8eab64692d764", "nonce": "0x1000000000000000000000000000000000000000000000000000000000000000", "factory": "0xd703aae79538628d27099b8c4f621be4ccd142d5", "factoryData": "0x...", "callData": "0xe9ae5c53...", "callGasLimit": "500000", "verificationGasLimit": "1000000", "preVerificationGas": "21000", "maxFeePerGas": "1000000000", "maxPriorityFeePerGas": "1000000000", "paymaster": "0xa784e6482bd5edbfe5991b18cbd545ebd46e1cc4", "paymasterVerificationGasLimit": "50000", "paymasterPostOpGasLimit": "0", "paymasterData": "0x", "signature": "0x1234...your_signature_here" }, "typedDataToSign": { "domain": { "name": "RoleBasedECDSAValidator", "version": "1.4.3", "chainId": 8453, "verifyingContract": "0xA24bD06230f3F54e5bf266AE7A41750eE3b789FA" }, "types": { "Approve": [ { "name": "callDataAndNonceHash", "type": "bytes32" } ] }, "primaryType": "Approve", "message": { "callDataAndNonceHash": "0xaab2ddac63e4a7e25b7d339cacdff2f9280025cef27699ffe5f5e9e9625b15eb" } }, "assetType": "eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "amount": "1000000" }, "tamperProofSignature": "0x5678...tamper_proof_signature", "fromAggregatedAssetId": "ds:usdc" }' ``` ### Aave V3 Supply Supply USDC to Aave V3 on Base to earn yield.```json aave-supply-prepare.json { "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 curl --request POST \ --url https://be.onebalance.io/api/quotes/prepare-call-quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: f9703eaqsbma20tmtphg2jirm0hk8z8v2hkodrfrvhfm6ziesi7p38u991bnih5f' \ --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 { "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 curl --request POST \ --url https://be.onebalance.io/api/quotes/prepare-call-quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: f9703eaqsbma20tmtphg2jirm0hk8z8v2hkodrfrvhfm6ziesi7p38u991bnih5f' \ --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 { "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 curl --request POST \ --url https://be.onebalance.io/api/quotes/prepare-call-quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: f9703eaqsbma20tmtphg2jirm0hk8z8v2hkodrfrvhfm6ziesi7p38u991bnih5f' \ --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 { "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 curl --request POST \ --url https://be.onebalance.io/api/quotes/prepare-call-quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: f9703eaqsbma20tmtphg2jirm0hk8z8v2hkodrfrvhfm6ziesi7p38u991bnih5f' \ --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 { "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 curl --request POST \ --url https://be.onebalance.io/api/quotes/prepare-call-quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: f9703eaqsbma20tmtphg2jirm0hk8z8v2hkodrfrvhfm6ziesi7p38u991bnih5f' \ --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 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 on testnet 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 prepare, sign and submit arbitrary calldata across chains using the OneBalance API This guide walks you through implementing 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 // 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 mkdir onebalance-calldata && cd onebalance-calldata pnpm init ``` Install dependencies: ```bash pnpm add viem axios pnpm add -D typescript @types/node ts-node tslib ``` Now, let's create and configure `tsconfig.json`: ```bash touch tsconfig.json ``` ```json tsconfig.json { "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 touch index.ts ``` ```typescript index.ts 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.staging.onebalance.io'; // Note: Using the production API endpoint will produce a different predicted address const PUBLIC_API_KEY = 'f9703eaqsbma20tmtphg2jirm0hk8z8v2hkodrfrvhfm6ziesi7p38u991bnih5f'; // 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 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 === 'ds:usdc'); } ``` ### Step 3: Add TypeScript Interfaces Add comprehensive TypeScript interfaces for type safety: ```typescript index.ts 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 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 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 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 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: 'ds: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 { "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 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/contract-calls-quickstart). ```typescript 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.staging.onebalance.io'; // Note: Using the production API endpoint will produce a different predicted address const PUBLIC_API_KEY = 'f9703eaqsbma20tmtphg2jirm0hk8z8v2hkodrfrvhfm6ziesi7p38u991bnih5f'; // 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 === 'ds: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: 'ds: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 Execute arbitrary smart contract calls across multiple chains with aggregated 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 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": [] }' ``` ## 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 Operations** - Sign the generated user operations with your wallet. This happens client-side using standard EIP-712 typed data signatures. 3. **Execute** - OneBalance coordinates cross-chain execution and settlement, handling token transfers, bridging, and smart contract calls automatically. ```mermaid flowchart LR A[Prepare Quote] --> B[Sign Operations] --> C[Execute Quote] ``` ### Detailed Flow For those interested in the technical details, here's the complete execution flow: ```mermaid 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 user operations
β’ Calculate routing OneBalance API-->>User: Unsigned operations + typed data Note over User, Target Chain: Phase 2: Signing User->>User: 2. Sign operations Note right of User: β’ Sign with session key
β’ Multiple signatures if needed User->>OneBalance API: Submit signed operations Note over User, Target Chain: Phase 3: Execution 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) & [POST /quotes/call-quote](/api-reference/quotes/get-call-quote) - Analyzes your calls and generates user operations * **Phase 2: Sign** - Client-side signing with your wallet or library * **Phase 3: Execute** - [POST /quotes/execute-quote](/api-reference/quotes/execute-quote) - Submits signed operations 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 Common issues, debugging techniques, and solutions for contract call operations 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 // β 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 // β 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 // β 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 // β 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 // 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 // β Wrong formats const wrong = [ 'usdc', 'USDC', 'erc20:0x...', 'ds:USDC' ]; // β Correct format const correct = 'ds:usdc'; ``` **Chain ID format errors:** ```typescript chain-validation.ts // β 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 // 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 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 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 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 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 // 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 // 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 Comprehensive logging helps identify issues quickly during development. This debug function provides structured output for all request parameters. ```typescript debug-helpers.ts // Comprehensive 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 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 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. # Guides Source: https://docs.onebalance.io/guides/overview Learn how to use OneBalance by following our official guides and tutorials ## 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. ### Core Operations### Integration Tutorials * **[Chain-Abstracted Swaps with Privy](/guides/chain-abstracted-swap-with-privy)**: Build token swap interfaces that work across multiple blockchains * **[Contract Call Examples](/guides/contract-calls/examples)**: Production-ready examples for DeFi, NFT, and gaming use cases * **[Quote Examples](/guides/quotes/examples)**: Overview of quotes and how to use them ## When to Use What Get started with OneBalance quotes for asset transfers and swaps across multiple chains. Execute smart contract interactions using your aggregated balance across blockchains. * Token transfers between addresses * Cross-chain swaps (ETH β USDC) * Simple asset movements * Basic DeFi operations **API Reference:** [/v1/quote](/api-reference/quotes/get-quote) * NFT minting and trading * DeFi protocol interactions (Uniswap, Aave, etc.) * Multi-step operations * Custom smart contract logic * Gaming and DAO interactions **API Reference:** [/quotes/prepare-call-quote](/api-reference/quotes/prepare-call-quote) and [/quotes/call-quote](/api-reference/quotes/get-call-quote) ## Getting StartedStart 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. No guesswork required. ## Need Help? * **[API Reference](/api-reference)**: Detailed documentation for all OneBalance endpoints and schemas * **[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)**: Complete source code for tutorial projects # Quote Examples Source: https://docs.onebalance.io/guides/quotes/examples Examples of quote requests for chain-abstracted operations 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.For interactive testing, use the [API Reference](/api-reference/quotes/get-quote) playground. **Account Types**: Learn more about the differences between Role-Based and Basic accounts in our [Account Models](/concepts/accounts) guide. Role-Based accounts offer enhanced security features but consume slightly more gas, while Basic accounts provide simpler setup and integration. ## 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 { "from": { "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "ds:eth" }, "amount": "1000000000000000" }, "to": { "asset": { "assetId": "ds:usdc" } } } ``` ```bash cURL curl --request POST \ --url https://be.onebalance.io/api/v1/quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: f9703eaqsbma20tmtphg2jirm0hk8z8v2hkodrfrvhfm6ziesi7p38u991bnih5f' \ --data '{ "from": { "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "ds:eth" }, "amount": "1000000000000000" }, "to": { "asset": { "assetId": "ds:usdc" } } }' ``` ```json JSON { "from": { "account": { "type": "kernel-v3.1-ecdsa", "signerAddress": "0x5d6fb4eb211a6a2e406a1111b54d26c534753c8e", "accountAddress": "0xb7bc0d7baf6761c302ff6772dfd8f9e22ec706e7" }, "asset": { "assetId": "ds:eth" }, "amount": "1000000000000000" }, "to": { "asset": { "assetId": "ds:usdc" } } } ``` ```bash cURL curl --request POST \ --url https://be.onebalance.io/api/v1/quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: f9703eaqsbma20tmtphg2jirm0hk8z8v2hkodrfrvhfm6ziesi7p38u991bnih5f' \ --data '{ "from": { "account": { "type": "kernel-v3.1-ecdsa", "signerAddress": "0x5d6fb4eb211a6a2e406a1111b54d26c534753c8e", "accountAddress": "0xb7bc0d7baf6761c302ff6772dfd8f9e22ec706e7" }, "asset": { "assetId": "ds:eth" }, "amount": "1000000000000000" }, "to": { "asset": { "assetId": "ds:usdc" } } }' ``` ## Chain-Specific Token to Chain-Specific Token Swap Direct swap between tokens on specific chains without aggregation. ```json JSON { "from": { "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "ds:usdc" }, "amount": "1000000" }, "to": { "asset": { "assetId": "eip155:8453/erc20:0x532f27101965dd16442e59d40670faf5ebb142e4" }, "account": "eip155:8453:0x742d35Cc6634C0532925a3b844Bc454e4438f44e" } } ``` ```bash cURL curl --request POST \ --url https://be.onebalance.io/api/v1/quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: f9703eaqsbma20tmtphg2jirm0hk8z8v2hkodrfrvhfm6ziesi7p38u991bnih5f' \ --data '{ "from": { "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "ds:usdc" }, "amount": "1000000" }, "to": { "asset": { "assetId": "eip155:8453/erc20:0x532f27101965dd16442e59d40670faf5ebb142e4" }, "account": "eip155:8453:0x742d35Cc6634C0532925a3b844Bc454e4438f44e" } }' ``` ```json JSON { "from": { "account": { "type": "kernel-v3.1-ecdsa", "signerAddress": "0x5d6fb4eb211a6a2e406a1111b54d26c534753c8e", "accountAddress": "0xb7bc0d7baf6761c302ff6772dfd8f9e22ec706e7" }, "asset": { "assetId": "ds:usdc" }, "amount": "1000000" }, "to": { "asset": { "assetId": "eip155:8453/erc20:0x532f27101965dd16442e59d40670faf5ebb142e4" }, "account": "eip155:8453:0x742d35Cc6634C0532925a3b844Bc454e4438f44e" } } ``` ```bash cURL curl --request POST \ --url https://be.onebalance.io/api/v1/quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: f9703eaqsbma20tmtphg2jirm0hk8z8v2hkodrfrvhfm6ziesi7p38u991bnih5f' \ --data '{ "from": { "account": { "type": "kernel-v3.1-ecdsa", "signerAddress": "0x5d6fb4eb211a6a2e406a1111b54d26c534753c8e", "accountAddress": "0xb7bc0d7baf6761c302ff6772dfd8f9e22ec706e7" }, "asset": { "assetId": "ds: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 { "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 curl --request POST \ --url https://be.onebalance.io/api/v1/quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: f9703eaqsbma20tmtphg2jirm0hk8z8v2hkodrfrvhfm6ziesi7p38u991bnih5f' \ --data '{ "from": { "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "eip155:8453/erc20:0x2da56acb9ea78330f947bd57c54119debda7af71" }, "amount": "1000000000000000000000000" }, "to": { "asset": { "assetId": "eip155:8453/erc20:0x9a26f5433671751c3276a065f57e5a02d2817973" } } }' ``` ```json JSON { "from": { "account": { "type": "kernel-v3.1-ecdsa", "signerAddress": "0x5d6fb4eb211a6a2e406a1111b54d26c534753c8e", "accountAddress": "0xb7bc0d7baf6761c302ff6772dfd8f9e22ec706e7" }, "asset": { "assetId": "eip155:8453/erc20:0x2da56acb9ea78330f947bd57c54119debda7af71" }, "amount": "1000000000000000000000000" }, "to": { "asset": { "assetId": "eip155:8453/erc20:0x9a26f5433671751c3276a065f57e5a02d2817973" } } } ``` ```bash cURL curl --request POST \ --url https://be.onebalance.io/api/v1/quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: f9703eaqsbma20tmtphg2jirm0hk8z8v2hkodrfrvhfm6ziesi7p38u991bnih5f' \ --data '{ "from": { "account": { "type": "kernel-v3.1-ecdsa", "signerAddress": "0x5d6fb4eb211a6a2e406a1111b54d26c534753c8e", "accountAddress": "0xb7bc0d7baf6761c302ff6772dfd8f9e22ec706e7" }, "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 { "from": { "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "eip155:8453/erc20:0x2da56acb9ea78330f947bd57c54119debda7af71" }, "amount": "1000000000000000000000000" }, "to": { "asset": { "assetId": "ds:usdc" } } } ``` ```bash cURL curl --request POST \ --url https://be.onebalance.io/api/v1/quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: f9703eaqsbma20tmtphg2jirm0hk8z8v2hkodrfrvhfm6ziesi7p38u991bnih5f' \ --data '{ "from": { "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "eip155:8453/erc20:0x2da56acb9ea78330f947bd57c54119debda7af71" }, "amount": "1000000000000000000000000" }, "to": { "asset": { "assetId": "ds:usdc" } } }' ``` ```json JSON { "from": { "account": { "type": "kernel-v3.1-ecdsa", "signerAddress": "0x5d6fb4eb211a6a2e406a1111b54d26c534753c8e", "accountAddress": "0xb7bc0d7baf6761c302ff6772dfd8f9e22ec706e7" }, "asset": { "assetId": "eip155:8453/erc20:0x2da56acb9ea78330f947bd57c54119debda7af71" }, "amount": "1000000000000000000000000" }, "to": { "asset": { "assetId": "ds:usdc" } } } ``` ```bash cURL curl --request POST \ --url https://be.onebalance.io/api/v1/quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: f9703eaqsbma20tmtphg2jirm0hk8z8v2hkodrfrvhfm6ziesi7p38u991bnih5f' \ --data '{ "from": { "account": { "type": "kernel-v3.1-ecdsa", "signerAddress": "0x5d6fb4eb211a6a2e406a1111b54d26c534753c8e", "accountAddress": "0xb7bc0d7baf6761c302ff6772dfd8f9e22ec706e7" }, "asset": { "assetId": "eip155:8453/erc20:0x2da56acb9ea78330f947bd57c54119debda7af71" }, "amount": "1000000000000000000000000" }, "to": { "asset": { "assetId": "ds:usdc" } } }' ``` # Quotes Overview Source: https://docs.onebalance.io/guides/quotes/overview Understanding and working with transaction quotes in OneBalance 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 { "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 curl --request POST \ --url https://be.onebalance.io/api/v1/quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: f9703eaqsbma20tmtphg2jirm0hk8z8v2hkodrfrvhfm6ziesi7p38u991bnih5f' \ --data '{ "from": { "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "eip155:42161/erc20:0xaf88d065e77c8cc2239327c5edb3a432268e5831" }, "amount": "1000000" }, "to": { "asset": { "assetId": "eip155:10/erc20:0x0b2c639c533813f4aa9d7837caf62653d097ff85" } } }' ``` ```json JSON { "from": { "account": { "type": "kernel-v3.1-ecdsa", "signerAddress": "0x5d6fb4eb211a6a2e406a1111b54d26c534753c8e", "accountAddress": "0xb7bc0d7baf6761c302ff6772dfd8f9e22ec706e7" }, "asset": { "assetId": "eip155:42161/erc20:0xaf88d065e77c8cc2239327c5edb3a432268e5831" }, "amount": "1000000" }, "to": { "asset": { "assetId": "eip155:10/erc20:0x0b2c639c533813f4aa9d7837caf62653d097ff85" } } } ``` ```bash cURL curl --request POST \ --url https://be.onebalance.io/api/v1/quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: f9703eaqsbma20tmtphg2jirm0hk8z8v2hkodrfrvhfm6ziesi7p38u991bnih5f' \ --data '{ "from": { "account": { "type": "kernel-v3.1-ecdsa", "signerAddress": "0x5d6fb4eb211a6a2e406a1111b54d26c534753c8e", "accountAddress": "0xb7bc0d7baf6761c302ff6772dfd8f9e22ec706e7" }, "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 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": "ds:eth" }, "amount": "1000000000000000000" }, "to": { "asset": { "assetId": "ds:usdc" } } }' ``` ### Contract Call Quotes For arbitrary smart contract interactions with automatic token routing. ```bash Terminal 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. # How OneBalance Works Source: https://docs.onebalance.io/overview/how-onebalance-works Learn how OneBalance Toolkit integrates with your application ## High-Level Overview of Toolkit Integration  ## 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 accountNote 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 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. ## 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 Your step-by-step guide to building with OneBalance - from first API call to production deploymentThis checklist will take you from zero to production-ready in under an hour. Each step builds on the previous one, so we recommend following them in order. ## Step 1: Understand OneBalance Get familiar with how OneBalance simplifies blockchain development through chain abstraction.## Step 2: Build Your First Chain Abstraction PoC (15 minutes) Experience the power of OneBalance by building a working proof of concept. ### Quickstart Path Learn the core concepts and benefits of chain abstraction Understand the technical architecture behind OneBalance ### Explore Code Examples Follow our [setup guide](/getting-started/setup) to configure your development environment Test the waters with a simple [balance query](/getting-started/first-api-call) Build a complete [chain-abstracted swap](/getting-started/chain-abstracted-swap) in minutes Complete tutorial with Privy wallet integration Ready-to-use code snippets for common operations Explore all available endpoints and their parameters ## Step 3: Build Your Production App Choose your stack and configure OneBalance for your specific use case. ### Choose Your Wallet Provider### Configure Advanced Features Recommended for most applications Great for enterprise solutions Build your own wallet integration ### Integration Patterns Set up smart account management for your users Configure monetization strategies and gas sponsorship for your app Review supported networks and choose the chains for your app Explore supported assets and configure which tokens your app will support ## Step 4: Deploy to Production Launch your app with confidence using OneBalance's production infrastructure. ### Pre-Production Requirements Execute arbitrary smart contract interactions Understand how transactions flow through OneBalance **Production Access Required**: Contact our team at [support@onebalance.io](mailto:support@onebalance.io) to receive your production API keys and deployment guidance. Need custom rate limits, dedicated support, or enterprise features? Talk to our sales team about tailored solutions. #### API Keys & Authentication * Request production API keys from the OneBalance team * Implement proper [authentication](/api-reference/authentication) in your app * Contact us if you need higher [rate limits](/api-reference/rate-limits) #### Gas Abstraction Setup * Configure Paymaster settings for gas sponsorship * Set up fee structures and limits * Test gas abstraction in staging environment #### Monitoring * Implement transaction status monitoring using our [status endpoints](/api-reference/status/get-quote-status) * Set up error handling and logging ### Launch Benefits## Resources & Support Users interact with one balance across all chains Optimized routing and gas management Reduced friction drives higher conversion Get technical help and production access from our engineering team Connect with other developers building on OneBalance We're happy to introduce you to our ecosystem partners for complementary services like analytics, security audits, and infrastructure providers. # Supported Networks Source: https://docs.onebalance.io/overview/supported-networks A list of all networks supported by OneBalance ## Chain Support OneBalance is constantly expanding network support. If you need support for a network not listed here, please [contact our team](mailto:support@onebalance.io). | Chain | ID | Status | | :---------------- | ------------ | :---------------------- | | Mainnet (EVM) | eip155:1 | β | | Optimism (EVM) | eip155:10 | β | | Arbitrum (EVM) | eip155:42161 | β | | Polygon (EVM) | eip155:137 | β | | Base (EVM) | eip155:8453 | β | | Linea (EVM) | eip155:59144 | β | | Avalanche (EVM) | eip155:43114 | β | | BSC (EVM) | eip155:56 | β | | Blast (EVM) | eip155:81457 | β | | Bera (EVM) | eip155:80094 | β | | Solana (non-EVM) | - | Early Q3 | | Bitcoin (non-EVM) | - | Mid Q3 | | 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: `eip155:{chainId}` * Example: `eip155:1` for Ethereum Mainnet When using the OneBalance API, you'll reference these networks using their CAIP-2 identifiers. ```javascript CAIP-2 identifier // Example of referencing Ethereum in the API const ethereumChain = 'eip155:1'; ``` # 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 our Toolkit brings the following benefits. Wallets, exchanges, 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 meme coins * 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 30% lower latency and greater reliability 14M+ tokens across EVMs, Solana, and Bitcoin ## Feature List | Category | Feature | Description | OneBalance Toolkit | Reference | | :------------------------- | :-------------------------------------------- | :--------------------------------------------------------------------- | :----------------------------------- | :------------------------------------------------------------------- | | **Account & Identity** | Smart-contract accounts (ERC-4337 & EIP-7702) | Gas abstraction accounts - users never worry about 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** | Token / LP / DeFi positions | Track complex DeFi positions for complete portfolio visibility | β | [Assets API](/api-reference/assets/list) | | | Aggregated balances & portfolio | Unified balance view across all chains without complexity | β | [Aggregated Balance](/api-reference/balances/aggregated-balance) | | | Token pricing & metadata | Real-time prices for accurate portfolio values | β | [Assets API](/api-reference/assets/list) | | | Deposit confirmation & intent status | Real-time tracking across multiple chains and solvers | β | [Quote Status](/api-reference/status/get-quote-status) | | | Account & transaction history | Complete audit trail for comprehensive user dashboards | β | [Transaction History](/api-reference/status/get-transaction-history) | | **Writes / Execution** | Swaps & transfers | Cross-chain token operations - core chain abstraction | β | [Quote API](/api-reference/quotes/get-quote) | | | Contract calls | Execute smart contracts across chains for complex DeFi strategies | β | [Call Quote](/api-reference/quotes/prepare-call-quote) | | | Multi-input spending | Auto-spend from multiple chains using entire balance | β | [Aggregated Balance](/api-reference/balances/aggregated-balance) | | | Exact input/output | Specify send or receive amount - perfect for payments & trading | β | [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) | | | Fast cross-chain (resource locks) | 30% 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** | Same-chain optimization | Direct DEX routing for faster & cheaper same-chain swaps | β | Coming Soon | | | Solana integration | Native Solana support for unified DeFi ecosystem access | β | 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 | ### Account-Specific Features Some advanced features depend on your account configuration: | Feature | Role-Based | OneBalance Default | Solana EOA | Benefit | | :----------------------- | :--------- | :----------------- | :--------- | :--------------------------------------- | | **Resource Lock Opt-in** | β | β | Q3 2025 | Enable fast path execution | | **Rage Quit Protection** | β οΈ Limited | β | β | Recover assets if service is unavailable | | **Version Upgrades** | β | β | β | Upgrade to latest account features | Connect your preferred key management service like Privy, Turnkey, and Fireblocks Use your preferred stablecoin onramps to deposit into your user accounts **Need a specific feature?** Contact our team at [support@onebalance.io](mailto:support@onebalance.io) to discuss custom implementations or priority development. # Benchmarks & Stress Tests Source: https://docs.onebalance.io/resources/benchmarks Performance metrics, stress test results, and benchmarks for OneBalance infrastructure ## 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, contact us at [support@onebalance.io](mailto:support@onebalance.io). # Glossary Source: https://docs.onebalance.io/resources/glossary Definitions of OneBalance-specific terms and relevant Web3 concepts ## 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. 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. 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 ## 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. **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. **Credible Accounts** - A new account model that extends external accounts, smart contracts, and stateful accounts with the ability to issue Resource Locks. **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 Connect with the OneBalance community and get help ## Get Help## Community Technical questions and integration help Developer guides and API reference ## 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, contact us directly at [support@onebalance.io](mailto:support@onebalance.io).