# Building with LLMs Source: https://docs.onebalance.io/ai/building-with-llms Learn how to use OneBalance with LLMs like ChatGPT or Claude. Our docs are AI-ready, making it easy to integrate into your models and dev tools. ## Using AI to integrate OneBalance OneBalance documentation is AI-enhanced. Whether you're using ChatGPT, Claude, or a custom LLM integration, we've made it easy to feed OneBalance docs directly into your models and dev tools. ## LLM Feed Files We expose two continuously updated files for AI ingestion: * [`llms.txt`](https://docs.onebalance.io/llms.txt) - Concise list of top-level pages for quick context * [`llms-full.txt`](https://docs.onebalance.io/llms-full.txt) - Complete documentation for full-context indexing Use these URLs in your custom GPTs or LLM apps to ensure accurate OneBalance-specific answers. ## Plain Text Docs Access any documentation page as markdown by adding `.md` to the URL. For example: [/ai/building-with-llms.md](/ai/building-with-llms.md) This helps AI tools consume content with: * Fewer formatting tokens * Complete content (including hidden tabs) * Proper markdown hierarchy ## Code Editor Integration ### Cursor Setup 1. Navigate to **Cursor Settings** > **Indexing & Docs** 2. Select "Add Doc" and paste: ``` https://docs.onebalance.io/llms-full.txt ``` 3. Use `@docs -> OneBalance` to reference docs in your code ## MCP Server Integration Connect to OneBalance directly through our hosted MCP server at: ``` https://docs.onebalance.io/mcp ``` Simply add this URL to your AI tool (Claude, Cursor, etc.) for instant access to OneBalance documentation search and API functions directly in your AI tools. Complete setup guide for Model Context Protocol integration Ready-to-use prompts to explore OneBalance capabilities ## Contextual Features We've enabled contextual features across all docs: * Copy any page as Markdown * Launch ChatGPT/Claude with page context preloaded * Perfect for troubleshooting and code generation The `/llms.txt` file follows an [emerging standard](https://llmstxt.org) for making websites more accessible to LLMs. # MCP Example Prompts Source: https://docs.onebalance.io/ai/mcp-prompts Ready-to-use prompts for OneBalance's MCP, helping you quickly test, explore and ship AI-driven chain abstracted features. ## Overview Once you've connected to OneBalance MCP, try these prompts to explore the platform's capabilities. These examples demonstrate everything from basic balance queries to complex cross-chain operations. Need to connect MCP first? Use our hosted server at `https://docs.onebalance.io/mcp` - follow our complete setup guide ## Simple Prompts Perfect for getting started and understanding basic OneBalance functionality: ### Account & Balance Queries What is my aggregated balance across all chains for address 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045? Show me all supported chains on OneBalance What aggregated assets are available on OneBalance? Predict a smart account address for session signer 0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1 and admin signer 0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8 ### Transaction Status & History Check the status of quote ID "0xfa6094cd9d65416c975e3498cc5c2ca17df6c4cfa37e16f84f2736296dd98b5d" Show me the transaction history for address 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 What's the latest transaction for my account? ### Quote Generation Get a quote to swap 10 USDC to ETH using my aggregated balance How much would it cost to transfer 50 USDT to address 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045? Show me a quote for swapping \$500 worth of aggregated assets to WBTC ## Advanced Integration Prompts These prompts showcase OneBalance's powerful cross-chain capabilities: ### Multi-Chain Operations Create a dashboard showing my aggregated balances, recent transactions, and pending quotes. Display this in an interactive table with real-time data. Generate a quote comparison table for swapping 1000 USDC to ETH vs WBTC vs SOL, showing the best rates and execution costs across different chains. Show me my transaction history over the last 30 days, organized by chain and transaction type. Include a summary of total volume and fees paid. ### Contract Interactions Prepare a contract call quote to stake 100 ETH on Ethereum Mainnet using my aggregated balance, and show me the gas estimates. Get a quote for interacting with a DeFi protocol: deposit 500 USDC into AAVE on Base using my cross-chain balance. Create a batch transaction to: * Swap 200 USDC to ETH * Transfer 50 USDT to address 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 * Execute a contract call on Arbitrum Show me the combined quote and execution plan. ### Portfolio Analysis Analyze my aggregated portfolio: * Total balance in USD * Asset allocation breakdown * Chain distribution * Recent performance over 7 days Display this as an interactive portfolio dashboard. ## Developer Integration Prompts For building applications with OneBalance: ### SDK Generation Generate a TypeScript SDK wrapper for OneBalance API that includes type definitions, error handling, and example usage for all endpoints. Create a React hook library for OneBalance that provides easy-to-use hooks for balance queries, transaction history, and quote generation. ### Testing & Monitoring Build a test suite for OneBalance integration that covers all API endpoints, error scenarios, and edge cases. Create a monitoring dashboard that tracks OneBalance API performance, success rates, and quota usage with alerts for anomalies. ## Tips for Better Results * **Be Specific**: Include actual addresses, amounts, and asset types for more accurate responses. * **Ask for Explanations**: Add "explain the process" to understand OneBalance's cross-chain mechanics. * **Request Multiple Formats**: Ask for data in tables, charts, or interactive formats for better visualization. * **Use Production API key**: These prompts work best when you have OneBalance MCP properly configured with your API key. Works with our hosted server (`https://docs.onebalance.io/mcp`). Some advanced features may require production API access. ## Need Help? Troubleshoot MCP server configuration Complete API reference and examples Use the Intercom chat widget or email [support@onebalance.io](mailto:support@onebalance.io) for custom use cases # MCP Server Setup Source: https://docs.onebalance.io/ai/mcp-server-setup How to connect the OneBalance Toolkit to your preferred AI tools like Claude and Cursor, using MCP to speed up your development process. ## Overview The Model Context Protocol (MCP) connects OneBalance's documentation and API directly to your AI tools using our hosted MCP server for instant setup. ## Setup Instructions ### General Our MCP server supports the Streamable HTTP transport and is centrally hosted at: ``` https://docs.onebalance.io/mcp ``` When using API endpoints, AI tools will prompt you for your OneBalance API key as needed. For testing purposes, you can use our public API key with limited usage: `42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11` ### Claude **Team, Enterprise (Claude.ai)** 1. Navigate to [Connectors](https://claude.ai/settings/connectors) in Claude settings 2. Select **Add custom connector** 3. Enter name: "OneBalance MCP" and URL: `https://docs.onebalance.io/mcp` 4. Select **Add** 5. When chatting with Claude, select the attachments button (plus icon) 6. Select "OneBalance MCP" to query Claude with OneBalance context and API access **Free, Pro (Claude desktop)** 1. Open the file `~/Library/Application Support/Claude/claude_desktop_config.json` 2. Add the following and restart the Claude desktop app: ```json theme={null} { "mcpServers": { "onebalance": { "command": "npx", "args": ["-y", "mcp-remote", "https://docs.onebalance.io/mcp"] } } } ``` ### Cursor Install from Cursor's settings or add manually: 1. Press Command + Shift + P (or Ctrl + Shift + P on Windows) 2. Search for "Open MCP settings" 3. Add to your `mcp.json`: ```json theme={null} { "mcpServers": { "onebalance": { "url": "https://docs.onebalance.io/mcp" } } } ``` 4. Ask in Cursor chat: "What tools do you have available?" to confirm connection ### Visual Studio Code 1. Press Ctrl/Cmd + P and search for **MCP: Add Server** 2. Select **Command (stdio)** 3. Enter the following configuration: ```bash theme={null} npx mcp-remote https://docs.onebalance.io/mcp ``` 4. Enter the name **OneBalance** and press enter 5. Activate using **MCP: List Servers**, select **OneBalance**, then **Start Server** ### Windsurf 1. Press Ctrl/Cmd + , to open Windsurf settings 2. Scroll to **Cascade** → **MCP servers** 3. Select **Add Server** → **Add custom server** 4. Add the following: ```json theme={null} { "mcpServers": { "onebalance": { "command": "npx", "args": ["-y", "mcp-remote", "https://docs.onebalance.io/mcp"] } } } ``` ### Zed 1. Press Cmd + , to open Zed settings 2. Add the following: ```json theme={null} { "context_servers": { "onebalance": { "source": "custom", "command": "npx", "args": ["-y", "mcp-remote", "https://docs.onebalance.io/mcp"], "env": {} } } } ``` ### Other MCP-Compatible Tools Many other tools support MCP servers. Configure them with these settings: * **Command**: `npx` * **Arguments**: `-y mcp-remote https://docs.onebalance.io/mcp` * **Environment**: None ## Available Tools The hosted MCP server provides these capabilities: **Documentation:** * `SearchOneBalanceDocs` - Search across the OneBalance Docs knowledge base **Account Management:** * `predictAddress` - Predict smart account addresses before deployment * `listAggregatedAssets` - List all supported aggregated assets * `getAggregatedBalance` - Fetch aggregated balances for EVM accounts * `getAggregatedBalanceV3` - Fetch balances with multi-chain support (EVM + Solana) * `supportedChains` - List all supported blockchain networks **Transaction Status & History:** * `getCallsStatus` - Check quote execution status and transaction hashes * `getCallsStatusV3` - Check status with multi-blockchain support * `getCallsHistory` - Retrieve transaction history for EVM accounts * `getCallsHistoryV3` - Retrieve history with multi-chain support **Quote Generation & Execution:** * `getQuote` - Request transfer/swap quotes for EVM chains * `getQuoteV3` - Request quotes with multi-account support (EVM + Solana) * `prepareCallQuote` - Prepare contract call operations * `getCallQuote` - Get quotes for contract interactions * `executeQuote` - Execute transfer/swap quotes * `executeQuoteV3` - Execute quotes with multi-chain support * `prepareCallQuoteV3` - Prepare contract calls with multi-account support * `getCallQuoteV3` - Get contract quotes with multi-account support ## Authentication Get your production API key from the [authentication page](/api-reference/authentication). For testing purposes, you can use our public API key: `42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11` AI tools will prompt you for the API key when accessing OneBalance functions. The MCP server handles authentication automatically once configured. ## Usage Once configured, use these functions directly in your AI tools: * Ask questions about OneBalance integration * Get live data from your smart contract accounts * Generate code with current API context * Search docs without leaving your editor ## FAQ Check that you're using the correct URL: `https://docs.onebalance.io/mcp`. If you're using Claude Desktop or other clients that require `mcp-remote`, ensure you have the latest version of Node.js installed. The MCP server will only prompt for API keys when you try to use OneBalance API functions. Documentation search works without authentication. Try asking: "What's my aggregated balance?" to trigger the API key prompt. Yes! Get your production API key from the [authentication page](/api-reference/authentication). The MCP server will prompt you to enter it when accessing OneBalance functions, and it will be securely stored for future use. OneBalance MCP server supports Streamable HTTP transport at `https://docs.onebalance.io/mcp`. For clients that don't support direct HTTP connections, use `mcp-remote` as shown in the setup instructions. Try updating your configuration to use WSL explicitly: ```json theme={null} { "mcpServers": { "onebalance": { "command": "wsl", "args": ["npx", "-y", "mcp-remote", "https://docs.onebalance.io/mcp"] } } } ``` Ready-to-use prompts to explore OneBalance capabilities Learn about other AI integration options with OneBalance Need help? Use the **Intercom chat widget** in the bottom right corner for instant assistance, or contact [support@onebalance.io](mailto:support@onebalance.io). # Predict address Source: https://docs.onebalance.io/api-reference/account/predict-address post /account/predict-address Predicts the address for a new OneBalance smart account based on the provided EVM-compatible signer addresses (session and admin). This allows you to determine the smart account's on-chain address before its actual deployment. This predicted address serves as a primary identifier for the user within the OneBalance ecosystem. # List aggregated assets Source: https://docs.onebalance.io/api-reference/assets/list get /assets/list Retrieves a list of all aggregated assets currently supported and enabled on the OneBalance platform. Each aggregated asset includes details such as its unique ID, symbol, name, decimals, and the underlying individual assets (chain-specific tokens) that constitute it, identified by their CAIP-19 asset types. Learn more about aggregated assets in our [documentation](https://docs.onebalance.io/concepts/aggregated-assets). # Authentication Source: https://docs.onebalance.io/api-reference/authentication Authenticate your API requests with OneBalance using API keys OneBalance uses API keys to authenticate all requests. You must include your API key in the `x-api-key` header with every request to access the API. ## Get your API key ### Testing and development Start building immediately with our public testing key. This key has limited functionality but lets you explore the API without setup. **Public testing key:** `42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11` This key works for all endpoints and has rate limits suitable for development. ### Production access Get a production API key with full access and custom configuration for your project. Use the **Intercom chat widget** in the bottom right corner or email [support@onebalance.io](mailto:support@onebalance.io) with your API key request. Include this information in your request: * **Company name** and project name * **Use case description** - what you're building * **Expected volume** - transactions per day/month * **Preferred chains** - which networks you plan to use (see [supported networks](/resources/supported-networks)) * **Asset preferences** - which tokens you need access to The more details you provide, the faster we can configure your key with the right permissions. Our team will generate your custom API key and send it to you, typically within 24 hours. You'll receive your production key via email along with any specific configuration details. We're building self-service API key generation to make this process instant. Stay tuned for updates! ## Make authenticated requests Include your API key in the `x-api-key` header with every request: ```bash cURL theme={null} curl -X GET 'https://be.onebalance.io/api/assets/list' \ -H 'x-api-key: YOUR_API_KEY' ``` ```javascript JavaScript theme={null} const response = await fetch('https://be.onebalance.io/api/assets/list', { headers: { 'x-api-key': 'YOUR_API_KEY' } }); const data = await response.json(); ``` ```python Python theme={null} import requests headers = { 'x-api-key': 'YOUR_API_KEY' } response = requests.get('https://be.onebalance.io/api/assets/list', headers=headers) data = response.json() ``` Replace `YOUR_API_KEY` with your actual key. For testing, use the public key: `42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11` ## Security requirements Keep your production API key secure. Never expose it in client-side code, public repositories, or logs. Follow these security practices: * **Use HTTPS only** - All requests must use HTTPS. HTTP requests will fail * **Store keys securely** - Use environment variables or secure key management * **Rotate keys regularly** - Contact support to rotate compromised keys * **Monitor usage** - Watch for unexpected API calls that might indicate misuse ## Troubleshooting ### Common authentication errors **401 Unauthorized** * Check that you're including the `x-api-key` header * Verify your API key is correct and not expired * Ensure you're making requests over HTTPS **403 Forbidden** * Your key may not have permission for this endpoint * Contact support if you need additional access **Rate limited** * You're exceeding your key's rate limits * Implement exponential backoff in your retry logic * Contact support for higher limits if needed ```json theme={null} { "error": "Unauthorized", "message": "Invalid or missing API key", "code": 401 } ``` Need help? Use the **Intercom chat widget** in the bottom right corner for instant assistance, or contact [support@onebalance.io](mailto:support@onebalance.io) for authentication issues. # Get aggregated balance Source: https://docs.onebalance.io/api-reference/balances/aggregated-balance get /v2/balances/aggregated-balance Fetches the aggregated balance of all supported assets for a given smart account address, with an option to filter by specific assetId`s. # Get aggregated balance Source: https://docs.onebalance.io/api-reference/balances/aggregated-balance-v3 get /v3/balances/aggregated-balance Fetches the aggregated balance of assets for given accounts with support for multiple chain types (EVM, Solana, etc.). Returns a unified view with all balances intermixed. Supports filtering by specific aggregated asset IDs. # List supported chains Source: https://docs.onebalance.io/api-reference/chains/list get /chains/supported-list Retrieves a list of all blockchain networks currently integrated with and supported by the OneBalance platform. # Cross Origin Resource Sharing Source: https://docs.onebalance.io/api-reference/cors Information about CORS support in the OneBalance API OneBalance API implements Cross Origin Resource Sharing (CORS) to allow requests from browsers on different domains. ## Overview CORS support enables web applications to make AJAX requests to the OneBalance API from domains different from where the API is hosted. This is essential for implementing features like dashboards or control panels that utilize the API. ## How CORS Works When a browser makes a cross-origin request: 1. For non-simple requests (like PUT, DELETE, or with custom headers), the browser first sends a "preflight" request using the OPTIONS method 2. This preflight request includes the `Origin` header identifying the requesting domain 3. The server responds with headers describing the allowed constraints 4. If the actual request falls within these constraints, the browser proceeds with the actual request ## CORS Headers The OneBalance API responds with the following CORS headers: | Header | Description | | ---------------------------------- | ----------------------------------------------------------------------------------------------- | | `Access-Control-Allow-Origin` | The domain that sent the request (from the `Origin` header) | | `Access-Control-Allow-Methods` | The HTTP methods allowed for cross-origin requests (typically includes all available methods) | | `Access-Control-Expose-Headers` | Headers that will be accessible to requests from the origin domain | | `Access-Control-Max-Age` | How long (in seconds) the preflight response can be cached before another preflight is needed | | `Access-Control-Allow-Credentials` | Set to `true` to allow sending authentication credentials (like Access tokens) with the request | ## Client Implementation Most browsers handle CORS details automatically. Your JavaScript code can make requests to the OneBalance API endpoints as it would to any API, and the browser will manage the preflight requests and handle the CORS headers. ```javascript request.js theme={null} // Example of a cross-origin fetch request fetch('https://be.onebalance.io/api/assets/list', { method: 'GET', headers: { 'x-api-key': 'your-api-key', 'Content-Type': 'application/json', }, }) .then(response => response.json()) .then(data => console.log(data)) .catch(error => console.error('Error:', error)); ``` ## Server Implementation in Next.js In a Next.js application using the App Router, you can set [CORS headers](https://nextjs.org/docs/app/building-your-application/routing/route-handlers#cors) for a specific Route Handler using the standard Web API methods: ```typescript route.ts theme={null} // 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 }); } } ``` # Error Codes Source: https://docs.onebalance.io/api-reference/error-codes Learn more about error codes and how to resolve them. Errors in OneBalance fall into two categories: * **[Quote errors](#quote-errors)** - returned during quote generation. The API responds with an error and no quote is created. Fix the issue and retry. * **[Quote execution failures](#quote-execution-failures)** - happen after a quote is executed. The API request succeeds (returns 200), but the quote is marked as `REFUNDED` with a `failReason` field explaining why. ## Quote Errors These errors are returned during quote generation. No quote is created - resolve the issue before retrying. | Error Code | Description | | ---------------- | ----------------------------------------------------------------------- | | `DELEGATION-001` | Account has a pre-existing EIP-7702 delegation to a non-Kernel contract | ### `DELEGATION-001` **Description**: Account has a pre-existing EIP-7702 delegation to a contract other than the Kernel v3.3 contract (`0xd6CEDDe84be40893d153Be9d467CD6aD37875b28`). **What it means**: OneBalance won't override existing delegations for security reasons (prevents double-spend attacks). The account can't be used until the conflicting delegation is removed. **Recommended action**: * Revoke the existing EIP-7702 delegation on the EOA, then retry. OneBalance will automatically delegate it to Kernel v3.3 on the next quote execution * Alternatively, use a different EOA that has no pre-existing delegation See also: [EIP-7702 delegation FAQ](/faq/signing#what-happens-if-my-wallet-already-has-an-eip-7702-delegation-to-another-contract) for more background on this behavior. ## Quote Execution Failures The following is a complete list of OneBalance fail reason codes, their descriptions, and recommended actions for resolution. ### General Failure Reasons | Error Code | Description | | --------------------------------------- | ----------------------------------------------- | | `UNKNOWN` | Unknown failure reason | | `SLIPPAGE` | Price moved beyond slippage tolerance | | `AMOUNT_TOO_LOW_TO_REFUND` | Amount is too low to process a refund | | `DEPOSIT_ADDRESS_MISMATCH` | Deposit address does not match expected address | | `DEPOSIT_CHAIN_MISMATCH` | Deposit chain does not match expected chain | | `INCORRECT_DEPOSIT_CURRENCY` | Deposited currency is incorrect | | `DOUBLE_SPEND` | Double spend detected | | `SOLVER_CAPACITY_EXCEEDED` | Solver capacity has been exceeded | | `DEPOSITED_AMOUNT_TOO_LOW_TO_FILL` | Deposited amount is too low to fill the order | | `NEGATIVE_NEW_AMOUNT_AFTER_FEES` | Negative amount after deducting fees | | `NO_QUOTES` | No quotes available | | `MISSING_REVERT_DATA` | Missing revert data from transaction | | `REVERSE_SWAP_FAILED` | Reverse swap operation failed | | `GENERATE_SWAP_FAILED` | Failed to generate swap | | `TOO_LITTLE_RECEIVED` | Received amount is too little | | `EXECUTION_REVERTED` | Execution was reverted | | `NEW_CALLDATA_INCLUDES_HIGHER_RENT_FEE` | New calldata includes higher rent fee | | `TRANSACTION_REVERTED` | Transaction was reverted | | `ORIGIN_CURRENCY_MISMATCH` | Origin currency mismatch | | `NO_INTERNAL_SWAP_ROUTES_FOUND` | No internal swap routes found | | `SWAP_USES_TOO_MUCH_GAS` | Swap uses too much gas | | `INSUFFICIENT_FUNDS_FOR_RENT` | Insufficient funds for rent (Solana-specific) | ### Execution Revert Specific Reasons | Error Code | Description | | ------------------------------------- | ----------------------------------- | | `ORDER_EXPIRED` | Order has expired | | `ORDER_IS_CANCELLED` | Order has been cancelled | | `TRANSFER_FROM_FAILED` | Transfer from operation failed | | `TRANSFER_FAILED` | Transfer operation failed | | `SIGNATURE_EXPIRED` | Signature has expired | | `INVALID_SIGNATURE` | Invalid signature provided | | `INSUFFICIENT_NATIVE_TOKENS_SUPPLIED` | Insufficient native tokens supplied | | `TRANSFER_AMOUNT_EXCEEDS_ALLOWANCE` | Transfer amount exceeds allowance | | `TRANSFER_AMOUNT_EXCEEDS_BALANCE` | Transfer amount exceeds balance | | `INVALID_SENDER` | Invalid sender | | `ACCOUNT_ABSTRACTION_INVALID_NONCE` | Account abstraction invalid nonce | | `ACCOUNT_ABSTRACTION_SIGNATURE_ERROR` | Account abstraction signature error | | `SEAPORT_INEXACT_FRACTION` | Seaport inexact fraction | | `TOKEN_NOT_TRANSFERABLE` | Token is not transferable | | `ZERO_SELL_AMOUNT` | Zero sell amount | | `MINT_NOT_ACTIVE` | Mint is not active | | `ERC_1155_TOO_MANY_REQUESTED` | ERC-1155 too many requested | | `INCORRECT_PAYMENT` | Incorrect payment | | `INVALID_GAS_PRICE` | Invalid gas price | | `FLUID_DEX_ERROR` | Fluid DEX error | | `ORDER_ALREADY_FILLED` | Order already filled | | `SEAPORT_INVALID_FULFILLER` | Seaport invalid fulfiller | ### Jupiter Specific Failures | Error Code | Description | | ------------------------------- | -------------------------------------- | | `JUPITER_INVALID_TOKEN_ACCOUNT` | Jupiter invalid token account (Solana) | ## Detailed Error Explanations ### `SLIPPAGE` **Description**: The swap failed because the price moved beyond the configured slippage tolerance. **What it means**: The market price of the tokens changed significantly between when the quote was generated and when it was executed, exceeding the maximum allowed price impact. **Recommended action**: * Try requesting a new quote with updated prices * Consider increasing the slippage tolerance if appropriate for your use case * For volatile assets, execute quotes more quickly after generation ### `TRANSFER_AMOUNT_EXCEEDS_BALANCE` **Description**: Transfer amount exceeds balance. **What it means**: Insufficient token balance for the transfer. **Recommended action**: * Check token balance * Reduce transfer amount ### `TRANSFER_AMOUNT_EXCEEDS_ALLOWANCE` **Description**: Transfer amount exceeds allowance. **What it means**: Token approval is less than transfer amount. **Recommended action**: * Increase token allowance * Approve exact or unlimited amount ### `ORDER_EXPIRED` **Description**: Order has expired. **What it means**: The order validity period has passed. **Recommended action**: * Submit a new order * Execute orders more quickly ### `INSUFFICIENT_NATIVE_TOKENS_SUPPLIED` **Description**: Insufficient native tokens supplied. **What it means**: Not enough ETH/SOL/etc. for gas fees. **Recommended action**: * Add native tokens for gas * Check gas price estimates ### `NO_QUOTES` **Description**: No quotes available. **What it means**: Unable to find any valid quotes for the requested swap. **Recommended action**: * Try different token pairs * Check if markets are available for the tokens * Try again during active trading hours ### `EXECUTION_REVERTED` **Description**: Execution was reverted. **What it means**: The on-chain execution failed and was reverted. **Recommended action**: * Check specific revert reasons below * Verify contract interactions ### `INSUFFICIENT_FUNDS_FOR_RENT` **Description**: Insufficient funds for rent. **What it means**: Not enough funds to cover rent fees (Solana-specific). **Recommended action**: * Add SOL for rent exemption * Maintain minimum balance requirements ### `JUPITER_INVALID_TOKEN_ACCOUNT` **Description**: Jupiter invalid token account. **What it means**: Token account issue on Jupiter (Solana). **Recommended action**: * Verify Solana token accounts * Initialize token account if needed ### `UNKNOWN` **Description**: Unknown failure reason. **What it means**: The failure reason could not be determined. **Recommended action**: * Contact support with the quote ID for investigation * Try generating and executing a new quote ### `DOUBLE_SPEND` **Description**: Double spend detected. **What it means**: An attempt to spend the same funds twice was detected. **Recommended action**: * Ensure transactions are properly sequenced * Wait for transaction confirmation before initiating new ones ### `SOLVER_CAPACITY_EXCEEDED` **Description**: Solver capacity has been exceeded. **What it means**: The system's solving capacity has been reached. **Recommended action**: * Try again after a short delay * Consider splitting large orders into smaller ones ### `TOO_LITTLE_RECEIVED` **Description**: Received amount is too little. **What it means**: The output amount is below acceptable minimums. **Recommended action**: * Increase slippage tolerance * Try a different route or time ### `INVALID_SIGNATURE` **Description**: Invalid signature provided. **What it means**: The signature doesn't match or is malformed. **Recommended action**: * Re-sign the transaction * Verify wallet connection ### `SIGNATURE_EXPIRED` **Description**: Signature has expired. **What it means**: The transaction signature is no longer valid. **Recommended action**: * Sign a new transaction * Execute signed transactions promptly ### `INVALID_SENDER` **Description**: Invalid sender. **What it means**: Transaction sender is not authorized. **Recommended action**: * Verify wallet connection * Check sender permissions ### `ACCOUNT_ABSTRACTION_INVALID_NONCE` **Description**: Account abstraction invalid nonce. **What it means**: Smart wallet nonce is incorrect. **Recommended action**: * Sync wallet state * Retry transaction ### `ACCOUNT_ABSTRACTION_SIGNATURE_ERROR` **Description**: Account abstraction signature error. **What it means**: Smart wallet signature validation failed. **Recommended action**: * Re-sign with correct wallet * Check wallet configuration ### `TOKEN_NOT_TRANSFERABLE` **Description**: Token is not transferable. **What it means**: Token has transfer restrictions. **Recommended action**: * Check token transfer rules * Verify token isn't locked ### `ZERO_SELL_AMOUNT` **Description**: Zero sell amount. **What it means**: Attempting to sell zero tokens. **Recommended action**: * Specify a valid amount * Check input validation ### `MINT_NOT_ACTIVE` **Description**: Mint is not active. **What it means**: Token minting is currently disabled. **Recommended action**: * Wait for minting to activate * Check mint schedule ### `ORDER_ALREADY_FILLED` **Description**: Order already filled. **What it means**: Attempting to fill an already completed order. **Recommended action**: * Submit a new order * Check order status first ### `INVALID_GAS_PRICE` **Description**: Invalid gas price. **What it means**: Gas price is outside acceptable range. **Recommended action**: * Update gas price settings * Use current market rates ### `AMOUNT_TOO_LOW_TO_REFUND` **Description**: Amount is too low to process a refund. **What it means**: The refund amount is below the minimum threshold. **Recommended action**: * Increase transaction amount * Contact support for small amounts ### `DEPOSIT_ADDRESS_MISMATCH` **Description**: Deposit address does not match expected address. **What it means**: Funds were sent to an incorrect address. **Recommended action**: * Verify correct deposit address * Contact support if funds sent to wrong address ### `DEPOSIT_CHAIN_MISMATCH` **Description**: Deposit chain does not match expected chain. **What it means**: Funds sent on wrong blockchain network. **Recommended action**: * Verify correct chain before sending * Ensure wallet connected to correct network ### `INCORRECT_DEPOSIT_CURRENCY` **Description**: Deposited currency is incorrect. **What it means**: Wrong token type was deposited. **Recommended action**: * Verify correct token type * Check token contract addresses ### `DEPOSITED_AMOUNT_TOO_LOW_TO_FILL` **Description**: Deposited amount is too low to fill the order. **What it means**: Deposited amount doesn't meet minimum requirements. **Recommended action**: * Increase deposit amount * Check minimum order requirements ### `NEGATIVE_NEW_AMOUNT_AFTER_FEES` **Description**: Negative amount after deducting fees. **What it means**: Fees exceed the transaction amount. **Recommended action**: * Increase transaction amount * Review fee structure ### `MISSING_REVERT_DATA` **Description**: Missing revert data from transaction. **What it means**: Transaction failed without detailed error information. **Recommended action**: * Contact support with transaction details * Try transaction again ### `REVERSE_SWAP_FAILED` **Description**: Reverse swap operation failed. **What it means**: Attempt to reverse a swap transaction failed. **Recommended action**: * Contact support for manual intervention * Review transaction history ### `GENERATE_SWAP_FAILED` **Description**: Failed to generate swap. **What it means**: System couldn't create a valid swap transaction. **Recommended action**: * Try with different parameters * Check token compatibility ### `NEW_CALLDATA_INCLUDES_HIGHER_RENT_FEE` **Description**: New calldata includes higher rent fee. **What it means**: Updated transaction requires higher fees than originally quoted. **Recommended action**: * Accept higher fee or cancel * Request fresh quote ### `TRANSACTION_REVERTED` **Description**: Transaction was reverted. **What it means**: Blockchain transaction failed and was reverted. **Recommended action**: * Check gas settings * Verify transaction parameters ### `ORIGIN_CURRENCY_MISMATCH` **Description**: Origin currency mismatch. **What it means**: Source currency doesn't match expectations. **Recommended action**: * Verify correct source token * Check wallet token selection ### `NO_INTERNAL_SWAP_ROUTES_FOUND` **Description**: No internal swap routes found. **What it means**: Unable to find a valid path for the swap. **Recommended action**: * Try alternative token pairs * Check liquidity availability ### `SWAP_USES_TOO_MUCH_GAS` **Description**: Swap uses too much gas. **What it means**: Swap would exceed gas limits. **Recommended action**: * Simplify swap path * Split into smaller transactions ### `ORDER_IS_CANCELLED` **Description**: Order has been cancelled. **What it means**: Order was cancelled before execution. **Recommended action**: * Submit new order if needed * Check cancellation reason ### `TRANSFER_FROM_FAILED` **Description**: Transfer from operation failed. **What it means**: Unable to transfer tokens from source. **Recommended action**: * Check token allowances * Verify token balance ### `TRANSFER_FAILED` **Description**: Transfer operation failed. **What it means**: Token transfer could not be completed. **Recommended action**: * Check token compatibility * Verify recipient address ### `SEAPORT_INEXACT_FRACTION` **Description**: Seaport inexact fraction. **What it means**: NFT marketplace order has fraction issues. **Recommended action**: * Adjust order parameters * Use whole units ### `SEAPORT_INVALID_FULFILLER` **Description**: Seaport invalid fulfiller. **What it means**: Not authorized to fulfill this Seaport order. **Recommended action**: * Check fulfiller requirements * Verify permissions ### `ERC_1155_TOO_MANY_REQUESTED` **Description**: ERC-1155 too many requested. **What it means**: Exceeds maximum ERC-1155 tokens per transaction. **Recommended action**: * Reduce number of tokens * Split into multiple transactions ### `INCORRECT_PAYMENT` **Description**: Incorrect payment. **What it means**: Payment amount or method is wrong. **Recommended action**: * Verify payment amount * Check payment currency ### `FLUID_DEX_ERROR` **Description**: Fluid DEX error. **What it means**: Error specific to Fluid DEX protocol. **Recommended action**: * Check Fluid DEX status * Try alternative DEX ## Integration Example ```typescript theme={null} // Check execution status const statusResponse = await fetch('/api/status/get-execution-status?quoteId=0x...', { headers: { 'x-api-key': 'your-api-key' } }); const status = await statusResponse.json(); if (status.status === 'REFUNDED' && status.failReason) { switch (status.failReason) { case 'SLIPPAGE': // Handle slippage - maybe retry with higher tolerance console.log('Quote refunded due to slippage'); break; case 'TRANSFER_AMOUNT_EXCEEDS_BALANCE': // Inform user about insufficient balance console.log('Insufficient balance for swap'); break; case 'TRANSFER_AMOUNT_EXCEEDS_ALLOWANCE': // Need to increase token approval console.log('Token allowance insufficient'); break; case 'ORDER_EXPIRED': // Order expired, need fresh quote console.log('Order expired, please request a new quote'); break; case 'NO_QUOTES': // No quotes available for this pair console.log('No quotes available for this token pair'); break; case 'EXECUTION_REVERTED': // Check for more specific revert reasons console.log('Transaction execution reverted'); break; case 'UNKNOWN': // Unknown failure console.log('Unknown failure reason'); break; // ... handle other cases default: console.log(`Quote refunded: ${status.failReason}`); } } ``` ## Best Practices * **Always check the failReason**: When a quote is refunded, use the `failReason` to provide appropriate feedback to users * **Implement retry logic**: For certain fail reasons like `SLIPPAGE` or `TIMEOUT`, consider implementing automatic retry with adjusted parameters * **Monitor patterns**: Track fail reasons to identify common issues and optimize your integration * **User communication**: Translate technical fail reasons into user-friendly messages ## Related Documentation Check quote execution status with multi-chain support and retrieve failReason codes Detailed explanations and handling strategies for each fail reason # Errors Source: https://docs.onebalance.io/api-reference/errors Complete list of error codes with explanations and troubleshooting tips OneBalance uses conventional HTTP response codes to indicate the success or failure of an API request. In general: Codes in the `2xx` range indicate success. Codes in the `4xx` range indicate an error that failed given the information provided (e.g., a required parameter was omitted, a charge failed, etc.). Codes in the `5xx` range indicate an error with OneBalance's servers. ## HTTP Status Codes | Status Code | Type | Description | | ------------------ | ----------------- | ------------------------------------------------------------------------------------------------ | | 200 | OK | Everything worked as expected. | | 400 | Bad Request | The request was unacceptable, often due to missing a required parameter. | | 401 | Unauthorized | No valid API key provided. | | 402 | Request Failed | The parameters were valid but the request failed. | | 403 | Forbidden | The API key doesn't have permissions to perform the request. | | 404 | Not Found | The requested resource doesn't exist. | | 429 | Too Many Requests | Too many requests hit the API too quickly. We recommend an exponential backoff of your requests. | | 500, 502, 503, 504 | Server Errors | Something went wrong on OneBalance's end. | ## Quote Execution Failures While HTTP status codes indicate API request success/failure, quote executions can fail for business logic reasons even when the API request succeeds (returns 200). When a quote execution fails, it will be marked as `REFUNDED` and include a `failReason` field explaining why. ### Understanding the Difference * **HTTP Errors** (4xx/5xx): Issues with the API request itself (authentication, malformed requests, server errors) * **Execution Failures**: Business logic failures where the quote couldn't be completed (insufficient balance, slippage, etc.) For a complete list of execution failure codes with detailed explanations and troubleshooting steps, see [Error Codes](/api-reference/error-codes). ## Error Response Format Both `400` and `500` level errors return a JSON object in the response body, containing specific attributes detailing the error. | Name | Type | Description | | ---------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | error | string | A short identifier corresponding to the HTTP status code returned. For example, the ID for a response returning a 404 status code would be "not\_found." | | message | string | A message providing additional information about the error, including details to help resolve it when possible. | | statusCode | number | HTTP status code. | | timestamp | string | Timestamp of the error. | | path | string | Path of the request that caused the error. | ## Example Error Response HTTP/1.1 401 Unauthorized ```json Unauthorized theme={null} { "error": "UnauthorizedException", "message": "Invalid or missing API key", "statusCode": 401, "timestamp": "2024-12-18T14:38:24.793Z", "path": "/api/xxx/xxx" } ``` ## Error Handling Best Practices * Always check for error responses in your API integrations * Use the `statusCode` to determine the type of error * Look at the `message` field for specific details on how to resolve the issue * For refunded quotes, check the `failReason` field and implement appropriate retry logic * Include the error `id` when using the **Intercom chat widget** or contacting support about any API issues * See [Error Codes](/api-reference/error-codes) for handling specific execution failure scenarios # Introduction Source: https://docs.onebalance.io/api-reference/introduction Overview of the OneBalance API, versioning, and general conventions The OneBalance API provides a programmatic interface for managing OneBalance resources using standard HTTP requests. The API documentation includes a design and technology overview, followed by detailed endpoint information. ## Features OneBalance API provides a complete chain abstraction toolkit: * **Smart Account Management**: Predict addresses and deploy accounts * **Cross-Chain Balances**: View aggregated balances across multiple chains * **Multichain Transactions**: Execute transfers and swaps using aggregated balances * **Arbitrary Contract Executions**: Execute any smart contract function across multiple chains using aggregated assets * **Transaction Status**: Track multichain transaction history and status * **Token Aggregation**: Access unified token lists and pricing information **Enterprise API Access**: Need higher rate limits, dedicated infrastructure, or custom SLAs? [Contact our sales team](mailto:sales@onebalance.io) for enterprise API solutions. ## Base URL OneBalance API is built on REST principles and is served over HTTPS. To ensure data privacy, unencrypted HTTP is not supported. The Base URL for all API endpoints is: ```bash theme={null} https://be.onebalance.io/api ``` ## Quickstart Try our reference application at [app.onebalance.io](https://app.onebalance.io) to see the API in action or jump straight into our [API Reference](/api-reference/account/predict-address) to start building. ## OpenAPI Specification You can access the complete OpenAPI specification in JSON format at: ```bash theme={null} https://docs.onebalance.io/api-reference/openapi.json ``` Use this specification to generate client libraries, import into API testing tools like Postman or Insomnia, or integrate with your development workflow. # Pagination Source: https://docs.onebalance.io/api-reference/pagination How to work with paginated API responses OneBalance API uses cursor-based pagination for endpoints that return large collections of items. This approach provides efficient navigation through result sets. ## How Pagination Works The `/status/get-tx-history` endpoint supports pagination through the following parameters: | Parameter | Type | Required | Description | | -------------- | ------ | -------- | ---------------------------------------------------------------------------- | | `limit` | string | Yes | Maximum number of transactions to return in a single request (e.g., `10`) | | `continuation` | string | No | A cursor value received from a previous response used to fetch the next page | ## Response Format Paginated responses include: ```json Paginated Response theme={null} { "transactions": [ // Array of transaction objects ], "continuation": "txn_00123456789abcdef" // Token to fetch the next page } ``` The `continuation` property will be present when more results are available beyond the current page. If it's absent or null, you've reached the end of the results. ## Pagination Example ### Initial Request ```bash Initial Request theme={null} GET /api/status/get-tx-history?user=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045&limit=10 ``` ### Response ```json Paginated Response theme={null} { "transactions": [ // 10 transaction objects ], "continuation": "txn_00123456789abcdef" } ``` ### Subsequent Request To fetch the next page, include the `continuation` token from the previous response: ```bash Subsequent Request theme={null} GET /api/status/get-tx-history?user=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045&limit=10&continuation=txn_00123456789abcdef ``` ## Pagination Best Practices 1. **Always specify a limit** - This controls how many items you receive per page. 2. **Store the continuation token** - Save the token from each response to allow users to navigate to the next page. 3. **Check for continuation** - If a response doesn't include a continuation token, it means you've reached the end of the results. 4. **Handle pagination in your UI** - Implement "Next" and "Previous" buttons that appear or disappear based on the availability of continuation tokens. ## Limits and Constraints * The `limit` parameter must be a positive number. * For optimal performance, we recommend using reasonable limit values (10-50). * The maximum number of results you can request per page may be capped by the server. * Continuation tokens are temporary and may expire after a certain period of time. # Execute quote Source: https://docs.onebalance.io/api-reference/quotes/execute-quote post /quotes/execute-quote Execute a previously generated quote to transfer funds. # Execute quote Source: https://docs.onebalance.io/api-reference/quotes/execute-quote-v3 post /v3/quote/execute-quote Execute a previously generated quote to transfer funds with support for multiple blockchain types including Solana. # Get call quote Source: https://docs.onebalance.io/api-reference/quotes/get-call-quote post /quotes/call-quote Gets a quote for arbitrary contract call operations on a specific chain. This endpoint handles the execution of pre-signed user operations with typed data signatures, supporting interactions with smart contracts. # Get call quote Source: https://docs.onebalance.io/api-reference/quotes/get-call-quote-v3 post /v3/quote/call-quote Gets a quote for arbitrary contract call operations with multi-account support. This V3 endpoint extends the V1 functionality by supporting cross-chain operations with mixed Solana and EVM accounts, and handles the execution of pre-signed user operations with typed data signatures. **V3 Multi-Account Features:** - Cross-chain operations with mixed Solana and EVM accounts - Source asset specification via `fromAggregatedAssetId` for bridging - EIP-7702 delegation support for EOA-based contract calls - Multi-account routing for complex cross-chain scenarios # Get quote Source: https://docs.onebalance.io/api-reference/quotes/get-quote post /v1/quote Request a quote for transferring or swapping assets. **Asset Flexibility:** - Use aggregated assets (e.g., "ob:usdc") for optimal cross-chain routing - Use chain-specific assets (e.g., "eip155:42161/erc20:0xaf88d065e77c8cC2239327C5EDb3A432268e5831") for precise control - Mix and match aggregated and specific assets in the same transaction # Get quote Source: https://docs.onebalance.io/api-reference/quotes/get-quote-v3 post /v3/quote Request a quote for transferring or swapping assets with support for multiple account types including Solana and EVM accounts. **Key Capabilities:** - Multiple accounts in a single request - Cross-chain operations between Solana and EVM chains - **Chain-specific token spending** for precise control alongside aggregated assets - Unified responses for different blockchain types **Account Requirements:** - At least one account must be provided in the accounts array - Cannot combine multiple accounts of the same chain type (e.g., two EVM accounts) - Can combine Solana account with any EVM account type - Single account operations are supported (Solana-only or EVM-only) # Prepare call quote Source: https://docs.onebalance.io/api-reference/quotes/prepare-call-quote post /quotes/prepare-call-quote Prepares the necessary data structure for executing one or more contract calls on a target chain. This endpoint helps construct the appropriate user operation, handles token allowance requirements, and generates a signed quote ready for execution. # Prepare call quote Source: https://docs.onebalance.io/api-reference/quotes/prepare-call-quote-v3 post /v3/quote/prepare-call-quote Prepares the necessary data structure for executing one or more contract calls on a target chain with multi-account support. This V3 endpoint extends the V1 functionality by supporting cross-chain operations with mixed Solana and EVM accounts. **V3 Multi-Account Features:** - Cross-chain operations with mixed Solana and EVM accounts - Source asset specification via `fromAssetId` for bridging - EIP-7702 delegation support for EOA-based contract calls - Multi-account routing for complex cross-chain scenarios # Rate Limits Source: https://docs.onebalance.io/api-reference/rate-limits Information about API rate limits and how to handle them The OneBalance API implements rate limiting to ensure fair usage and availability of the service for all users. Rate limits are applied on a per-API key basis. ## Current Rate Limits | User Type | Request Rate Limit | Concurrent Connections | | ------------------- | ---------------------- | ---------------------- | | Public API Key | 60 requests per minute | 1 per IP address | | Authenticated Users | Custom rate limits | Custom limits | Authenticated users receive higher rate limits based on their specific needs. Please contact our team if you require increased limits for your production application. Once you exceed your limit, your requests will be temporarily rejected until the rate limit window resets. ## Rate Limit Headers The rate limiting information is included in the response headers of each request: | Header | Description | | ----------------------- | ---------------------------------------------------------------------------- | | `x-ratelimit-limit` | The maximum number of requests you're permitted to make per minute | | `x-ratelimit-remaining` | The number of requests remaining in the current rate limit window | | `x-ratelimit-reset` | The time at which the current rate limit window resets in Unix epoch seconds | As long as the `x-ratelimit-remaining` count is above zero, you'll be able to make additional requests. ## How Rate Limiting Works Each request contributes toward your rate limit count for one complete minute. This means that the entire rate limit doesn't reset at once. Rather, each request expires individually one minute after it was made. The value of the `x-ratelimit-reset` header indicates when the oldest request will expire and no longer count toward your limit. ## Handling Rate Limits If you exceed the rate limit, the API will return a `429 Too Many Requests` status code. We recommend implementing the following strategies to handle rate limits effectively: 1. **Monitor the rate limit headers** in your API responses to track your usage 2. **Implement exponential backoff** when receiving 429 responses 3. **Pace your requests** to avoid hitting the limits, especially for batch operations 4. **Cache responses** when possible to reduce the number of API calls ## Sample Rate Limit Headers ```bash Terminal theme={null} x-ratelimit-limit: 60 x-ratelimit-remaining: 58 x-ratelimit-reset: 60 ``` ## Sample Rate Limit Exceeded Response ```json Rate Limit Exceeded theme={null} { "error": "TooManyRequests", "message": "Rate limit exceeded. Please retry after 45 seconds.", "statusCode": 429, "timestamp": "2024-12-18T14:38:24.793Z", "path": "/api/assets/list" } ``` ## Best Practices * Space out requests that would otherwise be issued in bursts * Implement retry logic with exponential backoff when receiving 429 responses * For high-volume operations, consider batching requests where appropriate * Use the public API key for testing and development only, as it has lower rate limits Some endpoints may have special rate limit requirements that are independent of the general limits defined above. # Get quote status Source: https://docs.onebalance.io/api-reference/status/get-quote-status get /status/get-execution-status Retrieves the execution status of a quote by its quote ID as well as transaction hashes for user operations # Get quote status Source: https://docs.onebalance.io/api-reference/status/get-quote-status-v3 get /v3/status/get-execution-status Retrieves the execution status of a quote by its quote ID as well as transaction hashes for user operations with support for multiple blockchain types including Solana. # Get transaction history Source: https://docs.onebalance.io/api-reference/status/get-transaction-history get /status/get-tx-history Retrieves the transaction history for an address, including the transaction type and token details. # Get transaction history Source: https://docs.onebalance.io/api-reference/status/get-transaction-history-v3 get /v3/status/get-tx-history Retrieves the transaction history for an address, including the transaction type and token details with support for multiple blockchain types including Solana. # Toolkit Changelog Source: https://docs.onebalance.io/changelog Stay updated with the latest launches, chain integrations, improvements, and fixes in the OneBalance Toolkit. ## Enhanced Error Handling: Fail Reason Field Status endpoints now include detailed fail reasons when quotes are refunded. Get specific, actionable feedback for different failure scenarios - from slippage issues to insufficient balance - improving both debugging and user experience. ## Slippage Tolerance Configuration Configure slippage tolerance to reduce quote refunds and improve transaction success rates. Optional parameter works with all quote endpoints while maintaining full backward compatibility. ## EIP-7702 Support: Cross-Chain Delegation Enable bundling of delegation + bridging + execution in single transactions. Transform EOAs into smart accounts through delegation while preserving existing addresses. ## Hosted OneBalance MCP Server Connect OneBalance to Claude, Cursor, and other AI tools with zero setup. Our hosted MCP server at `https://docs.onebalance.io/mcp` provides instant access to OneBalance API and documentation - no installations required. ## Solana Support Now Available (Beta) Cross-chain swaps and transfers between Solana and EVM chains are now supported through new API endpoints. Experience true chain abstraction with SOL ↔ EVM swaps across Solana, Arbitrum, Ethereum, and more. ## Announcing our beta MCP API and Doc Integration Integrate OneBalance directly into your AI workflow using Claude or Cursor. Our AI-friendly API and docs make it easy to build a one-click UX across any chain, action, or token. It's time to vibe code a 10x UX for your app. ## OneBalance Documentation is Live Our new developer documentation is now available! Get started with step-by-step guides, interactive API reference, and AI integration examples. # API Changelog Source: https://docs.onebalance.io/changelog/api Track OneBalance API updates, new endpoints, breaking changes, and feature releases with detailed migration guides Subscribe to API updates via RSS at [https://docs.onebalance.io/changelog/api/rss.xml](https://docs.onebalance.io/changelog/api/rss.xml). ## Improvements * Migrate `x-mcp` to `x-mint.mcp` in OpenAPI spec ## Improvements * Add complete list of all fail reason codes with detailed descriptions and categories (General Failure Reasons, Execution Revert Specific Reasons, Jupiter Specific Failures) * Add documentation link to [https://docs.onebalance.io/api-reference/error-codes](https://docs.onebalance.io/api-reference/error-codes) for detailed explanations and troubleshooting steps * Add "CALL" enum value to action.yaml and action-v3.yaml for contract call operations * Enhance API documentation with better categorization and explanations for developers ## New Features **Fail Reason Field:** * Add optional `failReason` field to execution status responses when status is REFUNDED * Add fail-reason enum schema with 12 standardized refund reasons (SLIPPAGE, INSUFFICIENT\_BALANCE, TIMEOUT, etc.) * Updated both v1 and v3 status endpoints ([`/status/get-execution-status`](/api-reference/status/get-quote-status) and [`/v3/status/get-execution-status`](/api-reference/status/get-quote-status-v3)) * Updated transaction history endpoints ([`/status/get-tx-history`](/api-reference/status/get-tx-history) and [`/v3/status/get-tx-history`](/api-reference/status/get-tx-history-v3)) to include failReason in historical records * Added response examples showing refunded transactions with detailed fail reasons * Backward compatible: field only appears when status is REFUNDED * Enhanced [error handling documentation](/api-reference/errors) with quote execution failure guidance * See [Refund Reasons Guide](/guides/quotes/refund-reasons) for detailed explanations and handling strategies ## New Features **Slippage Tolerance Configuration:** * Add optional `slippageTolerance` parameter to all quote request models (V1, V2, V3) * Parameter accepts positive integer values in basis points (e.g., 50 = 0.5%, 100 = 1%, 1000 = 10%) * Helps reduce quote refunds and improve user experience by allowing configurable slippage tolerance * Available on [quote endpoints](/api-reference/quotes/get-quote), [call-quote endpoints](/api-reference/quotes/get-call-quote), and [prepare-call-quote endpoints](/api-reference/quotes/prepare-call-quote) * Also supported on V3 endpoints: [`/v3/quote`](/api-reference/quotes/get-quote-v3), [`/v3/quote/call-quote`](/api-reference/quotes/get-call-quote-v3), and [`/v3/quote/prepare-call-quote`](/api-reference/quotes/prepare-call-quote-v3) * Backward compatible - parameter is optional to maintain existing integrations * See [Slippage Tolerance Guide](/guides/slippage/overview) for detailed configuration and best practices ## Improvements * Create shared slippage tolerance model to avoid code duplication across API versions * Remove deprecated `transfer-quote` and `swap-quote` endpoints and their associated models * Fix status schema validation in transaction history examples * Correct nested status structure to flat format across all history responses * Add missing chain properties to chain operations in examples ## Improvements * Added `symbol`, `name`, and `decimals` fields to the destination-asset-used model to match actual API responses. These optional fields provide token metadata when available. ## Improvements * Fix status field structure: changed from object wrapper to direct string enum * Add missing `chain` field to v1 operation-details model * Add missing `chainId` field to v3 operation-details model for backwards compatibility * Fix all response examples that incorrectly showed `status.status` instead of direct `status` string * Add multi-input response example showing cross-chain aggregation from multiple source chains ## Improvements * Updated account models ## Improvements Enhanced documentation for chain-specific token spending capability and V3 prepare-call-quote endpoint **Chain-Specific Token Spending:** * Added explicit mentions of chain-specific asset spending in [quote endpoint](/api-reference/quotes/get-quote) descriptions * Enhanced asset field descriptions with clear choice explanations between [aggregated and chain-specific assets](/concepts/aggregated-assets) * Added "Asset Flexibility" section to [V1 quote endpoint](/api-reference/quotes/get-quote) highlighting both aggregated and specific asset options * Enhanced [V3 quote endpoint](/api-reference/quotes/get-quote-v3) with prominent "Chain-specific token spending" capability callout * Improved asset field descriptions across quote models with concrete CAIP-19 examples * Better documentation of when to use aggregated vs chain-specific assets for optimal user guidance **V3 Prepare-Call-Quote Documentation:** * Created separate [V3 prepare-call-quote endpoint](/api-reference/quotes/prepare-call-quote-v3) documentation at `/v3/quote/prepare-call-quote` * Added [multi-account support](/concepts/accounts) with mixed Solana and EVM accounts * Documented cross-chain contract call capabilities with source asset bridging * Added examples for Solana USDC/SOL to EVM contract execution * Included [EIP-7702 delegation support](/guides/eip-7702/overview) documentation * Created V3-specific request and response models with multi-account schemas **V3 Call-Quote Documentation:** * Created separate [V3 call-quote endpoint](/api-reference/quotes/get-call-quote-v3) documentation at `/v3/quote/call-quote` * Added [multi-account call execution](/concepts/accounts) with cross-chain support * Documented complex DeFi interactions using multiple account types * Included [EIP-7702 delegation workflow](/guides/eip-7702/getting-started) examples * Created call-request-v3.yaml model for multi-account call requests * Enhanced error handling for multi-account configuration validation ## New Features Add aggregatedAssetId filtering to v3 aggregated balance endpoint and clarify token amount formatting **Breaking change for v3 aggregated balance endpoint:** * Add `aggregatedAssetId` parameter to [`/api/v3/balances/aggregated-balance`](/api-reference/balances/aggregated-balance-v3) endpoint for filtering specific [aggregated assets](/concepts/aggregated-assets) * Parameter supports comma-separated list of aggregated asset IDs (e.g., "ob:eth,ob:usdc,ob:arb") * When provided, response only includes the specified aggregated assets instead of all available assets * **Validation requirement:** At least one of `assetId` or `aggregatedAssetId` must be provided and non-empty * Add 400 Bad Request response for validation errors when neither parameter is provided * Add missing `accounts` field to response schema containing parsed EVM and Solana addresses * Update all response examples to match actual API behavior with correct asset ID prefixes and response structure * Add realistic examples based on actual API responses for both filtered and specific asset queries **Token amount documentation improvements:** * Update token amount field descriptions in schemas to specify that amounts should be provided in the smallest unit considering token decimals * For example, if a token has 6 decimals, "1" token should be represented as "1000000" ## New Features Add EIP-7702 support and improve multi-chain patterns for OpenAPI specification **EIP-7702 Features:** * Add delegation and delegation-signature models for [EIP-7702 authorization flow](/guides/eip-7702/overview) * Enhance [account models](/concepts/accounts) with deploymentType field to distinguish ERC4337 vs EIP7702 accounts * Update chain-operation model with optional delegation field for execution * Add EIP-7702 examples to [prepare-call-quote](/api-reference/quotes/prepare-call-quote) and [call-quote](/api-reference/quotes/get-call-quote) endpoints showing delegation workflow * Include cross-references to [EIP-7702 integration guides](/guides/eip-7702/getting-started) for detailed implementation steps **Multi-chain Improvements:** * Update hash patterns to support both EVM (0x format) and Solana (base58) transaction hashes * Update user address patterns to support both EVM and Solana address formats * Add callType enum and sourceAssetBalances to [prepare-call-quote](/api-reference/quotes/prepare-call-quote) responses * Make factory/factoryData optional in UserOperation schema for deployed accounts * Remove staging server references from [v3 endpoints](/api-reference/quotes/get-quote-v3) (Solana support now in production) **Schema Fixes:** * Fix schema validation warnings for fiatValue, recipientAccount, and account type properties * Improve pattern validation for cross-chain operation examples ## Improvements * Replace `ds:` prefix with `ob:` for [aggregated asset list](/api-reference/assets/list) ## Solana Support (Beta) ### New Features #### Multi-Blockchain Support * **Added Solana blockchain support** alongside existing EVM chains * **Cross-chain operations** between Solana and EVM networks (e.g., SOL → USDC on Arbitrum) * **CAIP standards compliance** using CAIP-2 chain identifiers and CAIP-10 account identifiers #### New v3 API Endpoints * **`POST /v3/quote`** - Generate quotes with multi-account support (EVM + Solana) * **`POST /v3/quote/execute`** - Execute quotes across multiple blockchain types * **`GET /v3/status/get-execution-status`** - Get execution status with cross-chain transaction details * **`GET /v3/status/get-tx-history`** - Retrieve transaction history supporting both EVM and Solana addresses * **`GET /v3/balances/aggregated-balance`** - Get aggregated balances across multiple accounts and chains #### Enhanced Capabilities * **Multi-account architecture** - Single API calls can handle both EVM and Solana accounts * **Cross-chain asset aggregation** - Unified view of assets like USDC across EVM and Solana * **Solana transaction support** - Native SOL and SPL token operations with proper Base58 signatures * **Enhanced examples** - Complete documentation with EVM-only, Solana-only, and cross-chain scenarios ### Technical Improvements * **CAIP-2 chain identifiers** (e.g., `eip155:1`, `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp`) * **CAIP-10 account identifiers** for recipient addressing * **Updated OpenAPI schemas** with new v3 models supporting multi-blockchain operations * **Backward compatibility** - All existing v1/v2 endpoints remain unchanged ### Supported Operations * **Solana native swaps** - SOL ↔ USDC on Solana mainnet * **Cross-chain swaps** - SOL → USDC on EVM chains (Arbitrum, etc.) * **Multi-chain balance queries** - Single request for balances across EVM and Solana accounts * **Transaction tracking** - Unified history view for cross-chain operations ## Improvements * Add new EXECUTED state to [execution status endpoint](/api-reference/status/get-quote-status) # Account Models Source: https://docs.onebalance.io/concepts/accounts Learn about our different account types, including 4337 and 7702 and how to choose the right setup for your app. ## Available Configurations for EVM | Configuration | Validator | Version | Deployment Type | Resource Lock Options | | :------------ | :----------------- | :--------- | :-------------------------------------------------- | :-------------------- | | Basic | ECDSAValidator | Kernel 3.1 | [ERC-4337](https://eips.ethereum.org/EIPS/eip-4337) | Can be enabled | | EIP-7702 | ECDSAValidator | Kernel 3.3 | [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702) | Can be enabled | | Role-Based | RoleBasedValidator | Kernel 3.1 | [EIP-4337](https://eips.ethereum.org/EIPS/eip-4337) | Always enabled | Screenshot2025 05 29at12 32 52 Pn OneBalance supports a modular architecture designed to accommodate various account types. We are happy to support other account versions on demand or advise which one works best for your application. ## Choosing the Right Configuration When selecting an account model for your integration, consider your specific requirements for compatibility, performance, and access control. | Configuration | Pros | Cons | | :------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------ | | Basic | • Most adopted 4337-type account
• Simplest in use and UI/UX
• Easy to integrate, if already in use within the application | • Contract upgrades, if required, are cumbersome | | Role-Based | • Secure in emergency cases related to WaaS/TEE | • Consumes slightly more gas | | EIP-7702 | • Preserves existing EOA addresses
• Gas efficient delegation model
• Built on ERC-4337 infrastructure
• Can act as both EOA and Smart Account | • Recently introduced by the Ethereum account standard, not yet widely battle-proven
• Not yet supported on all EVMs | ## Getting Started with Account Models Follow the [Getting Started guide](/getting-started/introduction), which uses Role-Based configuration. ## Signer Compatibility The OneBalance Toolkit is designed to work with various signer providers: | Signer | Account | Standard Path (no RL) | Enabling Resource Lock | | :------------ | :------------- | :---------------------- | :---------------------- | | `turnkey` | `Basic` | 🟢 Available | 🟢 Available | | | `EIP-7702` | 🟢 Available | 🟢 Available | | | `Role-Based` | RL can't be disabled | 🟢 Available | | | `solana` | 🟢 Available | In design | | `privy` | `Basic` | 🟢 Available | 🟢 Available | | | `EIP-7702` | 🟢 Available | In design | | | `Role-Based` | RL can't be disabled | 🟢 Available | | | `solana` | 🟢 Available | In design | | Other signers | Other accounts | On demand, case by case | On demand, case by case | ## Account Components Explanation The OneBalance account system consists of several key components that can be combined in different ways: **Authentication, Access Control, Permissions** Validator is a module of smart account **Optimized for various deployment methods** Smart Contract Implementation **How accounts are initialized**\ Directly affects user deposit (account) address **Allows fast path cross-chain execution** Requires gatekeeping at the account/signing level ### Supported Validator Types Validators are responsible for authenticating transactions and managing account access: Uses standard Elliptic Curve Digital Signature Algorithm for authentication, compatible with most existing wallet infrastructures. Allows to have a `user_admin` role (think user cold wallet) next to the signer role to rotate keys and execute trustless rage quit in emergency case. ### Supported Smart Account Versions **Compatible with:** Both ECDSAValidator and RoleBasedValidator The versatile version that supports multiple validator types and is widely compatible across different configurations. **Optimized for:** EIP-7702 deployments with ECDSAValidator A specialized version designed to take advantage of the latest EIP-7702 standard for improved gas efficiency. These account versions are based on an open-source smart contract account [implementation](https://github.com/zerodevapp/kernel). ### Deployment Methods The established standard for smart contract accounts across EVM chains. User address in the UI is the Smart Account address, the signer existence is hidden. Extends account abstraction to existing EOAs through delegation. User address in the UI is the EOA address with implementation referenced as a delegated smart contract. ### Resource Lock options for Account Execution happens only traditional way requiring sequential cross-chain transactions. Transactions can be sent both with OneBalance API and standard JSON-RPC. Enhanced execution using [Resource Locks](/concepts/resource-locks) for near-instant cross-chain transactions. Once enabled, transactions can be sent only through OneBalance API. To learn more about how Resource Locks enable Fast Path execution and improve transaction speed\ and reliability, see our detailed [Resource Locks](/concepts/resource-locks) documentation. ## Solana Support OneBalance supports Solana accounts. See our [Solana guides](/guides/solana/overview) for implementation details. ## EIP-7702 Support OneBalance supports [EIP-7702](https://eip7702.io) for applications with existing EOA user bases. See our [EIP-7702 guides](/guides/eip-7702/overview) for implementation details. ## Resources Ethereum Improvement Proposals * [EIP-4337: Account Abstraction Using Alt Mempool](https://eips.ethereum.org/EIPS/eip-4337) * [EIP-7702: Delegated Smart Contract Authentication](https://eips.ethereum.org/EIPS/eip-7702) Community Resources * [ERC-4337 Official Website](https://www.erc4337.io) * [EIP-7702 Official Website](https://eip7702.io) # Aggregated Assets Source: https://docs.onebalance.io/concepts/aggregated-assets Learn how OneBalance aggregates assets across chains into a unified balance, simplifying cross-chain token management and tracking. Aggregated assets are OneBalance's unified token representation that combines the same token across multiple blockchain networks into a single asset ID. Instead of tracking USDC separately on Ethereum, Polygon, and Arbitrum, you simply work with one `ob:usdc` asset that represents all your USDC holdings across supported chains. Think of aggregated assets as your "universal wallet view" - one asset ID that automatically manages all your cross-chain token holdings behind the scenes. ## How Aggregated Assets Work When you interact with an aggregated asset like `ob:usdc`, OneBalance automatically: 1. **Aggregates balances** across all supported chains where you hold that token 2. **Optimizes routing** to spend from the most efficient chain for your transaction 3. **Handles bridging** automatically when cross-chain operations are needed 4. **Provides unified pricing** and fiat value calculations ### The Magic Behind the Scenes Instead of you having to: * Track USDC balances on 5 different chains * Decide which chain has enough balance for your transaction * Manually bridge tokens when needed * Handle different contract addresses and decimals OneBalance does this for you automatically. You just say "spend 100 USDC" and we handle all the complexity. ## Supported Aggregated Assets Each aggregated asset includes: * **Unique ID**: Like `ob:usdc` or `ob:eth` * **Symbol and name**: Human-readable identifiers * **Decimals**: Precision for the aggregated asset * **Individual assets**: The specific chain tokens that make up the aggregated asset Retrieve all supported aggregated assets and their underlying chain-specific tokens ### Example: USDT Aggregated Asset ```json theme={null} { "aggregatedAssetId": "ob:usdt", "symbol": "USDT", "name": "Tether USD", "decimals": 6, "aggregatedEntities": [ { "assetType": "eip155:1/erc20:0xdAC17F958D2ee523a2206206994597C13D831ec7", "decimals": 6, "name": "Tether USD", "symbol": "USDT" }, { "assetType": "eip155:42161/erc20:0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9", "decimals": 6, "name": "Tether USD", "symbol": "USDT" } ] } ``` ## Asset Identification Formats OneBalance supports two types of asset identifiers, each serving different use cases: ### Aggregated Assets * **Format**: `ob:tokenSymbol` (e.g., `ob:usdc`, `ob:eth`) * **Purpose**: Unified representation across all supported chains * **Usage**: Use this for 99% of your applications - it provides automatic chain abstraction and optimization * **Benefits**: OneBalance automatically finds the best chain to execute from and handles cross-chain operations The `ob:` prefix stands for "OneBalance" and indicates you're working with our aggregated asset system. This is the recommended format for most integrations. ### Chain-Specific Assets * **Format**: CAIP-19 standard (e.g., `eip155:1/erc20:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48`) * **Purpose**: Target specific chain implementations only * **Usage**: Only when you need to force execution on a particular blockchain * **Trade-offs**: You lose automatic optimization and may need to handle bridging manually For new tokens that haven't been added to our aggregated list yet, they will temporarily appear as separate chain-specific assets until aggregation is available. View all blockchain networks integrated with OneBalance platform ## Working with Aggregated Assets Get unified token balances across all supported chains for any address Request quotes for swaps and transfers using aggregated assets ### API Examples **Listing Available Assets:** ```bash theme={null} curl -X 'GET' \ 'https://be.onebalance.io/api/assets/list' \ -H 'x-api-key: ONEBALANCE_API_KEY' ``` **Checking Aggregated Balances:** ```bash theme={null} curl -X 'GET' \ 'https://be.onebalance.io/api/v2/balances/aggregated-balance?address=0x123...' \ -H 'x-api-key: ONEBALANCE_API_KEY' ``` **Creating Quotes with Aggregated Assets:** ```json theme={null} { "from": { "account": { /* account details */ }, "asset": { "assetId": "ob:eth" }, "amount": "1000000000000000000" }, "to": { "asset": { "assetId": "ob:usdc" } } } ``` The amount shown above represents 1 ETH (1000000000000000000 wei). For testing, ensure you use amounts worth at least \$1 to avoid gas cost issues. ## Advanced Operations Prepare contract interactions using aggregated assets as funding Execute prepared quotes for aggregated asset operations ## Benefits of Aggregated Assets Work with one asset ID instead of managing multiple chain-specific tokens OneBalance routes transactions through the most efficient chains See total holdings across all chains in one view Cross-chain operations happen automatically when needed ## Monitoring and Status Monitor execution progress of aggregated asset transactions View complete history of aggregated asset operations ## Best Practices 1. **Use aggregated assets by default** for the best user experience 2. **Fall back to specific assets** only when you need chain-specific behavior 3. **Check supported assets** regularly as new tokens and chains are added 4. **Monitor aggregated balances** to understand your users' cross-chain holdings ## Common Issues and Solutions ### Testing with Small Amounts When testing, use at least \$1 worth of tokens. Testing with very small amounts (like \$0.30) can cause transactions to fail due to gas costs exceeding the transaction value. ### New Token Availability Most established tokens are automatically aggregated across chains. For newly launched tokens: * Initially, they may only be available as chain-specific assets * Aggregation is added once the token is verified across multiple chains ### Asset ID Prefix If you encounter asset ID issues: * Always use `ob:` prefixed assets for the best experience * Double-check the asset ID format in our [supported assets list](/api-reference/assets/list) * Contact support if a commonly used token isn't available as an aggregated asset ## Related Resources Set up smart accounts for aggregated asset operations API key setup and authentication for accessing aggregated assets Common issues and troubleshooting for aggregated asset operations API usage guidelines and rate limiting information # Fees & Monetization Source: https://docs.onebalance.io/concepts/fees Understand OneBalance's fee and monetization model, including how fees are calculated and how apps earn revenue through OneBalance transactions. ## Overview OneBalance enables applications to monetize their services through transparent and flexible fee structures. This page explains how fees work, who pays them, and how to configure them for your application. User pays **one consolidated fee** to the app, Toolkit helps app to handle the underlying gas & paymaster costs. ## Fee Types and Responsibilities | **Category** | **Fee Type** | **Who Pays** | **Description** | | :----------- | :----------------------------------------- | :----------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------- | | Gas Fees | Cross-Chain Source or same chain execution | Application => Paymaster | The app pays for the inclusion to the paymaster service in USD | | Gas Fees | Cross-Chain Destination | User=>Solver | This fee is included in the quote the solver provides for execution | | Monetization | Swaps & Transfers | User=>App | App configures the fees and collects them to a dedicated address. The fee is paid in the input token and delivered with every user transaction | | Monetization | Function Calls | User=>App | App calculates the fee it is willing to take and encodes this into the desired calldata to be executed as a transfer | ## Monetization for Swaps and Transfers Our transactional API lets you monetize every on-chain action initiated by your end users. Each time an end user sends a transaction through your application **one transparent service fee** is calculated, composed of: 1. **Percentage fee** - a fixed percentage of the asset amount 2. **Flat fee** - a per-chain USD-denominated amount that offsets per-chain operational costs ### Example of Flat Fee Configuration The following table shows a config example of base costs for different chains. | **Chain** | **CAIP-2 ID** | **Base Cost (USD)** | | :-------------------------------------------------------- | :------------- | :------------------ | |
Ethereum Ethereum Mainnet
| `eip155:1` | \$3.00 | |
Avalanche Avalanche
| `eip155:43114` | \$0.20 | |
BSC BNB Smart Chain
| `eip155:56` | \$0.10 | |
Linea Linea
| `eip155:59144` | \$0.5 | |
Base Base
| `eip155:8453` | \$0.02 | |
Arbitrum Arbitrum
| `eip155:42161` | \$0.02 | |
Polygon Polygon PoS
| `eip155:137` | \$0.02 | |
Blast Blast
| `eip155:81457` | \$0.02 | |
Optimism Optimism
| `eip155:10` | \$0.02 | ### Percentage and benificiary parameters | **Field** | **Type** | **Description** | | :-------------- | :------- | :------------------------------------------------------------------------------------------------------------ | | `percentageBps` | `uint16` | Fee in basis points (1 bp = 0.01%) | | `beneficiary` | `string` | The EVM address that collects the fees. Once non-EVM chains are supported, additional addresses will be added | ### How Fees Are Calculated | **Component** | **Formula** | | :-------------------- | :-------------------------------------------------------------- | | **Percent component** | `percentFee = (amount × percentageBps) / 10,000` | | **USD component** | `usdFee = Σ(chainCostUsd[chain])` | | **Conversion** | USD fee is converted to the input asset using a live price feed | | **Total fee** | `totalFee = percentFee + usdFeeAssetEquivalent` | ### Example Calculation **1 ETH to USDC Swap on Arbitrum** Given: * `percentageBps` = 30 (0.3%) * `1 ETH = 2,500 USDC` ```typescript theme={null} percentFee = 1 ETH × 0.3% = 0.003 ETH flatFee = 0.03 USD × 1 = 0.03 USD → 0.000012 ETH ------------------------------------------------ totalFee = 0.003012 ETH ``` ## Fee Settlement * Fees are transferred to the `beneficiary` address in the same token used for the transaction * For bridge + swap operations, network fees are summed across all chains involved ## Managing Fee Configuration 1. Fee configuration updates are necessary to reflect significant shifts in gas prices or native token values. 2. To update the configuration app needs to contact OneBalance team. ## Coming Soon 1. **Dynamic Fee Adjustment** - Fees will automatically adjust to market shifts based on average gas prices and native token prices in real-time 2. **Self-Service Updates** - Configuration will be updatable via APIs or through the admin dashboard UI **Custom Fee Structures**: Need specialized pricing models or volume discounts? [Contact our sales team](mailto:sales@onebalance.io) to discuss custom fee arrangements for your business. # Resource Locks Source: https://docs.onebalance.io/concepts/resource-locks How Resource Locks enable fast cross-chain transactions by eliminating finality wait times and preventing double-spending ## Overview Resource Locks are OneBalance's innovative solution that transforms cross-chain transactions by delivering three critical improvements: **speed**, **reliability**, and **cost efficiency**. Resource Locks enable cross-chain transactions to execute at the speed of the destination chain while providing cryptographic guarantees and reducing operational costs through optimized settlement patterns. ## How Resource Locks Work Resource Locks are managed by OneBalance's RL Service, which performs four critical functions: Validates user on-chain and locked balances Adds signatures to operations, ensures control over account state transition and prevents double-spending Issues security attestations to solvers for immediate delivery, given delayed settlement Ensures not yet settled balance follows risk policies to maintain system integrity This system enables asynchronous execution where intent fulfillment is separated from settlement, dramatically improving user experience. ## Key Benefits Resource Locks deliver three fundamental improvements to cross-chain operations: Execute cross-chain transactions in **seconds** instead of minutes by eliminating finality wait times and double-spend prevention delays. Cryptographic guarantees ensure **100% transaction success** with OneBalance's co-signing mechanism preventing failed or stuck transactions. Reduce gas costs by **up to 40%** through batched settlements and optimized cross-chain routing patterns. ### Speed: Near-Instant Execution Traditional cross-chain transactions require waiting for source chain finality (often 12+ minutes on Ethereum). Resource Locks eliminate this wait by providing immediate execution guarantees through cryptographic co-signing. ### Reliability: Guaranteed Settlement Unlike intent-based systems that can fail due to solver unavailability or market conditions, Resource Locks provide mathematical certainty that operations will complete successfully. ### Cost Efficiency: Optimized Economics Resource Locks enable transaction batching and optimal routing decisions that significantly reduce the total cost of cross-chain operations for both users and applications. ## Evolution of Cross-Chain Bridging

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

### Classical Bridge: Source Chain Finality + Destination Chain Inclusion Traditional bridges require waiting for source chain finality before processing on the destination chain, resulting in significant delays. ![Classical Bridge Flow](https://storage.googleapis.com/onebalance-public-assets/docs/concepts/ClassicalBridgeFlow.png) ### Intent Bridge: Solver Assumes Finality Risk Intent-based bridges improve speed by having solvers take on the finality risk, but still require source chain inclusion. ![Intent Bridge Flow](https://storage.googleapis.com/onebalance-public-assets/docs/concepts/IntentBridgeFlow.png) ### Fast Intent Bridge: Lock Provider Eliminates Double-Spend and Finality Risk OneBalance's Fast Intent Bridge achieves near-instant cross-chain execution by removing both double-spend risk and finality waiting times through Resource Locks. ![Fast Intent Bridge Flow](https://storage.googleapis.com/onebalance-public-assets/docs/concepts/FastIntentBridgeFlow.png) ## Types of Resource Locks Resource Locks fall into two main categories: 1. **Account-based locks** - Locks are implemented at the account level, allowing seamless compatibility with existing user flows and wallet interactions. 2. **Escrow-based locks** - Instead of locking accounts, this approach implements an escrow smart contract where users deposit tokens for cross-chain operations. Currently, OneBalance primarily uses account-based locks for optimal user experience and compatibility. Once Resource Lock is enabled, transactions can be sent only via OneBalance Toolkit, as otherwise it is impossible to prevent the double-spending in asynchronious execution environment. For detailed integration instructions, see our [Getting Started](/getting-started/introduction) guide. ## Additional Resources Dive deeper into Resource Locks with these resources: Technical deep-dive into Resource Locks architecture, cryptographic proofs, and formal verification of the system's security guarantees. A discussion on how Resource Locks solve cross-chain interoperability challenges and their impact on the ecosystem. Technical breakdown of OneBalance's implementation, covering Resource Locks, account abstraction, and cross-chain execution patterns. # Signing Source: https://docs.onebalance.io/concepts/signing Learn how to sign transactions and operations with OneBalance across different account types and blockchains OneBalance uses different signing patterns depending on your account type and target blockchain. This guide covers all signing methods with complete code examples. ## Overview OneBalance transactions require signing operations to authorize spending and execute actions. The signing process varies by: * **Account type**: Role-based, Kernel 3.3 (EIP-7702), or native Solana accounts * **Operation type**: Token transfers, contract calls, or cross-chain operations * **Target blockchain**: EVM chains use different patterns than Solana All OneBalance operations require proper signing before execution. The API provides structured data that must be signed using the correct method for your account type. ## Account Types and Signing Methods Sign EIP-712 typed data using `signTypedData()` method Sign UserOperation hash using `signMessage()` method Sign versioned transactions using wallet or private key ## TypeScript Types Essential types for implementing signing functionality: ```typescript theme={null} import { HashTypedDataParameters } from 'viem'; export type Hex = `0x${string}`; export enum ContractAccountType { RoleBased = 'role-based', KernelV31 = 'kernel-v3.1-ecdsa', KernelV33 = 'kernel-v3.3-ecdsa', Solana = 'solana', } export interface ChainOperation { userOp: SerializedUserOperation; typedDataToSign: HashTypedDataParameters; assetType: string; amount: string; delegation?: Delegation; } ``` Types specific to EIP-7702 delegation signing: ```typescript theme={null} export enum DelegationSignatureType { Signed = 'Signed', Unsigned = 'Unsigned', } export interface DelegationSignature { chainId: number; contractAddress: Hex; nonce: number; r: Hex; s: Hex; v: Hex; yParity: number; type: DelegationSignatureType; } export interface Delegation { contractAddress: Hex; nonce: number; signature?: DelegationSignature; } ``` Types for UserOperation handling (Kernel 3.1/3.3 accounts): ```typescript theme={null} export 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; } ``` Types specific to Solana operations: ```typescript theme={null} export interface SolanaOperation { type: 'solana'; dataToSign: string; // Base64 encoded message data signature: string; // Base58 encoded signature (initially "0x") accountAddress: string; assetType: string; amount: string; } export interface SolanaAccount { type: 'solana'; accountAddress: string; } export interface QuoteV3 { id: string; account?: EvmAccount | SolanaAccount; originChainsOperations: (ChainOperation | SolanaOperation)[]; destinationChainOperation?: ChainOperation; validUntil?: string; validAfter?: string; expirationTimestamp: string; tamperProofSignature: string; } ``` Account and quote-related types: ```typescript theme={null} export interface EvmAccount { type: 'kernel-v3.3-ecdsa'; deploymentType: 'EIP7702'; accountAddress: Hex; signerAddress: Hex; // Same as accountAddress for 7702 } export interface Quote { id: string; account: EvmAccount; originChainsOperations: ChainOperation[]; destinationChainOperation?: ChainOperation; validUntil?: string; validAfter?: string; expirationTimestamp: string; tamperProofSignature: string; } export type OperationStatus = | 'PENDING' | 'IN_PROGRESS' | 'COMPLETED' | 'REFUNDED' | 'FAILED'; ``` ## EVM Account Signing ### Role-Based Account Signing Role-based accounts use EIP-712 typed data signing for all operations: ```typescript theme={null} import { createWalletClient, custom } from 'viem'; import { privateKeyToAccount } from 'viem/accounts'; // Using private key const account = privateKeyToAccount('0x...' as Hex); const signature = await account.signTypedData(operation.typedDataToSign); // Using browser wallet const walletClient = createWalletClient({ transport: custom(window.ethereum), account: '0x...' }); const signature = await walletClient.signTypedData(operation.typedDataToSign); ``` ```typescript theme={null} import { createWalletClient, custom } from 'viem'; import { ConnectedWallet } from '@privy-io/react-auth'; export const signTypedDataWithPrivy = (embeddedWallet: ConnectedWallet) => async (typedData: any) => { const provider = await embeddedWallet.getEthereumProvider(); const walletClient = createWalletClient({ transport: custom(provider), account: embeddedWallet.address as Address, }); return walletClient.signTypedData(typedData); }; ``` ### EIP-7702 Account Signing EIP-7702 specifically uses Kernel 3.3 accounts which require signing the UserOperation hash, not typed data: **Note**: Kernel 3.3 accounts (EIP-7702) must sign UserOperation hash using `signMessage()`, not typed data. Using the wrong signing method will cause transaction failures. ```typescript theme={null} import { privateKeyToAccount } from 'viem/accounts'; import { entryPoint07Address, getUserOperationHash } from 'viem/account-abstraction'; function deserializeUserOp(userOp: SerializedUserOperation): UserOperation<'0.7'> { return { sender: userOp.sender, nonce: BigInt(userOp.nonce), factory: userOp.factory, factoryData: userOp.factoryData, callData: userOp.callData, callGasLimit: BigInt(userOp.callGasLimit), verificationGasLimit: BigInt(userOp.verificationGasLimit), preVerificationGas: BigInt(userOp.preVerificationGas), maxFeePerGas: BigInt(userOp.maxFeePerGas), maxPriorityFeePerGas: BigInt(userOp.maxPriorityFeePerGas), paymaster: userOp.paymaster, paymasterVerificationGasLimit: userOp.paymasterVerificationGasLimit ? BigInt(userOp.paymasterVerificationGasLimit) : undefined, paymasterPostOpGasLimit: userOp.paymasterPostOpGasLimit ? BigInt(userOp.paymasterPostOpGasLimit) : undefined, paymasterData: userOp.paymasterData, signature: userOp.signature, }; } async function signKernel33Operation( operation: ChainOperation, privateKey: Hex ): Promise { const signerAccount = privateKeyToAccount(privateKey); const chainId = Number(operation.typedDataToSign.domain.chainId); // Handle delegation signing for EIP-7702 if needed if (operation.delegation) { const authTuple = { contractAddress: operation.delegation.contractAddress, nonce: operation.delegation.nonce, chainId: chainId, }; const signedTuple = await signerAccount.signAuthorization(authTuple); if (signedTuple.yParity == null) { throw new Error('Y parity is required'); } operation.delegation.signature = { chainId: chainId, contractAddress: signedTuple.address, nonce: signedTuple.nonce, r: signedTuple.r, s: signedTuple.s, v: `0x${Number(signedTuple.v).toString(16).padStart(2, '0')}` as Hex, yParity: signedTuple.yParity, type: 'Signed', }; } // Sign UserOperation hash for Kernel accounts const deserializedUserOp = deserializeUserOp(operation.userOp); const userOpHash = getUserOperationHash<'0.7'>({ userOperation: deserializedUserOp, entryPointAddress: entryPoint07Address, entryPointVersion: '0.7', chainId: chainId, }); return { ...operation, userOp: { ...operation.userOp, signature: await signerAccount.signMessage({ message: { raw: userOpHash } }) }, }; } ``` ### Universal EVM Signing Function Here's a function that handles both account types automatically: ```typescript theme={null} import { privateKeyToAccount } from 'viem/accounts'; enum ContractAccountType { RoleBased = 'role-based', KernelV31 = 'kernel-v3.1-ecdsa', KernelV33 = 'kernel-v3.3-ecdsa' } async function signOperation( operation: ChainOperation, privateKey: Hex, accountType: ContractAccountType = ContractAccountType.RoleBased, ): Promise { const signerAccount = privateKeyToAccount(privateKey); // Kernel 3.1 and 3.3 accounts sign UserOperation hash if (accountType === ContractAccountType.KernelV31 || accountType === ContractAccountType.KernelV33) { if (!operation.userOp || !operation.typedDataToSign?.domain?.chainId) { throw new Error('UserOperation and Chain ID are required for Kernel signing.'); } const chainId = Number(operation.typedDataToSign.domain.chainId); // Handle delegation signing for EIP-7702 if (operation.delegation) { const authTuple = { contractAddress: operation.delegation.contractAddress, nonce: operation.delegation.nonce, chainId: chainId, }; const signedTuple = await signerAccount.signAuthorization(authTuple); if (signedTuple.yParity == null) { throw new Error('Y parity is required'); } operation.delegation.signature = { chainId: chainId, contractAddress: signedTuple.address, nonce: signedTuple.nonce, r: signedTuple.r, s: signedTuple.s, v: `0x${Number(signedTuple.v).toString(16).padStart(2, '0')}` as Hex, yParity: signedTuple.yParity, type: 'Signed', }; } // Sign UserOperation hash for Kernel 3.1/3.3 accounts const deserializedUserOp = deserializeUserOp(operation.userOp); const userOpHash = getUserOperationHash<'0.7'>({ userOperation: deserializedUserOp, entryPointAddress: entryPoint07Address, entryPointVersion: '0.7', chainId: chainId, }); return { ...operation, userOp: { ...operation.userOp, signature: await signerAccount.signMessage({ message: { raw: userOpHash } }) }, }; } // Role-based accounts sign typed data if (!operation.typedDataToSign) { throw new Error('TypedData is required for role-based account signing.'); } return { ...operation, userOp: { ...operation.userOp, signature: await signerAccount.signTypedData(operation.typedDataToSign) }, }; } ``` ## Solana Account Signing Solana uses a different transaction structure and signing process than EVM chains: **Solana vs EVM**: Solana operations provide `dataToSign` as base64-encoded message data, not typed data structures. This data must be deserialized into a VersionedTransaction for signing. ### Browser Wallet Signing For browser wallets like Phantom or Solflare: ```typescript theme={null} import { MessageV0, VersionedTransaction } from '@solana/web3.js'; import bs58 from 'bs58'; /** * Signs a Solana operation using a browser wallet * @param dataToSign - Base64 encoded data from quote response * @param wallet - Connected Solana wallet (Phantom, Solflare, etc.) * @returns Base58 encoded signature */ async function signSolanaOperation(dataToSign: string, wallet: any): Promise { try { // 1. Convert base64 data to message buffer const msgBuffer = Buffer.from(dataToSign, 'base64'); // 2. Deserialize into MessageV0 const message = MessageV0.deserialize(msgBuffer); // 3. Create versioned transaction const transaction = new VersionedTransaction(message); // 4. Sign with wallet const signedTx = await wallet.signTransaction(transaction); // 5. Extract signature and encode as base58 const signature = bs58.encode(Buffer.from(signedTx.signatures[signedTx.signatures.length - 1])); return signature; } catch (error) { console.error('Error signing Solana operation:', error); throw new Error(`Failed to sign Solana transaction: ${error}`); } } ``` ### Private Key Signing For server-side operations or direct private key access: ```typescript theme={null} import { MessageV0, VersionedTransaction, PublicKey } from '@solana/web3.js'; import bs58 from 'bs58'; /** * Signs a Solana chain operation with a private key * @param accountAddress - The address of the account to sign the chain operation * @param privateKey - The private key in base58 format * @param chainOp - The chain operation object from quote response * @returns The signed chain operation with signature added */ export function signSolanaChainOperation( accountAddress: string, privateKey: string, chainOp: SolanaOperation, ): SolanaOperation { const msgBuffer = Buffer.from(chainOp.dataToSign, 'base64'); const message = MessageV0.deserialize(msgBuffer); const transaction = new VersionedTransaction(message); const decodedKey = bs58.decode(privateKey); transaction.sign([ { publicKey: new PublicKey(accountAddress), secretKey: Buffer.from(decodedKey), }, ]); const signature = bs58.encode(Buffer.from(transaction.signatures[transaction.signatures.length - 1])); return { ...chainOp, signature, }; } ``` ### Quote-Level Solana Signing For signing complete V3 quotes with multiple operations: ```typescript theme={null} // Types available from the TypeScript Types section above /** * Sign a Solana quote using the provided wallet * @param quote - Quote response to sign * @param solanaWallet - Solana wallet for signing * @returns Signed quote ready for execution */ export async function signSolanaQuote(quote: QuoteV3, solanaWallet: any): Promise { try { const signedOperations = await Promise.all( quote.originChainsOperations.map(async operation => { if ('type' in operation && operation.type === 'solana') { const solanaOp = operation as SolanaOperation; // Skip if already signed (signature is not empty or "0x") if (solanaOp.signature && solanaOp.signature !== '0x' && solanaOp.signature !== '') { return solanaOp; } const signature = await signSolanaOperation(solanaOp.dataToSign, solanaWallet); // Create the signed operation, ensuring we replace the signature properly const signedOp = { ...solanaOp, signature: signature, // Replace the "0x" placeholder with actual signature }; return signedOp; } return operation; }) ); const signedQuote = { ...quote, originChainsOperations: signedOperations, }; return signedQuote; } catch (error) { console.error('Error signing Solana quote:', error); throw error; } } ``` ## Common Patterns ### Sequential Quote Signing For quotes with multiple operations that need to be signed in sequence: ```typescript theme={null} // 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([]) ); }; 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; }; ``` ### Error Handling Best Practices ```typescript theme={null} export function getReadableStatus(status: string): string { switch (status) { case 'PENDING': return 'Transaction pending...'; case 'IN_PROGRESS': return 'Processing transaction...'; case 'COMPLETED': return 'Transaction completed successfully!'; case 'FAILED': return 'Transaction failed'; case 'REFUNDED': return 'Transaction refunded'; default: return `Status: ${status}`; } } // Always wrap signing operations in try-catch async function safeSignOperation(operation: any, signer: any) { try { return await signOperation(operation, signer); } catch (error) { console.error('Signing failed:', error); throw new Error(`Failed to sign operation: ${error.message}`); } } ``` ## Troubleshooting ### Common Issues **Problem**: Using `signTypedData()` for Kernel 3.3 accounts or `signMessage()` for role-based accounts. **Solution**: Check your account type and use the correct signing method: * Role-based: Use `signTypedData()` * Kernel 3.3 (EIP-7702): Use `signMessage()` with UserOperation hash **Problem**: Solana signatures are not in base58 format or wrong length. **Solution**: Ensure you extract the signature correctly: ```typescript theme={null} const signature = bs58.encode(Buffer.from(signedTx.signatures[signedTx.signatures.length - 1])); ``` **Problem**: EIP-7702 operations fail due to missing delegation signatures. **Solution**: Always check if delegation is required and sign it first: ```typescript theme={null} if (operation.delegation) { const signedTuple = await signerAccount.signAuthorization(authTuple); // ... handle delegation signature } ``` **Problem**: Kernel 3.3 account signatures fail due to incorrect UserOperation hash calculation. **Solution**: Ensure proper deserialization and use correct entrypoint: ```typescript theme={null} const deserializedUserOp = deserializeUserOp(operation.userOp); const userOpHash = getUserOperationHash<'0.7'>({ userOperation: deserializedUserOp, entryPointAddress: entryPoint07Address, entryPointVersion: '0.7', chainId: chainId, }); ``` ## Next Steps Learn about EIP-7702 delegation and Kernel account setup Get started with Solana integration and cross-chain operations Execute smart contract calls using proper signing patterns Build a complete cross-chain swap with signing implementation # Transaction Lifecycle Source: https://docs.onebalance.io/concepts/transaction-lifecycle Learn the full lifecycle of a OneBalance transaction, from quote generation and signing to execution and final confirmation. The numbers shown below are indicative and strongly depend on the use case conditions. Not sure about the term? Visit [Glossary](/resources/glossary) to see all the definitions. Screenshot2025 05 28at21 11 09 Pn ## Transaction Types and Properties Swap and transfer any token on any supported chain. Execute any smart contract calls on any chain, spending the aggregated balance while preserving `msg.sender` **Fast Path** for cross-chain execution removes source chain txs latency. **Standard Path** is a basic sequential execution for cross-chain operations Read more on Fast path and Resource locks [here](/concepts/resource-locks). ## Transaction Execution Sequence (Swap example) ```mermaid theme={null} sequenceDiagram participant User participant OneBackend participant RL Service participant Solver participant SourceChain participant TargetChain User->>OneBackend: Submit request OneBackend->>Solver: Request quote Solver-->>OneBackend: Return quote OneBackend->>User: Return quote User->>User: Sign source chain operation User->>OneBackend: Submit signed operation OneBackend->>RL Service: Forward signed operation RL Service->>RL Service: Verify & cosign operation RL Service->>OneBackend: Return operation & guarantees OneBackend->>Solver: Submit operation & guarantees Note over User,TargetChain: Fast Path via same chain Solver->>TargetChain: Execute target chain operation OneBackend->>TargetChain: Query execution result OneBackend->>User: Confirm success Note over User,TargetChain: Fast Path via cross-chain Solver->>TargetChain: Execute target chain operation OneBackend->>TargetChain: Query execution result OneBackend->>User: Confirm success OneBackend->>SourceChain: Execute source chain operation Note over User,TargetChain: Standard Path OneBackend->>SourceChain: Execute source chain operation Solver->>SourceChain: Query execution result Solver->>TargetChain: Execute target chain operation OneBackend->>TargetChain: Query execution result OneBackend->>User: Confirm success ``` ## Execution Flow Walkthrough ### 1. Initial Request Phase * User submits an intent (e.g., swap ETH aggregated across chains for SOL on Solana) * OneBackend handles the request, performing routing and other optimizations * Note that spending can happen from multiple chains within the same intent without affecting the flow. Our service determines how many funds are available on each chain and whether they are eligible for Fast or Standard Path execution. ### 2. Providing the Quote * The solver system provides a quote that specifies pricing, including gas and swap fees * The user receives the quote and decides whether to proceed * The user signs the operation with their session key and sends it back to OneBackend ### 3. Validation and Security Phase * OneBackend forwards the signed operation to our RL Service * The RL Service verifies the user's request and provides security attestation * Based on security status, the transaction follows one of these paths: * Fast Path via same-chain execution * Fast Path via cross-chain execution (immediate execution with security guarantees) * Standard Path (execution after the escrow transaction is confirmed) ### 4. Execution Phase OneBackend submits operations and security guarantees to the solver. The execution then follows one of three paths: Same-chain Fast Path is the quickest execution method, completing in seconds. 1. Solver executes the operation on the target chain 2. OneBackend verifies execution and confirms success to the user 3. OneBackend executes the operation on the source chain Cross-chain Fast Path provides immediate execution with security guarantees, bypassing source chain finality wait times. 1. Solver executes the operation on the target chain 2. OneBackend verifies execution and confirms success to the user 3. OneBackend executes the operation on the source chain Standard Path follows traditional sequential execution and is used when Fast Path conditions are not met. This path is usually at least 2x slower than the Fast Path due to waiting for source chain finality. 1. OneBackend executes the operation on the source chain 2. Solver verifies execution on the source chain 3. Solver executes the operation on the target chain 4. OneBackend verifies execution and confirms success to the user ## Fast Path Execution Criteria A multichain intent leverages the Fast Path if it's routed on the same chain **OR** if all of the following conditions are met: 1. User has enough finalized, non-pre-approved on-chain balance (aggregated across chains) that is not yet locked (spent but not yet settled) 2. There is no source-chain swap in the execution flow 3. RL Service does not exceed the total open (not yet settled) position according to the risk policy # FAQ Source: https://docs.onebalance.io/faq Frequently asked questions about OneBalance, with quick answers on features, support, and developer resources. Get answers to the most frequently asked questions about OneBalance. General questions about OneBalance Interacting with OneBalance API Transaction signing across account types and blockchains Solana-specific questions and troubleshooting AI and LLM integration with OneBalance # FAQ: AI Source: https://docs.onebalance.io/faq/ai AI-focused FAQs covering how LLMs and MCP integrate with the OneBalance Toolkit for automated, AI-driven API interactions. ## How do I get started with AI integration? OneBalance offers multiple ways to integrate with AI tools. The easiest approach depends on your use case: * **For general LLM integration**: Use our [LLM feed files](/ai/building-with-llms#llm-feed-files) (`llms.txt` and `llms-full.txt`) * **For code editors like Cursor**: Add our documentation directly to your [editor's indexing](/ai/building-with-llms#code-editor-integration) * **For advanced API access**: Set up our [MCP server](/ai/mcp-server-setup) for live OneBalance API functions Start with the [Building with LLMs](/ai/building-with-llms) guide to explore all options. ## What's the difference between llms.txt and llms-full.txt? We provide two different feed files for different use cases: * **`llms.txt`** - A concise, high-signal list of top-level documentation pages. Perfect for smaller models or when you need quick context building. * **`llms-full.txt`** - Complete documentation content including all pages. Ideal for full-context indexing and AI training. Both files are automatically updated and available at: * [https://docs.onebalance.io/llms.txt](https://docs.onebalance.io/llms.txt) * [https://docs.onebalance.io/llms-full.txt](https://docs.onebalance.io/llms-full.txt) Learn more about [LLM feed files](/ai/building-with-llms#llm-feed-files). ## How do I use OneBalance docs in my code editor? For **Cursor users**: 1. Navigate to **Cursor Settings** > **Indexing & Docs** 2. Select "Add Doc" and paste: `https://docs.onebalance.io/llms-full.txt` 3. Use `@docs -> OneBalance` to reference docs in your code You can also access any documentation page as plain markdown by adding `.md` to the URL (e.g., `/getting-started/introduction.md`). See the complete [code editor integration guide](/ai/building-with-llms#code-editor-integration). ## What is the MCP server and how do I set it up? The Model Context Protocol (MCP) server connects OneBalance's documentation and API directly to your AI tools. It provides: * **Documentation search** across all OneBalance docs * **Live API access** to OneBalance functions (balances, quotes, transactions, etc.) * **Direct integration** with Cursor, Claude Desktop, and other MCP clients **Quick setup:** Simply connect to our hosted MCP server at `https://docs.onebalance.io/mcp` in your AI tool. The MCP server gives you documentation search and full OneBalance API access. Follow our complete [MCP server setup guide](/ai/mcp-server-setup) for detailed instructions. ## Can I test the OneBalance API without my own key? Yes! We provide a public API key for testing purposes with limited usage: ``` 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11 ``` You can use this key when setting up the [MCP server](/ai/mcp-server-setup) or making direct [API calls](/api-reference/introduction). This lets you explore OneBalance functionality before getting your own API key. For production use, you'll need your own API key from your dashboard or by using the **Intercom chat widget** in the bottom right corner or contacting [support@onebalance.io](mailto:support@onebalance.io). # FAQ: API Source: https://docs.onebalance.io/faq/api Common questions about OneBalance API usage, authentication, rate limits, error handling, and integration patterns ## How can I generate an API access token? The OneBalance API uses API keys to authenticate requests. All API requests require authentication using an API key passed in the `x-api-key` header: ```bash Terminal theme={null} curl -X 'GET' \ 'https://be.onebalance.io/api/assets/list' \ -H 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' ``` A public API key is available for testing purposes with limited usage: **42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11** For production use, use the **Intercom chat widget** in the bottom right corner or contact [support@onebalance.io](mailto:support@onebalance.io) to get your custom API token with higher rate limits and full access to all features. All API requests must be made over [HTTPS](https://en.wikipedia.org/wiki/HTTPS). Calls made over plain HTTP will fail. API requests without authentication will also fail. Learn more in our [Authentication](/api-reference/authentication) guide. ## What is a base URL? OneBalance API is built on REST principles and is served over HTTPS. To ensure data privacy, unencrypted HTTP is not supported. The Base URL for all API endpoints is: ```bash Terminal theme={null} https://be.onebalance.io/api ``` See our [API Reference Introduction](/api-reference/introduction) for more details. ## How to fix CORS errors? OneBalance API implements Cross Origin Resource Sharing (CORS) to allow requests from browsers on different domains. CORS is generally handled by the server, not the client. If you're experiencing CORS errors in your browser application, you have several options: 1. **Use a server-side proxy** - Create a backend service that makes requests to OneBalance API and returns the data to your frontend 2. **Implement a middleware in Next.js** - If using Next.js, create route handlers that proxy the requests to the API 3. **Contact us** - Use the Intercom chat widget or email support if you need your domain allowed for direct browser access Learn more about CORS in the [API Reference](/api-reference/cors). ## What are the rate limits? The OneBalance API implements rate limiting to ensure fair usage and availability of the service for all users: * Public API Key: 100 requests per minute * Authenticated Users: 400 requests per minute If you exceed the rate limit, the API will return a `429 Too Many Requests` response. To handle rate limits effectively, monitor the rate limit headers in your API responses: ```bash Terminal theme={null} X-RateLimit-Limit: 400 X-RateLimit-Remaining: 397 X-RateLimit-Reset: 1678364102 ``` Learn more in the [Rate Limits](/api-reference/rate-limits) documentation. ## How do I handle API errors? The API uses standard HTTP status codes to indicate success or failure: * `2xx` - Success * `4xx` - Client error (like invalid parameters) * `5xx` - Server error For errors, the API returns a JSON response with details about what went wrong: ```json Error Response theme={null} { "id": "some_error_id", "error": "ErrorType", "message": "A description of what went wrong", "statusCode": 400, "timestamp": "2024-12-18T14:38:24.793Z", "path": "/api/endpoint" } ``` Always check the `message` field for details on how to resolve the issue, and include the `id` when using the **Intercom chat widget** or contacting support. See the [Errors](/api-reference/errors) documentation for more information. ## What are quote execution failures and how do I handle them? Quote execution failures occur when a quote request succeeds (returns 200) but the actual execution fails due to business logic reasons. These failures are marked as `REFUNDED` and include a `failReason` field. **Common fail reasons include:** * **SLIPPAGE**: Price moved beyond tolerance * **TRANSFER\_AMOUNT\_EXCEEDS\_BALANCE**: Insufficient token balance * **TRANSFER\_AMOUNT\_EXCEEDS\_ALLOWANCE**: Token approval needed * **ORDER\_EXPIRED**: Quote validity period expired * **NO\_QUOTES**: No liquidity available for the token pair * **EXECUTION\_REVERTED**: On-chain execution failed **How to handle execution failures:** ```typescript theme={null} const status = await getExecutionStatus(quoteId); if (status.status === 'REFUNDED' && status.failReason) { switch (status.failReason) { case 'SLIPPAGE': // Retry with higher slippage tolerance console.log('Price changed, retrying with updated quote'); break; case 'TRANSFER_AMOUNT_EXCEEDS_BALANCE': // Inform user about insufficient balance console.log('Insufficient balance for this transaction'); break; case 'TRANSFER_AMOUNT_EXCEEDS_ALLOWANCE': // Request token approval console.log('Token approval needed'); break; case 'ORDER_EXPIRED': // Generate fresh quote console.log('Quote expired, generating new quote'); break; default: console.log(`Quote failed: ${status.failReason}`); } } ``` For a complete list of fail reasons with detailed explanations, see [Error Codes](/api-reference/error-codes) and [Refund Reasons Guide](/guides/quotes/refund-reasons). ## How can I paginate results? For endpoints that return large collections of items, the API uses cursor-based pagination. To paginate results: 1. Specify a `limit` parameter to control the number of items per page 2. Use the `continuation` token from the response to fetch the next page ```bash theme={null} // First request GET /api/status/get-tx-history?user=0x123...&limit=10 // Next page - use continuation token from previous response GET /api/status/get-tx-history?user=0x123...&limit=10&continuation=txn_abc123 ``` Check the [Pagination](/api-reference/pagination) documentation for details. ## How do I work with aggregated assets? Aggregated assets are OneBalance's unified representation of tokens across multiple chains. For example, `ob:usdc` represents USDC across all supported networks. Key endpoints for working with aggregated assets: * [List Assets](/api-reference/assets/list) - Get all supported aggregated assets * [Aggregated Balance](/api-reference/balances/aggregated-balance) - Check balances across chains * [Get Quote](/api-reference/quotes/get-quote) - Create quotes using aggregated assets Learn more about how aggregated assets work in our [Aggregated Assets](/concepts/aggregated-assets) guide. ## What does `ob:single` mean in API responses? `ob:single` is a display identifier for non-aggregated tokens in API responses. It appears when you request a quote to a specific CAIP format asset instead of an aggregated asset. **When you'll see `ob:single`:** ```json theme={null} { "destinationToken": { "aggregatedAssetId": "ob:single", "assetType": "eip155:59144/erc20:0x1789e0043623282d5dcc7f213d703c6d8bafbb04", "amount": "64631578570695154591", "fiatValue": "1.78" } } ``` `ob:single` is only a representation identifier. You cannot use it to request quotes or swaps. Use the actual CAIP format asset ID from the `assetType` field instead. **Example scenario:** When you swap from an aggregated asset to a specific token: * **From**: `ob:usdc` (aggregated) * **To**: `eip155:42161/erc20:0xaf88d065e77c8cC2239327C5EDb3A432268e5831` (specific CAIP format) * **Response shows**: `ob:single` as the `aggregatedAssetId` with the actual token in `assetType` This helps you identify non-aggregated assets in responses from endpoints like transaction history and quote status. ## How do I track transaction status? OneBalance provides transaction tracking through our Status API: * [Get Quote Status](/api-reference/status/get-quote-status) - Track individual quote execution * [Get Transaction History](/api-reference/status/get-transaction-history) - View complete transaction history Understanding the [Transaction Lifecycle](/concepts/transaction-lifecycle) will help you better interpret status responses and execution paths. ## What is slippage tolerance and when should I use it? Slippage tolerance defines the acceptable price movement between quote generation and execution. When prices move beyond your tolerance, the transaction will fail rather than execute at an unfavorable rate. **When to use slippage tolerance:** * **High-volume applications** - Reduce operational overhead from failed quotes * **Volatile market conditions** - Prevent unnecessary failures from minor price movements * **User-facing applications** - Give users control over their risk tolerance Add slippage tolerance to any quote request: ```json theme={null} { "from": { "account": { "sessionAddress": "0x...", "adminAddress": "0x...", "accountAddress": "0x..." }, "asset": { "assetId": "ob:usdc" }, "amount": "1000000" }, "to": { "asset": { "assetId": "ob:eth" } }, "slippageTolerance": 100 } ``` Learn more in our [Slippage Tolerance Overview](/guides/slippage/overview). ## What slippage tolerance values should I use? Slippage tolerance is specified in basis points as a positive integer: **Recommended values by asset type:** * **Stablecoins** (USDC ↔ USDT): `10-25` basis points (0.1-0.25%) * **Major tokens** (ETH ↔ USDC): `50-100` basis points (0.5-1%) * **Volatile assets** (altcoins): `100-500` basis points (1-5%) **Start with 100 basis points (1%)** for most use cases, then adjust based on: * Asset volatility * Market conditions * User preferences * Success rate monitoring See [working examples](/guides/slippage/examples) for different scenarios and implementation patterns. ## My transactions still fail despite setting slippage tolerance. What should I do? If transactions continue to fail with slippage tolerance, implement progressive retry logic: ```typescript theme={null} async function executeWithRetry(baseRequest) { const slippageValues = [50, 100, 200]; // 0.5%, 1%, 2% for (const slippage of slippageValues) { try { const quote = await getQuote({ ...baseRequest, slippageTolerance: slippage }); return await executeQuote(quote); } catch (error) { console.log(`Failed at ${slippage / 100}% slippage`); if (slippage === 200) { throw error; // Final attempt failed } } } } ``` **Common causes and solutions:** * **Slippage too low** → Increase tolerance or use adaptive values * **Network congestion** → Wait for lower congestion periods * **Large trade size** → Break into smaller trades * **Volatile market** → Use higher slippage for volatile periods Check our [troubleshooting guide](/guides/slippage/troubleshooting) for more solutions. ## EIP-7702 Implementation ### Should I choose EIP-7702 for my app? **EIP-7702 is designed for apps with existing EOA users.** It provides: * **Address preservation** - users keep their familiar addresses * **Single-click experience** with automatic delegation * **Gas abstraction** - sponsored transactions hide complexity * **Seamless integration** - existing endpoints remain unchanged Choose EIP-7702 if you have existing EOA users who need smart account benefits without changing addresses. ### What is the difference between EIP-7702 and ERC-4337 accounts? **EIP-7702 accounts** (For existing EOA users): * Use your existing EOA address - users keep familiar addresses * Seamless user experience with address preservation * Automatic delegation bundled with first transaction * Gas abstraction built-in * Cannot use Resource Locks with EOA-based setup **ERC-4337 accounts**: * Use a new Smart Contract Account address * Established standard with broad compatibility * More straightforward technical integration * Full Resource Lock compatibility ### Do users need to do anything special for EIP-7702 accounts? **No - it's completely automatic.** Users only see their intended action (swap, transfer, etc.). OneBalance automatically bundles the delegation with their first transaction on each chain. From the user's perspective, it's a single click with no gas fees. ### Do I need to change my existing code for EIP-7702? **No additional engineering work required.** All existing OneBalance endpoints remain unchanged. EIP-7702 support is handled automatically by the platform when you configure your account type: ```json theme={null} { "account": { "type": "kernel-v3.3-ecdsa", "deploymentType": "EIP7702" } } ``` ### What about multi-input scenarios with EIP-7702? The delegation process currently supports only one source chain asset different from the destination chain asset. For complex scenarios requiring multiple input assets from different chains, you can manually submit delegations ahead of requesting quotes. **Coverage**: Majority of flows work in single transactions, remainder require manual delegation fallback. We provide clear code examples for this approach using [viem library](https://viem.sh) calls and RPC methods. See our [troubleshooting guide](/guides/eip-7702/troubleshooting). ### Can I use Resource Locks with EIP-7702? Not with EOA-based EIP-7702 accounts. Resource Locks require security guarantees that are impossible when users control the private key directly. However, a **WaaS/Turnkey pathway** is in development that enables Resource Locks with EIP-7702 by using managed wallets with proper security controls. For applications requiring Resource Locks today, use ERC-4337 accounts. ### Is EIP-7702 production ready? **Yes, EIP-7702 support is live in production** on all supported EVM chains. Technical capabilities: * Majority of cross-chain flows complete in single transactions * Single-transaction bundling prevents stuck funds * Works with existing wallet infrastructure * Supports all EVM chains in OneBalance network ### What are common EIP-7702 use cases? EIP-7702 is used for: * **Cross-chain DeFi operations** (lending, DEX, derivatives) * **Multi-asset transactions** with single-transaction execution * **EOA migration to smart accounts** without address changes * **Gas-sponsored transactions** for improved user experience **Technical scope**: Majority of complex flows can execute in single transactions, with fallback options for edge cases requiring multiple input chains. ## Solana Support ### How do I use Solana with OneBalance? Solana operations require **v3 API endpoints** which support multiple blockchain accounts. Use the accounts array format with Solana account type: ```json theme={null} { "from": { "accounts": [{ "type": "solana", "accountAddress": "J5CCzBULFax899tcirb6wMbenQUd8whbaetG7EfSick5" }], "asset": { "assetId": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501" }, "amount": "10000000" } } ``` Key v3 endpoints: * [Get Quote v3](/api-reference/quotes/get-quote-v3) - `POST /api/v3/quote` * [Execute Quote v3](/api-reference/quotes/execute-quote-v3) - `POST /api/v3/quote/execute` * [Aggregated Balance v3](/api-reference/balances/aggregated-balance-v3) - `GET /api/v3/balances/aggregated-balance` ### Can I swap between Solana and EVM chains? Yes! OneBalance supports cross-chain operations between Solana and EVM chains. You can: * **Solana → EVM**: Swap SOL to USDC on Arbitrum * **EVM → Solana**: Swap USDC on Ethereum to SOL * **Multi-account**: Use aggregated balances from both chains Example cross-chain swap: ```json theme={null} { "from": { "accounts": [{ "type": "solana", "accountAddress": "..." }], "asset": { "assetId": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501" }, "amount": "10000000" }, "to": { "asset": { "assetId": "eip155:42161/erc20:0xaf88d065e77c8cC2239327C5EDb3A432268e5831" }, "account": "eip155:42161:0x895Cf62399bF1F8b88195E741b64278b41EB7F09" } } ``` ### How is Solana signing different from EVM? Solana uses a different transaction signing process: 1. **Data format**: Solana operations provide base64 encoded `dataToSign` 2. **Message format**: Convert to `MessageV0` using `@solana/web3.js` 3. **Transaction format**: Create `VersionedTransaction` 4. **Signature format**: Return base58 encoded signature ```typescript theme={null} import { MessageV0, VersionedTransaction } from '@solana/web3.js'; import bs58 from 'bs58'; // Convert OneBalance data to Solana transaction const message = MessageV0.deserialize(Buffer.from(dataToSign, 'base64')); const transaction = new VersionedTransaction(message); // Sign and encode const signedTx = await wallet.signTransaction(transaction); const signature = bs58.encode(Buffer.from(signedTx.signatures[signedTx.signatures.length - 1])); ``` ### What Solana tokens are supported? Currently supported on Solana: * **SOL** (native token) * **USDC** (SPL token) More tokens will be added based on demand. Check our [assets list](/api-reference/assets/list) for the most current supported tokens. ### Are call data operations supported on Solana? **Not yet.** Call data operations (contract interactions) are coming soon but currently not supported on Solana. Current Solana support includes: * ✅ Swaps and transfers * ✅ Cross-chain operations * ✅ Balance queries * 🚧 Call data (coming soon) ### Can I use only Solana accounts without EVM? Yes! You can use Solana-only operations by providing just a Solana account in the accounts array: ```json theme={null} { "accounts": [{ "type": "solana", "accountAddress": "J5CCzBULFax899tcirb6wMbenQUd8whbaetG7EfSick5" }] } ``` # FAQ: General Source: https://docs.onebalance.io/faq/general General FAQs about the OneBalance Toolkit, covering product usage, integration steps, troubleshooting, and developer support details. ## What is OneBalance? OneBalance is a chain abstraction toolkit that enables developers to create seamless blockchain experiences. It provides a set of APIs for managing smart accounts, tracking cross-chain balances, and executing multichain transactions, all through a unified interface. The platform simplifies complex blockchain operations by abstracting away the technical details, allowing developers to focus on building great user experiences. Learn more in our [What is OneBalance](/overview/what-is-onebalance) overview. ## What problems does OneBalance solve? OneBalance solves several key challenges in blockchain development: * **Fragmented Balances**: Consolidates assets across multiple chains into a single [aggregated balance](/concepts/aggregated-assets) * **Complex Transactions**: Simplifies cross-chain transfers and swaps through our [Transaction Lifecycle](/concepts/transaction-lifecycle) * **Account Management**: Streamlines the creation and management of [smart contract accounts](/concepts/accounts) * **Development Complexity**: Reduces the technical overhead of supporting multiple blockchains By addressing these issues, OneBalance makes blockchain applications more accessible and user-friendly. ## Which networks does OneBalance support? OneBalance supports a growing list of networks, including: * Ethereum (Mainnet) * Arbitrum * Optimism * Base * Polygon * Linea * Avalanche * BNB Smart Chain (BSC) * Blast * Berachain * Unichain * HyperEVM * Solana (Beta) For the most up-to-date list of supported chains, you can check the [supported networks](/resources/supported-networks) page or use our [Chains API](/api-reference/chains/list). ## What is an aggregated asset? An aggregated asset in OneBalance represents a collection of the same token across multiple blockchains. For example, USDC exists on Ethereum, Arbitrum, Base, and other networks. OneBalance treats these as a single aggregated asset with the identifier `ob:usdc`. This approach allows users to: * View total balances across all chains * Spend from the aggregated balance without worrying about which chain the tokens are on * Simplify transfers and swaps using unified token identifiers You can see the full list of aggregated assets via the [Assets API](/api-reference/assets/list) or learn more in our [Aggregated Assets](/concepts/aggregated-assets) guide. ## How does pricing work for OneBalance? OneBalance offers flexible pricing based on usage and features: * **API Access**: Different tiers based on request volume and features * **Transaction Processing**: Competitive fees for cross-chain operations * **Enterprise Solutions**: Custom pricing for high-volume users and specialized requirements For detailed pricing information and to discuss your specific needs, [contact our sales team](mailto:sales@onebalance.io). Learn more about how fees are calculated in our [Fees & Monetization](/concepts/fees) guide. ## How does this relate to account abstraction and 4337? Account abstraction and 4337 are often associated with Smart Contract Accounts and refer to both the set of UX improvements offered by an SCA (account abstraction) and a specific standard for implementation of these UX improvements (EIP-4337). The OneBalance account model provides the same UX improvements as SCAs and is backward compatible with using 4337 for the settlement of locks on the chain as user ops. However, since OneBalance is a general framework for the creation of credible accounts, it is not limited to chains where account abstraction and 4337 are actively being used. OneBalance is supportive of chains and proxy accounts which opt for using dos resistant user ops like 4337. Learn more about our account implementations in [Account Models](/concepts/accounts). ## Where does the OneBalance account live? The account lives in a secure machine that can make credible commitments about what messages it will and won't sign. On-chain identity can vary depending on the CCM design and the chain itself, overall it is either EOA or SCA. OneBalance accounts are not opinionated on the type of credible commitment enforcement mechanism used. Crucially, as the sophistication of these architectures evolves over time, the tradeoff space will change and so will user preferences. As such, OneBalance accounts will remain flexible to support different architectures. For technical details, see our [Account Models](/concepts/accounts) documentation. ## Are OneBalance accounts non-custodial? Yes. The system is designed such that nobody can use the funds or, generally, change the state of the account without the user's permission. Learn more about account security and [Resource Locks](/concepts/resource-locks) that enable this non-custodial model. ## How do I get started with OneBalance? The fastest way to get started is to follow our step-by-step [Onboarding Checklist](/overview/onboarding-checklist), which takes you from zero to production-ready in under an hour. For a quick overview, here's the basic flow: 1. **Understand the Basics**: Review [What is OneBalance](/overview/what-is-onebalance) and [How it Works](/overview/how-onebalance-works) 2. **Build a PoC**: Follow our [Getting Started](/getting-started/introduction) guide to make your first API call 3. **Explore Examples**: Check out our [guides](/guides/overview) and [API Reference](/api-reference/introduction) 4. **Go to Production**: Contact us for production API keys and deployment support ## Where can I get help with OneBalance? If you need assistance with OneBalance, we offer several support channels: * **Intercom Chat**: Use the chat widget in the bottom right corner for instant help from our team * **Documentation**: Complete guides available at [docs.onebalance.io](https://docs.onebalance.io) * **Community Support**: Follow us on [X/Twitter](https://x.com/OneBalance_io) for updates and community assistance * **Email Support**: Contact us at [support@onebalance.io](mailto:support@onebalance.io) for technical help * **Enterprise Support**: Enterprise customers receive dedicated support channels We're committed to helping developers successfully implement OneBalance in their applications. You can also check our [Glossary](/resources/glossary) for definitions of technical terms. # FAQ: Signing Source: https://docs.onebalance.io/faq/signing Common questions about transaction signing across different account types, blockchains, and integration patterns ## Which signing method should I use for my account type? The signing method depends on your account type: * **Role-Based Accounts**: Use `signTypedData()` to sign EIP-712 structured data * **Kernel 3.3 Accounts (EIP-7702)**: Use `signMessage()` to sign the UserOperation hash directly * **Solana Accounts**: Use wallet's `signTransaction()` method on VersionedTransaction objects **Note**: Using the wrong signing method will cause transaction failures. Kernel 3.3 accounts must sign UserOperation hash, not typed data. For complete implementation details, code examples, and troubleshooting, see the [Signing Guide](/concepts/signing). ## Why is my Solana transaction signing failing? Common Solana signing issues include: 1. **Incorrect data format**: OneBalance provides base64 `dataToSign` that must be converted to `MessageV0` 2. **Wrong signature encoding**: Solana signatures must be base58 encoded, not hex 3. **Wallet not connected**: Verify wallet connection before attempting to sign **Quick fix pattern:** ```typescript theme={null} import { MessageV0, VersionedTransaction } from '@solana/web3.js'; import bs58 from 'bs58'; // Convert base64 to MessageV0, create VersionedTransaction, sign, encode as base58 const message = MessageV0.deserialize(Buffer.from(dataToSign, 'base64')); const transaction = new VersionedTransaction(message); const signedTx = await wallet.signTransaction(transaction); const signature = bs58.encode(Buffer.from(signedTx.signatures[signedTx.signatures.length - 1])); ``` For complete Solana signing implementation and validation utilities, see the [Signing Guide](/concepts/signing#solana-account-signing). ## How do I handle EIP-7702 delegation signing? EIP-7702 accounts require two signing steps: 1. **Delegation Signature**: Sign authorization tuple using `signAuthorization()` method 2. **UserOperation Signature**: Sign UserOperation hash using `signMessage()` method **Key requirements:** * Always check `signedTuple.yParity` is not null before proceeding * Both `accountAddress` and `signerAddress` must be the same EOA address * Use account type `"kernel-v3.3-ecdsa"` with `deploymentType: "EIP7702"` **Critical difference from role-based accounts:** * Role-based: `signTypedData(operation.typedDataToSign)` * Kernel 3.3 (EIP-7702): `signMessage({ message: { raw: userOpHash } })` For complete EIP-7702 delegation implementation with error handling and validation, see the [Signing Guide](/concepts/signing#eip-7702-account-signing). ## What happens if my wallet already has an EIP-7702 delegation to another contract? OneBalance won't override pre-existing EIP-7702 delegations. This is by design to prevent potential security issues like double-spend attacks where a prior delegation could manipulate state before our transaction executes. If your wallet already has a delegation to a different contract, OneBalance will return a [`DELEGATION-001`](/api-reference/error-codes#delegation-001) error during quote generation. **Workaround:** * Revoke the existing delegation from your wallet, then retry. OneBalance will automatically delegate to Kernel v3.3 on the next quote execution * Alternatively, use a different EOA that doesn't have an existing EIP-7702 delegation This only affects wallets with pre-existing delegations to non-OneBalance contracts. New wallets or wallets without any prior EIP-7702 delegation work without issues. ## Getting Help Full implementation guide with code examples for all account types Step-by-step EIP-7702 integration with delegation signing Solana-specific signing patterns and troubleshooting For additional support with signing issues: * **Intercom chat** (bottom right corner) for instant help * **Email**: [support@onebalance.io](mailto:support@onebalance.io) * **Discord community** for peer support and updates # FAQ: Solana Source: https://docs.onebalance.io/faq/solana Frequently asked questions about Solana integration with OneBalance, covering token support, API configuration, limitations, and troubleshooting. ## Token Support and Discovery ### Why doesn't `/assets/list` return Solana tokens? The `/assets/list` endpoint currently only returns EVM tokens. For Solana tokens, you need to use specific asset IDs in CAIP-19 format: ```bash theme={null} # SOL (Solana native token) solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501 # USDC on Solana solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v # JUP token on Solana solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN ``` We're working on expanding our aggregated asset list to include more tokens across all chains, including Solana. This will improve asset discovery significantly. ### Which Solana tokens are currently supported? OneBalance supports any Solana token that exists on the network, but currently offers aggregated assets for major tokens: **Current Aggregated Assets:** * **SOL**: Native Solana token (`ob:sol`) * **USDC**: USD Coin on Solana (`ob:usdc`) **Any Other Token**: You can use any Solana token by specifying its CAIP-19 asset ID: * Format: `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:TOKEN_ADDRESS` * For SOL: `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501` For token metadata (names, symbols, images), we recommend using third-party services, then combining that data with OneBalance for quotes and executions. ## API Key Configuration ### Why don't Solana operations work with my custom API key? **Solana support must be explicitly enabled** for custom API keys. By default, new API keys only have EVM chain access. **Solutions:** 1. **Contact support** to enable Solana for your API key 2. **Use the public API key** for testing: `42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11` The public API key has rate limits and should only be used for testing and development, not production applications. ### How do I request Solana access for my API key? Contact our support team with your API key prefix: * **Intercom chat**: Use the widget in the bottom right corner * **Email**: [support@onebalance.io](mailto:support@onebalance.io) Enablement is typically completed within 24 hours. ## Asset ID Formats ### When should I use aggregated vs specific asset IDs? **Use Aggregated Assets (`ob:*`) when:** * You want to spend from combined balances across all chains * Working with major tokens (USDC, SOL, ETH, etc.) * Building simplified UX where users don't care about specific chains ```javascript theme={null} { asset: { assetId: "ob:usdc" } // Spends USDC from any supported chain } ``` **Use Specific Assets (CAIP-19) when:** * You need to spend from a specific chain * Working with tokens not yet aggregated * Building advanced UX where chain selection matters ```javascript theme={null} { asset: { assetId: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" } } ``` ### How do I find the CAIP-19 asset ID for a Solana token? **Format:** `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:TOKEN_MINT_ADDRESS` **For SOL (native token):** `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501` **For SPL tokens:** 1. Find the token mint address (from Solana Explorer, Jupiter, etc.) 2. Use format: `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:MINT_ADDRESS` **Examples:** * USDC: `...token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v` * JUP: `...token:JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN` ## Cross-Chain Operations ### Can I use Solana with EVM accounts in the same operation? **Yes!** This is one of OneBalance's key features. You can use multiple account types in a single transaction: ```javascript theme={null} { "from": { "accounts": [ // EVM smart account { "type": "kernel-v3.1-ecdsa", "accountAddress": "0x...", "deploymentType": "ERC4337", "signerAddress": "0x..." }, // Solana account { "type": "solana", "accountAddress": "J5CCzBULFax899tcirb6wMbenQUd8whbaetG7EfSick5" } ], "asset": { "assetId": "ob:usdc" }, // Spends from both chains "amount": "1000000" } } ``` This enables: * **EVM → Solana**: Use EVM tokens to buy Solana assets * **Solana → EVM**: Use Solana tokens to buy EVM assets * **Aggregated spending**: Use combined balances optimally ### Which v3 endpoints do I need for Solana? Solana requires **v3 API endpoints** that support multiple accounts: * [Get Quote v3](/api-reference/quotes/get-quote-v3) - `POST /api/v3/quote` * [Execute Quote v3](/api-reference/quotes/execute-quote-v3) - `POST /api/v3/quote/execute` * [Aggregated Balance v3](/api-reference/balances/aggregated-balance-v3) - `GET /api/v3/balances/aggregated-balance` * [Transaction History v3](/api-reference/status/get-transaction-history-v3) - `GET /api/v3/status/get-transaction-history` * [Quote Status v3](/api-reference/status/get-quote-status-v3) - `GET /api/v3/status/get-execution-status` v1 endpoints only support single EVM accounts and cannot be used with Solana. ## Integration Issues ### My Solana wallet signing isn't working **Common Issues:** 1. **Incorrect data format**: Ensure you're converting base64 data to `MessageV0` 2. **Wrong signature encoding**: Use `bs58.encode()` for signatures 3. **Wallet not connected**: Check `wallet.connected` before signing **Correct signing pattern:** ```typescript theme={null} import { MessageV0, VersionedTransaction } from '@solana/web3.js'; import bs58 from 'bs58'; async function signSolanaOperation(dataToSign: string, wallet: any) { // 1. Convert base64 to Message const message = MessageV0.deserialize(Buffer.from(dataToSign, 'base64')); // 2. Create versioned transaction const transaction = new VersionedTransaction(message); // 3. Sign with wallet const signedTx = await wallet.signTransaction(transaction); // 4. Extract signature in base58 return bs58.encode(Buffer.from(signedTx.signatures[signedTx.signatures.length - 1])); } ``` ### Why am I getting "insufficient balance" errors? **Check these common causes:** 1. **Wrong asset ID**: Ensure you're using correct CAIP-19 format 2. **No SOL for gas**: Fund account with \~0.005 SOL 3. **API key not enabled**: Verify Solana is enabled for your API key 4. **Decimals mismatch**: Check token decimals (SOL = 9, USDC = 6) **Debug with balance check:** ```bash theme={null} curl -X GET 'https://be.onebalance.io/api/v3/balances/aggregated-balance?account=solana:YOUR_ADDRESS&aggregatedAssetId=ob:sol,ob:usdc' \ -H 'x-api-key: YOUR_API_KEY' ``` ## Getting Help Complete working examples for all Solana operations Common issues and detailed debugging steps Full API documentation with Solana-specific notes For additional support: * **Intercom chat** (bottom right corner) for instant help * **Email**: [support@onebalance.io](mailto:support@onebalance.io) * **Join our Discord**: Community support and updates # Chain-Abstracted Swap Source: https://docs.onebalance.io/getting-started/chain-abstracted-swap Learn how a cross-chain token swap works with OneBalance and Privy, eliminating manual bridging, gas fees, and network switching. Now that we've set up our project with Privy and OneBalance, let's create a user interface that allows users to perform chain-abstracted swaps with just a few clicks. This is where the "wow" factor comes in - users can swap tokens across chains without even knowing they're dealing with multiple networks, gas fees, or bridges. **Simplified UX**: By combining Privy's seamless wallet experience with OneBalance's chain abstraction, users don't need to worry about networks, gas, or bridging. ## Type Definitions First, let's create type definitions for our quotes and operations. Create a new file at `src/lib/types/quote.ts`: ```typescript quote.ts theme={null} // src/lib/types/quote.ts export interface Account { sessionAddress: string; adminAddress: string; accountAddress: string; } export interface TokenInfo { aggregatedAssetId: string; amount: string; assetType: string | string[]; fiatValue: never; } export interface ChainOperation { userOp: { sender: string; nonce: string; callData: string; callGasLimit: string; verificationGasLimit: string; preVerificationGas: string; maxFeePerGas: string; maxPriorityFeePerGas: string; paymaster: string; paymasterVerificationGasLimit: string; paymasterPostOpGasLimit: string; paymasterData: string; signature: string; }; typedDataToSign: { domain: unknown; types: unknown; primaryType: string; message: unknown; }; assetType: string; amount: string; } export interface Quote { id: string; account: Account; originToken: TokenInfo; destinationToken: TokenInfo; expirationTimestamp: string; tamperProofSignature: string; originChainsOperations: ChainOperation[]; destinationChainOperation?: ChainOperation; } export interface QuoteStatus { quoteId: string; status: { status: 'PENDING' | 'COMPLETED' | 'FAILED' | 'IN_PROGRESS' | 'REFUNDED'; }; user: string; recipientAccountId: string; originChainOperations: { hash: string; chainId: number; explorerUrl: string; }[]; destinationChainOperations: { hash: string; chainId: number; explorerUrl: string; }[]; } ``` ## Setting Up the Signing Utilities Now, let's set up some helper functions for signing operations with the Privy wallet. Create a new file at `src/lib/privySigningUtils.ts`: ```typescript privySigningUtils.ts theme={null} // src/lib/privySigningUtils.ts import { Address, createWalletClient, custom, Hash } from 'viem'; import { ConnectedWallet } from '@privy-io/react-auth'; import { ChainOperation, Quote } from '@/lib/types/quote'; // Create a function to sign typed data with Privy wallet 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); }; // Create a function to sign a chain operation export const signOperation = (embeddedWallet: ConnectedWallet) => (operation: ChainOperation): (() => Promise) => async () => { const signature = await signTypedDataWithPrivy(embeddedWallet)(operation.typedDataToSign); return { ...operation, userOp: { ...operation.userOp, signature }, }; }; // Create a function to sign the entire quote export const signQuote = async (quote: Quote, embeddedWallet: ConnectedWallet) => { const signWithEmbeddedWallet = signOperation(embeddedWallet); const signedQuote = { ...quote, }; // Sign all operations in sequence if (quote.originChainsOperations) { 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([]) ); }; ``` When a user initiates a swap, they'll be prompted to sign the transaction with their Privy wallet: ![Privy Signing Message](https://storage.googleapis.com/onebalance-public-assets/docs/getting-started/sign-message.png) ## Using OneBalance API Functions For our chain-abstracted swap interface, we'll use the OneBalance API functions we previously implemented in the [setup section](/getting-started/setup#prerequisites): This implementation uses three key endpoints: * [Get Quote](/api-reference/quotes/get-quote) - Get a quote for cross-chain swaps or transfers * [ExecuteQuote](/api-reference/quotes/execute-quote) - Submit a signed quote for execution * [Check Status](/api-reference/status/get-quote-status) - Track the transaction status ## Creating the Dashboard Page Now, let's create a dashboard that allows users to swap USDC for ETH (and vice versa) with built-in transaction status polling: ```tsx dashboard.tsx theme={null} // src/app/dashboard/page.tsx 'use client'; import { useEffect, useState, useRef, ChangeEvent } from 'react'; import { usePrivy, useWallets } from '@privy-io/react-auth'; import { getQuote, executeQuote, checkTransactionStatus, predictAccountAddress, getAggregatedBalance } from '@/lib/onebalance'; import { Quote } from '@/lib/types/quote'; import { signQuote } from '@/lib/privySigningUtils'; import { formatUnits } from 'viem'; import { useRouter } from 'next/navigation'; export default function Dashboard() { const router = useRouter(); const { user, ready, authenticated, logout } = usePrivy(); const { wallets } = useWallets(); const [loading, setLoading] = useState(false); const [swapping, setSwapping] = useState(false); const [status, setStatus] = useState(null); const [accountAddress, setAccountAddress] = useState(null); const [usdcBalance, setUsdcBalance] = useState(null); const [ethBalance, setEthBalance] = useState(null); const [usdcChainBalances, setUsdcChainBalances] = useState>([]); const [ethChainBalances, setEthChainBalances] = useState>([]); const [quote, setQuote] = useState(null); const [error, setError] = useState(null); const [success, setSuccess] = useState(false); const statusPollingRef = useRef(null); const [isPolling, setIsPolling] = useState(false); const [swapAmount, setSwapAmount] = useState('5.00'); const [estimatedAmount, setEstimatedAmount] = useState(null); const [fetchingQuote, setFetchingQuote] = useState(false); const [swapDirection, setSwapDirection] = useState<'USDC_TO_ETH' | 'ETH_TO_USDC'>('USDC_TO_ETH'); const [showUsdcChainDetails, setShowUsdcChainDetails] = useState(false); const [showEthChainDetails, setShowEthChainDetails] = useState(false); // Helper function to get chain name from chain ID const getChainName = (chainId: string): string => { const chainMap: Record = { '1': 'Ethereum', '137': 'Polygon', '42161': 'Arbitrum', '10': 'Optimism', '8453': 'Base', '59144': 'Linea', '43114': 'Avalanche' }; return chainMap[chainId] || `Chain ${chainId}`; }; const embeddedWallet = wallets.find(wallet => wallet.walletClientType === 'privy'); // Handle logout and redirect to home page const handleLogout = async () => { await logout(); router.push('/'); }; // Get OneBalance account address based on Privy wallet useEffect(() => { async function setupAccount() { if (embeddedWallet && embeddedWallet.address) { try { // For this demo, we'll use the same address as both session and admin const predictedAddress = await predictAccountAddress( embeddedWallet.address, embeddedWallet.address ); setAccountAddress(predictedAddress); // Get aggregated balance for USDC and ETH fetchBalances(predictedAddress); } catch (err) { console.error('Error setting up account:', err); setError('Failed to set up OneBalance account'); } } } if (ready && authenticated) { setupAccount(); } // Clean up polling interval on unmount return () => { if (statusPollingRef.current) { clearInterval(statusPollingRef.current); } }; }, [embeddedWallet, ready, authenticated]); // Handle swap direction toggle const toggleSwapDirection = () => { // Reset current quote and estimated amount setQuote(null); setEstimatedAmount(null); // Toggle direction setSwapDirection(prevDirection => prevDirection === 'USDC_TO_ETH' ? 'ETH_TO_USDC' : 'USDC_TO_ETH' ); // Reset swap amount to a sensible default based on direction if (swapDirection === 'USDC_TO_ETH') { // Switching to ETH -> USDC, set a default ETH amount (0.001 ETH) setSwapAmount('0.001'); } else { // Switching to USDC -> ETH, set a default USDC amount (5 USDC) setSwapAmount('5.00'); } // Fetch a new quote after a brief delay to ensure state is updated setTimeout(() => { if (accountAddress && embeddedWallet) { fetchEstimatedQuote(swapDirection === 'USDC_TO_ETH' ? '0.001' : '5.00'); } }, 300); }; // Handle swap amount change const handleSwapAmountChange = async (e: ChangeEvent) => { const value = e.target.value; if (isNaN(Number(value)) || Number(value) <= 0) { return; } setSwapAmount(value); // Reset estimated amount when input changes setEstimatedAmount(null); // If we have a valid amount and an account, fetch a new quote if (Number(value) > 0 && accountAddress && embeddedWallet) { fetchEstimatedQuote(value); } }; // Fetch a quote for estimation purposes const fetchEstimatedQuote = async (amountStr: string) => { if (!accountAddress || !embeddedWallet) return; try { setFetchingQuote(true); // Convert to smallest unit based on direction // USDC has 6 decimals, ETH has 18 decimals const amount = swapDirection === 'USDC_TO_ETH' ? (parseFloat(amountStr) * 1_000_000).toString() // USDC -> ETH (6 decimals) : (parseFloat(amountStr) * 1e18).toString(); // ETH -> USDC (18 decimals) const quoteRequest = { from: { account: { sessionAddress: embeddedWallet.address, adminAddress: embeddedWallet.address, accountAddress: accountAddress, }, asset: { assetId: swapDirection === 'USDC_TO_ETH' ? 'ob:usdc' : 'ob:eth', }, amount, }, to: { asset: { assetId: swapDirection === 'USDC_TO_ETH' ? 'ob:eth' : 'ob:usdc', }, }, }; const quoteResponse = await getQuote(quoteRequest); setQuote(quoteResponse); // Extract estimated amount from the quote if (quoteResponse.destinationToken && quoteResponse.destinationToken.amount) { // Format based on direction if (swapDirection === 'USDC_TO_ETH') { // USDC -> ETH (ETH has 18 decimals) const ethAmount = parseFloat(formatUnits(BigInt(quoteResponse.destinationToken.amount), 18)).toFixed(6); setEstimatedAmount(ethAmount); } else { // ETH -> USDC (USDC has 6 decimals) const usdcAmount = parseFloat(formatUnits(BigInt(quoteResponse.destinationToken.amount), 6)).toFixed(2); setEstimatedAmount(usdcAmount); } } } catch (err) { console.error('Error fetching quote for estimation:', err); setEstimatedAmount(null); } finally { setFetchingQuote(false); } }; // Fetch USDC and ETH balances const fetchBalances = async (address: string) => { try { const balanceData = await getAggregatedBalance(address); // Find USDC in the balance data const usdcAsset = balanceData.balanceByAggregatedAsset.find( (asset: any) => asset.aggregatedAssetId === 'ob:usdc' ); // Find ETH in the balance data const ethAsset = balanceData.balanceByAggregatedAsset.find( (asset: any) => asset.aggregatedAssetId === 'ob:eth' ); if (usdcAsset) { // Format the balance (USDC has 6 decimals) const formattedBalance = parseFloat(formatUnits(BigInt(usdcAsset.balance), 6)).toFixed(2); setUsdcBalance(formattedBalance); // Extract individual chain balances for USDC if (usdcAsset.individualAssetBalances && usdcAsset.individualAssetBalances.length > 0) { const chainBalances = usdcAsset.individualAssetBalances .map((chainBalance: any) => { // Extract chain ID from assetType (format: eip155:CHAIN_ID/...) const chainId = chainBalance.assetType.split(':')[1].split('/')[0]; const formattedBalance = parseFloat(formatUnits(BigInt(chainBalance.balance), 6)).toFixed(2); return { chainId, balance: formattedBalance, numericBalance: parseFloat(formattedBalance), // For sorting assetType: chainBalance.assetType }; }) // Filter out zero balances .filter((chainBalance: {numericBalance: number}) => chainBalance.numericBalance > 0) // Sort by balance in descending order .sort((a: {numericBalance: number}, b: {numericBalance: number}) => b.numericBalance - a.numericBalance); setUsdcChainBalances(chainBalances); } } else { setUsdcBalance('0.00'); setUsdcChainBalances([]); } if (ethAsset) { // Format the balance (ETH has 18 decimals) const formattedBalance = parseFloat(formatUnits(BigInt(ethAsset.balance), 18)).toFixed(6); setEthBalance(formattedBalance); // Extract individual chain balances for ETH if (ethAsset.individualAssetBalances && ethAsset.individualAssetBalances.length > 0) { const chainBalances = ethAsset.individualAssetBalances .map((chainBalance: any) => { // Extract chain ID from assetType (format: eip155:CHAIN_ID/...) const chainId = chainBalance.assetType.split(':')[1].split('/')[0]; const formattedBalance = parseFloat(formatUnits(BigInt(chainBalance.balance), 18)).toFixed(6); return { chainId, balance: formattedBalance, numericBalance: parseFloat(formattedBalance), // For sorting assetType: chainBalance.assetType }; }) // Filter out zero balances .filter((chainBalance: {numericBalance: number}) => chainBalance.numericBalance > 0) // Sort by balance in descending order .sort((a: {numericBalance: number}, b: {numericBalance: number}) => b.numericBalance - a.numericBalance); setEthChainBalances(chainBalances); } } else { setEthBalance('0.000000'); setEthChainBalances([]); } // After getting balances, fetch an initial quote estimate using the default amount if (address && embeddedWallet) { fetchEstimatedQuote(swapAmount); } } catch (err) { console.error('Error fetching balances:', err); setUsdcBalance('0.00'); setEthBalance('0.000000'); setUsdcChainBalances([]); setEthChainBalances([]); } }; // Poll for transaction status const startStatusPolling = (quoteId: string) => { if (statusPollingRef.current) { clearInterval(statusPollingRef.current); } setIsPolling(true); statusPollingRef.current = setInterval(async () => { try { const statusData = await checkTransactionStatus(quoteId); setStatus(statusData); // If the transaction is completed or failed, stop polling if (statusData.status === 'COMPLETED' || statusData.status === 'FAILED') { if (statusPollingRef.current) { clearInterval(statusPollingRef.current); setIsPolling(false); } // Refresh balances after transaction is completed if (accountAddress && statusData.status === 'COMPLETED') { fetchBalances(accountAddress); } } } catch (err) { console.error('Error polling transaction status:', err); if (statusPollingRef.current) { clearInterval(statusPollingRef.current); setIsPolling(false); } } }, 1000); // Poll every 1 second }; // Request and execute a chain-abstracted swap const handleSwap = async () => { if (!accountAddress || !embeddedWallet) { setError('Wallet not connected or OneBalance account not set up'); return; } setLoading(true); setSwapping(true); setError(null); setSuccess(false); try { // Use already fetched quote if available let quoteResponse = quote; // If no quote available or it's stale, fetch a new one if (!quoteResponse) { // Convert to smallest unit based on direction const amount = swapDirection === 'USDC_TO_ETH' ? (parseFloat(swapAmount) * 1_000_000).toString() // USDC -> ETH (6 decimals) : (parseFloat(swapAmount) * 1e18).toString(); // ETH -> USDC (18 decimals) const quoteRequest = { from: { account: { sessionAddress: embeddedWallet.address, adminAddress: embeddedWallet.address, accountAddress: accountAddress, }, asset: { assetId: swapDirection === 'USDC_TO_ETH' ? 'ob:usdc' : 'ob:eth', }, amount, }, to: { asset: { assetId: swapDirection === 'USDC_TO_ETH' ? 'ob:eth' : 'ob:usdc', }, }, }; quoteResponse = await getQuote(quoteRequest); setQuote(quoteResponse); } if (!quoteResponse) { throw new Error('Failed to get a quote for the swap'); } // Step 2: Sign the quote const signedQuote = await signQuote(quoteResponse, embeddedWallet); // Step 3: Execute the quote setLoading(true); const executionResponse = await executeQuote(signedQuote); // Step 4: Start polling for transaction status startStatusPolling(quoteResponse.id); setSuccess(true); setLoading(false); } catch (err: any) { console.error('Error in swap process:', err); setError(err.message || 'Failed to complete swap'); setLoading(false); } finally { setSwapping(false); } }; if (!ready || !authenticated) { return (
); } return (

OneBalance Dashboard

Connected as
{user?.email?.address || embeddedWallet?.address || 'Anonymous'}
{/* Account Info Section */}

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
)}
)}

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 chain
No gas tokens needed - pay fees with your {swapDirection === 'USDC_TO_ETH' ? 'USDC' : 'ETH'} balance!
{error && (

Error

{error}

)} {success && (

Success!

Your chain-abstracted swap has been initiated. {isPolling && ' Monitoring transaction status...'}

)} {status && (

Transaction Status

Status: {status.status || 'Pending'}
{status.originChainOperations?.length > 0 && (
Origin Chain: View Transaction
)} {status.destinationChainOperations?.length > 0 && (
Destination Chain: View Transaction
)}
)}
); } ``` When the swap is successfully completed, users can see the status of the transaction: ![Successful Swap](https://storage.googleapis.com/onebalance-public-assets/docs/getting-started/successful-swap.png) ## Understanding the Chain-Abstracted Swap Flow This improved implementation offers several key enhancements over the basic version: 1. **Bidirectional Swapping** * Users can swap from USDC → ETH or ETH → USDC * The UI dynamically updates based on the selected direction 2. **Real-Time Quote Estimation** * When users input an amount, a quote is fetched to show the estimated output * This provides transparency about the expected exchange rate 3. **Balance Display** * Shows users their current USDC and ETH balances from across all chains * Updates balances automatically after a successful swap 4. **Robust Transaction Polling** * Implements a proper polling mechanism to track transaction status * Handles different transaction states (pending, completed, failed) * Automatically stops polling when the transaction completes or fails 5. **Error Handling & Recovery** * Provides clear error messages when issues occur * Implements clean-up logic to prevent resource leaks (clearing intervals) 6. **Enhanced User Experience** * Shows loading indicators during swap operations * Provides visual feedback for transaction states * Includes links to block explorers to view transactions ## Multichain Asset Visibility One of the most powerful aspects of this implementation is how it handles multichain asset visibility. When users interact with the application: * They see their USDC and ETH balances as a **single aggregated total** across all supported chains * They can select "from" and "to" assets without needing to specify which chain they're on * The underlying complexity of potentially interacting with multiple blockchains is completely hidden This creates an abstraction layer where users don't need to think about chains at all - they simply work with tokens as if they existed in a unified space, which dramatically simplifies the user experience. ## Key Technical Implementations ### 1. Transaction Status Polling ```typescript status-polling.ts theme={null} // Poll for transaction status const startStatusPolling = (quoteId: string) => { if (statusPollingRef.current) { clearInterval(statusPollingRef.current); } setIsPolling(true); statusPollingRef.current = setInterval(async () => { try { const statusData = await checkTransactionStatus(quoteId); setStatus(statusData); // If the transaction is completed or failed, stop polling if (statusData.status.status === 'COMPLETED' || statusData.status.status === 'FAILED') { if (statusPollingRef.current) { clearInterval(statusPollingRef.current); setIsPolling(false); } // Refresh balances after transaction is completed if (accountAddress && statusData.status.status === 'COMPLETED') { fetchBalances(accountAddress); } } } catch (err) { console.error('Error polling transaction status:', err); if (statusPollingRef.current) { clearInterval(statusPollingRef.current); setIsPolling(false); } } }, 1000); // Poll every 1 second }; ``` This polling mechanism uses the [Get Execution Status](/api-reference/status/get-quote-status) endpoint to track transaction progress in real-time. ### 2. Signing with Privy A critical part of the implementation is securely signing operations using the Privy wallet. The signing process handles: * Converting the typed data format required by EIP-712 * Managing the signature process using the embedded wallet * Ensuring all operations in a quote are properly signed * Handling both origin and destination chain operations ```typescript swap-flow.ts theme={null} // Example of the signing flow const handleSwap = async () => { // ... other code // Get the quote from OneBalance const quoteResponse = await getQuote(quoteRequest); // Sign the quote using the Privy wallet const signedQuote = await signQuote(quoteResponse, embeddedWallet); // Execute the signed quote await executeQuote(signedQuote); // ... other code }; ``` ### 3. Balance Checking Across Chains ```typescript balance-checker.ts theme={null} // Fetch USDC and ETH balances const fetchBalances = async (address: string) => { try { const balanceData = await getAggregatedBalance(address); // Find USDC in the balance data const usdcAsset = balanceData.balanceByAggregatedAsset.find( (asset: any) => asset.aggregatedAssetId === 'ob:usdc' ); // Find ETH in the balance data const ethAsset = balanceData.balanceByAggregatedAsset.find( (asset: any) => asset.aggregatedAssetId === 'ob:eth' ); if (usdcAsset) { // Format the balance (USDC has 6 decimals) const formattedBalance = parseFloat(formatUnits(BigInt(usdcAsset.balance), 6)).toFixed(2); setUsdcBalance(formattedBalance); } if (ethAsset) { // Format the balance (ETH has 18 decimals) const formattedBalance = parseFloat(formatUnits(BigInt(ethAsset.balance), 18)).toFixed(6); setEthBalance(formattedBalance); } } catch (err) { console.error('Error fetching balances:', err); } }; ``` This function leverages the [Aggregated Balance](/api-reference/balances/aggregated-balance) endpoint to retrieve all token balances across multiple chains with a single API call. ## What Makes This Implementation Powerful? 1. **Chain Abstraction** * Users can swap tokens across chains without even knowing which chains are involved * The UI treats USDC and ETH as unified assets regardless of their underlying chains 2. **Robust Error Handling** * Handles API errors gracefully * Provides clear feedback to users * Implements proper cleanup of resources 3. **Real-Time Transaction Tracking** * Users can see the status of their transactions as they progress * Provides links to block explorers for deeper inspection * Automatically refreshes balances when transactions complete 4. **Bidirectional Swapping** * Users can swap in either direction (USDC → ETH or ETH → USDC) * The UI adapts dynamically to the selected direction 5. **Gas Abstraction** * Users can pay transaction fees with the token they're swapping * No need to hold native gas tokens on each chain ## Next Steps Now that you've seen how to create a chain-abstracted swap interface with OneBalance, you can explore the complete working implementation: Get the full working code example to start building immediately Discover all available endpoints in our API documentation # Your First API Call Source: https://docs.onebalance.io/getting-started/first-api-call Learn to make your first OneBalance API call by setting up a smart contract account for chain-abstracted, gasless, cross-chain transactions. Before you can execute chain-abstracted operations with OneBalance, you need to set up a smart contract account (SCA). This smart contract account will hold your assets and enable gas abstraction across chains. **Key Concept**: OneBalance uses smart contract accounts (SCAs) to enable chain and gas abstraction. Your SCA is your actual deposit address where funds are stored, and your Privy wallet acts as a **signer** for this account. ## Predicting Your Account Address Now that we've set up our project with proper CORS handling, let's use the OneBalance API to predict a smart contract account (SCA) address based on Privy's embedded wallet addresses: First, create a component that will handle the account setup: ```tsx AccountSetup.tsx theme={null} // src/components/AccountSetup.tsx 'use client'; // Example usage in a component import { useEffect, useState } from 'react'; import { useWallets } from '@privy-io/react-auth'; import { predictAccountAddress } from '@/lib/onebalance'; export function AccountSetup() { const { wallets } = useWallets(); const [accountAddress, setAccountAddress] = useState(null); // Find the Privy-managed embedded wallet const embeddedWallet = wallets.find(wallet => wallet.walletClientType === 'privy'); useEffect(() => { async function setupAccount() { if (embeddedWallet && embeddedWallet.address) { try { // Using the same address as both session and admin for simplicity const predictedAddress = await predictAccountAddress( embeddedWallet.address, embeddedWallet.address ); setAccountAddress(predictedAddress); console.log(`Smart Contract Account Address: ${predictedAddress}`); } catch (err) { console.error('Error setting up account:', err); } } } setupAccount(); }, [embeddedWallet]); return (
{accountAddress ? (

Your OneBalance Smart Account: {accountAddress}

) : (

Loading account address...

)}
); } ``` This component uses the [Predict Address](/api-reference/account/predict-address) endpoint to determine your smart contract account address before it's deployed. Next, create a dashboard page that will use this component: ```tsx page.tsx theme={null} // src/app/dashboard/page.tsx import { AccountSetup } from '@/components/AccountSetup'; export default function Dashboard() { return (

OneBalance Dashboard

); } ``` 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. When a user logs in with Privy via email (as shown in the previous section), they receive a verification code to complete the authentication process. Once verified, Privy creates an embedded wallet for them automatically, which becomes available through the `useWallets()` hook shown above. ## Understanding Smart Contract Accounts (SCAs) OneBalance uses smart contract accounts to enable key features like: 1. **Gas Abstraction**: Pay for transactions using any token in your balance, not just the chain's native gas token 2. **Resource Locks**: Secure validation of transaction intents without requiring on-chain confirmations for every step 3. **Fast Path**: Near-instant transactions without waiting for block confirmations **Important Account Relationship**: - The **SCA address** (predicted by `/account/predict-address`) is your deposit address where funds are stored - The **Privy-managed EOA wallet** acts as a **signer** for your SCA, authorizing transactions - Always send funds to the SCA address, not to the Privy wallet address Smart contract accounts are **counterfactual**, meaning they don't need to be deployed until they're actually used in a transaction. This saves gas costs and simplifies the user experience. The account setup in this quickstart uses a common configuration for ease of use. OneBalance offers more advanced modular account options for specialized use cases, which you can explore in our [Core Concepts: Account Models](/concepts/accounts) section. ## Checking Account Balances Before performing operations, you'll want to check if your account has sufficient funds. The OneBalance API allows you to fetch aggregated balances across all chains: ```typescript balanceChecker.ts theme={null} // Example of checking for USDC and ETH balances import { getAggregatedBalance } from '@/lib/onebalance'; import { formatUnits } from 'viem'; async function checkBalances(accountAddress: string) { try { const balanceData = await getAggregatedBalance(accountAddress); // Find USDC in the balance data const usdcAsset = balanceData.balanceByAggregatedAsset.find( (asset: any) => asset.aggregatedAssetId === 'ob:usdc' ); // Find ETH in the balance data const ethAsset = balanceData.balanceByAggregatedAsset.find( (asset: any) => asset.aggregatedAssetId === 'ob:eth' ); if (usdcAsset) { // Format the balance (USDC has 6 decimals) const formattedBalance = parseFloat(formatUnits(BigInt(usdcAsset.balance), 6)).toFixed(2); console.log(`USDC Balance: ${formattedBalance} USDC`); } if (ethAsset) { // Format the balance (ETH has 18 decimals) const formattedBalance = parseFloat(formatUnits(BigInt(ethAsset.balance), 18)).toFixed(6); console.log(`ETH Balance: ${formattedBalance} ETH`); } } catch (err) { console.error('Error fetching balances:', err); } } ``` **Asset ID Format**: OneBalance uses the "ob:" prefix for aggregated asset identifiers (e.g., `ob:usdc`, `ob:eth`). This prefix indicates that these are aggregated assets that represent the same token across multiple chains. For example, `ob:usdc` represents USDC from Ethereum, Polygon, Arbitrum, and other supported networks as a single unified asset. Learn more about how aggregated assets work in [Aggregated Assets](/concepts/aggregated-assets). This example uses the [Get Aggregated Balance](/api-reference/balances/aggregated-balance) endpoint to fetch your token balances across all supported chains with a single API call. This function fetches the aggregated balance for an account and formats the values correctly: * USDC has 6 decimals, so we divide by 10^6 * ETH has 18 decimals, so we divide by 10^18 The balances shown represent the total amount available across all supported chains, providing users with a unified view of their assets. ![Dashboard Overview](https://storage.googleapis.com/onebalance-public-assets/docs/getting-started/dashboard-overview.png) 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 ![Dashboard Account Info](https://storage.googleapis.com/onebalance-public-assets/docs/getting-started/account-info.png) In the full implementation (next section), we'll enhance the dashboard to display both the aggregated balance and a breakdown of assets per chain. Users can toggle to see exactly how their funds are distributed across networks like Ethereum, Arbitrum, Optimism, and others. **Cross-Chain Visibility**: OneBalance automatically tracks your assets across all supported chains, so you don't need to switch networks or use separate wallets to view your complete holdings. All tokens of the same type (e.g., USDC) are presented as a single aggregated balance, even if they're distributed across multiple networks. ## Funding Your Account Before you can perform any operations with your smart contract account, you'll need to fund it. Send tokens to the `predictedAddress` returned from the API. This is your SCA's deposit address. Remember to send funds to the SCA address (predictedAddress), not to your Privy wallet addresses. The predictedAddress is where your funds will be stored and managed across all chains. ## Next Steps Now that you've learned how to set up a smart contract account and check balances, you're ready to execute chain-abstracted operations. In the next section, we'll use the predicted account address to create a quote for swapping tokens across chains, with no bridging required. Learn how to execute seamless chain-abstracted swaps using your new smart contract account # Introduction Source: https://docs.onebalance.io/getting-started/introduction Learn how to get started with the OneBalance Toolkit to begin removing onchain complexity like chains, bridges and gas in your app. OneBalance lets you create seamless chain-abstracted experiences with a single API. Instead of integrating with multiple blockchains individually, you can use OneBalance to access tokens across all chains with one simple integration. This guide uses the Role-Based account configuration. See more on configurations [here](/concepts/accounts). All examples in this guide are 100% free and open-source. Clone the repository to quickly get started, or try the [live demo app](https://onebalance-privy-demo.vercel.app). ## What You'll Build Follow our quickstart to make your first chain-abstracted transaction in under 5 minutes Access tokens across chains as if they were one token 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. 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 ## 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 You can learn more about these and other APIs in our [API Reference](/api-reference/introduction). ## Key Benefits No 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 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 Ready to get started? Let's begin with [setting up your environment](/getting-started/setup). # Set Up Your Environment Source: https://docs.onebalance.io/getting-started/setup Set up your development environment with OneBalance and Privy by configuring keys and settings for an enhanced embedded wallet experience. To create a truly seamless chain-abstracted experience, we'll combine OneBalance's chain abstraction with Privy's wallet infrastructure. This integration gives your users social login options and a smooth onboarding experience. **Skip the Setup?** If you want to skip the manual setup process, you can [clone the complete repository](https://github.com/OneBalance-io/onebalance-privy-demo) and jump straight to exploring the code. ```bash clone-and-run.sh theme={null} # Clone the repository git clone https://github.com/OneBalance-io/onebalance-privy-demo.git cd onebalance-privy-demo # Install dependencies pnpm install # Copy environment variables cp .env.example .env # Start the development server pnpm dev ``` **Complete Integration**: This guide combines Privy for wallet management with OneBalance for chain-abstracted functionality, providing the best experience for your users. ## Prerequisites Before you begin, make sure you have: * [Node.js](https://nodejs.org/) version 20 or higher * We recommend using [nvm](https://github.com/nvm-sh/nvm) to manage your Node.js versions * [pnpm](https://pnpm.io/installation) package manager installed * [Privy Dashboard](https://console.privy.io) account for your application * For a faster quickstart, you can use our pre-configured Privy App ID: `cmb94tjcj001xle0mdeerz9vp` (recommended) * Alternatively, you can set up your own Privy application * OneBalance API Key - we'll use the public test key for this example: `42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11` The pre-configured keys and IDs provided in this guide are strictly for learning purposes. For any production application, you must use your own Privy App ID and OneBalance API Key. If you don't have Node.js installed or need to update, we recommend using nvm: ```bash terminal-commands.sh theme={null} # Install nvm (if not already installed) curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash # Install and use Node.js 20 nvm install 20 nvm use 20 # Install pnpm globally # Using npm to install pnpm as a one-time step npm install -g pnpm ``` Set up a new React project using Next.js: ```bash project-setup.sh theme={null} pnpm create next-app onebalance-privy-demo cd onebalance-privy-demo ``` When prompted, select the following options: * Would you like to use TypeScript? › Yes * Would you like to use ESLint? › Yes * Would you like to use Tailwind CSS? › Yes * Would you like your code inside a `src/` directory? › Yes * Would you like to use App Router? (recommended) › Yes * Would you like to use Turbopack for `next dev`? › Yes * Would you like to customize the import alias (`@/*` by default)? › No ![Next.js project setup](https://storage.googleapis.com/onebalance-public-assets/docs/getting-started/setup.png) Install the required packages: ```bash install-deps.sh theme={null} pnpm add @privy-io/react-auth viem @tanstack/react-query dotenv axios ``` Create a `.env` file in the project root with your API keys: ```toml .env theme={null} # .env NEXT_PUBLIC_PRIVY_APP_ID=cmb94tjcj001xle0mdeerz9vp # For quickstart only; use your own in production NEXT_PUBLIC_ONEBALANCE_API_KEY=42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11 # For quickstart only ``` Alternatively, you can create the `.env` file using command line: ```bash create-env.sh theme={null} # Create .env file touch .env # Add environment variables echo "NEXT_PUBLIC_PRIVY_APP_ID=cmb94tjcj001xle0mdeerz9vp" >> .env echo "NEXT_PUBLIC_ONEBALANCE_API_KEY=42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11" >> .env ``` When working with the OneBalance API from a browser, you'll need to handle CORS restrictions. The recommended approach is to create a server-side proxy using Next.js API routes. Create a new file at `src/app/api/[...path]/route.ts`: ```typescript route.ts theme={null} // src/app/api/[...path]/route.ts import { NextRequest, NextResponse } from 'next/server'; // OneBalance API base URL and API key const API_BASE_URL = 'https://be.onebalance.io'; const API_KEY = process.env.NEXT_PUBLIC_ONEBALANCE_API_KEY || ''; export async function GET( request: NextRequest, { params }: { params: Promise<{ path: string[] }> } ) { const { path } = await params; const pathString = path.join('/'); const searchParams = request.nextUrl.searchParams; try { // Build the API URL with any query parameters const apiUrl = new URL(`/api/${pathString}`, API_BASE_URL); searchParams.forEach((value, key) => { apiUrl.searchParams.append(key, value); }); const response = await fetch(apiUrl.toString(), { headers: { 'Content-Type': 'application/json', 'x-api-key': API_KEY, }, }); const data = await response.json(); return NextResponse.json(data); } catch (error) { return NextResponse.json({ message: 'Failed to fetch data', error }, { status: 400 }); } } export async function POST( request: NextRequest, { params }: { params: Promise<{ path: string[] }> } ) { const { path } = await params; const pathString = path.join('/'); try { const body = await request.json(); const response = await fetch(`${API_BASE_URL}/api/${pathString}`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': API_KEY, }, body: JSON.stringify(body), }); const data = await response.json(); return NextResponse.json(data); } catch (error) { return NextResponse.json({ message: 'Failed to fetch data', error }, { status: 400 }); } } ``` This proxy implementation ensures proper [authentication](/api-reference/authentication) and CORS handling for all API calls. Learn more about [CORS handling](/api-reference/cors) in our documentation. This proxy will allow your frontend to make requests to the OneBalance API without CORS issues. Now that we have the CORS handling in place, let's create a client to interact with the OneBalance API through our proxy. Create a file at `src/lib/onebalance.ts`: ```typescript onebalance.ts theme={null} // src/lib/onebalance.ts import axios from 'axios'; // Create an axios client that points to our proxy export const apiClient = axios.create({ baseURL: '/api', headers: { 'Content-Type': 'application/json', }, }); // OneBalance API base URL and API key (for reference) export const API_BASE_URL = 'https://be.onebalance.io/api'; export const API_KEY = process.env.NEXT_PUBLIC_ONEBALANCE_API_KEY; // Predict account address for a user based on their Privy wallet export async function predictAccountAddress(sessionAddress: string, adminAddress: string) { try { const response = await apiClient.post('/account/predict-address', { sessionAddress, adminAddress }); return response.data?.predictedAddress; } catch (error) { console.error('Error predicting account address:', error); throw error; } } // Get aggregated balance for a smart account export async function getAggregatedBalance(address: string) { try { const response = await apiClient.get(`/v2/balances/aggregated-balance?address=${address}`); return response.data; } catch (error) { console.error('Error fetching aggregated balance:', error); throw error; } } // Get a quote for swapping or transferring tokens export async function getQuote(params: { from: { account: { sessionAddress: string; adminAddress: string; accountAddress: string; }; asset: { assetId: string; }; amount: string; }; to: { asset: { assetId: string; }; }; }) { try { const response = await apiClient.post('/v1/quote', params); return response.data; } catch (error) { console.error('Error getting quote:', error); throw error; } } // Execute a quote after getting user signature export async function executeQuote(signedQuote: any) { try { const response = await apiClient.post('/quotes/execute-quote', signedQuote); return response.data; } catch (error) { console.error('Error executing quote:', error); throw error; } } // Check transaction status export async function checkTransactionStatus(quoteId: string) { try { const response = await apiClient.get(`/status/get-execution-status?quoteId=${quoteId}`); return response.data; } catch (error) { console.error('Error checking transaction status:', error); throw error; } } ``` This client implements several core OneBalance API endpoints: * [Predict Account Address](/api-reference/account/predict-address) - Get a smart contract account address before deployment * [Get Aggregated Balance](/api-reference/balances/aggregated-balance) - View multichain token balances * [Get Quote](/api-reference/quotes/get-quote) - Request a quote for token swaps or transfers * [Execute Quote](/api-reference/quotes/execute-quote) - Execute a signed swap or transfer * [Check Transaction Status](/api-reference/status/get-quote-status) - Monitor transaction progress Create a Privy provider component to wrap your application. Create a new file at `src/components/PrivyProvider.tsx`: ```tsx PrivyProvider.tsx theme={null} // src/components/PrivyProvider.tsx 'use client'; import { PrivyProvider } from '@privy-io/react-auth'; export function PrivyClientProvider({ children }: { children: React.ReactNode }) { const privyAppId = process.env.NEXT_PUBLIC_PRIVY_APP_ID as string; // Configure Privy with your app ID return ( {children} ); } ``` Update your app's layout to include the Privy provider: ```tsx layout.tsx theme={null} // src/app/layout.tsx import type { Metadata } from 'next'; import { Inter } from 'next/font/google'; import './globals.css'; import { PrivyClientProvider } from '@/components/PrivyProvider'; const inter = Inter({ subsets: ['latin'] }); export const metadata: Metadata = { title: 'OneBalance + Privy Demo', description: 'Seamless chain-abstracted transfers with OneBalance and Privy', }; export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( {children} ); } ``` Let's create a simple login page that uses Privy's login UI: ```tsx page.tsx theme={null} // src/app/page.tsx 'use client'; import { usePrivy } from '@privy-io/react-auth'; import { useRouter } from 'next/navigation'; import { useEffect } from 'react'; export default function Home() { const { login, ready, authenticated } = usePrivy(); const router = useRouter(); // Redirect to dashboard if already authenticated useEffect(() => { if (ready && authenticated) { router.push('/dashboard'); } }, [ready, authenticated, router]); return (

OneBalance Demo

Experience seamless chain-abstracted transactions with OneBalance and Privy

Login with email, social, or connect your wallet to get started

); } ``` 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: ![Privy Login](https://storage.googleapis.com/onebalance-public-assets/docs/getting-started/privy-login.png) 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. ![Privy Email Login Code](https://storage.googleapis.com/onebalance-public-assets/docs/getting-started/email-privy-login-code.png)
Let's create a simple dashboard page that we'll enhance in the next section. Create a new directory and file at `src/app/dashboard/page.tsx`: ```tsx page.tsx theme={null} // src/app/dashboard/page.tsx 'use client'; import { usePrivy } from '@privy-io/react-auth'; import { useRouter } from 'next/navigation'; export default function Dashboard() { const { user, logout } = usePrivy(); const router = useRouter(); const handleLogout = async () => { await logout(); router.push('/'); }; return (

OneBalance Dashboard

Welcome!
{user?.email?.address || 'Anonymous User'}

🎉 Setup Complete!

You've successfully logged in with Privy. In the next section, we'll enhance this dashboard to:

  • Set up your OneBalance smart contract account
  • Display your aggregated token balances
  • Enable chain-abstracted swaps
); } ```
Start the development server: ```bash start-dev.sh theme={null} pnpm dev ``` Your app should now be running at [http://localhost:3000](http://localhost:3000). Open this URL in your browser to see it in action.
## 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 For this quickstart guide, OneBalance sponsors the underlying network fees for some operations to provide a smoother demonstration experience. In a production environment, fees are handled according to the method outlined in our [Fees documentation](/concepts/fees). **Important**: Always send funds to your SCA address (which you'll create in the next step), not to the Privy EOA address. ## The User Journey With Privy integration, users can experience a seamless onboarding flow: 1. **Simple Authentication**: Users can login via email, social accounts, or connecting an existing wallet 2. **Embedded Wallet Creation**: For users without a crypto wallet, Privy automatically creates one 3. **Smart Contract Account**: The Privy wallet acts as a signer for the user's OneBalance SCA 4. **Chain-Abstracted Transactions**: Users can swap and transfer assets across chains without switching networks This approach eliminates many barriers to entry for new users while providing a powerful experience for crypto-native users as well. ## Understanding the Integration Our setup combines: 1. **Privy for Wallet Infrastructure**: * Social login options (email, passkey) * Embedded wallet creation and management * Simplified transaction signing 2. **OneBalance for Chain Abstraction**: * Unified access to assets across chains * Chain-abstracted swaps and transfers with no bridging * Gas abstraction (pay fees with any token) * Aggregated balances for a unified view of your assets 3. **CORS Handling with Next.js API Routes**: * Server-side proxy to avoid browser CORS restrictions * Secure API key management * Consistent error handling ## What's Next? Now that your environment is set up, we'll create a dashboard for chain-abstracted operations in the [next section](/getting-started/first-api-call). # Building cross-chain swaps with OneBalance and Privy Tutorial Source: https://docs.onebalance.io/guides/chain-abstracted-swap-with-privy Learn the key concepts for building a production-ready chain-abstracted token swap interface using OneBalance API and Privy embedded wallets. This guide covers the essential concepts and patterns for building a swap interface that abstracts away blockchain complexity. Complete demo available at [onebalance-chain-abstracted-swap.vercel.app](https://onebalance-chain-abstracted-swap.vercel.app). All examples in this guide are 100% free and open-source. Clone the repository to quickly get started. ## Core Architecture Overview A successful chain-abstracted swap application requires understanding these key components: * **Smart Account Prediction**: Determine account addresses before deployment * **Quote Management**: Real-time quote fetching with expiration handling * **Transaction Signing**: EIP-712 typed data signing with Privy * **Status Monitoring**: Real-time transaction status polling * **Balance Validation**: Ensure sufficient funds before execution ## Prerequisites Before diving into building the cross-chain swap interface, you'll need to have the following prerequisites in place: ### Development Environment * **Node.js 20+**: Our project uses modern TypeScript features. Install using [nvm](https://github.com/nvm-sh/nvm) or download from [nodejs.org](https://nodejs.org/). * **pnpm**: We'll use pnpm as our package manager for its speed and efficiency. Install it by running `npm install -g pnpm` or follow the [official installation guide](https://pnpm.io/installation). ### Technical Knowledge * **Next.js App Router**: Understanding of Next.js 15's App Router architecture is essential for this project. * **React Hooks**: Familiarity with React's useState, useEffect, useCallback, and custom hooks. * **TypeScript**: Basic knowledge of TypeScript types and interfaces. * **Tailwind CSS & shadcn/ui**: We'll use these for styling and components. ### Privy Setup To integrate Privy into your codebase, follow [Privy's Quickstart documentation](https://docs.privy.io/guide/react/quickstart). Once Privy is set up, ensure you have the following: * **Privy App ID** * **PrivyProvider** configured on your client * User login workflow organized and tested on the client side ## Setting Up the Project We will use [Next.js 15](https://nextjs.org) with app router & TypeScript for this project, along with [Tailwind CSS](https://tailwindcss.com) for styling and [shadcn/ui](https://ui.shadcn.com) for components. Let's initialize the project: ```bash terminal theme={null} pnpm dlx shadcn@latest init ``` For wallet connection and transaction signing, we will use [Privy](https://www.privy.io): ```bash terminal theme={null} pnpm add @tanstack/react-query wagmi viem @privy-io/react-auth ``` ### Environment Setup Make sure to copy `.env.example` into `.env`: ```jsx .env theme={null} NEXT_PUBLIC_API_URL=https://be.onebalance.io NEXT_PUBLIC_API_KEY= NEXT_PUBLIC_PRIVY_APP_ID= ``` Get your OneBalance API key from [www.onebalance.io](https://www.onebalance.io) and Privy App ID from [console.privy.io](https://console.privy.io) ## 1. Smart Account Integration ### Understanding OneBalance Smart Accounts OneBalance uses Smart Contract Accounts (SCAs) that can be **predicted** before deployment. This enables: * Receiving funds before the account exists on-chain * Seamless transaction execution across chains * Gas sponsorship and batched operations ### Implementation Pattern ```typescript lib/api/account.ts theme={null} export const accountApi = { predictAddress: async (sessionAddress: string, adminAddress: string) => { const response = await apiClient.post('/account/predict-address', { sessionAddress, adminAddress, }); return response.data?.predictedAddress; }, }; ``` **Key Concept**: Use the same wallet address for both `sessionAddress` and `adminAddress` for simplicity. The predicted address becomes your user's primary account identifier. ## 2. Quote Management & Lifecycle ### Quote Request Structure OneBalance quotes follow a specific request format that defines the swap parameters: ```typescript theme={null} interface QuoteRequest { from: { account: { sessionAddress: string; // Privy wallet address adminAddress: string; // Same as session for simplicity accountAddress: string; // Predicted smart account }; asset: { assetId: string }; // e.g., "ob:usdc" amount: string; // Token amount in wei }; to: { asset: { assetId: string }; // e.g., "ob:eth" account?: string; // Optional: for transfers to other addresses }; } ``` ### Quote Lifecycle Management The quote lifecycle involves several critical stages: 1. **Validation**: Check user balance before requesting quotes 2. **Expiration Handling**: Quotes expire in 30 seconds - implement countdown timers 3. **Auto-refresh**: Automatically fetch new quotes when current ones expire 4. **Debouncing**: Prevent excessive API calls during user input **Implementation Pattern** (from `useQuotes.ts`): ```typescript theme={null} // Debounced quote fetching const debouncedGetQuote = useCallback( debounce(async (request) => { await getQuote(request); }, 1000), [getQuote] ); // Balance validation before quote request const hasSufficientBalance = (amount: string) => { if (!sourceBalance || !selectedAsset || !amount) return false; const parsedAmount = parseTokenAmount(amount, selectedAsset.decimals); return BigInt(sourceBalance.balance) >= BigInt(parsedAmount); }; ``` ## 3. Transaction Signing with Privy ### EIP-712 Typed Data Signing OneBalance transactions require signing structured data (EIP-712) for each chain operation. The signing process handles multiple operations sequentially. **Key Implementation** (from `privySigningUtils.ts`): ```typescript theme={null} import { Address, createWalletClient, custom, Hash } from 'viem'; import { ConnectedWallet } from '@privy-io/react-auth'; import { ChainOperation, Quote } from '@/lib/types/quote'; export const signTypedDataWithPrivy = (embeddedWallet: ConnectedWallet) => async (typedData: any): Promise => { const provider = await embeddedWallet.getEthereumProvider(); const walletClient = createWalletClient({ transport: custom(provider), account: embeddedWallet.address as Address, }); return walletClient.signTypedData(typedData); }; export const signOperation = (embeddedWallet: ConnectedWallet) => (operation: ChainOperation): (() => Promise) => async () => { const signature = await signTypedDataWithPrivy(embeddedWallet)(operation.typedDataToSign); return { ...operation, userOp: { ...operation.userOp, signature }, }; }; export const signQuote = async (quote: Quote, embeddedWallet: ConnectedWallet) => { const signWithEmbeddedWallet = signOperation(embeddedWallet); const signedQuote = { ...quote, }; signedQuote.originChainsOperations = await sequentialPromises( quote.originChainsOperations.map(signWithEmbeddedWallet) ); if (quote.destinationChainOperation) { signedQuote.destinationChainOperation = await signWithEmbeddedWallet( quote.destinationChainOperation )(); } return signedQuote; }; // Helper to run an array of lazy promises in sequence export const sequentialPromises = (promises: (() => Promise)[]): Promise => { return promises.reduce>( (acc, curr) => acc.then(results => curr().then(result => [...results, result])), Promise.resolve([]) ); }; ``` **Critical Points**: * Each quote contains multiple `ChainOperation` objects that need individual signatures * Operations must be signed **sequentially** to avoid nonce conflicts * The `typedDataToSign` field contains the EIP-712 structure for each operation ## 4. Transaction Execution Flow ### Complete Transaction Workflow 1. **Quote Validation**: Check expiration before execution 2. **Signing**: Sign all required operations using Privy 3. **Execution**: Submit signed quote to OneBalance 4. **Monitoring**: Poll transaction status until completion **Implementation Pattern**: ```typescript theme={null} const executeQuote = async () => { // 1. Validate quote expiration const expirationTime = parseInt(quote.expirationTimestamp) * 1000; if (Date.now() > expirationTime) { throw new Error('Quote has expired'); } // 2. Sign quote with Privy const signedQuote = await signQuote(quote, embeddedWallet); // 3. Execute signed quote await quotesApi.executeQuote(signedQuote); // 4. Start status polling startStatusPolling(quote.id); }; ``` ## 5. Real-Time Status Monitoring ### Status Polling Implementation After execution, implement real-time polling to track transaction progress: ```typescript theme={null} // Polling pattern from useQuotes.ts const pollTransactionStatus = (quoteId: string) => { const interval = setInterval(async () => { try { const status = await quotesApi.getQuoteStatus(quoteId); if (status.status === 'COMPLETED' || status.status === 'FAILED') { clearInterval(interval); handleTransactionComplete(status); } } catch (error) { console.error('Polling error:', error); clearInterval(interval); } }, 1000); // Poll every second }; ``` **Status Types**: * `PENDING`: Transaction submitted to blockchain * `IN_PROGRESS`: Being processed across chains * `COMPLETED`: Successfully completed * `FAILED`: Transaction failed * `REFUNDED`: Funds returned to user ## 6. User Experience Enhancements ### Quote Countdown Timer Show users when quotes will expire: ```typescript theme={null} // From QuoteCountdown component import { useState, useEffect, useCallback } from 'react'; export function useCountdown(timestamp: number, onExpire?: () => void) { const [timeLeft, setTimeLeft] = useState(0); useEffect(() => { const calculateTimeLeft = () => { const difference = timestamp * 1000 - Date.now(); return Math.max(0, Math.floor(difference / 1000)); }; setTimeLeft(calculateTimeLeft()); const timer = setInterval(() => { const newTimeLeft = calculateTimeLeft(); setTimeLeft(newTimeLeft); if (newTimeLeft < 1) { clearInterval(timer); onExpire?.(); } }, 1000); return () => clearInterval(timer); }, [timestamp, onExpire]); const formatTime = useCallback((seconds: number) => { if (seconds === 0) { return 'Expired'; } const secs = seconds % 60; return `${secs.toString()}`; }, []); return { timeLeft, formattedTime: formatTime(timeLeft), isExpired: timeLeft === 0, }; } ``` ![Quote Details](https://storage.googleapis.com/onebalance-public-assets/docs/guides/chain-abstracted-swap-with-privy/quote-details.png) ## 7. Error Handling & Recovery ### Graceful Error Management Implement error handling for all failure scenarios: ```typescript theme={null} // Quote error handling if (quote?.error) { return ( Quote Error {quote.message} ); } // Transaction failure recovery const handleTransactionFailure = (error) => { // Clear state resetQuote(); // Show user-friendly error setError(error.message || 'Transaction failed'); // Refresh balances to ensure accuracy fetchBalances(); }; ``` ## 8. API Integration Patterns ### Proxy Pattern for CORS Use Next.js API routes to handle CORS and secure API keys: ```typescript app/api/[...path]/route.ts theme={null} import { NextRequest, NextResponse } from 'next/server'; import { API_BASE_URL, API_KEY } from '@/lib/constants'; export async function GET( request: NextRequest, { params }: { params: Promise<{ path: string[] }> } ) { const { path } = await params; const pathString = path.join('/'); const searchParams = request.nextUrl.searchParams; try { // Build the API URL with any query parameters const apiUrl = new URL(`/api/${pathString}`, API_BASE_URL); searchParams.forEach((value, key) => { apiUrl.searchParams.append(key, value); }); const response = await fetch(apiUrl.toString(), { headers: { 'Content-Type': 'application/json', 'x-api-key': API_KEY, }, }); const data = await response.json(); return NextResponse.json(data); } catch (error) { return NextResponse.json({ message: 'Failed to fetch data', error }, { status: 400 }); } } export async function POST( request: NextRequest, { params }: { params: Promise<{ path: string[] }> } ) { const { path } = await params; const pathString = path.join('/'); try { const body = await request.json(); const response = await fetch(`${API_BASE_URL}/api/${pathString}`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': API_KEY, }, body: JSON.stringify(body), }); const data = await response.json(); return NextResponse.json(data); } catch (error) { return NextResponse.json({ message: 'Failed to fetch data', error }, { status: 400 }); } } ``` ## Key Integration Concepts ### Chain Abstraction Benefits * **Unified Token Balances**: Users see aggregated balances across all chains * **Automatic Routing**: OneBalance finds optimal paths for swaps * **Gas Sponsorship**: No need for users to hold native tokens for gas * **Network Abstraction**: Users never need to think about which chain they're using ### Production Considerations 1. **Error Boundaries**: Implement React error boundaries for graceful failures 2. **Rate Limiting**: Implement client-side rate limiting for API calls 3. **Cache Management**: Cache asset data and balance information appropriately 4. **Security**: Never expose API keys in client-side code The complete implementation demonstrates these patterns in production. Check the [GitHub repository](https://github.com/dzimiks/onebalance-chain-abstracted-swap) for detailed examples and best practices. # Advanced Contract Call Patterns Source: https://docs.onebalance.io/guides/contract-calls/advanced-patterns Explore advanced cross-chain contract call patterns in OneBalance, including multi-step flows and production optimizations. Implement sophisticated contract call patterns for production applications. Learn advanced techniques for complex workflows and robust error handling. ## Transaction Batching ### Aggregate, Swap, and Stake Pattern This pattern combines multiple DeFi operations into a single transaction, using aggregated funds to open leveraged positions across protocols. ```typescript cross-chain-leverage.ts theme={null} async function openLeveragedPosition( marginAmount: bigint, leverage: number, marginAsset: string, isLong: boolean ) { const calls = []; // 1. Swap aggregated USDC to margin asset if needed if (marginAsset !== USDC_ADDRESS) { calls.push({ to: DEX_ROUTER, data: encodeFunctionData({ abi: parseAbi(['function swap(address,address,uint256,uint256,address)']), functionName: 'swap', args: [USDC_ADDRESS, marginAsset, marginAmount, minOut, account.accountAddress] }), value: '0' }); } // 2. Deposit margin to perpetual protocol calls.push({ to: PERP_CONTRACT, data: encodeFunctionData({ abi: parseAbi(['function depositMargin(address,uint256)']), functionName: 'depositMargin', args: [marginAsset, marginAmount] }), value: '0' }); // 3. Open leveraged position calls.push({ to: PERP_CONTRACT, data: encodeFunctionData({ abi: parseAbi(['function openPosition(uint256,bool)']), functionName: 'openPosition', args: [marginAmount * BigInt(leverage), isLong] }), value: '0' }); return await executeCall({ targetChain: 'eip155:42161', // Arbitrum calls, tokensRequired: [{ assetType: `eip155:42161/erc20:${marginAsset}`, amount: marginAmount.toString() }], allowanceRequirements: [ { assetType: `eip155:42161/erc20:${USDC_ADDRESS}`, amount: marginAmount.toString(), spender: DEX_ROUTER }, { assetType: `eip155:42161/erc20:${marginAsset}`, amount: marginAmount.toString(), spender: PERP_CONTRACT } ], fromAggregatedAssetId: 'ob:usdc' // Pull from any chain }); } ``` ### Cross-Chain Swap and Stake Use USDC from multiple chains to swap and stake in a single operation. This pattern optimizes for users who want to participate in staking protocols using their distributed token holdings. ```typescript cross-chain-staking.ts theme={null} const crossChainStake = async (stakeAmount: bigint) => { const WETH = '0x4200000000000000000000000000000000000006'; const STAKING_CONTRACT = '0x796f1793599D7b6acA6A87516546DdF8E5F3aA9d'; const UNISWAP_ROUTER = '0x2626664c2603336E57B271c5C0b26F421741e481'; // 1. Swap USDC to WETH on Base const swapData = encodeFunctionData({ abi: parseAbi([ 'function exactInputSingle((address,address,uint24,address,uint256,uint256,uint256,uint160)) returns (uint256)' ]), functionName: 'exactInputSingle', args: [{ tokenIn: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC tokenOut: WETH, fee: 3000, recipient: account.accountAddress, deadline: Math.floor(Date.now() / 1000) + 1800, amountIn: stakeAmount, amountOutMinimum: 0n, sqrtPriceLimitX96: 0n }] }); // 2. Stake WETH (approval handled by allowanceRequirements) const stakeData = encodeFunctionData({ abi: parseAbi(['function stake(uint256 amount)']), functionName: 'stake', args: [stakeAmount] // Will use actual WETH received from swap }); return await executeCall({ targetChain: 'eip155:8453', // Base calls: [ { to: UNISWAP_ROUTER, data: swapData, value: '0' }, { to: STAKING_CONTRACT, data: stakeData, value: '0' } ], tokensRequired: [{ assetType: 'eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', amount: stakeAmount.toString() }], allowanceRequirements: [ { assetType: 'eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', amount: stakeAmount.toString(), spender: UNISWAP_ROUTER }, { assetType: `eip155:8453/erc20:${WETH}`, amount: stakeAmount.toString(), // Approximate amount for approval spender: STAKING_CONTRACT } ], fromAggregatedAssetId: 'ob:usdc' }); }; ``` ## Batch Operations ### Multi-Recipient Payment System Process multiple payments efficiently by grouping them by token type and using multicall patterns to reduce gas costs. ```typescript batch-payments.ts theme={null} interface Payment { recipient: string; amount: bigint; token: string; } async function batchPayout(payments: Payment[]) { const calls = []; const tokensRequired = []; // Group payments by token to optimize gas usage const paymentsByToken = payments.reduce((acc, payment) => { if (!acc[payment.token]) acc[payment.token] = []; acc[payment.token].push(payment); return acc; }, {} as Record); for (const [token, tokenPayments] of Object.entries(paymentsByToken)) { // Use multicall for same-token transfers const multicallData = encodeFunctionData({ abi: parseAbi(['function multicall(bytes[] calldata data)']), functionName: 'multicall', args: [ tokenPayments.map(p => encodeFunctionData({ abi: parseAbi(['function transfer(address,uint256)']), functionName: 'transfer', args: [p.recipient, p.amount] }) ) ] }); calls.push({ to: token, data: multicallData, value: '0' }); const totalAmount = tokenPayments.reduce( (sum, p) => sum + p.amount, 0n ); tokensRequired.push({ assetType: `eip155:8453/erc20:${token}`, amount: totalAmount.toString() }); } return await executeCall({ targetChain: 'eip155:8453', calls, tokensRequired, fromAggregatedAssetId: 'ob:usdc' }); } ``` ## Gas Optimization Patterns ### Conditional Execution Pattern Skip unnecessary operations by checking on-chain state before execution, reducing gas costs for operations that may not be needed. ```typescript conditional-execution.ts theme={null} async function optimizedDeFiOperation(amount: bigint) { const calls = []; const AAVE_POOL = '0xA238Dd80C259a72e81d7e4664a9801593F98d1c5'; const USDC_ADDRESS = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'; // Check if we need to withdraw from lending protocol first const currentDeposit = await checkAaveDeposit(account.accountAddress); if (currentDeposit > 0n) { calls.push({ to: AAVE_POOL, data: encodeFunctionData({ abi: parseAbi(['function withdraw(address,uint256,address)']), functionName: 'withdraw', args: [USDC_ADDRESS, currentDeposit, account.accountAddress] }), value: '0' }); } // Main operation (e.g., swap, stake, etc.) calls.push({ to: TARGET_CONTRACT, data: mainOperationData, value: '0' }); // Only re-deposit excess if amount justifies gas cost const minDepositAmount = parseUnits('100', 6); // $100 minimum if (amount > minDepositAmount) { calls.push({ to: AAVE_POOL, data: encodeFunctionData({ abi: parseAbi(['function supply(address,uint256,address,uint16)']), functionName: 'supply', args: [USDC_ADDRESS, amount, account.accountAddress, 0] }), value: '0' }); } return await executeCall({ targetChain: 'eip155:8453', calls, tokensRequired: [{ assetType: 'eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', amount: amount.toString() }], allowanceRequirements: [{ assetType: 'eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', amount: amount.toString(), spender: AAVE_POOL }] }); } ``` ## Error Recovery Patterns ### Safe Execution with Fallbacks Implement fallback mechanisms for DEX operations when primary routes fail due to liquidity or slippage issues. ```typescript error-recovery.ts theme={null} interface CallData { to: string; data: string; value: string; } async function safeExecuteWithFallback( primaryOperation: CallData[], fallbackOperation: CallData[] ) { try { // Attempt primary operation first return await executeCall({ targetChain: 'eip155:8453', calls: primaryOperation, tokensRequired: calculateTokensRequired(primaryOperation) }); } catch (error: any) { // Handle specific errors with fallback strategies if (error.message.includes('INSUFFICIENT_LIQUIDITY') || error.message.includes('EXCESSIVE_SLIPPAGE')) { console.log('Primary route failed, attempting fallback...'); return await executeCall({ targetChain: 'eip155:8453', calls: fallbackOperation, tokensRequired: calculateTokensRequired(fallbackOperation) }); } // Re-throw unexpected errors throw error; } } function calculateTokensRequired(calls: CallData[]) { // Implementation to analyze calls and determine token requirements return []; } ``` ## State Management Patterns ### Transaction Lifecycle Tracking Track and monitor complex transactions with automatic retry and status monitoring capabilities. ```typescript transaction-tracker.ts theme={null} interface OperationStatus { status: 'PENDING' | 'COMPLETED' | 'FAILED' | 'IN_PROGRESS' | 'REFUNDED'; } interface OperationDetails { hash: string; chainId: number; explorerUrl: string; } interface OriginAssetUsed { aggregatedAssetId: string; amount: string; assetType: string[]; fiatValue: Array<{ assetType: string; fiatValue: string; }>; } interface DestinationAssetUsed { aggregatedAssetId: string; amount: string; assetType: string; fiatValue: string; minimumAmount?: string; minimumFiatValue?: string; } interface Transaction { quoteId: string; status: OperationStatus; user: string; recipientAccountId: string; originChainOperations: OperationDetails[]; destinationChainOperations: OperationDetails[]; type: string; originToken: OriginAssetUsed; destinationToken: DestinationAssetUsed; timestamp: string; } interface LocalTransactionState { id: string; request: any; localStatus: 'preparing' | 'executing' | 'monitoring' | 'completed' | 'failed'; preparedAt: number; transaction?: Transaction; error?: any; } class TransactionTracker { private pending = new Map(); async executeAndTrack(request: any): Promise { const txId = this.generateId(); // Store initial transaction state this.pending.set(txId, { id: txId, request, localStatus: 'preparing', preparedAt: Date.now() }); try { // Step 1: Prepare and sign const quote = await this.prepareAndSign(request); this.updateLocalStatus(txId, 'executing'); // Step 2: Execute const result = await this.executeQuote(quote); this.updateLocalStatus(txId, 'monitoring'); // Step 3: Monitor with exponential backoff const finalResult = await this.monitorWithBackoff(quote.id); this.updateLocalStatus(txId, 'completed', finalResult); return finalResult; } catch (error) { this.updateLocalStatus(txId, 'failed', undefined, error); throw error; } finally { // Cleanup after 24 hours setTimeout(() => this.pending.delete(txId), 86400000); } } private async monitorWithBackoff(quoteId: string): Promise { const delays = [1000, 2000, 5000, 10000, 15000]; // Progressive delays const maxAttempts = 30; for (let attempt = 0; attempt < maxAttempts; attempt++) { const status = await getQuoteStatus(quoteId); if (['COMPLETED', 'FAILED', 'REFUNDED'].includes(status.status.status)) { return status; } const delay = delays[Math.min(attempt, delays.length - 1)]; await new Promise(resolve => setTimeout(resolve, delay)); } throw new Error('Transaction monitoring timeout'); } private generateId(): string { return `tx_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } private updateLocalStatus( id: string, localStatus: LocalTransactionState['localStatus'], transaction?: Transaction, error?: any ) { const tx = this.pending.get(id); if (tx) { tx.localStatus = localStatus; if (transaction) tx.transaction = transaction; if (error) tx.error = error; } } // Get current transaction status getTransactionStatus(id: string): LocalTransactionState | undefined { return this.pending.get(id); } // Get all pending transactions getPendingTransactions(): LocalTransactionState[] { return Array.from(this.pending.values()); } } ``` ## Integration Patterns ### Wallet-Agnostic Implementation Create universal interfaces that work with any wallet provider, making your integration flexible across different wallet ecosystems. ```typescript wallet-integration.ts theme={null} interface WalletAdapter { signTypedData(data: any): Promise; getAddress(): Promise; isConnected(): Promise; } class UniversalOneBalance { constructor( private apiKey: string, private wallet: WalletAdapter ) {} async executeCall(params: any) { // Ensure wallet is connected const isConnected = await this.wallet.isConnected(); if (!isConnected) { throw new Error('Wallet not connected'); } // Prepare operation const account = await this.getAccount(); const preparedQuote = await this.prepare({ ...params, account }); // Sign with wallet adapter const signature = await this.wallet.signTypedData( preparedQuote.chainOperation.typedDataToSign ); preparedQuote.chainOperation.userOp.signature = signature; // Execute operation return await this.execute(preparedQuote); } private async getAccount() { const address = await this.wallet.getAddress(); return { sessionAddress: address, adminAddress: address, // Simplified for basic account accountAddress: await this.predictAccountAddress(address) }; } } // MetaMask adapter example const metamaskAdapter: WalletAdapter = { signTypedData: (data) => window.ethereum.request({ method: 'eth_signTypedData_v4', params: [address, JSON.stringify(data)] }), getAddress: async () => { const accounts = await window.ethereum.request({ method: 'eth_accounts' }); return accounts[0]; }, isConnected: async () => { const accounts = await window.ethereum.request({ method: 'eth_accounts' }); return accounts.length > 0; } }; // Usage const oneBalance = new UniversalOneBalance(apiKey, metamaskAdapter); ``` ## Performance Optimization Tips 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 ### Implementation Best Practices Design your call sequence to minimize gas usage and maximize success probability Implement fallbacks for common failure scenarios like insufficient liquidity Track transaction status and provide users with real-time updates Validate complex patterns with small amounts before production deployment **Advanced Features**: For even more sophisticated patterns, consider combining these techniques with OneBalance's resource locks and time-based permissions for enhanced security and user experience. # Allowances & Approvals Source: https://docs.onebalance.io/guides/contract-calls/allowances-and-approvals Learn how OneBalance manages token allowances and approvals, ensuring secure and gas-efficient transactions automatically. Learn how OneBalance automatically handles token approvals for DeFi operations while maintaining security best practices. **Important**: Never manually call `approve()` functions when using OneBalance. The platform automatically manages all required approvals as part of the contract call preparation process. ## How Approval Management Works When you specify `allowanceRequirements`, OneBalance automatically: 1. **Checks existing allowances** - Verifies current approval amounts 2. **Adds approval transactions** - Only when needed, for exact amounts 3. **Executes your operations** - Runs your contract calls 4. **Removes excess approvals** - Cleans up remaining allowances for security This prevents front-running attacks and ensures optimal gas usage. OneBalance bundles approvals, your calls, and approval cleanup into a single user operation for atomic execution. ## Basic Example Here's how to properly handle approvals for a DEX swap: ```typescript swap-with-approval.ts theme={null} import { encodeFunctionData, parseAbi, parseUnits } from 'viem'; const USDC_BASE = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'; const UNISWAP_ROUTER = '0x2626664c2603336E57B271c5C0b26F421741e481'; // ✅ Correct - Let OneBalance handle approvals const prepareRequest = { account: { sessionAddress: '0x...', adminAddress: '0x...', accountAddress: '0x...' }, targetChain: 'eip155:8453', // Base calls: [{ to: UNISWAP_ROUTER, data: encodeFunctionData({ abi: parseAbi(['function exactInputSingle((address tokenIn, address tokenOut, uint24 fee, address recipient, uint256 deadline, uint256 amountIn, uint256 amountOutMinimum, uint160 sqrtPriceLimitX96))']), functionName: 'exactInputSingle', args: [{ tokenIn: USDC_BASE, tokenOut: '0x...', fee: 3000, recipient: account.accountAddress, deadline: Math.floor(Date.now() / 1000) + 3600, amountIn: parseUnits('100', 6), // 100 USDC amountOutMinimum: 0, sqrtPriceLimitX96: 0 }] }), value: '0' }], allowanceRequirements: [{ assetType: `eip155:8453/erc20:${USDC_BASE}`, amount: parseUnits('100', 6).toString(), // Exact amount needed spender: UNISWAP_ROUTER }], tokensRequired: [{ assetType: `eip155:8453/erc20:${USDC_BASE}`, amount: parseUnits('100', 6).toString() }] }; ``` ## What NOT to Do **Common Mistake**: Including approval calls manually will cause transaction failures. ```typescript wrong-approach.ts theme={null} // ❌ WRONG - Don't do this! const badRequest = { calls: [ { // This will cause the transaction to fail to: USDC_BASE, data: encodeFunctionData({ abi: parseAbi(['function approve(address spender, uint256 amount)']), functionName: 'approve', args: [UNISWAP_ROUTER, parseUnits('100', 6)] }), value: '0' }, { to: UNISWAP_ROUTER, data: swapCallData, value: '0' } ] // No allowanceRequirements - this is the problem! }; ``` ## Common Patterns ### DEX Swaps ```typescript dex-swap.ts theme={null} // Uniswap V3 swap requiring token approval allowanceRequirements: [{ assetType: `eip155:8453/erc20:${inputToken}`, amount: inputAmount.toString(), spender: UNISWAP_V3_ROUTER }] ``` ### Lending Protocols ```typescript aave-deposit.ts theme={null} // Aave deposit requiring pool approval allowanceRequirements: [{ assetType: `eip155:1/erc20:${asset}`, amount: depositAmount.toString(), spender: AAVE_POOL_ADDRESS }] ``` ### Multiple Token Operations ```typescript multi-token-defi.ts theme={null} // Complex DeFi operation requiring multiple approvals allowanceRequirements: [ { assetType: `eip155:1/erc20:${tokenA}`, amount: amountA.toString(), spender: DEX_ROUTER }, { assetType: `eip155:1/erc20:${tokenB}`, amount: amountB.toString(), spender: LENDING_POOL } ] ``` ### NFT Purchases ```typescript nft-purchase.ts theme={null} // NFT marketplace purchase with USDC allowanceRequirements: [{ assetType: 'eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', amount: nftPrice.toString(), spender: NFT_MARKETPLACE_ADDRESS }] ``` ## Special Cases ### Native Tokens (ETH, MATIC, etc.) Native tokens don't require approvals - they're sent directly: ```typescript native-token.ts theme={null} // No allowanceRequirements needed for ETH calls: [{ to: CONTRACT_ADDRESS, data: contractCallData, value: ethAmount.toString() // Send ETH directly }], tokensRequired: [{ assetType: 'eip155:1/slip44:60', // ETH amount: ethAmount.toString() }] // No allowanceRequirements array needed ``` ### Existing Allowances OneBalance optimizes gas by checking existing allowances: If `current allowance ≥ required amount`, no approval transaction is added Only approves the exact amount needed, not unlimited approvals ## 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 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 ## TypeScript Interface ```typescript interfaces.ts theme={null} interface AllowanceRequirement { assetType: string; // Format: "eip155:chainId/erc20:tokenAddress" amount: string; // Amount in smallest unit (e.g., wei) spender: string; // Contract address that needs approval } interface PrepareCallRequest { account: Account; targetChain: string; calls: CallData[]; allowanceRequirements: AllowanceRequirement[]; tokensRequired: TokenRequirement[]; } ``` ## Troubleshooting * **Transaction fails with 'Approval not found'** - Make sure you're using `allowanceRequirements` instead of manual approval calls. * **Gas costs seem high** - OneBalance only adds approvals when needed. High gas might indicate multiple tokens or complex operations. * **Spender address mismatch** - Verify that the spender in `allowanceRequirements` matches the contract address in your `calls`. * **Amount calculation errors** - Ensure you're using the correct decimals for the token (e.g., 6 for USDC, 18 for most ERC20s). Need help? Check the [troubleshooting guide](/guides/contract-calls/troubleshooting) for common issues and solutions. # Contract Call Examples Source: https://docs.onebalance.io/guides/contract-calls/examples Explore OneBalance Toolkit contract call examples for DeFi, NFT, and payments, showing how cross-chain smart contract operations work. Real-world examples of smart contract calls using OneBalance APIs. Each example shows the complete flow from preparing quotes to execution with actual API payloads. ## Overview These examples demonstrate the three-step flow for contract calls: 1. **Prepare** - Generate user operation and typed data 2. **Sign** - Sign the typed data (handled by your wallet/signer) 3. **Execute** - Submit the signed quote for execution For interactive testing, use the [API Reference playground](/api-reference/quotes/prepare-call-quote) or copy the cURL commands directly. ## DeFi Examples ### Uniswap V3 Swap Swap USDC for WETH on Base using Uniswap V3 router. ```json uniswap-prepare.json theme={null} { "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "targetChain": "eip155:8453", "calls": [ { "to": "0x2626664c2603336E57B271c5C0b26F421741e481", "data": "0x414bf389000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda029130000000000000000000000004200000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000bb8000000000000000000000000e20295ec513def805d9c3083b0c8eab64692d764000000000000000000000000000000000000000000000000000000006748d28800000000000000000000000000000000000000000000000000000000000f42400000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000000", "value": "0x0" } ], "allowanceRequirements": [ { "assetType": "eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "amount": "1000000", "spender": "0x2626664c2603336E57B271c5C0b26F421741e481" } ], "tokensRequired": [ { "assetType": "eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "amount": "1000000" } ] } ``` ```bash prepare-uniswap-swap.sh theme={null} curl --request POST \ --url https://be.onebalance.io/api/quotes/prepare-call-quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "targetChain": "eip155:8453", "calls": [ { "to": "0x2626664c2603336E57B271c5C0b26F421741e481", "data": "0x414bf389000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda029130000000000000000000000004200000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000bb8000000000000000000000000e20295ec513def805d9c3083b0c8eab64692d764000000000000000000000000000000000000000000000000000000006748d28800000000000000000000000000000000000000000000000000000000000f42400000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000000", "value": "0x0" } ], "allowanceRequirements": [ { "assetType": "eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "amount": "1000000", "spender": "0x2626664c2603336E57B271c5C0b26F421741e481" } ], "tokensRequired": [ { "assetType": "eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "amount": "1000000" } ] }' ``` ### Aave V3 Supply Supply USDC to Aave V3 on Base to earn yield. ```json aave-supply-prepare.json theme={null} { "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "targetChain": "eip155:8453", "calls": [ { "to": "0xA238Dd80C259a72e81d7e4664a9801593F98d1c5", "data": "0x617ba037000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000000000000000000000000000000000000098968000000000000000000000000e20295ec513def805d9c3083b0c8eab64692d7640000000000000000000000000000000000000000000000000000000000000000", "value": "0x0" } ], "allowanceRequirements": [ { "assetType": "eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "amount": "10000000", "spender": "0xA238Dd80C259a72e81d7e4664a9801593F98d1c5" } ], "tokensRequired": [ { "assetType": "eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "amount": "10000000" } ] } ``` ```bash prepare-aave-supply.sh theme={null} curl --request POST \ --url https://be.onebalance.io/api/quotes/prepare-call-quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "targetChain": "eip155:8453", "calls": [ { "to": "0xA238Dd80C259a72e81d7e4664a9801593F98d1c5", "data": "0x617ba037000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000000000000000000000000000000000000098968000000000000000000000000e20295ec513def805d9c3083b0c8eab64692d7640000000000000000000000000000000000000000000000000000000000000000", "value": "0x0" } ], "allowanceRequirements": [ { "assetType": "eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "amount": "10000000", "spender": "0xA238Dd80C259a72e81d7e4664a9801593F98d1c5" } ], "tokensRequired": [ { "assetType": "eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "amount": "10000000" } ] }' ``` ## NFT Examples ### Mint NFT Collection Mint 2 NFTs from a collection on Base with ETH payment. ```json nft-mint-prepare.json theme={null} { "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "targetChain": "eip155:8453", "calls": [ { "to": "0x1234567890123456789012345678901234567890", "data": "0xa0712d680000000000000000000000000000000000000000000000000000000000000002", "value": "0x1bc16d674ec80000" } ], "allowanceRequirements": [], "tokensRequired": [ { "assetType": "eip155:8453/slip44:60", "amount": "2000000000000000000" } ] } ``` ```bash prepare-nft-mint.sh theme={null} curl --request POST \ --url https://be.onebalance.io/api/quotes/prepare-call-quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "targetChain": "eip155:8453", "calls": [ { "to": "0x1234567890123456789012345678901234567890", "data": "0xa0712d680000000000000000000000000000000000000000000000000000000000000002", "value": "0x1bc16d674ec80000" } ], "allowanceRequirements": [], "tokensRequired": [ { "assetType": "eip155:8453/slip44:60", "amount": "2000000000000000000" } ] }' ``` ## Gaming Examples ### In-Game Purchase Purchase game item #1 using USDC as payment token. ```json game-purchase-prepare.json theme={null} { "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "targetChain": "eip155:8453", "calls": [ { "to": "0x1234567890123456789012345678901234567890", "data": "0x96a81f59000000000000000000000000000000000000000000000000000000000000000100000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000000000000000000000000000000000000098968", "value": "0x0" } ], "allowanceRequirements": [ { "assetType": "eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "amount": "10000000", "spender": "0x1234567890123456789012345678901234567890" } ], "tokensRequired": [ { "assetType": "eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "amount": "10000000" } ] } ``` ```bash prepare-game-purchase.sh theme={null} curl --request POST \ --url https://be.onebalance.io/api/quotes/prepare-call-quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "targetChain": "eip155:8453", "calls": [ { "to": "0x1234567890123456789012345678901234567890", "data": "0x96a81f59000000000000000000000000000000000000000000000000000000000000000100000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000000000000000000000000000000000000098968", "value": "0x0" } ], "allowanceRequirements": [ { "assetType": "eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "amount": "10000000", "spender": "0x1234567890123456789012345678901234567890" } ], "tokensRequired": [ { "assetType": "eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "amount": "10000000" } ] }' ``` ## Basic Account Examples Basic accounts use a simpler structure with just signer and account addresses. ### Token Transfer with Basic Account Transfer USDC tokens using a basic account configuration. ```json basic-transfer-prepare.json theme={null} { "account": { "type": "kernel-v3.1-ecdsa", "signerAddress": "0x5d6fb4eb211a6a2e406a1111b54d26c534753c8e", "accountAddress": "0xb7bc0d7baf6761c302ff6772dfd8f9e22ec706e7" }, "targetChain": "eip155:8453", "calls": [ { "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "data": "0xa9059cbb000000000000000000000000742d35cc6634c0532925a3b844bc454e4438f44e0000000000000000000000000000000000000000000000000000000000989680", "value": "0x0" } ], "allowanceRequirements": [], "tokensRequired": [ { "assetType": "eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "amount": "10000000" } ] } ``` ```bash prepare-basic-transfer.sh theme={null} curl --request POST \ --url https://be.onebalance.io/api/quotes/prepare-call-quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "account": { "type": "kernel-v3.1-ecdsa", "signerAddress": "0x5d6fb4eb211a6a2e406a1111b54d26c534753c8e", "accountAddress": "0xb7bc0d7baf6761c302ff6772dfd8f9e22ec706e7" }, "targetChain": "eip155:8453", "calls": [ { "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "data": "0xa9059cbb000000000000000000000000742d35cc6634c0532925a3b844bc454e4438f44e0000000000000000000000000000000000000000000000000000000000989680", "value": "0x0" } ], "allowanceRequirements": [], "tokensRequired": [ { "assetType": "eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "amount": "10000000" } ] }' ``` ## Multi-Step Operations ### Batch Token Transfers Send tokens to multiple recipients in a single transaction. ```json batch-transfer-prepare.json theme={null} { "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "targetChain": "eip155:8453", "calls": [ { "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "data": "0xa9059cbb000000000000000000000000742d35cc6634c0532925a3b844bc454e4438f44e0000000000000000000000000000000000000000000000000000000000989680", "value": "0x0" }, { "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "data": "0xa9059cbb000000000000000000000000456d35cc6634c0532925a3b844bc454e4438f44e0000000000000000000000000000000000000000000000000000000000989680", "value": "0x0" }, { "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "data": "0xa9059cbb000000000000000000000000789d35cc6634c0532925a3b844bc454e4438f44e0000000000000000000000000000000000000000000000000000000000989680", "value": "0x0" } ], "allowanceRequirements": [], "tokensRequired": [ { "assetType": "eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "amount": "30000000" } ] } ``` ```bash prepare-batch-transfer.sh theme={null} curl --request POST \ --url https://be.onebalance.io/api/quotes/prepare-call-quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "targetChain": "eip155:8453", "calls": [ { "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "data": "0xa9059cbb000000000000000000000000742d35cc6634c0532925a3b844bc454e4438f44e0000000000000000000000000000000000000000000000000000000000989680", "value": "0x0" }, { "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "data": "0xa9059cbb000000000000000000000000456d35cc6634c0532925a3b844bc454e4438f44e0000000000000000000000000000000000000000000000000000000000989680", "value": "0x0" }, { "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "data": "0xa9059cbb000000000000000000000000789d35cc6634c0532925a3b844bc454e4438f44e0000000000000000000000000000000000000000000000000000000000989680", "value": "0x0" } ], "allowanceRequirements": [], "tokensRequired": [ { "assetType": "eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "amount": "30000000" } ] }' ``` ## Integration Helpers ### TypeScript Helper Functions Utility functions to simplify contract call integration. ```typescript helper-functions.ts theme={null} import { encodeFunctionData, parseAbi } from 'viem'; // Generate transfer call data export function createTransferCall( tokenAddress: string, recipient: string, amount: bigint ) { return { to: tokenAddress, data: encodeFunctionData({ abi: parseAbi(['function transfer(address to, uint256 amount)']), functionName: 'transfer', args: [recipient, amount] }), value: '0x0' }; } // Generate approve call data export function createApprovalRequirement( tokenAddress: string, spender: string, amount: bigint, chainId: number ) { return { assetType: `eip155:${chainId}/erc20:${tokenAddress}`, amount: amount.toString(), spender }; } // Generate Uniswap V3 swap call data export function createUniswapV3Swap( tokenIn: string, tokenOut: string, fee: number, recipient: string, amountIn: bigint, amountOutMinimum: bigint, deadline: number ) { const swapRouter = '0x2626664c2603336E57B271c5C0b26F421741e481'; return { to: swapRouter, data: encodeFunctionData({ abi: parseAbi([ 'function exactInputSingle((address,address,uint24,address,uint256,uint256,uint256,uint160)) returns (uint256)' ]), functionName: 'exactInputSingle', args: [{ tokenIn, tokenOut, fee, recipient, deadline, amountIn, amountOutMinimum, sqrtPriceLimitX96: 0n }] }), value: '0x0' }; } ``` ## Production Best Practices Always validate addresses, amounts, and parameters before preparing quotes. Use checksummed addresses. Implement proper error handling for failed preparations, rejections, and execution failures. OneBalance handles gas estimation, but monitor network congestion and adjust timeouts accordingly. Test all operations with small amounts before production deployment. **Next Steps**: Learn about [troubleshooting common issues](/guides/contract-calls/troubleshooting) or explore [advanced patterns](/guides/contract-calls/advanced-patterns) for complex use cases. # How to Execute Smart Contract Calls Source: https://docs.onebalance.io/guides/contract-calls/getting-started Learn to execute smart contract calls with OneBalance by preparing call data, signing, and submitting transactions across chains. This guide walks you through implementing smart contract calls in your application. We'll cover the complete three-step flow with production-ready code examples. ## Quickstart Execute any smart contract function across multiple blockchains with a single integration. This guide shows you how to transfer ERC20 tokens on Arbitrum using OneBalance's calldata API. ## What You'll Build 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 ## Quick Example Here's what you'll accomplish - a complete working script: ```typescript index.ts theme={null} // Complete working example - see full code below async function main() { // Step 1: Predict account address from session/admin keys const accountAddress = await predictAddress(sessionKey.address, adminKey.address); console.log('Predicted Address:', accountAddress); // Step 2: Check USDC balance const usdcBalances = await fetchUSDCBalance(accountAddress); if (!usdcBalances) throw new Error('No USDC balance found'); // Step 3-5: Execute ERC20 transfer on Arbitrum await transferErc20OnChain({ accountAddress, sessionAddress: sessionKey.address, adminAddress: adminKey.address, }, usdcBalances); } ``` Now let's build this step by step. ## Project Setup Set up your development environment: ```bash theme={null} mkdir onebalance-calldata && cd onebalance-calldata pnpm init ``` Install dependencies: ```bash theme={null} pnpm add viem axios pnpm add -D typescript @types/node ts-node tslib ``` Now, let's create and configure `tsconfig.json`: ```bash theme={null} touch tsconfig.json ``` ```json tsconfig.json theme={null} { "compilerOptions": { "target": "ES2020", "module": "commonjs", "lib": ["ES2020"], "outDir": "./dist", "rootDir": "./", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "declaration": true, "declarationMap": true, "sourceMap": true, "moduleResolution": "node", "allowSyntheticDefaultImports": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, "typeRoots": ["./node_modules/@types"], "types": ["node"] }, "include": ["**/*.ts", "**/*.tsx"], "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"], "ts-node": { "esm": false, "experimentalSpecifierResolution": "node" } } ``` This guide uses a public API key for testing. For production, get your API key from [sales@onebalance.io](mailto:sales@onebalance.io). ## Calldata Flow ### Step 1: Generate Keys & Predict Account Create your main file with key generation and account prediction: ```bash theme={null} touch index.ts ``` ```typescript index.ts theme={null} import { existsSync, readFileSync, writeFileSync } from 'node:fs'; import axios, { AxiosResponse } from 'axios'; import { HashTypedDataParameters, encodeFunctionData, parseAbi } from 'viem'; import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'; const BASE_URL = 'https://be.onebalance.io'; // Note: Using the production API endpoint will produce a different predicted address const PUBLIC_API_KEY = '42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11'; // Helper function to create authenticated headers function createAuthHeaders(): Record { return { 'x-api-key': PUBLIC_API_KEY, }; } async function apiRequest( method: 'get' | 'post', endpoint: string, data: RequestData, isParams = false, ): Promise { try { const config = { headers: createAuthHeaders(), ...(isParams ? { params: data } : {}), }; const url = `${BASE_URL}${endpoint}`; const response: AxiosResponse = method === 'post' ? await axios.post(url, data, config) : await axios.get(url, { ...config, params: data }); return response.data; } catch (error) { if (axios.isAxiosError(error) && error.response) { throw new Error(JSON.stringify(error.response.data)); } throw error; } } // API methods async function apiPost(endpoint: string, data: RequestData): Promise { return apiRequest('post', endpoint, data); } async function apiGet(endpoint: string, params: RequestData): Promise { return apiRequest('get', endpoint, params, true); } // Generate session key pair function generateEOAKey() { const privateKey = generatePrivateKey(); const account = privateKeyToAccount(privateKey); return { privateKey, address: account.address, }; } function readOrCacheEOAKey(key: string) { if (existsSync(`${key}-key.json`)) { const cachedKeys = readFileSync(`${key}-key.json`, 'utf8'); return JSON.parse(cachedKeys); } const keys = generateEOAKey(); writeFileSync(`${key}-key.json`, JSON.stringify(keys, null, 2)); return keys; } // Usage example const sessionKey = readOrCacheEOAKey('session'); console.log('Session Address:', sessionKey.address); const adminKey = readOrCacheEOAKey('admin'); console.log('Admin Address:', adminKey.address); async function predictAddress(sessionAddress: string, adminAddress: string): Promise { const response = await apiPost<{ sessionAddress: string; adminAddress: string }, { predictedAddress: string }>( '/api/account/predict-address', { sessionAddress, adminAddress, }, ); return response.predictedAddress; } ``` ### Step 2: Check Balance Add balance checking functionality with proper TypeScript interfaces: ```typescript index.ts theme={null} async function fetchBalances(address: string) { const response = await apiGet< { address: string }, { balanceByAggregatedAsset: { aggregatedAssetId: string; balance: string; individualAssetBalances: { assetType: string; balance: string; fiatValue: number }[]; fiatValue: number; }[]; balanceBySpecificAsset: { assetType: string; balance: string; fiatValue: number; }[]; totalBalance: { fiatValue: number; }; } >('/api/v2/balances/aggregated-balance', { address }); return response; } async function fetchUSDCBalance(address: string) { const response = await fetchBalances(address); return response.balanceByAggregatedAsset.find((asset) => asset.aggregatedAssetId === 'ob:usdc'); } ``` ### Step 3: Add TypeScript Interfaces Add TypeScript interfaces for type safety: ```typescript index.ts theme={null} type Hex = `0x${string}`; interface EvmAccount { accountAddress: Hex; sessionAddress: Hex; adminAddress: Hex; } interface EvmCall { to: Hex; value?: Hex; data?: Hex; } interface TokenRequirement { assetType: string; amount: string; } interface TokenAllowanceRequirement extends TokenRequirement { spender: Hex; } type StateMapping = { [slot: Hex]: Hex; }; type StateDiff = { stateDiff?: StateMapping; code?: Hex; balance?: Hex; }; type Override = StateDiff & { address: Hex; }; interface PrepareCallRequest { account: EvmAccount; targetChain: string; // CAIP-2 calls: EvmCall[]; tokensRequired: TokenRequirement[]; allowanceRequirements?: TokenAllowanceRequirement[]; overrides?: Override[]; // permits validAfter?: string; validUntil?: string; } interface SerializedUserOperation { sender: Hex; nonce: string; factory?: Hex; factoryData?: Hex; callData: Hex; callGasLimit: string; verificationGasLimit: string; preVerificationGas: string; maxFeePerGas: string; maxPriorityFeePerGas: string; paymaster?: Hex; paymasterVerificationGasLimit?: string; paymasterPostOpGasLimit?: string; paymasterData?: Hex; signature: Hex; initCode?: Hex; paymasterAndData?: Hex; } interface ChainOperationBasic { userOp: SerializedUserOperation; typedDataToSign: HashTypedDataParameters; } interface ChainOperation extends ChainOperationBasic { assetType: string; amount: string; } interface TargetCallQuote { account: EvmAccount; chainOperation: ChainOperation; tamperProofSignature: string; } interface CallRequest { account: EvmAccount; chainOperation: ChainOperation; tamperProofSignature: string; fromAggregatedAssetId: string; } interface AssetUsed { aggregatedAssetId: string; assetType: string[] | string; amount: string; minimumAmount?: string; } interface FiatValue { fiatValue: string; amount: string; } interface OriginAssetUsed extends AssetUsed { assetType: string[]; fiatValue: FiatValue[]; } interface DestinationAssetUsed extends AssetUsed { assetType: string; fiatValue: string; minimumAmount?: string; minimumFiatValue?: string; } interface Quote { id: string; account: EvmAccount; originChainsOperations: ChainOperation[]; destinationChainOperation?: ChainOperation; originToken?: OriginAssetUsed; destinationToken?: DestinationAssetUsed; validUntil?: string; // block number, if empty the valid until will be MAX_UINT256 validAfter?: string; // block number, if empty the valid after will be 0 expirationTimestamp: string; tamperProofSignature: string; } interface OpGuarantees { non_equivocation: boolean; reorg_protection: boolean; valid_until?: number; valid_after?: number; } type BundleGuarantees = Record; interface BundleResponse { success: boolean; guarantees: BundleGuarantees | null; error: string | null; } type TransactionType = 'SWAP' | 'TRANSFER' | 'CALL'; type OperationStatus = | 'PENDING' // not yet begun processing but has been submitted | 'IN_PROGRESS' // processing the execution steps of the operation | 'COMPLETED' // all steps completed with success | 'REFUNDED' // none or some steps completed, some required step failed causing the whole operation to be refunded | 'FAILED'; // all steps failed interface OperationDetails { hash?: Hex; chainId?: number; explorerUrl?: string; } interface HistoryTransaction { quoteId: string; type: TransactionType; originToken?: OriginAssetUsed; destinationToken?: DestinationAssetUsed; status: OperationStatus; user: Hex; recipientAccountId: string; // the caip-10 address of the recipient // if type is SWAP or TRANSFER originChainOperations?: OperationDetails[]; // the asset(s) that were sent from the source destinationChainOperations?: OperationDetails[]; // the asset that was received to the final destination } interface HistoryResponse { transactions: HistoryTransaction[]; continuation?: string; } ``` ### Step 4: Prepare Quote Add the quote preparation logic with proper type safety: ```typescript index.ts theme={null} async function prepareCallQuote(quoteRequest: PrepareCallRequest): Promise { return apiPost('/api/quotes/prepare-call-quote', quoteRequest); } async function fetchCallQuote(callRequest: CallRequest): Promise { return apiPost('/api/quotes/call-quote', callRequest); } async function executeQuote(quote: Quote): Promise { return apiPost('/api/quotes/execute-quote', quote); } ``` See all quote options in our [Quotes API reference](/api-reference/quotes/prepare-call-quote). ### Step 5: Sign Operations Add signing functionality: ```typescript index.ts theme={null} async function signOperation(operation: ChainOperation, key: Hex): Promise { return { ...operation, userOp: { ...operation.userOp, signature: await privateKeyToAccount(key).signTypedData(operation.typedDataToSign) }, }; } ``` ### Step 6: Track Status Add transaction monitoring with proper error handling: ```typescript index.ts theme={null} async function fetchTransactionHistory(address: string): Promise { return apiGet<{ user: string; limit: number; sortBy: string }, HistoryResponse>('/api/status/get-tx-history', { user: address, limit: 1, sortBy: 'createdAt', }); } ``` Track all transaction states in our [Status API reference](/api-reference/status/get-transaction-history). ## Put It Together Add the main transfer function and execution: ```typescript index.ts theme={null} async function transferErc20OnChain( account: EvmAccount, usdcBalances: { aggregatedAssetId: string; balance: string; individualAssetBalances: { assetType: string; balance: string; fiatValue: number }[]; }, ) { const largestUsdcBalanceEntry = usdcBalances.individualAssetBalances.reduce((max, current) => { return Number(current.balance) > Number(max.balance) ? current : max; }); const chain = 'eip155:42161'; // Arbitrum const usdcAddress = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831'; // Arbitrum USDC address if (largestUsdcBalanceEntry.balance === '0') { throw new Error('No USDC balance found'); } const transferDefinition = parseAbi(['function transfer(address to, uint256 amount) returns (bool)']); const transferCallData = encodeFunctionData({ abi: transferDefinition, functionName: 'transfer', args: [adminKey.address, 1n], }); const quoteRequest: PrepareCallRequest = { account, targetChain: chain, calls: [ { to: usdcAddress as Hex, data: transferCallData, value: '0x0', }, ], tokensRequired: [ { assetType: `${chain}/erc20:${usdcAddress}`, amount: '100000', }, ], }; console.log(quoteRequest); const preparedQuote = await prepareCallQuote(quoteRequest); const signedChainOp = await signOperation(preparedQuote.chainOperation, sessionKey.privateKey); const callRequest: CallRequest = { fromAggregatedAssetId: 'ob:usdc', account, tamperProofSignature: preparedQuote.tamperProofSignature, chainOperation: signedChainOp, }; console.log('callRequest', callRequest); const quote = await fetchCallQuote(callRequest); for (let i = 0; i < quote.originChainsOperations.length; i++) { const callQuoteSignedChainOperation = await signOperation(quote.originChainsOperations[i], sessionKey.privateKey); quote.originChainsOperations[i] = callQuoteSignedChainOperation; } console.log('quote', quote); const bundle = await executeQuote(quote); if (bundle.success) { console.log('Bundle executed'); const timeout = 60_000; let completed = false; const startTime = Date.now(); while (!completed) { try { console.log('fetching transaction history...'); const transactionHistory = await fetchTransactionHistory(quote.account.accountAddress); console.log('transactionHistory', transactionHistory); if (transactionHistory.transactions.length > 0) { const [tx] = transactionHistory.transactions; if (tx.quoteId === quote.id) { if (tx.status === 'COMPLETED') { console.log('Transaction completed and operation executed'); completed = true; break; } console.log('Transaction status: ', tx.status); } } } catch {} if (Date.now() - startTime > timeout) { throw new Error('Transaction not completed in time'); } await new Promise((resolve) => setTimeout(resolve, 1_000)); } } else { console.log('Bundle execution failed'); } } async function main() { const predictedAddress = await predictAddress(sessionKey.address, adminKey.address); console.log('Predicted Address:', predictedAddress); const usdcBalances = await fetchUSDCBalance(predictedAddress); console.log('USDC Balances:', usdcBalances); if (!usdcBalances) { throw new Error('No USDC balance found'); } await transferErc20OnChain( { accountAddress: predictedAddress as Hex, sessionAddress: sessionKey.address as Hex, adminAddress: adminKey.address as Hex, }, usdcBalances, ); } main(); ``` Add scripts to your `package.json` to run the `index.ts` file: ```json package.json theme={null} { "name": "onebalance-calldata", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "calldata": "ts-node index.ts", "build": "tsc", "clean": "rm -rf dist" }, "keywords": [], "author": "", "license": "ISC", "packageManager": "pnpm@10.9.0", "dependencies": { "axios": "^1.9.0", "viem": "^2.30.5" }, "devDependencies": { "@types/node": "^22.15.28", "ts-node": "^10.9.2", "tslib": "^2.8.1", "typescript": "^5.8.3" } } ``` Run your calldata execution: ```bash theme={null} pnpm run calldata ``` After running the script for the first time, you'll see a predicted address printed in the console. You need to fund this address with USDC on any supported chain before the transaction will succeed. Transfer some USDC to the predicted address, then run the script again. **Checkpoint:** You should see session/admin addresses printed, account prediction, balance check, and transaction execution in your console. ## Next Steps 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 ## Common Issues * **No USDC balance found** - User needs to fund their `predicted address` with USDC. * **Balance Too Low** - Ensure your account has sufficient USDC. The API requires tokens for both gas and the transfer amount. * **Transaction Timeout** - Network congestion can delay execution. Increase timeout or check status manually using the quote ID. * **Invalid Calldata** - Verify your ABI encoding matches the target contract's interface exactly. ## Complete Example The complete implementation is available in our [OneBalance Examples repository](https://github.com/OneBalance-io/onebalance-examples/tree/main/calldata). ```typescript theme={null} import { existsSync, readFileSync, writeFileSync } from 'node:fs'; import axios, { AxiosResponse } from 'axios'; import { HashTypedDataParameters, encodeFunctionData, parseAbi } from 'viem'; import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'; const BASE_URL = 'https://be.onebalance.io'; // Note: Using the production API endpoint will produce a different predicted address const PUBLIC_API_KEY = '42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11'; // Helper function to create authenticated headers function createAuthHeaders(): Record { return { 'x-api-key': PUBLIC_API_KEY, }; } async function apiRequest( method: 'get' | 'post', endpoint: string, data: RequestData, isParams = false, ): Promise { try { const config = { headers: createAuthHeaders(), ...(isParams ? { params: data } : {}), }; const url = `${BASE_URL}${endpoint}`; const response: AxiosResponse = method === 'post' ? await axios.post(url, data, config) : await axios.get(url, { ...config, params: data }); return response.data; } catch (error) { if (axios.isAxiosError(error) && error.response) { throw new Error(JSON.stringify(error.response.data)); } throw error; } } // API methods async function apiPost(endpoint: string, data: RequestData): Promise { return apiRequest('post', endpoint, data); } async function apiGet(endpoint: string, params: RequestData): Promise { return apiRequest('get', endpoint, params, true); } // Generate session key pair function generateEOAKey() { const privateKey = generatePrivateKey(); const account = privateKeyToAccount(privateKey); return { privateKey, address: account.address, }; } function readOrCacheEOAKey(key: string) { if (existsSync(`${key}-key.json`)) { const cachedKeys = readFileSync(`${key}-key.json`, 'utf8'); return JSON.parse(cachedKeys); } const keys = generateEOAKey(); writeFileSync(`${key}-key.json`, JSON.stringify(keys, null, 2)); return keys; } // Usage example const sessionKey = readOrCacheEOAKey('session'); console.log('Session Address:', sessionKey.address); const adminKey = readOrCacheEOAKey('admin'); console.log('Admin Address:', adminKey.address); async function predictAddress(sessionAddress: string, adminAddress: string): Promise { const response = await apiPost<{ sessionAddress: string; adminAddress: string }, { predictedAddress: string }>( '/api/account/predict-address', { sessionAddress, adminAddress, }, ); return response.predictedAddress; } async function fetchBalances(address: string) { const response = await apiGet< { address: string }, { balanceByAggregatedAsset: { aggregatedAssetId: string; balance: string; individualAssetBalances: { assetType: string; balance: string; fiatValue: number }[]; fiatValue: number; }[]; balanceBySpecificAsset: { assetType: string; balance: string; fiatValue: number; }[]; totalBalance: { fiatValue: number; }; } >('/api/v2/balances/aggregated-balance', { address }); return response; } async function fetchUSDCBalance(address: string) { const response = await fetchBalances(address); return response.balanceByAggregatedAsset.find((asset) => asset.aggregatedAssetId === 'ob:usdc'); } type Hex = `0x${string}`; interface EvmAccount { accountAddress: Hex; sessionAddress: Hex; adminAddress: Hex; } interface EvmCall { to: Hex; value?: Hex; data?: Hex; } interface TokenRequirement { assetType: string; amount: string; } interface TokenAllowanceRequirement extends TokenRequirement { spender: Hex; } type StateMapping = { [slot: Hex]: Hex; }; type StateDiff = { stateDiff?: StateMapping; code?: Hex; balance?: Hex; }; type Override = StateDiff & { address: Hex; }; interface PrepareCallRequest { account: EvmAccount; targetChain: string; // CAIP-2 calls: EvmCall[]; tokensRequired: TokenRequirement[]; allowanceRequirements?: TokenAllowanceRequirement[]; overrides?: Override[]; // permits validAfter?: string; validUntil?: string; } interface SerializedUserOperation { sender: Hex; nonce: string; factory?: Hex; factoryData?: Hex; callData: Hex; callGasLimit: string; verificationGasLimit: string; preVerificationGas: string; maxFeePerGas: string; maxPriorityFeePerGas: string; paymaster?: Hex; paymasterVerificationGasLimit?: string; paymasterPostOpGasLimit?: string; paymasterData?: Hex; signature: Hex; initCode?: Hex; paymasterAndData?: Hex; } interface ChainOperationBasic { userOp: SerializedUserOperation; typedDataToSign: HashTypedDataParameters; } interface ChainOperation extends ChainOperationBasic { assetType: string; amount: string; } interface TargetCallQuote { account: EvmAccount; chainOperation: ChainOperation; tamperProofSignature: string; } interface CallRequest { account: EvmAccount; chainOperation: ChainOperation; tamperProofSignature: string; fromAggregatedAssetId: string; } interface AssetUsed { aggregatedAssetId: string; assetType: string[] | string; amount: string; minimumAmount?: string; } interface FiatValue { fiatValue: string; amount: string; } interface OriginAssetUsed extends AssetUsed { assetType: string[]; fiatValue: FiatValue[]; } interface DestinationAssetUsed extends AssetUsed { assetType: string; fiatValue: string; minimumAmount?: string; minimumFiatValue?: string; } interface Quote { id: string; account: EvmAccount; originChainsOperations: ChainOperation[]; destinationChainOperation?: ChainOperation; originToken?: OriginAssetUsed; destinationToken?: DestinationAssetUsed; validUntil?: string; // block number, if empty the valid until will be MAX_UINT256 validAfter?: string; // block number, if empty the valid after will be 0 expirationTimestamp: string; tamperProofSignature: string; } interface OpGuarantees { non_equivocation: boolean; reorg_protection: boolean; valid_until?: number; valid_after?: number; } type BundleGuarantees = Record; interface BundleResponse { success: boolean; guarantees: BundleGuarantees | null; error: string | null; } type TransactionType = 'SWAP' | 'TRANSFER' | 'CALL'; type OperationStatus = | 'PENDING' // not yet begun processing but has been submitted | 'IN_PROGRESS' // processing the execution steps of the operation | 'COMPLETED' // all steps completed with success | 'REFUNDED' // none or some steps completed, some required step failed causing the whole operation to be refunded | 'FAILED'; // all steps failed interface OperationDetails { hash?: Hex; chainId?: number; explorerUrl?: string; } interface HistoryTransaction { quoteId: string; type: TransactionType; originToken?: OriginAssetUsed; destinationToken?: DestinationAssetUsed; status: OperationStatus; user: Hex; recipientAccountId: string; // the caip-10 address of the recipient // if type is SWAP or TRANSFER originChainOperations?: OperationDetails[]; // the asset(s) that were sent from the source destinationChainOperations?: OperationDetails[]; // the asset that was received to the final destination } interface HistoryResponse { transactions: HistoryTransaction[]; continuation?: string; } async function prepareCallQuote(quoteRequest: PrepareCallRequest): Promise { return apiPost('/api/quotes/prepare-call-quote', quoteRequest); } async function fetchCallQuote(callRequest: CallRequest): Promise { return apiPost('/api/quotes/call-quote', callRequest); } async function executeQuote(quote: Quote): Promise { return apiPost('/api/quotes/execute-quote', quote); } async function fetchTransactionHistory(address: string): Promise { return apiGet<{ user: string; limit: number; sortBy: string }, HistoryResponse>('/api/status/get-tx-history', { user: address, limit: 1, sortBy: 'createdAt', }); } async function signOperation(operation: ChainOperation, key: Hex): Promise { return { ...operation, userOp: { ...operation.userOp, signature: await privateKeyToAccount(key).signTypedData(operation.typedDataToSign) }, }; } async function transferErc20OnChain( account: EvmAccount, usdcBalances: { aggregatedAssetId: string; balance: string; individualAssetBalances: { assetType: string; balance: string; fiatValue: number }[]; }, ) { const largestUsdcBalanceEntry = usdcBalances.individualAssetBalances.reduce((max, current) => { return Number(current.balance) > Number(max.balance) ? current : max; }); const chain = 'eip155:42161'; // Arbitrum const usdcAddress = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831'; // Arbitrum USDC address if (largestUsdcBalanceEntry.balance === '0') { throw new Error('No USDC balance found'); } const transferDefinition = parseAbi(['function transfer(address to, uint256 amount) returns (bool)']); const transferCallData = encodeFunctionData({ abi: transferDefinition, functionName: 'transfer', args: [adminKey.address, 1n], }); const quoteRequest: PrepareCallRequest = { account, targetChain: chain, calls: [ { to: usdcAddress as Hex, data: transferCallData, value: '0x0', }, ], tokensRequired: [ { assetType: `${chain}/erc20:${usdcAddress}`, amount: '100000', }, ], }; console.log(quoteRequest); const preparedQuote = await prepareCallQuote(quoteRequest); const signedChainOp = await signOperation(preparedQuote.chainOperation, sessionKey.privateKey); const callRequest: CallRequest = { fromAggregatedAssetId: 'ob:usdc', account, tamperProofSignature: preparedQuote.tamperProofSignature, chainOperation: signedChainOp, }; console.log('callRequest', callRequest); const quote = await fetchCallQuote(callRequest); for (let i = 0; i < quote.originChainsOperations.length; i++) { const callQuoteSignedChainOperation = await signOperation(quote.originChainsOperations[i], sessionKey.privateKey); quote.originChainsOperations[i] = callQuoteSignedChainOperation; } console.log('quote', quote); const bundle = await executeQuote(quote); if (bundle.success) { console.log('Bundle executed'); const timeout = 60_000; let completed = false; const startTime = Date.now(); while (!completed) { try { console.log('fetching transaction history...'); const transactionHistory = await fetchTransactionHistory(quote.account.accountAddress); console.log('transactionHistory', transactionHistory); if (transactionHistory.transactions.length > 0) { const [tx] = transactionHistory.transactions; if (tx.quoteId === quote.id) { if (tx.status === 'COMPLETED') { console.log('Transaction completed and operation executed'); completed = true; break; } console.log('Transaction status: ', tx.status); } } } catch {} if (Date.now() - startTime > timeout) { throw new Error('Transaction not completed in time'); } await new Promise((resolve) => setTimeout(resolve, 1_000)); } } else { console.log('Bundle execution failed'); } } async function main() { const predictedAddress = await predictAddress(sessionKey.address, adminKey.address); console.log('Predicted Address:', predictedAddress); const usdcBalances = await fetchUSDCBalance(predictedAddress); console.log('USDC Balances:', usdcBalances); if (!usdcBalances) { throw new Error('No USDC balance found'); } await transferErc20OnChain( { accountAddress: predictedAddress as Hex, sessionAddress: sessionKey.address as Hex, adminAddress: adminKey.address as Hex, }, usdcBalances, ); } main(); ``` ## Integration Checklist Before going to production, ensure you have: * Stored API keys securely (environment variables) * Implemented proper error handling * Added transaction monitoring * Tested with small amounts first * Implemented retry logic for network failures * Added logging for debugging * Validated all addresses and amounts * Handled signature rejection cases # Contract Calls Overview Source: https://docs.onebalance.io/guides/contract-calls/overview Overview of OneBalance smart contract calls, enabling cross-chain execution with gas abstraction and unified asset management. Contract calls enable you to execute any smart contract operation using your aggregated balance. This powerful feature allows for complex DeFi operations, NFT interactions, gaming actions, and custom business logic - all while benefiting from unified asset management. ## What Are Contract Calls? Contract calls let you interact with smart contracts on any supported chain while OneBalance handles: * **Cross-chain token routing** - Use tokens from any chain to pay for operations * **Automatic gas abstraction** - No need to hold native tokens on target chains * **Token approvals** - Automatic ERC20 approvals when needed * **Transaction bundling** - Multiple operations in a single user experience **Account Configuration Required**: Contract calls require proper account setup. Choose your account model based on your security and operational needs. See [Account Models](/concepts/accounts) for setup instructions. ## Quick Example Here's how to transfer USDC on Arbitrum using funds from your aggregated balance: ```bash Terminal theme={null} curl -X POST "https://be.onebalance.io/api/quotes/prepare-call-quote" \ -H "x-api-key: ONEBALANCE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "account": { "sessionAddress": "0x1cBF...", "adminAddress": "0xc162...", "accountAddress": "0xE202..." }, "targetChain": "eip155:42161", "calls": [{ "to": "0xaf88d065e77c8cc2239327c5edb3a432268e5831", "data": "0xa9059cbb000000000000000000000000742d35cc6634c0532925a3b844bc454e4438f44e0000000000000000000000000000000000000000000000000000000005f5e100", "value": "0x0" }], "tokensRequired": [{ "assetType": "eip155:42161/erc20:0xaf88d065e77c8cc2239327c5edb3a432268e5831", "amount": "100000000" }], "allowanceRequirements": [] }' ``` ## Cross-Chain Funding with Solana You can fund EVM contract calls using assets from Solana for cost efficiency and liquidity access: Complete examples for funding EVM contract calls with SOL and USDC from Solana Use Solana assets with EIP-7702 delegated accounts for atomic cross-chain execution Solana funding enables you to leverage cheaper transaction fees and access SOL/USDC liquidity for EVM contract execution. ## How It Works OneBalance contract calls are executed through a three-phase process that handles all the complexity behind the scenes. ### Phase Breakdown 1. **Prepare Quote** - Submit your contract calls and requirements to get executable operations. OneBalance analyzes your requirements, generates user operations, and calculates optimal routing across chains. 2. **Sign Target Operation** - Sign the generated target chain operation with your wallet. This happens client-side using standard EIP-712 typed data signatures. See [Signing Guide](/concepts/signing) for complete implementation details. 3. **Get Full Quote** - Submit the signed target operation to get the complete cross-chain quote including all origin chain operations required. 4. **Sign & Execute** - Sign all origin chain operations, then submit the fully signed quote for execution across all chains. ```mermaid theme={null} flowchart LR A[Prepare Quote] --> B[Sign Target] --> C[Get Full Quote] --> D[Sign & Execute] ``` ### Detailed Flow For those interested in the technical details, here's the complete execution flow: ```mermaid theme={null} sequenceDiagram participant User participant OneBalance API participant Origin Chain participant Target Chain Note over User, Target Chain: Phase 1: Preparation User->>OneBalance API: 1. Prepare call quote Note right of OneBalance API: • Analyze requirements
• Generate target operation
• Calculate routing OneBalance API-->>User: Target operation + typed data Note over User, Target Chain: Phase 2: Sign Target User->>User: 2. Sign target operation Note right of User: • Sign with session key
• EIP-712 signature User->>OneBalance API: 3. Get full quote Note over User, Target Chain: Phase 3: Get Full Quote OneBalance API-->>User: Complete quote with origin operations User->>User: 4. Sign all origin operations Note right of User: • Sign each origin operation
• Multiple signatures Note over User, Target Chain: Phase 4: Execution User->>OneBalance API: 5. Execute quote OneBalance API->>Origin Chain: Execute origin operations Note right of Origin Chain: • Token transfers
• Cross-chain bridging OneBalance API->>Target Chain: Execute target operation Note right of Target Chain: • Smart contract calls
• Token settlements OneBalance API-->>User: Transaction confirmations ``` Each phase uses specific API endpoints: * **Phase 1: Prepare** - [POST /quotes/prepare-call-quote](/api-reference/quotes/prepare-call-quote) - Analyzes your calls and generates target operation * **Phase 2: Sign Target** - Client-side signing with your wallet or library * **Phase 3: Get Full Quote** - [POST /quotes/call-quote](/api-reference/quotes/get-call-quote) - Returns complete cross-chain quote * **Phase 4: Execute** - [POST /quotes/execute-quote](/api-reference/quotes/execute-quote) - Submits fully signed quote for execution ## Common Use Cases Cross-chain transfers, swaps, and token management Lending, borrowing, staking, and yield farming Minting, trading, and marketplace operations Proposal voting and treasury management ### 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 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 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 # Troubleshooting Contract Calls Source: https://docs.onebalance.io/guides/contract-calls/troubleshooting Solutions to common issues in cross-chain contract calls with OneBalance, including debugging and error-handling best practices. This guide covers the most common issues developers encounter when implementing OneBalance contract calls and their solutions. **Quick Debug Tip**: Most contract call failures happen during the prepare phase. Always validate your inputs before calling the API. ## Common Errors ### Including Manual Approval Calls **Most Common Error**: Adding `approve()` calls to your transaction array will cause failures. OneBalance automatically handles all token approvals through the `allowanceRequirements` field. Including manual `approve()` calls in your transaction array conflicts with this system and will cause the request to fail. ```typescript wrong-approval-approach.ts theme={null} // ❌ WRONG - This will fail const badRequest = { calls: [ { to: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC data: encodeFunctionData({ abi: parseAbi(['function approve(address spender, uint256 amount)']), functionName: 'approve', args: [spenderAddress, amount] }), value: '0' }, { to: spenderAddress, data: actualCallData, value: '0' } ] // This causes "Invalid call data" errors }; ``` ```typescript correct-approval-approach.ts theme={null} // ✅ CORRECT - Use allowanceRequirements const correctRequest = { calls: [{ to: spenderAddress, data: actualCallData, value: '0' }], allowanceRequirements: [{ assetType: 'eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', amount: amount.toString(), spender: spenderAddress }] }; ``` ### Invalid Tamper Proof Signature This error occurs when the quote structure is modified after preparation. The tamper-proof signature validates the exact quote structure, so any changes will invalidate it. **Common Causes:** * JSON key ordering changed during serialization * Extra fields added to the quote object * Quote passed through JSON.stringify/parse incorrectly ```typescript preserve-quote-structure.ts theme={null} // ❌ Wrong - modifies structure const modifiedQuote = { ...preparedQuote, extraField: 'value' }; // ❌ Wrong - changes key order const reordered = JSON.parse(JSON.stringify(preparedQuote)); // ✅ Correct - preserve exact structure preparedQuote.chainOperation.userOp.signature = signature; ``` ### Account Configuration Errors Account validation ensures all required addresses are present and properly formatted. Missing or invalid addresses will cause authentication failures during quote preparation. ```typescript account-validation.ts theme={null} // ✅ Validate account before using function validateAccount(account: Account) { if (!account.sessionAddress || !account.adminAddress || !account.accountAddress) { throw new Error('Missing required account addresses'); } if (!isAddress(account.sessionAddress)) { throw new Error('Invalid session address format'); } if (account.sessionAddress === account.adminAddress) { throw new Error('Session and admin addresses must be different'); } return true; } ``` ## API Response Errors ### 400 Bad Request These errors occur when request data doesn't match expected formats. Always validate input data before sending API requests. **Asset ID format errors:** ```typescript asset-id-validation.ts theme={null} // ❌ Wrong formats const wrong = [ 'usdc', 'USDC', 'erc20:0x...', 'ob:USDC' ]; // ✅ Correct format const correct = 'ob:usdc'; ``` **Chain ID format errors:** ```typescript chain-validation.ts theme={null} // ❌ Wrong formats const wrongChains = ['base', '8453', 'Base']; // ✅ Correct format const correctChain = 'eip155:8453'; ``` ### 401 Unauthorized This error indicates missing or invalid API key configuration. Verify your environment variables are set correctly. ```typescript api-key-check.ts theme={null} // Check your API key configuration const headers = { 'x-api-key': process.env.ONEBALANCE_API_KEY }; if (!headers['x-api-key']) { throw new Error('OneBalance API key not configured'); } ``` ### 422 Unprocessable Entity Usually indicates business logic errors: * Insufficient balance * Amount below minimum (\$0.50) * Unsupported token/chain combination ### 503 Service Unavailable Server errors are typically temporary. Implement retry logic with exponential backoff to handle these gracefully. ```typescript retry-logic.ts theme={null} async function retryableApiCall( apiCall: () => Promise, maxRetries = 3 ): Promise { for (let attempt = 0; attempt < maxRetries; attempt++) { try { return await apiCall(); } catch (error: any) { const isRetryable = error.response?.status >= 500 || error.response?.status === 503; if (!isRetryable || attempt === maxRetries - 1) { throw error; } // Exponential backoff: 1s, 2s, 4s const delay = Math.pow(2, attempt) * 1000; await new Promise(resolve => setTimeout(resolve, delay)); } } throw new Error('Max retries exceeded'); } ``` ## Balance & Amount Issues ### Insufficient Balance Always verify the account has sufficient balance before attempting operations. This prevents failed transactions and provides better user feedback. ```typescript balance-validation.ts theme={null} // Always check balance before operations async function validateSufficientBalance( accountAddress: string, assetId: string, requiredAmount: bigint ) { const response = await apiGet('/v2/balances/aggregated-balance', { address: accountAddress, assetId }); const assetBalance = response.balanceByAggregatedAsset.find( asset => asset.aggregatedAssetId === assetId ); if (!assetBalance) { throw new Error(`No balance found for asset ${assetId}`); } const availableBalance = BigInt(assetBalance.balance); if (availableBalance < requiredAmount) { throw new Error( `Insufficient balance. Required: ${requiredAmount}, Available: ${availableBalance}` ); } return true; } ``` ### Decimal Calculation Errors Token amounts must be calculated using the correct decimal places. Using wrong decimals is a common cause of "insufficient balance" or "amount too small" errors. * **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. ```typescript decimal-handling.ts theme={null} import { parseUnits, formatUnits } from 'viem'; // ✅ Correct decimal handling function formatTokenAmount(amount: string, decimals: number) { // Convert user input to smallest unit const parsedAmount = parseUnits(amount, decimals); // Validate minimum amount ($0.50 equivalent) const minimumUSD = parseUnits('0.50', 6); // $0.50 in USDC terms return { amount: parsedAmount.toString(), formatted: formatUnits(parsedAmount, decimals) }; } ``` ## Signing & Execution Issues ### Signature Validation Verify signatures locally before sending to prevent failed transactions. This helps debug signing issues early in the development process. ```typescript signature-validation.ts theme={null} import { verifyTypedData } from 'viem'; async function validateSignature( account: Account, typedData: any, signature: string ) { const isValid = await verifyTypedData({ address: account.sessionAddress as `0x${string}`, domain: typedData.domain, types: typedData.types, primaryType: typedData.primaryType, message: typedData.message, signature: signature as `0x${string}` }); if (!isValid) { throw new Error('Invalid signature'); } return true; } ``` ### Transaction Status Monitoring Implement proper status polling to track transaction progress and handle different completion states. This provides users with real-time feedback on their transactions. ```typescript status-monitoring.ts theme={null} async function waitForTransactionCompletion( quoteId: string, timeoutMs = 60000 ): Promise { const startTime = Date.now(); while (Date.now() - startTime < timeoutMs) { const status = await apiGet('/status/get-execution-status', { quoteId }); switch (status.status.status) { case 'COMPLETED': return status; case 'FAILED': throw new Error(`Transaction failed: ${status.quoteId}`); case 'REFUNDED': throw new Error(`Transaction refunded - likely amount too small or gas costs too high`); case 'PENDING': // Continue polling await new Promise(resolve => setTimeout(resolve, 2000)); break; default: throw new Error(`Unknown status: ${status.status.status}`); } } throw new Error('Transaction timeout'); } ``` ## Contract-Specific Issues ### DEX Integration Problems DEX protocols have specific parameter requirements that must be validated before execution. Common issues include expired deadlines and invalid fee tiers. ```typescript dex-troubleshooting.ts theme={null} // Common Uniswap V3 issues function validateUniswapV3Call(params: any) { // Check deadline is in future const now = Math.floor(Date.now() / 1000); if (params.deadline <= now) { throw new Error('Deadline must be in the future'); } // Validate fee tier const validFees = [100, 500, 3000, 10000]; if (!validFees.includes(params.fee)) { throw new Error(`Invalid fee tier: ${params.fee}`); } // Check recipient matches account if (params.recipient !== account.accountAddress) { console.warn('Recipient does not match account address'); } } ``` ### NFT Marketplace Issues NFT purchases require additional validation for contract addresses and reasonable price limits. This helps prevent accidental overpayments or invalid transactions. ```typescript nft-troubleshooting.ts theme={null} // Common NFT purchase validation function validateNFTPurchase(contractAddress: string, tokenId: string, price: bigint) { if (!isAddress(contractAddress)) { throw new Error('Invalid NFT contract address'); } // Check if price seems reasonable (basic sanity check) const maxReasonablePrice = parseUnits('1000', 6); // $1000 USDC if (price > maxReasonablePrice) { console.warn('Price seems unusually high - please verify'); } return true; } ``` ## Debugging Tools ### Enhanced Logging The logging helps identify issues quickly during development. This debug function provides structured output for all request parameters. ```typescript debug-helpers.ts theme={null} // Request logging function debugPrepareRequest(request: PrepareCallRequest) { console.group('🔍 OneBalance Prepare Request Debug'); console.log('📋 Account:', { session: request.account.sessionAddress, admin: request.account.adminAddress, smart: request.account.accountAddress }); console.log('🌐 Target Chain:', request.targetChain); console.log(`📞 Calls (${request.calls.length}):`); request.calls.forEach((call, i) => { console.log(` Call ${i + 1}:`, { to: call.to, value: call.value, dataLength: call.data.length, dataPreview: call.data.slice(0, 10) + '...' }); }); console.log(`💰 Tokens Required (${request.tokensRequired.length}):`); request.tokensRequired.forEach((token, i) => { console.log(` Token ${i + 1}:`, token); }); console.log(`🔐 Allowances Required (${request.allowanceRequirements.length}):`); request.allowanceRequirements.forEach((allowance, i) => { console.log(` Allowance ${i + 1}:`, allowance); }); console.groupEnd(); } ``` ### Health Check Function Run this function before implementing your main logic to verify all components are working correctly. It tests API connectivity, account prediction, and balance retrieval. ```typescript health-check.ts theme={null} async function performHealthCheck(account: Account) { const checks = []; try { // 1. Check API connectivity await apiGet('/chains/supported-list', {}); checks.push('✅ API connectivity'); } catch { checks.push('❌ API connectivity failed'); } try { // 2. Check account prediction const prediction = await apiPost('/account/predict-address', { sessionAddress: account.sessionAddress, adminAddress: account.adminAddress }); checks.push(`✅ Account prediction: ${prediction.predictedAddress}`); } catch { checks.push('❌ Account prediction failed'); } try { // 3. Check balance retrieval const balance = await apiGet('/v2/balances/aggregated-balance', { address: account.accountAddress }); checks.push(`✅ Balance check: ${balance.balanceByAggregatedAsset.length} assets`); } catch { checks.push('❌ Balance check failed'); } console.log('🏥 OneBalance Health Check Results:'); checks.forEach(check => console.log(check)); return checks.every(check => check.startsWith('✅')); } ``` ## Common Pitfalls **Top 5 Mistakes to Avoid:** 1. **Manual Approvals** - Never include `approve()` in your calls array 2. **Wrong Decimals** - Always verify token decimals (USDC = 6, not 18) 3. **Invalid Chain Format** - Use `eip155:chainId`, not just the chain ID 4. **Modifying Quotes** - Never alter the quote structure after preparation 5. **Insufficient Balance** - Always check balance before attempting operations ## Error Code Reference | Error Code | Meaning | Solution | | ---------- | -------------------- | --------------------------------------- | | `400` | Bad Request | Check input format and validation | | `401` | Unauthorized | Verify API key configuration | | `422` | Unprocessable Entity | Check business logic (balance, amounts) | | `503` | Service Unavailable | Implement retry logic | ## Getting Help Review the specific error section above for your issue Run the health check and debug logging functions Check [working examples](/guides/contract-calls/examples) for reference implementations Use the **Intercom chat widget** in the bottom right corner for instant help, or email [support@onebalance.io](mailto:support@onebalance.io). Provide your quote ID, error message, and debug logs for faster resolution. **Pro Tip**: Most issues can be prevented by running the health check function before implementing your main logic. # Getting Started with EIP-7702 Source: https://docs.onebalance.io/guides/eip-7702/getting-started Implement cross-chain contract calls using EIP-7702 delegation This guide shows you how to implement EIP-7702 with OneBalance using a complete, runnable example. You'll build a cross-chain USDC transfer that automatically handles delegation. **Working Code Examples**: Complete, production-ready EIP-7702 examples are available in our open-source repository: [OneBalance Examples - EIP-7702](https://github.com/OneBalance-io/onebalance-examples/tree/main/eip-7702). These examples include proper signature handling, multi-chain operations, and error recovery patterns. ## Prerequisites * OneBalance API key ([Get one here](/api-reference/authentication)) * Wallet library with `signAuthorization` support (viem) * TypeScript/JavaScript environment ## Account Configuration EIP-7702 accounts use your existing EOA address: ```typescript theme={null} const account = { type: "kernel-v3.3-ecdsa", deploymentType: "EIP7702", signerAddress: "0x5Cb2369421F8a00Ef556d662D6E97C1419B1d37c", // Your EOA accountAddress: "0x5Cb2369421F8a00Ef556d662D6E97C1419B1d37c" // Same address }; ``` Unlike [ERC-4337 accounts](/concepts/accounts), you don't need to predict a new address. Your EOA address becomes your smart account address through delegation. ## Step 1: Prepare Quote Call [prepare-call-quote](/api-reference/quotes/prepare-call-quote) to analyze your requirements. If your EOA needs delegation, you'll receive delegation objects to sign: ```typescript TypeScript theme={null} const prepareResponse = await fetch('https://be.onebalance.io/api/quotes/prepare-call-quote', { method: 'POST', headers: { 'x-api-key': 'YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ account: { type: "kernel-v3.3-ecdsa", deploymentType: "EIP7702", signerAddress: "0x5Cb2369421F8a00Ef556d662D6E97C1419B1d37c", accountAddress: "0x5Cb2369421F8a00Ef556d662D6E97C1419B1d37c" }, targetChain: "eip155:42161", // Arbitrum calls: [{ to: "0xaf88d065e77c8cc2239327c5edb3a432268e5831", // USDC on Arbitrum data: "0xa9059cbb000000000000000000000000742d35cc6634c0532925a3b844bc454e4438f44e0000000000000000000000000000000000000000000000000000000000989680", // transfer(address,uint256) value: "0x0" }], tokensRequired: [{ assetType: "eip155:42161/erc20:0xaf88d065e77c8cc2239327c5edb3a432268e5831", amount: "10000000" // 10 USDC }] }) }); const prepareData = await prepareResponse.json(); ``` ```bash cURL theme={null} curl -X POST 'https://be.onebalance.io/api/quotes/prepare-call-quote' \ -H 'x-api-key: YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "account": { "type": "kernel-v3.3-ecdsa", "deploymentType": "EIP7702", "signerAddress": "0x5Cb2369421F8a00Ef556d662D6E97C1419B1d37c", "accountAddress": "0x5Cb2369421F8a00Ef556d662D6E97C1419B1d37c" }, "targetChain": "eip155:42161", "calls": [{ "to": "0xaf88d065e77c8cc2239327c5edb3a432268e5831", "data": "0xa9059cbb000000000000000000000000742d35cc6634c0532925a3b844bc454e4438f44e0000000000000000000000000000000000000000000000000000000000989680", "value": "0x0" }], "tokensRequired": [{ "assetType": "eip155:42161/erc20:0xaf88d065e77c8cc2239327c5edb3a432268e5831", "amount": "10000000" }] }' ``` ## Step 2: Sign Delegations If your EOA isn't delegated yet, sign the delegation objects using viem's [`signAuthorization`](https://viem.sh/docs/eip7702/signAuthorization) method: EIP-7702 enables EOAs to delegate execution to smart contracts. Delegation is required on **source chains** (for spending) and **destination chains** (only for contract calls). ### Delegation Signature Structure When your EOA needs delegation, OneBalance returns a delegation object that you need to sign: ```typescript theme={null} interface DelegationSignature { chainId: number; contractAddress: Hex; // Kernel v3.3 smart contract address nonce: number; // Current delegation nonce for the EOA r: Hex; s: Hex; v: Hex; yParity: number; type: 'Signed' | 'Unsigned'; } ``` ### Signing Process **Kernel Account Signing**: EIP-7702 accounts using Kernel v3.3 require special signing patterns. Unlike role-based accounts that sign typed data, Kernel accounts sign the UserOperation hash directly. For complete signing documentation including error handling and troubleshooting, see the [Signing Guide](/concepts/signing). ```typescript theme={null} import { privateKeyToAccount } from 'viem/accounts'; import { entryPoint07Address, getUserOperationHash } from 'viem/account-abstraction'; const signerAccount = privateKeyToAccount('0x...' as `0x${string}`); const operation = prepareData.chainOperation; const chainId = Number(operation.typedDataToSign.domain.chainId); // Step 1: Sign delegation if needed if (operation.delegation) { const authTuple = { contractAddress: operation.delegation.contractAddress, nonce: operation.delegation.nonce, chainId: chainId, }; const signedTuple = await signerAccount.signAuthorization(authTuple); if (signedTuple.yParity == null) { throw new Error('Y parity is required for EIP-7702 delegation'); } // Add signature to delegation object operation.delegation.signature = { chainId: chainId, contractAddress: signedTuple.address, nonce: signedTuple.nonce, r: signedTuple.r, s: signedTuple.s, v: `0x${Number(signedTuple.v).toString(16).padStart(2, '0')}`, yParity: signedTuple.yParity, type: 'Signed', }; } // Step 2: Sign UserOperation hash (Kernel v3.3 specific) // Deserialize UserOp with proper BigInt conversions const deserializedUserOp = { sender: operation.userOp.sender, nonce: BigInt(operation.userOp.nonce), factory: operation.userOp.factory, factoryData: operation.userOp.factoryData, callData: operation.userOp.callData, callGasLimit: BigInt(operation.userOp.callGasLimit), verificationGasLimit: BigInt(operation.userOp.verificationGasLimit), preVerificationGas: BigInt(operation.userOp.preVerificationGas), maxFeePerGas: BigInt(operation.userOp.maxFeePerGas), maxPriorityFeePerGas: BigInt(operation.userOp.maxPriorityFeePerGas), paymaster: operation.userOp.paymaster, paymasterVerificationGasLimit: operation.userOp.paymasterVerificationGasLimit ? BigInt(operation.userOp.paymasterVerificationGasLimit) : undefined, paymasterPostOpGasLimit: operation.userOp.paymasterPostOpGasLimit ? BigInt(operation.userOp.paymasterPostOpGasLimit) : undefined, paymasterData: operation.userOp.paymasterData, signature: operation.userOp.signature, }; // Get UserOperation hash for EntryPoint 0.7 const userOpHash = getUserOperationHash({ userOperation: deserializedUserOp, entryPointAddress: entryPoint07Address, entryPointVersion: '0.7', chainId: chainId, }); // Sign the hash directly (not typed data) const signature = await signerAccount.signMessage({ message: { raw: userOpHash } }); operation.userOp.signature = signature; ``` The [`signAuthorization`](https://viem.sh/docs/eip7702/signAuthorization) method is part of viem's EIP-7702 support. It signs an authorization that allows your EOA to delegate execution to the Kernel v3.3 smart contract. Other wallet providers like [Privy](https://docs.privy.io/wallets/using-wallets/ethereum/sign-7702-authorization) also support this method. You must sign delegation objects for ALL chain operations returned by `prepare-call-quote` - both source and destination chains. ## Step 3: Get Quote Submit the signed delegation to [call-quote](/api-reference/quotes/get-call-quote) to get the executable quote: ```typescript TypeScript theme={null} const quoteResponse = await fetch('https://be.onebalance.io/api/quotes/call-quote', { method: 'POST', headers: { 'x-api-key': 'YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ account: prepareData.account, chainOperation: operation, // Contains signed delegation tamperProofSignature: prepareData.tamperProofSignature, fromAggregatedAssetId: 'ob:usdc' // Specify which aggregated asset to use }) }); const quote = await quoteResponse.json(); ``` ```bash cURL theme={null} curl -X POST 'https://be.onebalance.io/api/quotes/call-quote' \ -H 'x-api-key: YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "account": { "type": "kernel-v3.3-ecdsa", "deploymentType": "EIP7702", "signerAddress": "0x5Cb2369421F8a00Ef556d662D6E97C1419B1d37c", "accountAddress": "0x5Cb2369421F8a00Ef556d662D6E97C1419B1d37c" }, "chainOperation": { "delegation": { "contractAddress": "0xd6CEDDe84be40893d153Be9d467CD6aD37875b28", "nonce": 0, "signature": { "chainId": 42161, "contractAddress": "0xd6CEDDe84be40893d153Be9d467CD6aD37875b28", "nonce": 0, "r": "0x...", "s": "0x...", "v": "0x1c", "yParity": 1, "type": "Signed" } }, "userOp": { "signature": "0x..." } }, "tamperProofSignature": "0x...", "fromAggregatedAssetId": "ob:usdc" }' ``` ## Step 4: Execute Execute the quote using [execute-quote](/api-reference/quotes/execute-quote). **Important**: You must also sign any origin chain operations: ```typescript theme={null} // Sign origin chain operations using the same pattern as Step 2 for (let i = 0; i < quote.originChainsOperations.length; i++) { const originOperation = quote.originChainsOperations[i]; // Use same signing logic as Step 2 for each origin chain operation // (delegation + UserOperation hash signing) quote.originChainsOperations[i] = await signOperation(originOperation); } ``` ```typescript TypeScript theme={null} const executeResponse = await fetch('https://be.onebalance.io/api/quotes/execute-quote', { method: 'POST', headers: { 'x-api-key': 'YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify(quote) }); const result = await executeResponse.json(); console.log('Transaction executed:', result); ``` ```bash cURL theme={null} curl -X POST 'https://be.onebalance.io/api/quotes/execute-quote' \ -H 'x-api-key: YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "quoteId": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", "account": { "type": "kernel-v3.3-ecdsa", "deploymentType": "EIP7702", "signerAddress": "0x5Cb2369421F8a00Ef556d662D6E97C1419B1d37c", "accountAddress": "0x5Cb2369421F8a00Ef556d662D6E97C1419B1d37c" }, "chainOperations": [ { "chainId": "eip155:42161", "userOp": { "sender": "0x5Cb2369421F8a00Ef556d662D6E97C1419B1d37c", "nonce": "0x0", "signature": "0x..." }, "delegation": { "contractAddress": "0xd6CEDDe84be40893d153Be9d467CD6aD37875b28", "signature": { "chainId": 42161, "r": "0x...", "s": "0x...", "yParity": 1, "type": "Signed" } } } ] }' ``` The operation will: 1. **Delegate** your EOA on chains that need it (automatically) 2. **Bridge** 10 USDC from source chains to the destination 3. **Execute** your contract call on the destination chain All in a single user interaction. ## Implementation Notes ### Critical Requirements * **Account type**: Must use `type: "kernel-v3.3-ecdsa"` and `deploymentType: "EIP7702"` * **Address configuration**: Both `accountAddress` and `signerAddress` must be the same EOA address * **Signing method**: Kernel accounts sign UserOperation hash (`signMessage()`) not typed data * **Y parity validation**: Always check `signedTuple.yParity` is not null * **Multi-chain signing**: Sign both destination operation (Step 2) and origin operations (Step 4) ### Key Differences from Role-Based Accounts | Aspect | Kernel v3.3 (EIP-7702) | Role-Based | | ------------------ | -------------------------------------- | -------------------------------- | | **Signing method** | UserOperation hash via `signMessage()` | Typed data via `signTypedData()` | | **Address** | Same as EOA | Predicted new address | | **EntryPoint** | Requires 0.7 | Not applicable | | **Delegation** | Required on spending chains | Not applicable | ## Next Steps Handle common errors and multi-input scenarios Complete production-ready code examples Your EOA now has smart account capabilities whenever needed, without changing addresses or migrating funds. # EIP-7702 Overview Source: https://docs.onebalance.io/guides/eip-7702/overview Enable EOAs to delegate execution to smart contracts for cross-chain operations ## What is EIP-7702? [EIP-7702](https://eip7702.io) allows Externally Owned Accounts (EOAs) to delegate their execution logic to smart contracts. When an EOA signs an authorization, it temporarily gains smart account capabilities without changing its address. ## Supported Operations EIP-7702 accounts work with all OneBalance transaction types: * **Swaps**: Cross-chain token exchanges with automatic bridging * **Transfers**: Send assets across chains to any address * **Contract Calls**: Execute smart contract functions with call data All operations benefit from gas abstraction and single-transaction UX. ### Key Concept: Delegation vs Deployment **EIP-7702 Accounts (Delegation)**: * EOA signs authorization to delegate to smart contract implementation * Address remains unchanged - no migration needed * Delegation happens when first transaction is needed **ERC-4337 Accounts (Deployment)**: * New smart contract deployed with predicted address * Requires address migration from existing EOA * Deployment happens when account is created ## Integration Flow OneBalance implements EIP-7702 through a clear process: Call [prepare-call-quote](/api-reference/quotes/prepare-call-quote) to analyze your requirements and get delegation objects if needed Sign any delegation objects using viem's [`signAuthorization`](https://viem.sh/docs/eip7702/signAuthorization) method Submit signed delegations to [call-quote](/api-reference/quotes/get-call-quote) to get the executable quote Execute the quote using [execute-quote](/api-reference/quotes/execute-quote) to perform the cross-chain operation OneBalance handles delegation automatically. If your EOA is already delegated on a chain, no additional signing is required. ## How EIP-7702 Works When you configure an EIP-7702 account with OneBalance: 1. **Account Configuration**: Set `deploymentType: "EIP7702"` and use your EOA address for both `signerAddress` and `accountAddress` 2. **Smart Contract Implementation**: OneBalance uses Kernel 3.3 as the delegation target 3. **Automatic Delegation**: When needed, OneBalance generates authorization objects for you to sign 4. **Execution**: Your EOA temporarily gains smart account capabilities during the transaction ## Example: Account Configuration ```typescript theme={null} const account = { type: "kernel-v3.3-ecdsa", deploymentType: "EIP7702", signerAddress: "0x5Cb2369421F8a00Ef556d662D6E97C1419B1d37c", // Your EOA accountAddress: "0x5Cb2369421F8a00Ef556d662D6E97C1419B1d37c" // Same address }; ``` When delegation is needed: * **Source chain**: Always required for spending operations (swaps, transfers, contract calls) * **Destination chain**: Only required for contract calls (not for swaps or transfers) ## When to Use EIP-7702 Choose EIP-7702 when you want to preserve existing EOA addresses: * **Existing EOA users**: No address migration needed * **Embedded wallets**: Upgrade EOAs to smart account capabilities * **Address preservation**: Keep on-chain history and familiar addresses **Choose ERC-4337 when you need:** * [Resource Locks](/concepts/resource-locks) (requires managed key infrastructure) * Custom account implementations beyond Kernel v3.3 EIP-7702 uses the same ERC-4337 infrastructure (bundlers, paymasters, UserOps) but extends it to work with existing EOA addresses. Learn more about [Account Types](/concepts/accounts). ## Current Limitations OneBalance's EIP-7702 implementation works for most scenarios but has one constraint: **Multi-Input Limitation**: When you have assets on more than 1 source chain different from the destination chain, you'll need to use a [manual delegation workaround](/guides/eip-7702/troubleshooting#multi-input-limitation). | Example Assets | Destination | Result | | --------------------------- | ----------- | -------------------- | | USDC on Optimism | Arbitrum | ✅ Single transaction | | USDC on Optimism + Arbitrum | Arbitrum | ✅ Single transaction | | USDC on Optimism + Base | Arbitrum | ⚠️ Needs workaround | ## Cross-Chain Funding with Solana EIP-7702 accounts can be funded using Solana assets for cost efficiency and expanded liquidity access: Fund EIP-7702 contract calls using SOL or USDC from Solana Complete working examples of cross-chain contract execution Using Solana funding with EIP-7702 enables atomic operations: SOL → USDC swap → bridge → delegation → contract execution. ## Next Steps Build your first EIP-7702 swap with delegation Handle multi-input limitations and edge cases All existing OneBalance API endpoints work with EIP-7702. Just update your account configuration to use `type: "kernel-v3.3-ecdsa"` and `deploymentType: "EIP7702"`. # EIP-7702 Troubleshooting Source: https://docs.onebalance.io/guides/eip-7702/troubleshooting Handle multi-input limitations and edge cases ## Common Implementation Issues Quick reference for resolving EIP-7702 implementation questions: ### Signature-Related Errors | Issue | Cause | Solution | | --------------------------------- | -------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | **"Y parity is required"** | Wallet doesn't return yParity in signature | Use [viem](https://viem.sh) or [compatible wallet library](https://docs.privy.io/wallets/using-wallets/ethereum/sign-7702-authorization) | | **"Invalid signature"** | Using `signTypedData()` instead of `signMessage()` | Use UserOperation hash signing for Kernel accounts | | **"UserOperation hash mismatch"** | Incorrect BigInt conversion or EntryPoint version | Ensure all numeric fields are `BigInt` and use EntryPoint 0.7 | ### Multi-Chain Operation Errors | Issue | Cause | Solution | | ---------------------------------------- | --------------------------------------------------- | ---------------------------------------------------------------------- | | **"Missing origin chain signatures"** | Forgot to sign origin chain operations | Sign all operations in `quote.originChainsOperations[]` | | **"Transaction failed on origin chain"** | Origin chain delegation or UserOp signature missing | Check that both delegation and UserOp are signed for each origin chain | ### Delegation Errors | Issue | Cause | Solution | | ------------------------ | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | | **`DELEGATION-001`** | EOA has a pre-existing delegation to a non-Kernel contract | Revoke existing delegation or use a different EOA. See [error code reference](/api-reference/error-codes#delegation-001) | | **"Nonce too low"** | Outdated delegation nonce | OneBalance handles this automatically; retry the request | | **"Already delegated"** | EOA already delegated to this contract | Normal - no delegation object will be returned | | **"Contract not found"** | Using wrong Kernel v3.3 address | Use the contract address returned by OneBalance | ### Account Configuration Errors | Issue | Cause | Solution | | -------------------------------- | -------------------------------------- | ---------------------------------------------------------------------- | | **"Account type not supported"** | Wrong account type configuration | Must use `type: "kernel-v3.3-ecdsa"` and `deploymentType: "EIP7702"` | | **"Address mismatch"** | Different signer and account addresses | Both `signerAddress` and `accountAddress` must be the same EOA address | **Critical**: Kernel v3.3 accounts require different signing patterns than role-based accounts. Always use UserOperation hash signing (`signMessage()`) instead of typed data signing (`signTypedData()`). ## Multi-Input Limitation OneBalance's EIP-7702 implementation has one architectural limitation for all operations (swaps, transfers, contract calls): it can only handle single-transaction execution when you have assets on **≤1 source chain different from the destination chain**. This limitation exists because OneBalance relies on external routing providers that don't yet support EIP-7702 authorization tuples in their multi-input APIs. Learn more about [how OneBalance works](/overview/how-onebalance-works). ### When You Need the Workaround | Your Assets | Destination | Result | | --------------------------- | --------------- | -------------------- | | USDC on Optimism | Arbitrum | ✅ Single transaction | | USDC on Optimism + Arbitrum | Arbitrum | ✅ Single transaction | | USDC on Optimism + Base | Arbitrum | ⚠️ Needs workaround | | USDC on 3+ different chains | Any destination | ⚠️ Needs workaround | This affects approximately 20% of operations. Most users will use the single-transaction flow without issues. ## Manual Delegation Workaround When you encounter the multi-input limitation, you need to manually delegate your EOAs before using OneBalance: Submit EIP-7702 delegations directly to each chain where you have assets Once delegated, use the normal OneBalance integration flow If the process is interrupted, you may need to manually complete transactions ### Manual Delegation Process This requires a wallet library that supports EIP-7702 [`signAuthorization`](https://viem.sh/docs/eip7702/signAuthorization) method, such as [viem](https://viem.sh). ```typescript theme={null} import { createWalletClient, http } from 'viem'; import { optimism, base } from 'viem/chains'; // Submit delegation manually on each required chain async function submitDelegation(chainId: number) { const walletClient = createWalletClient({ chain: chainId === 10 ? optimism : base, transport: http() }); const authorization = await walletClient.signAuthorization({ contractAddress: "0xd6CEDDe84be40893d153Be9d467CD6aD37875b28", // Kernel v3.3 nonce: 0 // Get current nonce from chain }); const hash = await walletClient.sendTransaction({ authorizationList: [authorization], data: "0x", to: walletClient.account.address, }); return hash; } ``` ## Best Practices * **Optimize for the 80%**: Most users will use the single-transaction flow without issues * **Detect automatically**: Route users based on their asset distribution * **Provide clear messaging**: Explain when and why the workaround is needed * **Handle errors gracefully**: Implement proper error handling and recovery * **Monitor success rates**: Track how often the workaround is needed Consider implementing a fallback that automatically detects when the single-transaction flow fails and offers the manual delegation alternative. Most errors are automatically handled by OneBalance. The main issue you'll encounter is the multi-input limitation, which affects about 20% of operations. Learn more about [transaction lifecycle](/concepts/transaction-lifecycle). ## Next Steps Learn the basic implementation flow Complete API documentation # Guides Overview Source: https://docs.onebalance.io/guides/overview Learn how to use the OneBalance Toolkit with guides, code snippets and examples to help you with your integration journey. ## What You'll Find Here Our guides are designed to get you building quickly with real-world examples that you can adapt for your own projects. ### Account Models & Advanced Features Enable single-transaction cross-chain execution using EOA delegation Get started with OneBalance quotes for asset transfers and swaps across multiple chains Execute smart contract interactions using your aggregated balance across blockchains ### Integration Tutorials Build token swap interfaces that work across multiple blockchains Integrate OneBalance with Turnkey wallet infrastructure ### Platform Integrations Cross-chain operations between Solana and EVM chains Build AI-powered applications with OneBalance MCP integration ### Error Handling & Troubleshooting Complete reference of fail reason codes with troubleshooting steps Detailed explanations and handling strategies for quote execution failures ## Getting Started Start with our API reference to understand core concepts and available endpoints. Jump into our most popular tutorial: building a chain-abstracted swap interface. All our guides include complete, working code examples that you can run immediately. ## Popular Examples Production-ready examples for DeFi, NFT, and gaming use cases Overview of quotes and how to use them for transfers and swaps Handle multi-input limitations and common implementation issues Cross-chain operations between Solana and EVM networks ## Need Help? * **[API Reference](/api-reference/introduction)**: Detailed documentation for all OneBalance endpoints and schemas * **[Error Codes](/api-reference/error-codes)**: Complete reference for troubleshooting quote execution failures * **[Contract Calls Troubleshooting](/guides/contract-calls/troubleshooting)**: Common issues and solutions for contract calls * **[Discord Community](https://discord.com/invite/vHkw7rpdT8)**: Join our Discord for questions, discussions, and community support * **[GitHub Examples](https://github.com/OneBalance-io/onebalance-examples)**: Complete source code for tutorial projects # Quote Examples Source: https://docs.onebalance.io/guides/quotes/examples Explore OneBalance API quote request examples for transfers and swaps, showing how to structure cross-chain API calls. The quote endpoint enables you to request quotes for various operations including token swaps, cross-chain bridges, and complex multichain transactions. Below are practical examples for different scenarios. ## Overview Each example below shows both the JSON request body and the complete cURL command. You can copy the cURL commands directly to test the API, or use the JSON format with your preferred HTTP client. For interactive testing, use the [API Reference](/api-reference/quotes/get-quote) playground. **Account Types**: Learn more about the differences between Basic, EIP-7702, and Role-Based accounts in our [Account Models](/concepts/accounts) guide. Basic accounts provide simple setup, EIP-7702 preserves existing EOA addresses, and Role-Based accounts offer enhanced security features. ## Aggregated Token to Aggregated Token Swap Both source and destination chains are optimized by the Toolkit. ```json JSON theme={null} { "from": { "account": { "type": "kernel-v3.1-ecdsa", "signerAddress": "0x5d6fb4eb211a6a2e406a1111b54d26c534753c8e", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "ob:eth" }, "amount": "1000000000000000" }, "to": { "asset": { "assetId": "ob:usdc" } } } ``` ```bash cURL theme={null} curl --request POST \ --url https://be.onebalance.io/api/v1/quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "from": { "account": { "type": "kernel-v3.1-ecdsa", "signerAddress": "0x5d6fb4eb211a6a2e406a1111b54d26c534753c8e", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "ob:eth" }, "amount": "1000000000000000" }, "to": { "asset": { "assetId": "ob:usdc" } } }' ``` ```json JSON theme={null} { "from": { "account": { "type": "kernel-v3.3-ecdsa", "deploymentType": "EIP7702", "signerAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "ob:eth" }, "amount": "1000000000000000" }, "to": { "asset": { "assetId": "ob:usdc" } } } ``` ```bash cURL theme={null} curl --request POST \ --url https://be.onebalance.io/api/v1/quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "from": { "account": { "type": "kernel-v3.3-ecdsa", "deploymentType": "EIP7702", "signerAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "ob:eth" }, "amount": "1000000000000000" }, "to": { "asset": { "assetId": "ob:usdc" } } }' ``` ```json JSON theme={null} { "from": { "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "ob:eth" }, "amount": "1000000000000000" }, "to": { "asset": { "assetId": "ob:usdc" } } } ``` ```bash cURL theme={null} curl --request POST \ --url https://be.onebalance.io/api/v1/quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "from": { "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "ob:eth" }, "amount": "1000000000000000" }, "to": { "asset": { "assetId": "ob:usdc" } } }' ``` ## Aggregated Token to Chain-Specific Token with Custom Recipient The source chain (where to take the assets from) is optimized by the Toolkit, while the destination chain is explicitly specified. There is a different recipient on the destination chain. ```json JSON theme={null} { "from": { "account": { "type": "kernel-v3.1-ecdsa", "signerAddress": "0x5d6fb4eb211a6a2e406a1111b54d26c534753c8e", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "ob:usdc" }, "amount": "1000000" }, "to": { "asset": { "assetId": "eip155:8453/erc20:0x532f27101965dd16442e59d40670faf5ebb142e4" }, "account": "eip155:8453:0x742d35Cc6634C0532925a3b844Bc454e4438f44e" } } ``` ```bash cURL theme={null} curl --request POST \ --url https://be.onebalance.io/api/v1/quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "from": { "account": { "type": "kernel-v3.1-ecdsa", "signerAddress": "0x5d6fb4eb211a6a2e406a1111b54d26c534753c8e", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "ob:usdc" }, "amount": "1000000" }, "to": { "asset": { "assetId": "eip155:8453/erc20:0x532f27101965dd16442e59d40670faf5ebb142e4" }, "account": "eip155:8453:0x742d35Cc6634C0532925a3b844Bc454e4438f44e" } }' ``` ```json JSON theme={null} { "from": { "account": { "type": "kernel-v3.3-ecdsa", "deploymentType": "EIP7702", "signerAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "ob:usdc" }, "amount": "1000000" }, "to": { "asset": { "assetId": "eip155:8453/erc20:0x532f27101965dd16442e59d40670faf5ebb142e4" }, "account": "eip155:8453:0x742d35Cc6634C0532925a3b844Bc454e4438f44e" } } ``` ```bash cURL theme={null} curl --request POST \ --url https://be.onebalance.io/api/v1/quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "from": { "account": { "type": "kernel-v3.3-ecdsa", "deploymentType": "EIP7702", "signerAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "ob:usdc" }, "amount": "1000000" }, "to": { "asset": { "assetId": "eip155:8453/erc20:0x532f27101965dd16442e59d40670faf5ebb142e4" }, "account": "eip155:8453:0x742d35Cc6634C0532925a3b844Bc454e4438f44e" } }' ``` ```json JSON theme={null} { "from": { "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "ob:usdc" }, "amount": "1000000" }, "to": { "asset": { "assetId": "eip155:8453/erc20:0x532f27101965dd16442e59d40670faf5ebb142e4" }, "account": "eip155:8453:0x742d35Cc6634C0532925a3b844Bc454e4438f44e" } } ``` ```bash cURL theme={null} curl --request POST \ --url https://be.onebalance.io/api/v1/quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "from": { "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "ob:usdc" }, "amount": "1000000" }, "to": { "asset": { "assetId": "eip155:8453/erc20:0x532f27101965dd16442e59d40670faf5ebb142e4" }, "account": "eip155:8453:0x742d35Cc6634C0532925a3b844Bc454e4438f44e" } }' ``` ## Chain-Specific Token to Chain-Specific Token Swap Direct swap between tokens on specific chains without aggregation. ```json JSON theme={null} { "from": { "account": { "type": "kernel-v3.1-ecdsa", "signerAddress": "0x5d6fb4eb211a6a2e406a1111b54d26c534753c8e", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "eip155:8453/erc20:0x2da56acb9ea78330f947bd57c54119debda7af71" }, "amount": "1000000000000000000000000" }, "to": { "asset": { "assetId": "eip155:8453/erc20:0x9a26f5433671751c3276a065f57e5a02d2817973" } } } ``` ```bash cURL theme={null} curl --request POST \ --url https://be.onebalance.io/api/v1/quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "from": { "account": { "type": "kernel-v3.1-ecdsa", "signerAddress": "0x5d6fb4eb211a6a2e406a1111b54d26c534753c8e", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "eip155:8453/erc20:0x2da56acb9ea78330f947bd57c54119debda7af71" }, "amount": "1000000000000000000000000" }, "to": { "asset": { "assetId": "eip155:8453/erc20:0x9a26f5433671751c3276a065f57e5a02d2817973" } } }' ``` ```json JSON theme={null} { "from": { "account": { "type": "kernel-v3.3-ecdsa", "deploymentType": "EIP7702", "signerAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "eip155:8453/erc20:0x2da56acb9ea78330f947bd57c54119debda7af71" }, "amount": "1000000000000000000000000" }, "to": { "asset": { "assetId": "eip155:8453/erc20:0x9a26f5433671751c3276a065f57e5a02d2817973" } } } ``` ```bash cURL theme={null} curl --request POST \ --url https://be.onebalance.io/api/v1/quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "from": { "account": { "type": "kernel-v3.3-ecdsa", "deploymentType": "EIP7702", "signerAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "eip155:8453/erc20:0x2da56acb9ea78330f947bd57c54119debda7af71" }, "amount": "1000000000000000000000000" }, "to": { "asset": { "assetId": "eip155:8453/erc20:0x9a26f5433671751c3276a065f57e5a02d2817973" } } }' ``` ```json JSON theme={null} { "from": { "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "eip155:8453/erc20:0x2da56acb9ea78330f947bd57c54119debda7af71" }, "amount": "1000000000000000000000000" }, "to": { "asset": { "assetId": "eip155:8453/erc20:0x9a26f5433671751c3276a065f57e5a02d2817973" } } } ``` ```bash cURL theme={null} curl --request POST \ --url https://be.onebalance.io/api/v1/quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "from": { "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "eip155:8453/erc20:0x2da56acb9ea78330f947bd57c54119debda7af71" }, "amount": "1000000000000000000000000" }, "to": { "asset": { "assetId": "eip155:8453/erc20:0x9a26f5433671751c3276a065f57e5a02d2817973" } } }' ``` ## Chain-Specific Token to Aggregated Token Convert a chain-specific token to an aggregated token for broader liquidity access. ```json JSON theme={null} { "from": { "account": { "type": "kernel-v3.1-ecdsa", "signerAddress": "0x5d6fb4eb211a6a2e406a1111b54d26c534753c8e", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "eip155:8453/erc20:0x2da56acb9ea78330f947bd57c54119debda7af71" }, "amount": "1000000000000000000000000" }, "to": { "asset": { "assetId": "ob:usdc" } } } ``` ```bash cURL theme={null} curl --request POST \ --url https://be.onebalance.io/api/v1/quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "from": { "account": { "type": "kernel-v3.1-ecdsa", "signerAddress": "0x5d6fb4eb211a6a2e406a1111b54d26c534753c8e", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "eip155:8453/erc20:0x2da56acb9ea78330f947bd57c54119debda7af71" }, "amount": "1000000000000000000000000" }, "to": { "asset": { "assetId": "ob:usdc" } } }' ``` ```json JSON theme={null} { "from": { "account": { "type": "kernel-v3.3-ecdsa", "deploymentType": "EIP7702", "signerAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "eip155:8453/erc20:0x2da56acb9ea78330f947bd57c54119debda7af71" }, "amount": "1000000000000000000000000" }, "to": { "asset": { "assetId": "ob:usdc" } } } ``` ```bash cURL theme={null} curl --request POST \ --url https://be.onebalance.io/api/v1/quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "from": { "account": { "type": "kernel-v3.3-ecdsa", "deploymentType": "EIP7702", "signerAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "eip155:8453/erc20:0x2da56acb9ea78330f947bd57c54119debda7af71" }, "amount": "1000000000000000000000000" }, "to": { "asset": { "assetId": "ob:usdc" } } }' ``` ```json JSON theme={null} { "from": { "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "eip155:8453/erc20:0x2da56acb9ea78330f947bd57c54119debda7af71" }, "amount": "1000000000000000000000000" }, "to": { "asset": { "assetId": "ob:usdc" } } } ``` ```bash cURL theme={null} curl --request POST \ --url https://be.onebalance.io/api/v1/quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "from": { "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "eip155:8453/erc20:0x2da56acb9ea78330f947bd57c54119debda7af71" }, "amount": "1000000000000000000000000" }, "to": { "asset": { "assetId": "ob:usdc" } } }' ``` ## Cross-Chain Bridge Operation Bridge the same token between different chains (e.g., USDC from Arbitrum to Optimism). ```json JSON theme={null} { "from": { "account": { "type": "kernel-v3.1-ecdsa", "signerAddress": "0x5d6fb4eb211a6a2e406a1111b54d26c534753c8e", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "eip155:42161/erc20:0xaf88d065e77c8cc2239327c5edb3a432268e5831" }, "amount": "1000000" }, "to": { "asset": { "assetId": "eip155:10/erc20:0x0b2c639c533813f4aa9d7837caf62653d097ff85" } } } ``` ```bash cURL theme={null} curl --request POST \ --url https://be.onebalance.io/api/v1/quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "from": { "account": { "type": "kernel-v3.1-ecdsa", "signerAddress": "0x5d6fb4eb211a6a2e406a1111b54d26c534753c8e", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "eip155:42161/erc20:0xaf88d065e77c8cc2239327c5edb3a432268e5831" }, "amount": "1000000" }, "to": { "asset": { "assetId": "eip155:10/erc20:0x0b2c639c533813f4aa9d7837caf62653d097ff85" } } }' ``` ```json JSON theme={null} { "from": { "account": { "type": "kernel-v3.3-ecdsa", "deploymentType": "EIP7702", "signerAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "eip155:42161/erc20:0xaf88d065e77c8cc2239327c5edb3a432268e5831" }, "amount": "1000000" }, "to": { "asset": { "assetId": "eip155:10/erc20:0x0b2c639c533813f4aa9d7837caf62653d097ff85" } } } ``` ```bash cURL theme={null} curl --request POST \ --url https://be.onebalance.io/api/v1/quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "from": { "account": { "type": "kernel-v3.3-ecdsa", "deploymentType": "EIP7702", "signerAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "eip155:42161/erc20:0xaf88d065e77c8cc2239327c5edb3a432268e5831" }, "amount": "1000000" }, "to": { "asset": { "assetId": "eip155:10/erc20:0x0b2c639c533813f4aa9d7837caf62653d097ff85" } } }' ``` ```json JSON theme={null} { "from": { "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "eip155:42161/erc20:0xaf88d065e77c8cc2239327c5edb3a432268e5831" }, "amount": "1000000" }, "to": { "asset": { "assetId": "eip155:10/erc20:0x0b2c639c533813f4aa9d7837caf62653d097ff85" } } } ``` ```bash cURL theme={null} curl --request POST \ --url https://be.onebalance.io/api/v1/quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "from": { "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "eip155:42161/erc20:0xaf88d065e77c8cc2239327c5edb3a432268e5831" }, "amount": "1000000" }, "to": { "asset": { "assetId": "eip155:10/erc20:0x0b2c639c533813f4aa9d7837caf62653d097ff85" } } }' ``` # Quotes Overview Source: https://docs.onebalance.io/guides/quotes/overview Overview of OneBalance's quoting system: what quotes are, how they're generated, and how to use them in cross-chain operations. OneBalance quotes enable cross-chain operations including asset transfers, swaps, and arbitrary contract interactions. They facilitate the core transaction lifecycle by providing cost estimates and execution plans. **Account Setup Required**: Quotes require proper account configuration. See [Account Models](/concepts/accounts) for setup instructions. ## Quote Types ### Unified Quotes For transfers and swaps between aggregated or chain-specific assets. ```bash Terminal theme={null} curl -X POST "https://be.onebalance.io/api/v1/quote" \ -H "x-api-key: ONEBALANCE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "from": { "account": { "sessionAddress": "0x1cBF...", "adminAddress": "0xc162...", "accountAddress": "0xE202..." }, "asset": { "assetId": "ob:eth" }, "amount": "1000000000000000000" }, "to": { "asset": { "assetId": "ob:usdc" } } }' ``` ### Contract Call Quotes For arbitrary smart contract interactions with automatic token routing. ```bash Terminal theme={null} curl -X POST "https://be.onebalance.io/api/quotes/prepare-call-quote" \ -H "x-api-key: ONEBALANCE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "account": { "sessionAddress": "0x1cBF...", "adminAddress": "0xc162...", "accountAddress": "0xE202..." }, "targetChain": "eip155:8453", "calls": [{ "to": "0x...", "data": "0x...", "value": "0x0" }], "tokensRequired": [{ "assetType": "eip155:8453/erc20:0x...", "amount": "1000000" }] }' ``` ## Quote Lifecycle 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 ## 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 Start with [quote examples](/guides/quotes/examples) for hands-on implementation guidance. # Understanding Quote Refund Reasons Source: https://docs.onebalance.io/guides/quotes/refund-reasons Learn about the different reasons why quotes get refunded and how to handle them in your application. When a quote execution fails, it gets marked as `REFUNDED` and includes a `failReason` field that explains why the refund occurred. Understanding these reasons helps you build better error handling and improve user experience. For a complete reference of all fail reason codes with detailed explanations, see [Error Codes](/api-reference/error-codes). ## How Refund Reasons Work When you check the status of a quote using the [Get Execution Status](/api-reference/status/get-quote-status) endpoint, refunded quotes include additional context: ```json theme={null} { "quoteId": "0xfa6094cd...", "status": "REFUNDED", "user": "0x9b747cC14...", "failReason": "SLIPPAGE", "originChainOperations": [], "destinationChainOperations": [] } ``` ## Implementation Patterns by Category ### Balance and Allowance Issues Handle insufficient funds and token approval problems: ```typescript TypeScript theme={null} function handleBalanceAndAllowanceErrors(failReason: string) { switch (failReason) { case 'TRANSFER_AMOUNT_EXCEEDS_BALANCE': // Check current balance before retrying return handleInsufficientBalance(); case 'TRANSFER_AMOUNT_EXCEEDS_ALLOWANCE': // Request token approval return handleInsufficientAllowance(); case 'INSUFFICIENT_NATIVE_TOKENS_SUPPLIED': // Add gas tokens return handleInsufficientGas(); case 'TRANSFER_FROM_FAILED': case 'TRANSFER_FAILED': // Transfer operation failed return handleTransferFailure(); } } async function handleInsufficientBalance() { const balance = await getBalance(userAddress, tokenAddress); if (balance.gte(requiredAmount)) { // Balance recovered, safe to retry return { action: 'retry', message: 'Balance sufficient, retrying...' }; } else { // Show balance error to user return { action: 'error', message: 'Insufficient balance for this transaction' }; } } async function handleInsufficientAllowance() { // Request token approval return { action: 'approval_needed', message: 'Token approval required', nextStep: 'approve_token' }; } ``` ```javascript JavaScript theme={null} function handleBalanceAndAllowanceErrors(failReason) { switch (failReason) { case 'TRANSFER_AMOUNT_EXCEEDS_BALANCE': return handleInsufficientBalance(); case 'TRANSFER_AMOUNT_EXCEEDS_ALLOWANCE': return handleInsufficientAllowance(); case 'INSUFFICIENT_NATIVE_TOKENS_SUPPLIED': return handleInsufficientGas(); case 'TRANSFER_FROM_FAILED': case 'TRANSFER_FAILED': return handleTransferFailure(); } } async function handleInsufficientBalance() { const balance = await getBalance(userAddress, tokenAddress); if (balance >= requiredAmount) { return { action: 'retry', message: 'Balance sufficient, retrying...' }; } else { return { action: 'error', message: 'Insufficient balance for this transaction' }; } } ``` ### Price and Market Issues Handle slippage, expired orders, and market conditions: ```typescript theme={null} function handleMarketErrors(failReason: string, originalQuote: any) { switch (failReason) { case 'SLIPPAGE': // Retry with higher slippage tolerance return retryWithHigherSlippage(originalQuote); case 'ORDER_EXPIRED': // Generate fresh quote return generateFreshQuote(originalQuote); case 'ORDER_IS_CANCELLED': // Order was cancelled return { action: 'new_quote', message: 'Order cancelled, please create a new order' }; case 'ORDER_ALREADY_FILLED': // Order already completed return { action: 'error', message: 'Order already filled' }; case 'NO_QUOTES': // No liquidity available return handleNoLiquidity(originalQuote); case 'TOO_LITTLE_RECEIVED': // Output below minimum return handleLowOutput(originalQuote); case 'NO_INTERNAL_SWAP_ROUTES_FOUND': // No routing path available return { action: 'error', message: 'No swap route available for this pair' }; case 'SWAP_USES_TOO_MUCH_GAS': // Gas cost too high return { action: 'retry', message: 'Gas cost too high, try smaller amount' }; } } async function retryWithHigherSlippage(originalQuote: any) { const currentSlippage = originalQuote.slippageTolerance || 100; const newSlippage = Math.min(currentSlippage * 1.5, 500); // Cap at 5% return { action: 'retry', message: `Retrying with ${newSlippage / 100}% slippage tolerance`, newParams: { ...originalQuote, slippageTolerance: newSlippage } }; } async function generateFreshQuote(originalQuote: any) { return { action: 'new_quote', message: 'Order expired, generating new quote with current prices', newParams: originalQuote }; } ``` ### Signature and Authentication Issues Handle wallet and signing problems: ```typescript theme={null} function handleSignatureErrors(failReason: string) { switch (failReason) { case 'INVALID_SIGNATURE': return { action: 'resign', message: 'Invalid signature, please sign again' }; case 'SIGNATURE_EXPIRED': return { action: 'resign', message: 'Signature expired, please sign the new transaction' }; case 'INVALID_SENDER': return { action: 'reconnect', message: 'Please reconnect your wallet' }; case 'ACCOUNT_ABSTRACTION_INVALID_NONCE': case 'ACCOUNT_ABSTRACTION_SIGNATURE_ERROR': return { action: 'sync_wallet', message: 'Smart wallet sync required, please retry' }; } } ``` ### System and Protocol Issues Handle system capacity and protocol-specific errors: ```typescript theme={null} function handleSystemErrors(failReason: string) { switch (failReason) { case 'SOLVER_CAPACITY_EXCEEDED': return { action: 'retry', message: 'System at capacity, retrying in a moment', delay: 2000 }; case 'EXECUTION_REVERTED': case 'TRANSACTION_REVERTED': return { action: 'error', message: 'Transaction reverted, please check parameters' }; case 'GENERATE_SWAP_FAILED': case 'REVERSE_SWAP_FAILED': return { action: 'retry', message: 'Swap generation failed, retrying' }; case 'MISSING_REVERT_DATA': return { action: 'contact_support', message: 'Transaction failed without details, contact support' }; } } ``` ### Deposit and Validation Issues Handle deposit-related errors: ```typescript theme={null} function handleDepositErrors(failReason: string) { switch (failReason) { case 'DEPOSIT_ADDRESS_MISMATCH': case 'DEPOSIT_CHAIN_MISMATCH': case 'INCORRECT_DEPOSIT_CURRENCY': return { action: 'error', message: 'Deposit details incorrect, verify and retry' }; case 'AMOUNT_TOO_LOW_TO_REFUND': case 'DEPOSITED_AMOUNT_TOO_LOW_TO_FILL': return { action: 'error', message: 'Amount too low, increase amount' }; case 'NEGATIVE_NEW_AMOUNT_AFTER_FEES': return { action: 'error', message: 'Fees exceed amount, increase amount' }; case 'ORIGIN_CURRENCY_MISMATCH': return { action: 'error', message: 'Source currency mismatch, check token' }; } } ``` ### Token and Protocol Specific Issues Handle token-specific and protocol errors: ```typescript theme={null} function handleTokenErrors(failReason: string) { switch (failReason) { case 'TOKEN_NOT_TRANSFERABLE': return { action: 'error', message: 'Token has transfer restrictions' }; case 'ZERO_SELL_AMOUNT': return { action: 'error', message: 'Invalid amount, must be greater than zero' }; case 'SEAPORT_INEXACT_FRACTION': case 'SEAPORT_INVALID_FULFILLER': return { action: 'error', message: 'NFT order validation failed' }; case 'MINT_NOT_ACTIVE': return { action: 'wait', message: 'Minting not active, try later' }; case 'ERC_1155_TOO_MANY_REQUESTED': return { action: 'error', message: 'Reduce number of tokens requested' }; case 'INCORRECT_PAYMENT': return { action: 'error', message: 'Payment amount incorrect' }; case 'INVALID_GAS_PRICE': return { action: 'retry', message: 'Invalid gas price, retrying with updated price' }; case 'FLUID_DEX_ERROR': return { action: 'retry', message: 'DEX protocol error, retrying' }; case 'NEW_CALLDATA_INCLUDES_HIGHER_RENT_FEE': return { action: 'confirm', message: 'Higher fees required, confirm to proceed' }; } } ``` ### Solana-Specific Issues Handle Solana blockchain specific problems: ```typescript theme={null} function handleSolanaErrors(failReason: string) { switch (failReason) { case 'INSUFFICIENT_FUNDS_FOR_RENT': return { action: 'add_sol', message: 'Insufficient SOL for rent. Add SOL to your wallet.', minAmount: '0.002 SOL' }; case 'JUPITER_INVALID_TOKEN_ACCOUNT': return { action: 'init_account', message: 'Token account needs initialization. This will be handled automatically.', autoRetry: true }; } } ``` ## Automatic Retry Logic Implement smart retry mechanisms for recoverable errors: ```typescript theme={null} const RETRYABLE_REASONS = [ 'SLIPPAGE', 'ORDER_EXPIRED', 'SOLVER_CAPACITY_EXCEEDED', 'SWAP_USES_TOO_MUCH_GAS', 'EXECUTION_REVERTED', 'TRANSACTION_REVERTED', 'GENERATE_SWAP_FAILED', 'REVERSE_SWAP_FAILED', 'TOO_LITTLE_RECEIVED', 'INVALID_GAS_PRICE', 'FLUID_DEX_ERROR' ]; const USER_ACTION_REQUIRED = [ 'TRANSFER_AMOUNT_EXCEEDS_BALANCE', 'TRANSFER_AMOUNT_EXCEEDS_ALLOWANCE', 'INSUFFICIENT_NATIVE_TOKENS_SUPPLIED', 'INVALID_SIGNATURE', 'SIGNATURE_EXPIRED', 'INVALID_SENDER', 'ACCOUNT_ABSTRACTION_INVALID_NONCE', 'ACCOUNT_ABSTRACTION_SIGNATURE_ERROR', 'TRANSFER_FROM_FAILED', 'TRANSFER_FAILED', 'INSUFFICIENT_FUNDS_FOR_RENT', 'JUPITER_INVALID_TOKEN_ACCOUNT' ]; const NON_RETRYABLE_ERRORS = [ 'UNKNOWN', 'DOUBLE_SPEND', 'AMOUNT_TOO_LOW_TO_REFUND', 'DEPOSIT_ADDRESS_MISMATCH', 'DEPOSIT_CHAIN_MISMATCH', 'INCORRECT_DEPOSIT_CURRENCY', 'DEPOSITED_AMOUNT_TOO_LOW_TO_FILL', 'NEGATIVE_NEW_AMOUNT_AFTER_FEES', 'NO_QUOTES', 'NO_INTERNAL_SWAP_ROUTES_FOUND', 'ORDER_IS_CANCELLED', 'ORDER_ALREADY_FILLED', 'TOKEN_NOT_TRANSFERABLE', 'ZERO_SELL_AMOUNT', 'SEAPORT_INEXACT_FRACTION', 'SEAPORT_INVALID_FULFILLER', 'MINT_NOT_ACTIVE', 'ERC_1155_TOO_MANY_REQUESTED', 'INCORRECT_PAYMENT', 'ORIGIN_CURRENCY_MISMATCH', 'MISSING_REVERT_DATA' ]; async function executeWithSmartRetry(quoteParams: QuoteParams, maxRetries = 3) { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { const quote = await getQuote(quoteParams); const result = await executeQuote(quote); if (result.status === 'COMPLETED') { return { success: true, result }; } // Handle refunded quotes if (result.status === 'REFUNDED' && result.failReason) { const errorHandler = getErrorHandler(result.failReason); const handlerResult = errorHandler(result.failReason, quoteParams); if (handlerResult.action === 'retry' && attempt < maxRetries) { // Automatic retry with adjusted parameters quoteParams = { ...quoteParams, ...handlerResult.newParams }; await delay(1000 * attempt); // Exponential backoff continue; } // Return error for user action or final attempt return { success: false, error: result.failReason, action: handlerResult.action, message: handlerResult.message, retryable: RETRYABLE_REASONS.includes(result.failReason) }; } } catch (error) { if (attempt === maxRetries) throw error; await delay(1000 * attempt); } } } function getErrorHandler(failReason: string) { // Balance and Allowance Issues if ([ 'TRANSFER_AMOUNT_EXCEEDS_BALANCE', 'TRANSFER_AMOUNT_EXCEEDS_ALLOWANCE', 'INSUFFICIENT_NATIVE_TOKENS_SUPPLIED', 'TRANSFER_FROM_FAILED', 'TRANSFER_FAILED' ].includes(failReason)) { return handleBalanceAndAllowanceErrors; } // Price and Market Issues if ([ 'SLIPPAGE', 'ORDER_EXPIRED', 'ORDER_IS_CANCELLED', 'ORDER_ALREADY_FILLED', 'NO_QUOTES', 'TOO_LITTLE_RECEIVED', 'NO_INTERNAL_SWAP_ROUTES_FOUND', 'SWAP_USES_TOO_MUCH_GAS' ].includes(failReason)) { return handleMarketErrors; } // Signature and Authentication Issues if ([ 'INVALID_SIGNATURE', 'SIGNATURE_EXPIRED', 'INVALID_SENDER', 'ACCOUNT_ABSTRACTION_INVALID_NONCE', 'ACCOUNT_ABSTRACTION_SIGNATURE_ERROR' ].includes(failReason)) { return handleSignatureErrors; } // System and Protocol Issues if ([ 'SOLVER_CAPACITY_EXCEEDED', 'EXECUTION_REVERTED', 'TRANSACTION_REVERTED', 'GENERATE_SWAP_FAILED', 'REVERSE_SWAP_FAILED', 'MISSING_REVERT_DATA' ].includes(failReason)) { return handleSystemErrors; } // Deposit and Validation Issues if ([ 'DEPOSIT_ADDRESS_MISMATCH', 'DEPOSIT_CHAIN_MISMATCH', 'INCORRECT_DEPOSIT_CURRENCY', 'AMOUNT_TOO_LOW_TO_REFUND', 'DEPOSITED_AMOUNT_TOO_LOW_TO_FILL', 'NEGATIVE_NEW_AMOUNT_AFTER_FEES', 'ORIGIN_CURRENCY_MISMATCH' ].includes(failReason)) { return handleDepositErrors; } // Token and Protocol Specific Issues if ([ 'TOKEN_NOT_TRANSFERABLE', 'ZERO_SELL_AMOUNT', 'SEAPORT_INEXACT_FRACTION', 'SEAPORT_INVALID_FULFILLER', 'MINT_NOT_ACTIVE', 'ERC_1155_TOO_MANY_REQUESTED', 'INCORRECT_PAYMENT', 'INVALID_GAS_PRICE', 'FLUID_DEX_ERROR', 'NEW_CALLDATA_INCLUDES_HIGHER_RENT_FEE' ].includes(failReason)) { return handleTokenErrors; } // Solana-Specific Issues if ([ 'INSUFFICIENT_FUNDS_FOR_RENT', 'JUPITER_INVALID_TOKEN_ACCOUNT' ].includes(failReason)) { return handleSolanaErrors; } // Default handler for unknown or unhandled errors return () => ({ action: 'manual', message: 'Unexpected error, please try again or contact support' }); } ``` ## User Experience Patterns ### Progress Indicators Show users what's happening during retry flows: ```typescript theme={null} interface RetryState { attempt: number; maxAttempts: number; currentAction: string; failReason?: string; } function ProgressIndicator({ retryState }: { retryState: RetryState }) { const progress = (retryState.attempt / retryState.maxAttempts) * 100; return (

Attempt {retryState.attempt} of {retryState.maxAttempts}: {retryState.currentAction}

{retryState.failReason && (

Previous attempt failed: {getUserFriendlyMessage(retryState.failReason)}

)}
); } function getUserFriendlyMessage(failReason: string): string { const messages = { // Balance and Allowance TRANSFER_AMOUNT_EXCEEDS_BALANCE: 'Insufficient token balance', TRANSFER_AMOUNT_EXCEEDS_ALLOWANCE: 'Token approval needed', INSUFFICIENT_NATIVE_TOKENS_SUPPLIED: 'Insufficient gas tokens', TRANSFER_FROM_FAILED: 'Token transfer failed', TRANSFER_FAILED: 'Token transfer failed', // Price and Market SLIPPAGE: 'Price changed during execution', ORDER_EXPIRED: 'Quote expired', ORDER_IS_CANCELLED: 'Order was cancelled', ORDER_ALREADY_FILLED: 'Order already completed', NO_QUOTES: 'No liquidity available', TOO_LITTLE_RECEIVED: 'Output amount too low', NO_INTERNAL_SWAP_ROUTES_FOUND: 'No swap route found', SWAP_USES_TOO_MUCH_GAS: 'Gas cost too high', // Signature and Authentication INVALID_SIGNATURE: 'Invalid signature', SIGNATURE_EXPIRED: 'Signature expired', INVALID_SENDER: 'Invalid sender', ACCOUNT_ABSTRACTION_INVALID_NONCE: 'Wallet sync needed', ACCOUNT_ABSTRACTION_SIGNATURE_ERROR: 'Wallet signature error', // System and Protocol SOLVER_CAPACITY_EXCEEDED: 'System at capacity', EXECUTION_REVERTED: 'Transaction reverted', TRANSACTION_REVERTED: 'Transaction reverted', GENERATE_SWAP_FAILED: 'Swap generation failed', REVERSE_SWAP_FAILED: 'Reverse swap failed', MISSING_REVERT_DATA: 'Transaction failed', // Deposit and Validation DEPOSIT_ADDRESS_MISMATCH: 'Wrong deposit address', DEPOSIT_CHAIN_MISMATCH: 'Wrong blockchain', INCORRECT_DEPOSIT_CURRENCY: 'Wrong token deposited', AMOUNT_TOO_LOW_TO_REFUND: 'Amount too low', DEPOSITED_AMOUNT_TOO_LOW_TO_FILL: 'Amount too low', NEGATIVE_NEW_AMOUNT_AFTER_FEES: 'Fees exceed amount', ORIGIN_CURRENCY_MISMATCH: 'Source token mismatch', DOUBLE_SPEND: 'Duplicate transaction', // Token and Protocol Specific TOKEN_NOT_TRANSFERABLE: 'Token transfer restricted', ZERO_SELL_AMOUNT: 'Invalid amount', SEAPORT_INEXACT_FRACTION: 'NFT order error', SEAPORT_INVALID_FULFILLER: 'NFT fulfiller error', MINT_NOT_ACTIVE: 'Minting not active', ERC_1155_TOO_MANY_REQUESTED: 'Too many tokens requested', INCORRECT_PAYMENT: 'Payment amount incorrect', INVALID_GAS_PRICE: 'Invalid gas price', FLUID_DEX_ERROR: 'DEX protocol error', NEW_CALLDATA_INCLUDES_HIGHER_RENT_FEE: 'Higher fees required', // Solana-Specific INSUFFICIENT_FUNDS_FOR_RENT: 'Insufficient SOL for rent', JUPITER_INVALID_TOKEN_ACCOUNT: 'Solana account issue', // Unknown UNKNOWN: 'Unknown error' }; return messages[failReason] || 'Transaction failed'; } ``` ### Error Recovery UI Provide clear next steps for users: ```typescript theme={null} function ErrorRecoveryUI({ error, onRetry, onCancel }) { const getActionButton = (action: string) => { switch (action) { case 'retry': return ; case 'approval_needed': return ; case 'add_sol': return ; case 'reconnect': return ; default: return ; } }; return (

Transaction Failed

{error.message}

{getActionButton(error.action)}
{error.retryable && (

This error can be automatically retried. Click "Try Again" to retry with adjusted parameters.

)}
); } ``` ## Analytics and Monitoring Track fail reason patterns to optimize your integration: ```typescript theme={null} interface FailReasonAnalytics { failReason: string; tokenPair: string; amount: string; attempt: number; userAgent: string; timestamp: string; } function trackFailReason(data: FailReasonAnalytics) { // Send to your analytics service analytics.track('quote_refunded', { fail_reason: data.failReason, token_pair: data.tokenPair, amount_usd: data.amount, retry_attempt: data.attempt, user_agent: data.userAgent, timestamp: data.timestamp }); } function analyzeFailReasonTrends(timeRange: string) { // Query your analytics to identify patterns return { mostCommonReasons: ['SLIPPAGE', 'TRANSFER_AMOUNT_EXCEEDS_BALANCE'], successRateByReason: { SLIPPAGE: 0.85, // 85% success after retry ORDER_EXPIRED: 0.92, NO_QUOTES: 0.45 }, recommendations: [ 'Increase default slippage tolerance to 1.5%', 'Implement balance checking before quote generation', 'Add liquidity warnings for low-liquidity pairs' ] }; } ``` ## Best Practices Summary 1. **Categorize Error Handling**: Group similar fail reasons and handle them with consistent patterns 2. **Implement Smart Retries**: Automatically retry recoverable errors with adjusted parameters 3. **Provide Clear UI**: Show users exactly what went wrong and what they need to do 4. **Monitor Patterns**: Track fail reasons to identify optimization opportunities 5. **Progressive Enhancement**: Start with basic error handling and add sophistication over time ## Related Resources Complete reference of all fail reason codes with detailed explanations Understand the complete journey of a quote from creation to completion Working examples of quote generation and execution with proper error handling Deep dive into slippage tolerance and price impact management # Slippage Tolerance Examples Source: https://docs.onebalance.io/guides/slippage/examples Working code examples for implementing slippage tolerance across different OneBalance operations and asset types. Practical examples of implementing slippage tolerance in various scenarios. Each example includes complete, runnable code that you can adapt for your specific use case. ## Basic Slippage Implementation ### Stablecoin Swap (Low Slippage) Use minimal slippage for stable asset pairs: ```typescript TypeScript theme={null} const stablecoinSwap = await getQuote({ from: { account: { sessionAddress: "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", adminAddress: "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", accountAddress: "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, asset: { assetId: "ob:usdc" }, amount: "1000000" // 1 USDC }, to: { asset: { assetId: "ob:usdt" } }, slippageTolerance: 10 // 0.1% for stablecoins }); ``` ```bash cURL theme={null} curl --request POST \ --url https://be.onebalance.io/api/v1/quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "from": { "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "ob:usdc" }, "amount": "1000000" }, "to": { "asset": { "assetId": "ob:usdt" } }, "slippageTolerance": 10 }' ``` ### Major Token Swap (Medium Slippage) Standard slippage for major cryptocurrency pairs: ```typescript TypeScript theme={null} const ethToUsdcSwap = await getQuote({ from: { account: { sessionAddress: "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", adminAddress: "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", accountAddress: "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, asset: { assetId: "ob:eth" }, amount: "100000000000000" // 1 ETH }, to: { asset: { assetId: "ob:usdc" } }, slippageTolerance: 100 // 1% for major tokens }); ``` ```bash cURL theme={null} curl --request POST \ --url https://be.onebalance.io/api/v1/quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "from": { "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "ob:eth" }, "amount": "100000000000000" }, "to": { "asset": { "assetId": "ob:usdc" } }, "slippageTolerance": 100 }' ``` ### Volatile Token Swap (High Slippage) Higher slippage tolerance for volatile or newer tokens: ```typescript TypeScript theme={null} const volatileTokenSwap = await getQuote({ from: { account: { sessionAddress: "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", adminAddress: "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", accountAddress: "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, asset: { assetId: "eip155:1/erc20:0x1234567890123456789012345678901234567890" }, amount: "100000000000000" // 1000 tokens }, to: { asset: { assetId: "ob:eth" } }, slippageTolerance: 300 // 3% for volatile tokens }); ``` ```bash cURL theme={null} curl --request POST \ --url https://be.onebalance.io/api/v1/quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "from": { "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "asset": { "assetId": "eip155:1/erc20:0x1234567890123456789012345678901234567890" }, "amount": "100000000000000" }, "to": { "asset": { "assetId": "ob:eth" } }, "slippageTolerance": 300 }' ``` ## Contract Call Examples ### DeFi Protocol Interaction Smart contract calls with slippage tolerance for DEX operations: ```typescript TypeScript theme={null} const uniswapSwapWithSlippage = await prepareCallQuote({ account: { sessionAddress: "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", adminAddress: "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", accountAddress: "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, targetChain: "eip155:8453", // Base calls: [{ to: "0x2626664c2603336E57B271c5C0b26F421741e481", // Uniswap V3 Router data: "0x414bf389000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda029130000000000000000000000004200000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000bb8000000000000000000000000e20295ec513def805d9c3083b0c8eab64692d764000000000000000000000000000000000000000000000000000000006748d28800000000000000000000000000000000000000000000000000000000000f42400000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000000", value: "0x0" }], allowanceRequirements: [{ assetType: "eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", amount: "1000000", spender: "0x2626664c2603336E57B271c5C0b26F421741e481" }], tokensRequired: [{ assetType: "eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", amount: "1000000" }], slippageTolerance: 100 // 1% slippage for DEX operations }); ``` ```bash cURL theme={null} curl --request POST \ --url https://be.onebalance.io/api/quotes/prepare-call-quote \ --header 'Content-Type: application/json' \ --header 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ --data '{ "account": { "sessionAddress": "0x1cBFbFd62a276BF6D79d504eA4CA75a7baDcf5b1", "adminAddress": "0xc162a3cE45ad151eeCd0a5532D6E489D034aB3B8", "accountAddress": "0xE20295ec513DEf805D9c3083b0C8EaB64692D764" }, "targetChain": "eip155:8453", "calls": [{ "to": "0x2626664c2603336E57B271c5C0b26F421741e481", "data": "0x414bf389000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda029130000000000000000000000004200000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000bb8000000000000000000000000e20295ec513def805d9c3083b0c8eab64692d764000000000000000000000000000000000000000000000000000000006748d28800000000000000000000000000000000000000000000000000000000000f42400000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000000", "value": "0x0" }], "allowanceRequirements": [{ "assetType": "eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "amount": "1000000", "spender": "0x2626664c2603336E57B271c5C0b26F421741e481" }], "tokensRequired": [{ "assetType": "eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "amount": "1000000" }], "slippageTolerance": 100 }' ``` ## Advanced Implementation Patterns ### Adaptive Slippage Based on Market Conditions ```typescript Market-Adaptive Slippage theme={null} interface MarketConditions { volatility: 'low' | 'medium' | 'high'; liquidityDepth: 'shallow' | 'medium' | 'deep'; networkCongestion: 'low' | 'medium' | 'high'; } function calculateAdaptiveSlippage( baseSlippage: number, conditions: MarketConditions ): number { let multiplier = 1; // Adjust for volatility switch (conditions.volatility) { case 'high': multiplier *= 2; break; case 'medium': multiplier *= 1.5; break; case 'low': multiplier *= 1; break; } // Adjust for liquidity switch (conditions.liquidityDepth) { case 'shallow': multiplier *= 1.8; break; case 'medium': multiplier *= 1.3; break; case 'deep': multiplier *= 1; break; } // Adjust for network congestion switch (conditions.networkCongestion) { case 'high': multiplier *= 1.2; break; case 'medium': multiplier *= 1.1; break; case 'low': multiplier *= 1; break; } // Cap at 10% (1000 basis points) return Math.min(Math.round(baseSlippage * multiplier), 1000); } // Usage example const marketConditions: MarketConditions = { volatility: 'high', liquidityDepth: 'medium', networkCongestion: 'low' }; const adaptiveSlippage = calculateAdaptiveSlippage(100, marketConditions); const quote = await getQuote({ from: { account: yourAccount, asset: { assetId: "ob:eth" }, amount: "100000000000000" }, to: { asset: { assetId: "ob:usdc" } }, slippageTolerance: adaptiveSlippage // Dynamically calculated }); ``` ### Retry Logic with Progressive Slippage ```typescript Progressive Retry theme={null} async function executeWithProgressiveSlippage( baseRequest: QuoteRequest, options?: { maxRetries?: number; baseSlippage?: number; maxSlippage?: number; } ) { const { maxRetries = 3, baseSlippage = 50, // 0.5% maxSlippage = 500 // 5% } = options || {}; // Generate progressive slippage values const slippageSteps = []; for (let i = 0; i < maxRetries; i++) { const step = baseSlippage * Math.pow(2, i); // 50, 100, 200, 400... slippageSteps.push(Math.min(step, maxSlippage)); } let lastError; for (let attempt = 0; attempt < maxRetries; attempt++) { const currentSlippage = slippageSteps[attempt]; try { console.log(`Attempt ${attempt + 1}: Using ${currentSlippage / 100}% slippage`); const quote = await getQuote({ ...baseRequest, slippageTolerance: currentSlippage }); const result = await executeQuote(quote); console.log(`Success on attempt ${attempt + 1}`); return result; } catch (error: any) { lastError = error; if (error.code === 'QUOTE_REFUNDED' && attempt < maxRetries - 1) { console.log(`Attempt ${attempt + 1} failed, retrying with higher slippage...`); // Wait before retry to avoid rate limiting await new Promise(resolve => setTimeout(resolve, 1000 * (attempt + 1))); continue; } // Final attempt failed or non-retryable error break; } } throw new Error(`All ${maxRetries} attempts failed. Last error: ${lastError?.message}`); } // Usage try { const result = await executeWithProgressiveSlippage({ from: { account: yourAccount, asset: { assetId: "ob:eth" }, amount: "100000000000000" }, to: { asset: { assetId: "ob:usdc" } } }, { maxRetries: 4, baseSlippage: 75, // Start at 0.75% maxSlippage: 400 // Cap at 4% }); console.log('Transaction successful:', result); } catch (error) { console.error('All retry attempts failed:', error); } ``` ### User-Configurable Slippage Settings ```typescript User Settings theme={null} interface SlippagePreferences { mode: 'conservative' | 'balanced' | 'aggressive' | 'custom'; customValue?: number; autoRetry?: boolean; maxRetries?: number; } class SlippageManager { private presets = { conservative: { stablecoin: 5, // 0.05% major: 50, // 0.5% volatile: 150 // 1.5% }, balanced: { stablecoin: 10, // 0.1% major: 100, // 1% volatile: 300 // 3% }, aggressive: { stablecoin: 25, // 0.25% major: 200, // 2% volatile: 500 // 5% } }; getSlippageForAssets( fromAsset: string, toAsset: string, preferences: SlippagePreferences ): number { if (preferences.mode === 'custom' && preferences.customValue) { return preferences.customValue; } const assetType = this.categorizeAssetPair(fromAsset, toAsset); const preset = this.presets[preferences.mode as keyof typeof this.presets]; return preset?.[assetType] || this.presets.balanced[assetType]; } private categorizeAssetPair(fromAsset: string, toAsset: string): 'stablecoin' | 'major' | 'volatile' { const stablecoins = ['usdc', 'usdt', 'dai', 'busd']; const majorTokens = ['eth', 'btc', 'bnb', 'matic', 'avax']; const isStablecoin = (asset: string) => stablecoins.some(stable => asset.toLowerCase().includes(stable)); const isMajor = (asset: string) => majorTokens.some(major => asset.toLowerCase().includes(major)) || isStablecoin(asset); if (isStablecoin(fromAsset) && isStablecoin(toAsset)) { return 'stablecoin'; } if (isMajor(fromAsset) && isMajor(toAsset)) { return 'major'; } return 'volatile'; } async executeWithUserPreferences( request: QuoteRequest, preferences: SlippagePreferences ) { const slippage = this.getSlippageForAssets( request.from.asset.assetId, request.to.asset.assetId, preferences ); const enhancedRequest = { ...request, slippageTolerance: slippage }; if (preferences.autoRetry) { return await executeWithProgressiveSlippage(enhancedRequest, { maxRetries: preferences.maxRetries || 3, baseSlippage: slippage }); } const quote = await getQuote(enhancedRequest); return await executeQuote(quote); } } // Usage example const slippageManager = new SlippageManager(); // Conservative user const conservativeResult = await slippageManager.executeWithUserPreferences({ from: { account: yourAccount, asset: { assetId: "ob:eth" }, amount: "100000000000000" }, to: { asset: { assetId: "ob:usdc" } } }, { mode: 'conservative', autoRetry: true, maxRetries: 2 }); // Custom slippage user const customResult = await slippageManager.executeWithUserPreferences({ from: { account: yourAccount, asset: { assetId: "ob:usdc" }, amount: "1000000" }, to: { asset: { assetId: "ob:eth" } } }, { mode: 'custom', customValue: 75, // 0.75% autoRetry: false }); ``` ## Multi-Chain Examples ### Cross-Chain with Solana Assets ```typescript Solana + EVM theme={null} const crossChainQuote = await getQuote({ accounts: [ { type: "kernel-v3.1-ecdsa", signerAddress: "0x5d6fb4eb211a6a2e406a1111b54d26c534753c8e", accountAddress: "0xb7bc0d7baf6761c302ff6772dfd8f9e22ec706e7" }, { type: "solana", signerAddress: "5Cb2369421F8a00Ef556d662D6E97C1419B1d37c", accountAddress: "5Cb2369421F8a00Ef556d662D6E97C1419B1d37c" } ], from: { asset: { assetId: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/spl-token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" }, // USDC on Solana amount: "1000000" }, to: { asset: { assetId: "eip155:8453/erc20:0x4200000000000000000000000000000000000006" } // WETH on Base }, slippageTolerance: 150 // 1.5% for cross-chain operations }); ``` ## Error Handling Examples ### Complete Error Management ```typescript Error Handling theme={null} async function robustSlippageExecution(request: QuoteRequest) { try { const quote = await getQuote(request); const result = await executeQuote(quote); return { success: true, result }; } catch (error: any) { console.error('Quote execution failed:', error); // Handle specific slippage-related errors switch (error.code) { case 'QUOTE_REFUNDED': return { success: false, error: 'SLIPPAGE_EXCEEDED', message: 'Price moved beyond tolerance. Try again with higher slippage.', suggestedSlippage: (request.slippageTolerance || 100) * 1.5, retryable: true }; case 'SLIPPAGE_TOO_HIGH': return { success: false, error: 'SLIPPAGE_TOO_HIGH', message: 'Slippage tolerance too high. Please use a lower value.', maxRecommended: 500, // 5% retryable: true }; case 'INSUFFICIENT_BALANCE': return { success: false, error: 'INSUFFICIENT_BALANCE', message: 'Insufficient balance for this transaction.', retryable: false }; case 'RATE_LIMITED': return { success: false, error: 'RATE_LIMITED', message: 'Too many requests. Please wait before retrying.', retryAfter: error.retryAfter || 5000, retryable: true }; default: return { success: false, error: 'UNKNOWN_ERROR', message: error.message || 'An unexpected error occurred.', retryable: false }; } } } // Usage with error handling const result = await robustSlippageExecution({ from: { account: yourAccount, asset: { assetId: "ob:eth" }, amount: "100000000000000" }, to: { asset: { assetId: "ob:usdc" } }, slippageTolerance: 100 }); if (!result.success) { console.log('Error:', result.message); if (result.retryable) { console.log('This error is retryable'); if (result.suggestedSlippage) { console.log(`Suggested slippage: ${result.suggestedSlippage / 100}%`); } } } ``` ## Recommended Values by Scenario * **Stablecoins**: 10-25 basis points (0.1-0.25%) * **Major tokens**: 100 basis points (1%) * **Volatile tokens**: 300 basis points (3%) * **Cross-chain**: +50% of single-chain values * **Start conservative**: 50 basis points (0.5%) * **Auto-retry**: Enable with 2-3 attempts * **Max slippage**: 500 basis points (5%) * **Monitor success rates**: Adjust based on data ## Next Steps Common issues and solutions for slippage tolerance implementation Step-by-step implementation guide for slippage tolerance Test your slippage implementation with small amounts first to ensure it works correctly before processing larger transactions. # Slippage Tolerance Overview Source: https://docs.onebalance.io/guides/slippage/overview Learn how slippage tolerance works in OneBalance to reduce quote refunds and improve transaction success rates. Slippage tolerance helps reduce quote refunds by allowing price movement during cross-chain execution. Configure tolerance levels to balance transaction success rates with price protection. ## What is Slippage Tolerance? Slippage tolerance defines the acceptable price movement between quote generation and execution. When prices move beyond your tolerance, the transaction will fail rather than execute at an unfavorable rate. **Key Benefits:** * **Fewer Refunds** - Transactions succeed despite minor price movements * **Better UX** - Users can retry with adjusted parameters instead of failed quotes * **Cost Efficiency** - Reduces wasted gas from failed transactions Slippage tolerance is **optional** and maintains backward compatibility with existing integrations. ## How It Works OneBalance accepts slippage tolerance as a positive integer in basis points: * 50 = 0.5% * 100 = 1% * 1000 = 10% Transactions fail if price moves beyond tolerance rather than executing at unfavorable rates Failed transactions can retry with higher tolerance for better success rates ## Quick Example Add slippage tolerance to any quote request: ```json Basic Usage theme={null} { "from": { "account": { "sessionAddress": "0x...", "adminAddress": "0x...", "accountAddress": "0x..." }, "asset": { "assetId": "ob:usdc" }, "amount": "1000000" }, "to": { "asset": { "assetId": "ob:eth" } }, "slippageTolerance": 100 } ``` ## Supported Endpoints Slippage tolerance works with all quote endpoints: Regular swaps and transfers between any assets Smart contract interactions with automatic token routing Multi-account operations including Solana V2 execute-quote and call-quote endpoints ## When to Use Slippage Tolerance ### High-Volume Applications Applications processing many transactions should implement slippage tolerance to reduce operational overhead from failed quotes. ### Volatile Market Conditions During high volatility periods, slippage tolerance prevents unnecessary transaction failures from minor price movements. ### User-Facing Applications Give users control over their risk tolerance by allowing them to configure slippage levels. ## Common Use Cases ```typescript theme={null} // Low slippage for stable assets const request = { from: { asset: { assetId: 'ob:usdc' }, amount: '1000000' }, to: { asset: { assetId: 'ob:usdt' } }, slippageTolerance: 10 // 0.1% for stablecoins }; ``` ```typescript theme={null} // Medium slippage for major tokens const request = { from: { asset: { assetId: 'ob:eth' }, amount: '1000000000000000000' }, to: { asset: { assetId: 'ob:usdc' } }, slippageTolerance: 100 // 1% for ETH }; ``` ```typescript theme={null} // Higher slippage for volatile tokens const request = { from: { asset: { assetId: 'eip155:1/erc20:0x...' }, amount: '1000000' }, to: { asset: { assetId: 'ob:eth' } }, slippageTolerance: 300 // 3% for volatile tokens }; ``` ## Integration Patterns ### Basic Implementation Simply add the parameter to existing quote requests: ```typescript theme={null} // Add to existing quote request const existingRequest = { // ... your existing quote parameters }; const withSlippage = { ...existingRequest, slippageTolerance: 100 // 1% }; ``` ### Dynamic Adjustment Implement retry logic with increasing slippage: ```typescript theme={null} async function executeWithRetry(baseRequest: QuoteRequest) { const slippageValues = [50, 100, 200]; // 0.5%, 1%, 2% for (const slippage of slippageValues) { try { const quote = await getQuote({ ...baseRequest, slippageTolerance: slippage }); return await executeQuote(quote); } catch (error) { if (error.code === 'QUOTE_REFUNDED' && slippage < 200) { continue; // Try next slippage level } throw error; } } } ``` ## Technical Details ### Validation * Must be a positive integer greater than 0 * Recommended maximum: 1000 basis points (10%) * No minimum enforced, but very low values may cause frequent failures ### Processing * Converted internally when sending to routing providers * Applied during execution, not quote generation * Maintains quote structure for tamper-proof signatures ## Next Steps Working code examples for different scenarios and asset types Common issues and solutions for slippage tolerance implementation Start with 100 basis points (1%) for most use cases. Adjust based on asset volatility and user preferences. # Slippage Tolerance Troubleshooting Source: https://docs.onebalance.io/guides/slippage/troubleshooting Common issues and solutions when implementing slippage tolerance in OneBalance integrations. This guide covers common issues when implementing slippage tolerance and provides solutions for optimal transaction success rates. ## Parameter Validation ### Invalid Value Type **Issue**: Getting validation errors when adding slippage tolerance parameter. ```bash Error theme={null} { "error": "Invalid slippageTolerance: must be a positive integer" } ``` **Cause**: Passing string, float, or negative values instead of positive integer. ```typescript ❌ Wrong theme={null} const request = { slippageTolerance: "100" // String slippageTolerance: 1.5 // Float slippageTolerance: -50 // Negative }; ``` ```typescript ✅ Correct theme={null} const request = { slippageTolerance: 100 // Positive integer in basis points }; ``` ### Value Too High **Issue**: Setting slippage tolerance above reasonable levels. ```typescript ❌ Wrong theme={null} const request = { slippageTolerance: 1500 // 15% - extremely high }; ``` ```typescript ✅ Correct theme={null} const request = { slippageTolerance: 500 // 5% - more reasonable maximum }; ``` ## Quote Still Failing **Issue**: Transactions still failing despite setting slippage tolerance. | Problem | Likely Cause | Solution | | ---------------------- | ----------------------------------- | --------------------------- | | **Slippage too low** | Market moving faster than tolerance | Increase slippage tolerance | | **Network congestion** | High gas costs affecting execution | Wait for lower congestion | | **Large trade size** | Price impact exceeding slippage | Break into smaller trades | | **Volatile market** | Rapid price movements | Use higher slippage | ### Simple Retry Pattern ```typescript theme={null} async function executeWithRetry(baseRequest: QuoteRequest) { const slippageValues = [50, 100, 200]; // 0.5%, 1%, 2% for (const slippage of slippageValues) { try { const quote = await getQuote({ ...baseRequest, slippageTolerance: slippage }); return await executeQuote(quote); } catch (error: any) { console.log(`Failed at ${slippage / 100}% slippage`); if (slippage === 200) { throw error; // Final attempt failed } } } } ``` ## Choosing Appropriate Values **Issue**: Unsure what slippage values to use for different scenarios. ### Recommended Values **10-50 basis points** USDC ↔ USDT: `10` (0.1%) DAI ↔ USDC: `25` (0.25%) **50-100 basis points** ETH ↔ USDC: `100` (1%) BTC ↔ ETH: `100` (1%) **100-500 basis points** Altcoins: `300` (3%) New tokens: `500` (5%) ### Asset Classification Helper ```typescript theme={null} function getRecommendedSlippage(fromAsset: string, toAsset: string): number { const stablecoins = ['usdc', 'usdt', 'dai']; const majorTokens = ['eth', 'weth', 'btc', 'wbtc']; const isStablecoin = (asset: string) => stablecoins.some(stable => asset.toLowerCase().includes(stable)); const isMajor = (asset: string) => majorTokens.some(major => asset.toLowerCase().includes(major)); // Stablecoin to stablecoin if (isStablecoin(fromAsset) && isStablecoin(toAsset)) { return 25; // 0.25% } // Major token pairs if (isMajor(fromAsset) && isMajor(toAsset)) { return 100; // 1% } // Default for volatile tokens return 300; // 3% } ``` ## Integration Issues ### React State Management ```typescript theme={null} import { useState } from 'react'; function useSlippageTolerance(defaultSlippage = 100) { const [slippage, setSlippage] = useState(defaultSlippage); const executeWithSlippage = async (quoteRequest: QuoteRequest) => { const quote = await getQuote({ ...quoteRequest, slippageTolerance: slippage }); return await executeQuote(quote); }; return { slippage, setSlippage, executeWithSlippage }; } ``` ### Basic Error Handling ```typescript theme={null} async function handleSlippageExecution(request: QuoteRequest) { try { const quote = await getQuote(request); return await executeQuote(quote); } catch (error: any) { console.error('Transaction failed:', error.message); // Suggest increasing slippage if current value is low const currentSlippage = request.slippageTolerance || 0; if (currentSlippage < 100) { console.log('Consider increasing slippage tolerance to 1% (100 basis points)'); } throw error; } } ``` ## Best Practices Begin with 1% (100 basis points) for most use cases Use lower slippage for stablecoins, higher for volatile tokens Add automatic retry with progressive slippage increases Track success rates and adjust defaults based on real data ## Getting Help Use the **Intercom chat widget** in the bottom right corner for instant help, or email [support@onebalance.io](mailto:support@onebalance.io). Include your slippage values, error messages, and asset pair information for faster resolution. Most issues can be resolved by starting with 100 basis points (1%) and implementing simple retry logic with higher values. # Solana Contract Calls Source: https://docs.onebalance.io/guides/solana/contract-calls Execute smart contract calls on EVM chains using Solana assets as funding source Execute smart contract calls on EVM chains using Solana assets as the funding source. These examples use the V3 calldata endpoints that support cross-chain contract execution. ## Prerequisites Install the required dependencies: ```bash npm theme={null} npm install @solana/web3.js bs58 ``` ```bash pnpm theme={null} pnpm add @solana/web3.js bs58 ``` ```bash yarn theme={null} yarn add @solana/web3.js bs58 ``` ## Signing Utility for Contract Calls For complete signing documentation covering all account types (EVM, Solana, EIP-7702), see the [Signing Guide](/concepts/signing) with detailed explanations and troubleshooting. ```typescript theme={null} import { MessageV0, VersionedTransaction, PublicKey } from '@solana/web3.js'; import bs58 from 'bs58'; /** * Signs a Solana chain operation with a private key * @param accountAddress - The address of the account to sign the chain operation * @param privateKey - The private key in base58 format * @param chainOp - The chain operation object from quote response * @returns The signed chain operation with signature added */ export function signSolanaChainOperation( accountAddress: string, privateKey: string, chainOp: any, ): any { const msgBuffer = Buffer.from(chainOp.dataToSign, 'base64'); const message = MessageV0.deserialize(msgBuffer); const transaction = new VersionedTransaction(message); const decodedKey = bs58.decode(privateKey); transaction.sign([ { publicKey: new PublicKey(accountAddress), secretKey: Buffer.from(decodedKey), }, ]); const signature = bs58.encode(Buffer.from(transaction.signatures[transaction.signatures.length - 1])); return { ...chainOp, signature, }; } ``` ## Example 1: USDC (Solana) → EVM Contract Call Use USDC on Solana to fund and execute contract calls on Arbitrum: First, prepare the contract call by specifying the target chain operations and token requirements: ```bash cURL theme={null} curl -X POST 'https://be.onebalance.io/api/v3/quote/prepare-call-quote' \ -H 'Content-Type: application/json' \ -H 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ -d '{ "accounts": [ { "type": "solana", "accountAddress": "EB8Hi4LoqUVCGUPJ2y9MsHbEsJQJvmpQRUWrLpjEZxC6" }, { "type": "kernel-v3.3-ecdsa", "accountAddress": "0xDb69A4Ded06AaD92C69c42232b691CFD8bF347e8", "signerAddress": "0xDb69A4Ded06AaD92C69c42232b691CFD8bF347e8", "deploymentType": "EIP7702" } ], "targetChain": "eip155:42161", "calls": [ { "to": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", "data": "0xa9059cbb0000000000000000000000004EbcFae0C3e747C95504CA7c79c46f725Cb4c7520000000000000000000000000000000000000000000000000000000000000001", "value": "0x0" } ], "tokensRequired": [ { "assetType": "eip155:42161/erc20:0xaf88d065e77c8cC2239327C5EDb3A432268e5831", "amount": "10000" } ], "fromAssetId": "ob:usdc" }' ``` ```typescript TypeScript theme={null} const prepareRequest = { accounts: [ { type: "solana", accountAddress: "EB8Hi4LoqUVCGUPJ2y9MsHbEsJQJvmpQRUWrLpjEZxC6" }, { type: "kernel-v3.3-ecdsa", accountAddress: "0xDb69A4Ded06AaD92C69c42232b691CFD8bF347e8", signerAddress: "0xDb69A4Ded06AaD92C69c42232b691CFD8bF347e8", deploymentType: "EIP7702" } ], targetChain: "eip155:42161", // Arbitrum calls: [ { to: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", // USDC on Arbitrum data: "0xa9059cbb0000000000000000000000004EbcFae0C3e747C95504CA7c79c46f725Cb4c7520000000000000000000000000000000000000000000000000000000000000001", value: "0x0" } ], tokensRequired: [ { assetType: "eip155:42161/erc20:0xaf88d065e77c8cC2239327C5EDb3A432268e5831", amount: "10000" // 0.01 USDC needed for the contract call } ], fromAssetId: "ob:usdc" // Use aggregated USDC }; const response = await fetch('https://be.onebalance.io/api/v3/quote/prepare-call-quote', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': '42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' }, body: JSON.stringify(prepareRequest) }); const prepareData = await response.json(); ``` The `fromAssetId: "ob:usdc"` tells OneBalance to use USDC from your Solana account to fund the EVM contract call. Use the prepared data to get a signed quote for execution: ```bash cURL theme={null} curl -X POST 'https://be.onebalance.io/api/v3/quote/call-quote' \ -H 'Content-Type: application/json' \ -H 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ -d '{ "fromAggregatedAssetId": "ob:usdc", "accounts": [ { "type": "solana", "accountAddress": "EB8Hi4LoqUVCGUPJ2y9MsHbEsJQJvmpQRUWrLpjEZxC6" }, { "type": "kernel-v3.3-ecdsa", "accountAddress": "0xDb69A4Ded06AaD92C69c42232b691CFD8bF347e8", "signerAddress": "0xDb69A4Ded06AaD92C69c42232b691CFD8bF347e8", "deploymentType": "EIP7702" } ], "tamperProofSignature": "0x6316c2fe49d51af45b7bb7bb311395595fe29b7bd9de2e219cba0942fda2461c1c3f19108d6f391cd02c7cdc7d218ff0945d32ba784be1357b3c954ff7ae254e1c", "chainOperation": { "userOp": { "sender": "0xdb69a4ded06aad92c69c42232b691cfd8bf347e8", "nonce": "913479994650515257524606220465835134743662536739504622017003723935449089", "callData": "0xe9ae5c53010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e5831000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000004ebcfae0c3e747c95504ca7c79c46f725cb4c752000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000", "callGasLimit": "2831389", "verificationGasLimit": "46313", "preVerificationGas": "0", "maxFeePerGas": "0", "maxPriorityFeePerGas": "0", "paymaster": "0xa784e6482bd5edbfe5991b18cbd545ebd46e1cc4", "paymasterVerificationGasLimit": "13011", "paymasterPostOpGasLimit": "0", "paymasterData": "0x", "signature": "0x8ca6ea251b08d488919a3c8708c6c9cdc782190d57151a1e83c0b4627aa819fa6f668b46d894e8f68291338bdf05111bf98ad8dbc6f7d4b2aa484a2749a26ebc1b" }, "typedDataToSign": { "domain": { "name": "RoleBasedECDSAValidator", "version": "1.4.3", "chainId": 42161, "verifyingContract": "0xA24bD06230f3F54e5bf266AE7A41750eE3b789FA" }, "types": { "Approve": [ { "name": "callDataAndNonceHash", "type": "bytes32" } ] }, "primaryType": "Approve", "message": { "callDataAndNonceHash": "0x68752061ab5e1f42561b192a3ea5e70c4005b62a7ea4698b14a35ab0004f6687" } }, "assetType": "eip155:42161/erc20:0xaf88d065e77c8cC2239327C5EDb3A432268e5831", "amount": "10000" } } }' ``` ```typescript TypeScript theme={null} // Use the tamperProofSignature from step 1 response const callQuoteRequest = { fromAggregatedAssetId: "ob:usdc", accounts: [ { type: "solana", accountAddress: "EB8Hi4LoqUVCGUPJ2y9MsHbEsJQJvmpQRUWrLpjEZxC6" }, { type: "kernel-v3.3-ecdsa", accountAddress: "0xDb69A4Ded06AaD92C69c42232b691CFD8bF347e8", signerAddress: "0xDb69A4Ded06AaD92C69c42232b691CFD8bF347e8", deploymentType: "EIP7702" } ], tamperProofSignature: prepareData.tamperProofSignature, chainOperation: prepareData.chainOperation }; const callQuoteResponse = await fetch('https://be.onebalance.io/api/v3/quote/call-quote', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': '42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' }, body: JSON.stringify(callQuoteRequest) }); const callQuote = await callQuoteResponse.json(); ``` You must use the exact `tamperProofSignature` and `chainOperation` data from the prepare step response. Sign the Solana operation and execute the cross-chain contract call: ```typescript TypeScript theme={null} import { MessageV0, VersionedTransaction, PublicKey } from '@solana/web3.js'; import bs58 from 'bs58'; // Sign the Solana operation from the quote function signSolanaChainOperation( accountAddress: string, privateKey: string, chainOp: any ) { const msgBuffer = Buffer.from(chainOp.dataToSign, 'base64'); const message = MessageV0.deserialize(msgBuffer); const transaction = new VersionedTransaction(message); const decodedKey = bs58.decode(privateKey); transaction.sign([{ publicKey: new PublicKey(accountAddress), secretKey: Buffer.from(decodedKey), }]); const signature = bs58.encode(Buffer.from(transaction.signatures[transaction.signatures.length - 1])); return { ...chainOp, signature }; } // Sign and execute const solanaOp = callQuote.originChainsOperations[0]; const signedSolanaOp = signSolanaChainOperation( "EB8Hi4LoqUVCGUPJ2y9MsHbEsJQJvmpQRUWrLpjEZxC6", "your-private-key", solanaOp ); const executeRequest = { ...callQuote, originChainsOperations: [signedSolanaOp], tamperProofSignature: "0xb83221a24cf7df0ece73a97b9888ea2091bcc5478aa142ec77b5fe3ebe83f92b2ef121ace109ea79f3b032bb6186c8af3fdc0e9c3dd5e968bf05cabe9adcab421c" }; // Execute the quote const executeResponse = await fetch('https://be.onebalance.io/api/v3/quote/execute-quote', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': '42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' }, body: JSON.stringify(executeRequest) }); const result = await executeResponse.json(); console.log('Contract call executed:', result); ``` Successfully executed a USDC transfer contract call on Arbitrum using USDC from your Solana account as funding. ## Example 2: SOL → EVM Contract Call Use native SOL to fund contract calls on Arbitrum. This example shows more complex operations including Jupiter swaps: ```bash cURL theme={null} curl -X POST 'https://be.onebalance.io/api/v3/quote/prepare-call-quote' \ -H 'Content-Type: application/json' \ -H 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ -d '{ "accounts": [ { "type": "solana", "accountAddress": "EB8Hi4LoqUVCGUPJ2y9MsHbEsJQJvmpQRUWrLpjEZxC6" }, { "type": "kernel-v3.3-ecdsa", "accountAddress": "0xDb69A4Ded06AaD92C69c42232b691CFD8bF347e8", "signerAddress": "0xDb69A4Ded06AaD92C69c42232b691CFD8bF347e8", "deploymentType": "EIP7702" } ], "targetChain": "eip155:42161", "calls": [ { "to": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", "data": "0xa9059cbb0000000000000000000000004EbcFae0C3e747C95504CA7c79c46f725Cb4c7520000000000000000000000000000000000000000000000000000000000000001", "value": "0x0" } ], "tokensRequired": [ { "assetType": "eip155:42161/erc20:0xaf88d065e77c8cC2239327C5EDb3A432268e5831", "amount": "10000" } ], "fromAssetId": "ob:sol" }' ``` ```typescript TypeScript theme={null} const prepareWithSOL = { accounts: [ { type: "solana", accountAddress: "EB8Hi4LoqUVCGUPJ2y9MsHbEsJQJvmpQRUWrLpjEZxC6" }, { type: "kernel-v3.3-ecdsa", accountAddress: "0xDb69A4Ded06AaD92C69c42232b691CFD8bF347e8", signerAddress: "0xDb69A4Ded06AaD92C69c42232b691CFD8bF347e8", deploymentType: "EIP7702" } ], targetChain: "eip155:42161", calls: [ { to: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", data: "0xa9059cbb0000000000000000000000004EbcFae0C3e747C95504CA7c79c46f725Cb4c7520000000000000000000000000000000000000000000000000000000000000001", value: "0x0" } ], tokensRequired: [ { assetType: "eip155:42161/erc20:0xaf88d065e77c8cC2239327C5EDb3A432268e5831", amount: "10000" } ], fromAssetId: "ob:sol" // Use SOL instead of USDC }; ``` When using `fromAssetId: "ob:sol"`, OneBalance will swap SOL to USDC on Solana via Jupiter, then bridge to Arbitrum. The response includes Jupiter swap instructions and address lookup tables: ```json theme={null} { "originChainsOperations": [ { "type": "solana", "instructions": [ { "keys": [ { "pubkey": "EB8Hi4LoqUVCGUPJ2y9MsHbEsJQJvmpQRUWrLpjEZxC6", "isSigner": true, "isWritable": true }, { "pubkey": "ATw6BVTaLgdDAJ9Deki3Zs1bvruoNfs99TWi7L3XhtFD", "isSigner": false, "isWritable": true } ], "programId": "11111111111111111111111111111111", "data": "02000000561b160000000000" }, { "programId": "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4", "data": "d033ef977b2bed5c010000001a640001c4d3030000000000d80d150000000000f40100" } ], "addressLookupTableAddresses": [ "D6XNrxMsDoABJVVY5YyHxJuAB6WGzYCXpZeKyNtqu2v4" ], "assetType": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501", "amount": "1448790" } ] } ``` SOL operations often use address lookup tables for efficiency. OneBalance handles this automatically. ```typescript theme={null} // The signing process is identical, but notice the complex swap operation const signedOperation = signSolanaChainOperation( "EB8Hi4LoqUVCGUPJ2y9MsHbEsJQJvmpQRUWrLpjEZxC6", "your-solana-private-key", solanaOperation ); // Execute with the complex operation including Jupiter swaps const result = await executeQuote(signedOperation); // Result shows: // - SOL swapped to USDC on Solana via Jupiter // - USDC bridged to Arbitrum // - Contract call executed on Arbitrum // - All in a single atomic operation ``` ## EIP-7702 Integration OneBalance supports EIP-7702 delegated accounts that can be funded using Solana assets. This enables seamless cross-chain contract execution where Solana provides the funding and EIP-7702 provides the execution context. ### Multi-Account Setup (Solana + EIP-7702) When using Solana assets to fund EIP-7702 operations, you need both account types: ```typescript Account Configuration theme={null} const multiAccountSetup = { accounts: [ { type: "solana", accountAddress: "EB8Hi4LoqUVCGUPJ2y9MsHbEsJQJvmpQRUWrLpjEZxC6" }, { type: "kernel-v3.3-ecdsa", accountAddress: "0xDb69A4Ded06AaD92C69c42232b691CFD8bF347e8", signerAddress: "0xDb69A4Ded06AaD92C69c42232b691CFD8bF347e8", deploymentType: "EIP7702" // This triggers delegation requirements } ] }; ``` ```bash Balance Check theme={null} # Check combined balance across both accounts curl -X GET 'https://be.onebalance.io/api/v3/balances/aggregated-balance?account=solana:EB8Hi4LoqUVCGUPJ2y9MsHbEsJQJvmpQRUWrLpjEZxC6,eip155:42161:0xDb69A4Ded06AaD92C69c42232b691CFD8bF347e8' \ -H 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' ``` ### Delegation Signature Requirements EIP-7702 accounts require delegation signatures before execution: **Delegation Required**: When using `deploymentType: "EIP7702"`, you must sign the delegation object from the `prepare-call-quote` response before proceeding to `call-quote`. The `prepare-call-quote` response includes delegation data that must be signed: ```typescript theme={null} const prepareResponse = await prepareCallQuote(multiAccountRequest); // EIP-7702 delegation object is included in response const delegationObject = prepareResponse.delegation; console.log('Delegation to sign:', delegationObject); ``` Sign the delegation using your EIP-7702 signer: ```typescript theme={null} // Sign the delegation object with your EIP-7702 signer const delegationSignature = await evmSigner.signTypedData(delegationObject); // Include the signature in your call-quote request const callQuoteRequest = { ...prepareResponse, delegationSignature: delegationSignature }; ``` The signed delegation enables atomic execution: ```typescript theme={null} // The operation will: // 1. Process delegation signature // 2. Use Solana assets for funding // 3. Execute contract calls via delegated account // 4. All in a single atomic transaction const result = await executeQuote(signedQuote); ``` ### Complete EIP-7702 Example Here's a complete example using SOL to fund an EIP-7702 contract call: ```typescript theme={null} import { signSolanaChainOperation } from './signing-utils'; async function solanaToEIP7702ContractCall() { // 1. Prepare the call with both accounts const prepareRequest = { accounts: [ { type: "solana", accountAddress: "EB8Hi4LoqUVCGUPJ2y9MsHbEsJQJvmpQRUWrLpjEZxC6" }, { type: "kernel-v3.3-ecdsa", accountAddress: "0xDb69A4Ded06AaD92C69c42232b691CFD8bF347e8", signerAddress: "0xDb69A4Ded06AaD92C69c42232b691CFD8bF347e8", deploymentType: "EIP7702" } ], targetChain: "eip155:42161", calls: [ { to: "0xA0b86a33E6Af8661A8B9e0A4B9b10b8Abc7e1234", // Your contract data: "0x...", // Your contract call data value: "0x0" } ], tokensRequired: [ { assetType: "eip155:42161/erc20:0xaf88d065e77c8cC2239327C5EDb3A432268e5831", amount: "1000000" // 1 USDC } ], fromAssetId: "ob:sol" // Fund with SOL }; // 2. Get preparation data including delegation const prepareResponse = await fetch('/api/v3/quote/prepare-call-quote', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(prepareRequest) }); const prepareData = await prepareResponse.json(); // 3. Sign the EIP-7702 delegation const delegationSignature = await evmSigner.signTypedData(prepareData.delegation); // 4. Get the call quote with delegation signature const callQuoteRequest = { ...prepareData, delegationSignature: delegationSignature }; const callQuoteResponse = await fetch('/api/v3/quote/call-quote', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(callQuoteRequest) }); const callQuote = await callQuoteResponse.json(); // 5. Sign the Solana operation const solanaOp = callQuote.originChainsOperations.find(op => op.type === 'solana'); const signedSolanaOp = signSolanaChainOperation( "EB8Hi4LoqUVCGUPJ2y9MsHbEsJQJvmpQRUWrLpjEZxC6", solanaPrivateKey, solanaOp ); // 6. Execute the quote const executeRequest = { ...callQuote, originChainsOperations: [signedSolanaOp] }; const result = await fetch('/api/v3/quote/execute-quote', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(executeRequest) }); return await result.json(); } ``` **Atomic Cross-Chain Execution:** * SOL swapped to USDC on Solana * USDC bridged to target EVM chain * EIP-7702 delegation processed * Contract call executed * All happens in coordinated atomic operations **Cost Efficiency:** * Use cheaper Solana transaction fees * Leverage SOL liquidity for EVM operations * Batch multiple operations together **Developer Experience:** * Single API call for complex operations * No manual bridging or swapping required * Built-in slippage protection EIP-7702 integration is supported on chains with EIP-7702 enabled. Check the [supported networks](/resources/supported-networks) for current availability. ## Error Handling Common issues with contract calls and their solutions: ### Insufficient Balance for Contract Execution ```typescript theme={null} // Always check aggregated balance before contract calls const balanceResponse = await fetch( 'https://be.onebalance.io/api/v3/balances/aggregated-balance?account=solana:YOUR_ACCOUNT', { headers: { 'x-api-key': 'YOUR_API_KEY' } } ); const balance = await balanceResponse.json(); const requiredAsset = balance.balanceByAggregatedAsset.find(b => b.aggregatedAssetId === 'ob:usdc'); if (parseInt(requiredAsset.balance) < requiredAmount) { throw new Error('Insufficient balance for contract call'); } ``` ### Missing EIP-7702 Delegation ```typescript theme={null} // EIP-7702 accounts must include delegation signature if (account.deploymentType === "EIP7702" && !callQuoteRequest.delegationSignature) { throw new Error('EIP-7702 requires delegation signature from prepare-call-quote'); } ``` ### Invalid Target Chain ```typescript theme={null} // Verify target chain supports the contract const supportedChains = ['eip155:1', 'eip155:42161', 'eip155:137']; if (!supportedChains.includes(targetChain)) { throw new Error(`Contract not available on chain: ${targetChain}`); } ``` ## Related Guides Basic concepts and account setup for Solana integration Simple swap and transfer examples using Solana assets Learn about EIP-7702 delegated accounts and setup General contract call patterns and best practices For questions about Solana contract calls or integration support, use the **Intercom chat widget** in the bottom right corner, join our [Discord](https://discord.com/invite/vHkw7rpdT8), or reach out via [support](mailto:support@onebalance.io). # Solana Examples Source: https://docs.onebalance.io/guides/solana/examples Working code examples for Solana integration with OneBalance, including transaction signing and cross-chain operations This page provides complete, working code examples for integrating Solana with OneBalance. All examples use real data from our testing environment and are copy-paste ready. ## Prerequisites Install the required dependencies: ```bash npm theme={null} npm install @solana/web3.js bs58 ``` ```bash pnpm theme={null} pnpm add @solana/web3.js bs58 ``` ```bash yarn theme={null} yarn add @solana/web3.js bs58 ``` ## Signing Utilities Here's the signing function you'll need for Solana operations: For browser wallets like Phantom or Solflare: ```typescript theme={null} import { MessageV0, VersionedTransaction } from '@solana/web3.js'; import bs58 from 'bs58'; /** * Signs a Solana operation using a browser wallet * @param dataToSign - Base64 encoded data from quote response * @param wallet - Connected Solana wallet (Phantom, Solflare, etc.) * @returns Base58 encoded signature */ async function signSolanaOperation(dataToSign: string, wallet: any): Promise { const msgBuffer = Buffer.from(dataToSign, 'base64'); const message = MessageV0.deserialize(msgBuffer); const transaction = new VersionedTransaction(message); const signedTx = await wallet.signTransaction(transaction); return bs58.encode(Buffer.from(signedTx.signatures[signedTx.signatures.length - 1])); } ``` For server-side operations or direct private key access: ```typescript theme={null} import { MessageV0, VersionedTransaction, PublicKey } from '@solana/web3.js'; import bs58 from 'bs58'; /** * Signs a Solana operation with a private key * @param accountAddress - Your Solana account address * @param privateKey - Your private key in base58 format * @param chainOp - Chain operation from quote response * @returns Signed chain operation */ function signSolanaChainOperation(accountAddress: string, privateKey: string, chainOp: any) { const msgBuffer = Buffer.from(chainOp.dataToSign, 'base64'); const message = MessageV0.deserialize(msgBuffer); const transaction = new VersionedTransaction(message); const decodedKey = bs58.decode(privateKey); transaction.sign([{ publicKey: new PublicKey(accountAddress), secretKey: Buffer.from(decodedKey), }]); const signature = bs58.encode(Buffer.from(transaction.signatures[transaction.signatures.length - 1])); return { ...chainOp, signature }; } ``` ## Example 1: SOL → USDC (Same Chain) The simplest Solana operation - swap SOL to USDC within Solana: ```bash cURL theme={null} curl -X POST 'https://be.onebalance.io/api/v3/quote' \ -H 'Content-Type: application/json' \ -H 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ -d '{ "from": { "accounts": [{ "type": "solana", "accountAddress": "J5CCzBULFax899tcirb6wMbenQUd8whbaetG7EfSick5" }], "asset": { "assetId": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501" }, "amount": "10000000" }, "to": { "asset": { "assetId": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" } } }' ``` ```typescript TypeScript theme={null} const quoteRequest = { from: { accounts: [{ type: "solana", accountAddress: "J5CCzBULFax899tcirb6wMbenQUd8whbaetG7EfSick5" }], asset: { assetId: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501" }, amount: "10000000" // 0.01 SOL (9 decimals) }, to: { asset: { assetId: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" } } }; const response = await fetch('https://be.onebalance.io/api/v3/quote', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': '42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' }, body: JSON.stringify(quoteRequest) }); const quote = await response.json(); ``` ```typescript TypeScript theme={null} // Sign the Solana operation const solanaOperation = quote.originChainsOperations.find(op => op.type === 'solana'); const signature = await signSolanaOperation(solanaOperation.dataToSign, wallet); // Add signature to the quote const signedQuote = { ...quote, originChainsOperations: quote.originChainsOperations.map(op => op.type === 'solana' ? { ...op, signature } : op ) }; // Execute the quote const executeResponse = await fetch('https://be.onebalance.io/api/v3/quote/execute', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': '42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' }, body: JSON.stringify(signedQuote) }); const result = await executeResponse.json(); console.log('Swap completed:', result); ``` Successfully swapped 0.01 SOL to \~1.63 USDC on Solana ## Example 2: SOL → USDC (Cross-Chain) Swap SOL on Solana to USDC on Arbitrum - the most common cross-chain pattern: ```bash cURL theme={null} curl -X POST 'https://be.onebalance.io/api/v3/quote' \ -H 'Content-Type: application/json' \ -H 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ -d '{ "from": { "accounts": [{ "type": "solana", "accountAddress": "J5CCzBULFax899tcirb6wMbenQUd8whbaetG7EfSick5" }], "asset": { "assetId": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501" }, "amount": "10000000" }, "to": { "asset": { "assetId": "eip155:42161/erc20:0xaf88d065e77c8cC2239327C5EDb3A432268e5831" }, "account": "eip155:42161:0x895Cf62399bF1F8b88195E741b64278b41EB7F09" } }' ``` ```typescript TypeScript theme={null} const crossChainQuote = { from: { accounts: [{ type: "solana", accountAddress: "J5CCzBULFax899tcirb6wMbenQUd8whbaetG7EfSick5" }], asset: { assetId: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501" }, amount: "10000000" // 0.01 SOL }, to: { asset: { assetId: "eip155:42161/erc20:0xaf88d065e77c8cC2239327C5EDb3A432268e5831" }, account: "eip155:42161:0x895Cf62399bF1F8b88195E741b64278b41EB7F09" } }; const response = await fetch('https://be.onebalance.io/api/v3/quote', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': '42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' }, body: JSON.stringify(crossChainQuote) }); const quote = await response.json(); ``` ```json theme={null} { "id": "0xcd3a5cfe80d1b84db755bfb8ebe0a617ff153cc48ab6d5ab28436386f06ce100", "accounts": [{ "type": "solana", "accountAddress": "J5CCzBULFax899tcirb6wMbenQUd8whbaetG7EfSick5" }], "originToken": { "assetType": ["solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501"], "amount": "10000000", "fiatValue": "1.67" }, "destinationToken": { "assetType": "eip155:42161/erc20:0xaf88d065e77c8cC2239327C5EDb3A432268e5831", "amount": "1570450", // ~1.57 USDC on Arbitrum "minimumAmount": "1516171", "fiatValue": "1.57", "recipientAccount": "eip155:42161:0x895Cf62399bF1F8b88195E741b64278b41EB7F09" }, "fees": { "cumulativeUSD": "0.12" } } ``` ```typescript theme={null} // Sign and execute the cross-chain operation const solanaOp = quote.originChainsOperations.find(op => op.type === 'solana'); const signedOp = await signSolanaOperation(solanaOp.dataToSign, wallet); const executeRequest = { ...quote, originChainsOperations: [{ ...solanaOp, signature: signedOp }] }; const result = await fetch('https://be.onebalance.io/api/v3/quote/execute', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': '42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' }, body: JSON.stringify(executeRequest) }); const execution = await result.json(); console.log('Cross-chain swap completed:', execution); ``` Successfully swapped 0.01 SOL to 1.57 USDC and bridged to Arbitrum ## Example 3: USDC (Solana) → USDC (Arbitrum) Transfer USDC from Solana to Arbitrum: ```bash cURL theme={null} curl -X POST 'https://be.onebalance.io/api/v3/quote' \ -H 'Content-Type: application/json' \ -H 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ -d '{ "from": { "accounts": [{ "type": "solana", "accountAddress": "J5CCzBULFax899tcirb6wMbenQUd8whbaetG7EfSick5" }], "asset": { "assetId": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" }, "amount": "1000000" }, "to": { "asset": { "assetId": "eip155:42161/erc20:0xaf88d065e77c8cC2239327C5EDb3A432268e5831" }, "account": "eip155:42161:0x895Cf62399bF1F8b88195E741b64278b41EB7F09" } }' ``` ```typescript TypeScript theme={null} const usdcTransfer = { from: { accounts: [{ type: "solana", accountAddress: "J5CCzBULFax899tcirb6wMbenQUd8whbaetG7EfSick5" }], asset: { assetId: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" }, amount: "1000000" // 1 USDC (6 decimals) }, to: { asset: { assetId: "eip155:42161/erc20:0xaf88d065e77c8cC2239327C5EDb3A432268e5831" }, account: "eip155:42161:0x895Cf62399bF1F8b88195E741b64278b41EB7F09" } }; const response = await fetch('https://be.onebalance.io/api/v3/quote', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': '42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' }, body: JSON.stringify(usdcTransfer) }); const quote = await response.json(); ``` This operation only requires a Solana account since funds come entirely from Solana. The recipient is specified in `to.account`. ## Example 4: Aggregated USDC → SOL (Multi-Account) Use aggregated USDC balance from both EVM and Solana accounts to buy SOL: ```bash cURL theme={null} curl -X POST 'https://be.onebalance.io/api/v3/quote' \ -H 'Content-Type: application/json' \ -H 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' \ -d '{ "from": { "accounts": [ { "type": "kernel-v3.1-ecdsa", "accountAddress": "0xd4f5A60c9b500f875ADf757BC3027A4424079E05", "deploymentType": "ERC4337", "signerAddress": "0x9b747cC14A5672a7166b4eccdc92d7F4003f8081" }, { "type": "solana", "accountAddress": "J5CCzBULFax899tcirb6wMbenQUd8whbaetG7EfSick5" } ], "asset": { "assetId": "ob:usdc" }, "amount": "10000000" }, "to": { "asset": { "assetId": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501" } } }' ``` ```typescript TypeScript theme={null} const aggregatedSwap = { from: { accounts: [ { type: "kernel-v3.1-ecdsa", accountAddress: "0xd4f5A60c9b500f875ADf757BC3027A4424079E05", deploymentType: "ERC4337", signerAddress: "0x9b747cC14A5672a7166b4eccdc92d7F4003f8081" }, { type: "solana", accountAddress: "J5CCzBULFax899tcirb6wMbenQUd8whbaetG7EfSick5" } ], asset: { assetId: "ob:usdc" // Aggregated USDC across chains }, amount: "10000000" // 10 USDC }, to: { asset: { assetId: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501" } } }; const response = await fetch('https://be.onebalance.io/api/v3/quote', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': '42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' }, body: JSON.stringify(aggregatedSwap) }); const quote = await response.json(); ``` This operation involves both EVM and Solana accounts: ```typescript theme={null} async function signMultiAccountQuote(quote: any, solanaWallet: any, evmSigner: any) { const signedOperations = await Promise.all( quote.originChainsOperations.map(async (operation: any) => { if (operation.type === 'solana') { const signature = await signSolanaOperation(operation.dataToSign, solanaWallet); return { ...operation, signature }; } else { // Sign EVM operation with typed data const signature = await evmSigner.signTypedData(operation.typedDataToSign); return { ...operation, userOp: { ...operation.userOp, signature } }; } }) ); return { ...quote, originChainsOperations: signedOperations }; } // Execute the multi-account operation const signedQuote = await signMultiAccountQuote(quote, solanaWallet, evmSigner); const result = await fetch('https://be.onebalance.io/api/v3/quote/execute', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': '42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' }, body: JSON.stringify(signedQuote) }); const execution = await result.json(); console.log('Aggregated swap completed:', execution); ``` Used aggregated USDC from multiple chains to purchase SOL This example shows OneBalance's power - using funds from multiple blockchains in a single operation. ## Check Balances Always verify your balances before creating quotes: ```bash cURL - Single Account theme={null} curl -X GET 'https://be.onebalance.io/api/v3/balances/aggregated-balance?account=solana:J5CCzBULFax899tcirb6wMbenQUd8whbaetG7EfSick5' \ -H 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' ``` ```bash cURL - Multi-Account theme={null} curl -X GET 'https://be.onebalance.io/api/v3/balances/aggregated-balance?account=eip155:42161:0xfe52613d747E20F2f62e0A5cC36B0DFAe771C442,solana:J5CCzBULFax899tcirb6wMbenQUd8whbaetG7EfSick5' \ -H 'x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' ``` ```typescript TypeScript theme={null} // Check Solana account balance const balance = await fetch( 'https://be.onebalance.io/api/v3/balances/aggregated-balance?account=solana:J5CCzBULFax899tcirb6wMbenQUd8whbaetG7EfSick5', { headers: { 'x-api-key': '42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11' } } ); const balanceData = await balance.json(); console.log('Available assets:', balanceData.balanceByAggregatedAsset); ``` ## Quick Error Handling ```typescript Validate Account theme={null} import { PublicKey } from '@solana/web3.js'; // Ensure Solana address is valid before making requests try { new PublicKey(accountAddress); } catch (error) { throw new Error('Invalid Solana address format'); } ``` ```typescript Check Quote Status theme={null} // Monitor transaction progress const statusResponse = await fetch( `https://be.onebalance.io/api/v3/status/get-execution-status?quoteId=${quote.id}`, { headers: { 'x-api-key': 'YOUR_API_KEY' } } ); const status = await statusResponse.json(); console.log('Transaction status:', status.status.status); ``` ## Next Steps Use Solana assets to fund smart contract calls on EVM chains Common issues and solutions for Solana integration Complete API documentation for v3 endpoints Concepts and setup guide for Solana integration For additional support, use the **Intercom chat widget** in the bottom right corner, join our [Discord](https://discord.com/invite/vHkw7rpdT8), or reach out via [support](mailto:support@onebalance.io). # Solana Getting Started Source: https://docs.onebalance.io/guides/solana/getting-started Get your first Solana cross-chain swap working in 5 minutes with this complete working example Learn how to swap SOL to USDC on Arbitrum using OneBalance APIs. Get the full working example with setup instructions. 100% free and ready to run. ## Setup Install packages and create environment file: ```bash theme={null} pnpm install @solana/web3.js bs58 dotenv viem ``` ```bash .env theme={null} SOLANA_PRIVATE_KEY=your_base58_private_key_here ONEBALANCE_API_KEY=your_api_key_here ARBITRUM_RECIPIENT=0x895Cf62399bF1F8b88195E741b64278b41EB7F09 ``` ## Example ```typescript swap.ts theme={null} import { Keypair, MessageV0, VersionedTransaction, PublicKey } from '@solana/web3.js'; import { formatUnits, parseUnits } from 'viem'; import bs58 from 'bs58'; import dotenv from 'dotenv'; // Load environment variables dotenv.config(); // Configuration from environment const SOLANA_PRIVATE_KEY = process.env.SOLANA_PRIVATE_KEY!; const ARBITRUM_RECIPIENT = process.env.ARBITRUM_RECIPIENT!; const API_KEY = process.env.ONEBALANCE_API_KEY || "42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11"; // Create Solana keypair from private key const keypair = Keypair.fromSecretKey(bs58.decode(SOLANA_PRIVATE_KEY)); const SOLANA_ACCOUNT = keypair.publicKey.toString(); // Solana asset IDs const SOL_ASSET_ID = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501"; const USDC_ARB_ASSET_ID = "eip155:42161/erc20:0xaf88d065e77c8cC2239327C5EDb3A432268e5831"; console.log(`Using Solana account: ${SOLANA_ACCOUNT}`); console.log(`Arbitrum recipient: ${ARBITRUM_RECIPIENT}`); /** * Check SOL balance before swapping */ async function checkSOLBalance() { try { console.log('Checking SOL balance...'); const response = await fetch( `https://be.onebalance.io/api/v3/balances/aggregated-balance?account=solana:${SOLANA_ACCOUNT}&aggregatedAssetId=ob:sol`, { headers: { 'x-api-key': API_KEY } } ); if (!response.ok) { throw new Error('Failed to fetch balance'); } const balanceData = await response.json(); const solBalance = balanceData.balanceByAggregatedAsset?.find( (asset: any) => asset.aggregatedAssetId === 'ob:sol' ); if (!solBalance) { throw new Error('No SOL balance found'); } const balanceInSOL = parseFloat(formatUnits(BigInt(solBalance.balance), 9)); console.log(`Available SOL: ${balanceInSOL.toFixed(4)} SOL`); return balanceInSOL; } catch (error) { console.error('Balance check failed:', error); throw error; } } /** * Swap SOL to USDC on Arbitrum (cross-chain) */ async function swapSOLtoUSDC() { try { console.log('Starting SOL → USDC (Arbitrum) swap...\n'); // Validate Solana address format try { new PublicKey(SOLANA_ACCOUNT); } catch (error) { throw new Error('Invalid Solana address format'); } // Check balance const balance = await checkSOLBalance(); const swapAmount = 0.01; // 0.01 SOL if (balance < swapAmount) { throw new Error(`Insufficient balance. Need ${swapAmount} SOL, have ${balance.toFixed(4)} SOL`); } console.log(`Swapping ${swapAmount} SOL to USDC on Arbitrum...`); // Step 1: Get quote console.log('Getting quote...'); const quoteRequest = { from: { accounts: [{ type: "solana", accountAddress: SOLANA_ACCOUNT }], asset: { assetId: SOL_ASSET_ID }, amount: parseUnits(swapAmount.toString(), 9).toString() // SOL has 9 decimals }, to: { asset: { assetId: USDC_ARB_ASSET_ID // USDC on Arbitrum }, account: `eip155:42161:${ARBITRUM_RECIPIENT}` } }; const quoteResponse = await fetch('https://be.onebalance.io/api/v3/quote', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': API_KEY }, body: JSON.stringify(quoteRequest) }); if (!quoteResponse.ok) { const error = await quoteResponse.json(); throw new Error(`Quote failed: ${error.message}`); } const quote = await quoteResponse.json(); console.log('Quote received:', { id: quote.id, willReceive: `${formatUnits(BigInt(quote.destinationToken.amount), 6)} USDC`, fiatValue: `$${quote.destinationToken.fiatValue}`, fees: `$${quote.fees.cumulativeUSD}` }); // Step 2: Sign the Solana operation console.log('Signing Solana transaction...'); const solanaOperation = quote.originChainsOperations.find((op: any) => op.type === 'solana'); if (!solanaOperation) { throw new Error('No Solana operation found in quote'); } const signedOperation = signSolanaOperation(solanaOperation); console.log('Transaction signed successfully'); const signedQuote = { ...quote, originChainsOperations: quote.originChainsOperations.map((op: any) => op.type === 'solana' ? signedOperation : op ) }; // Step 3: Execute the swap console.log('Executing cross-chain swap...'); const executeResponse = await fetch('https://be.onebalance.io/api/v3/quote/execute', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': API_KEY }, body: JSON.stringify(signedQuote) }); if (!executeResponse.ok) { const error = await executeResponse.json(); throw new Error(`Execution failed: ${error.message}`); } const result = await executeResponse.json(); console.log('Swap submitted successfully!'); // Step 4: Monitor transaction completion console.log('Monitoring transaction...'); await monitorCompletion(quote.id); console.log('Cross-chain swap completed successfully!'); console.log(`USDC delivered to Arbitrum address: ${ARBITRUM_RECIPIENT}`); return result; } catch (error) { console.error('Swap failed:', (error as Error).message); throw error; } } // Real Solana signing implementation function signSolanaOperation(chainOp: any): any { try { const msgBuffer = Buffer.from(chainOp.dataToSign, 'base64'); const message = MessageV0.deserialize(msgBuffer); const transaction = new VersionedTransaction(message); // Sign with keypair transaction.sign([keypair]); // Extract signature const signature = bs58.encode(Buffer.from(transaction.signatures[transaction.signatures.length - 1])); return { ...chainOp, signature, }; } catch (error) { console.error('Signing failed:', error); throw new Error(`Failed to sign Solana transaction: ${(error as Error).message}`); } } // Monitor transaction completion async function monitorCompletion(quoteId: string): Promise { const timeout = 60_000; // 60 seconds const startTime = Date.now(); while (Date.now() - startTime < timeout) { try { const statusResponse = await fetch( `https://be.onebalance.io/api/v3/status/get-execution-status?quoteId=${quoteId}`, { headers: { 'x-api-key': API_KEY } } ); if (statusResponse.ok) { const status = await statusResponse.json(); console.log(`Status: ${status.status.status}`); if (status.status.status === 'COMPLETED') { console.log('Transaction completed successfully!'); return; } else if (status.status.status === 'FAILED') { throw new Error('Transaction failed'); } } } catch (error) { console.log('Status check error (retrying):', (error as Error).message); } // Wait 3 seconds before next check await new Promise(resolve => setTimeout(resolve, 3000)); } throw new Error('Transaction monitoring timeout'); } // Run the swap async function main() { try { await swapSOLtoUSDC(); } catch (error) { console.error('Failed:', error); process.exit(1); } } main(); ``` ## How It Works The script performs a complete cross-chain swap with validation and monitoring: 1. **Balance Check**: Verify you have sufficient SOL for the swap 2. **Request Quote**: Get pricing for 0.01 SOL → USDC on Arbitrum 3. **Sign Transaction**: Sign the Solana operation with your private key 4. **Execute Swap**: Submit the signed transaction to OneBalance 5. **Monitor Progress**: Track the transaction until completion **Expected Result**: \~0.015 USDC delivered to your Arbitrum address with full monitoring ## Run the Example Set your environment variables and run: ```bash theme={null} npx tsx swap.ts ``` Make sure your Solana account has at least 0.01 SOL for the swap. ## Next Steps Explore additional swap patterns and advanced operations Use Solana assets to fund smart contract calls on EVM chains Got stuck? Check the [Solana Troubleshooting Guide](/guides/solana/troubleshooting) for common issues and solutions. # Solana Overview Source: https://docs.onebalance.io/guides/solana/overview Learn how to aggregate both EVM and Solana balances for chain abstraction, enabling unified operations across all supported blockchains. OneBalance now supports **Solana blockchain**, enabling you to aggregate balances and assets from both Solana and EVM chains into unified operations. Use your SOL, USDC, or other Solana assets alongside EVM tokens for optimized cross-chain transactions. ## What You Can Do Use EVM balances to buy tokens and pay fees on Solana, and vice versa - no manual bridging required Automatically find the best liquidity and lowest fees across Solana and EVM ecosystems Execute smart contract functions on EVM chains using combined EVM and Solana balance funding Execute programs on Solana using combined balances (coming soon) ## Key Differences from EVM ### v3 API Endpoints Required Solana operations require the **v3 API endpoints** which support multiple accounts: ```json theme={null} // v1 (EVM only) - Single account object { "from": { "account": { "sessionAddress": "0x...", ... } } } // v3 (Cross-chain) - Accounts array { "from": { "accounts": [ { "type": "role-based", "sessionAddress": "0x...", ... }, { "type": "solana", "accountAddress": "J5CC..." } ] } } ``` ### Solana Account Structure Solana accounts are simpler than EVM smart accounts: ```json theme={null} { "type": "solana", "accountAddress": "J5CCzBULFax899tcirb6wMbenQUd8whbaetG7EfSick5" } ``` Solana uses **base58-encoded addresses** and doesn't require session/admin addresses like EVM smart accounts. ### Different Signing Process Solana transactions use a different signing mechanism than EVM: ```typescript Browser Wallet theme={null} import { MessageV0, VersionedTransaction } from '@solana/web3.js'; import bs58 from 'bs58'; async function signSolanaOperation(dataToSign: string, wallet: any) { // 1. Convert base64 data to Message const message = MessageV0.deserialize(Buffer.from(dataToSign, 'base64')); // 2. Create versioned transaction const transaction = new VersionedTransaction(message); // 3. Sign with wallet const signedTx = await wallet.signTransaction(transaction); // 4. Extract signature in base58 format return bs58.encode(Buffer.from(signedTx.signatures[signedTx.signatures.length - 1])); } ``` ```typescript Private Key theme={null} import { MessageV0, VersionedTransaction, PublicKey } from '@solana/web3.js'; import bs58 from 'bs58'; /** * Signs a Solana chain operation with a private key * * @param accountAddress - The address of the account to sign the chain operation * @param privateKey - The private key to sign the chain operation * @param chainOp - The chain operation to sign * @returns The signed chain operation */ export function signSolanaOperation( accountAddress: string, privateKey: string, chainOp: any, ): any { const msgBuffer = Buffer.from(chainOp.dataToSign, 'base64'); const message = MessageV0.deserialize(msgBuffer); const transaction = new VersionedTransaction(message); const decodedKey = bs58.decode(privateKey); transaction.sign([ { publicKey: new PublicKey(accountAddress), secretKey: Buffer.from(decodedKey), }, ]); const signature = bs58.encode(Buffer.from(transaction.signatures[transaction.signatures.length - 1])); return { ...chainOp, signature, }; } ``` ## Prerequisites Before integrating Solana with OneBalance: **API Key Configuration**: Custom API keys need Solana explicitly enabled. Contact [support@onebalance.io](mailto:support@onebalance.io) or use the public test key `42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11` for development. ## Integration Steps Switch from v1 to v3 endpoints to support Solana accounts: * [Get Quote v3](/api-reference/quotes/get-quote-v3) - `POST /api/v3/quote` * [Execute Quote v3](/api-reference/quotes/execute-quote-v3) - `POST /api/v3/quote/execute` * [Aggregated Balance v3](/api-reference/balances/aggregated-balance-v3) - `GET /api/v3/balances/aggregated-balance` * [Prepare Call Quote v3](/api-reference/quotes/prepare-call-quote-v3) - `POST /api/v3/quote/prepare-call-quote` * [Get Call Quote v3](/api-reference/quotes/get-call-quote-v3) - `POST /api/v3/quote/call-quote` Integrate Solana wallet providers like Phantom, Solflare, or others in your frontend. Update your code to use the accounts array format to support both EVM and Solana accounts. Add Solana-specific transaction signing using `@solana/web3.js` and `bs58` libraries. Test your integration with the API ## Account Setup Patterns You can configure accounts in different ways based on your needs: ### Single Account Operations ```typescript theme={null} // Solana-only operation const quote = await fetch('/api/v3/quote', { method: 'POST', body: JSON.stringify({ from: { accounts: [{ type: "solana", accountAddress: "J5CCzBULFax899tcirb6wMbenQUd8whbaetG7EfSick5" }], asset: { assetId: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501" }, amount: "10000000" // 0.01 SOL }, to: { asset: { assetId: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" } } }) }); ``` ### Cross-Chain Operations ```typescript theme={null} // Cross-chain: Solana → EVM const quote = await fetch('/api/v3/quote', { method: 'POST', body: JSON.stringify({ from: { accounts: [{ type: "solana", accountAddress: "J5CCzBULFax899tcirb6wMbenQUd8whbaetG7EfSick5" }], asset: { assetId: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501" }, amount: "10000000" }, to: { asset: { assetId: "eip155:42161/erc20:0xaf88d065e77c8C2239327C5EDb3A432268e5831" }, account: "eip155:42161:0x895Cf62399bF1F8b88195E741b64278b41EB7F09" } }) }); ``` ### Unified Balance Operations ```typescript theme={null} // Use aggregated balances from both EVM and Solana accounts const quote = await fetch('/api/v3/quote', { method: 'POST', body: JSON.stringify({ from: { accounts: [ // Your EVM smart account { type: "kernel-v3.1-ecdsa", accountAddress: "0xd4f5A60c9b500f875ADf757BC3027A4424079E05", deploymentType: "ERC4337", signerAddress: "0x9b747cC14A5672a7166b4eccdc92d7F4003f8081" }, // Your Solana account { type: "solana", accountAddress: "J5CCzBULFax899tcirb6wMbenQUd8whbaetG7EfSick5" } ], asset: { assetId: "ob:usdc" }, // Aggregated USDC across all chains amount: "10000000" }, to: { asset: { assetId: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501" } } }) }); ``` ## Next Steps Get your first Solana cross-chain swap working in 5 minutes Complete code examples for swaps and cross-chain operations Token discovery, API keys, limitations, and common questions Use Solana assets to fund smart contract calls on EVM chains Common issues, solutions, and debugging tips for Solana integration For questions or support with Solana integration, use the **Intercom chat widget** in the bottom right corner for instant help, join our [Discord](https://discord.com/invite/vHkw7rpdT8), or reach out via [support](mailto:support@onebalance.io). # Solana Troubleshooting Source: https://docs.onebalance.io/guides/solana/troubleshooting Common issues and solutions when integrating Solana with OneBalance This guide covers common issues you might encounter when working with Solana and OneBalance, along with their solutions. All TypeScript examples assume you have the required dependencies installed: ```bash theme={null} npm install @solana/web3.js bs58 ``` ## Wallet Connection Issues ### Wallet Not Connected Properly ```typescript theme={null} // Check if wallet is connected and supports signing if (!wallet.connected || !wallet.signTransaction) { throw new Error('Wallet not properly connected'); } ``` **Solution:** Ensure the wallet is connected and has the required methods before attempting to sign transactions. ## Asset Configuration Issues ### Invalid Asset IDs ```typescript theme={null} // Ensure you're using correct Solana asset IDs const SOL_ASSET_ID = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501"; const USDC_SOLANA_ASSET_ID = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"; ``` **Solution:** Use the correct CAIP-19 format for Solana asset identifiers. Check the [Aggregated Assets API](/api-reference/assets/list) for supported assets. ### Signature Encoding Issues ```typescript theme={null} // Always use base58 encoding for Solana signatures const signature = bs58.encode(Buffer.from(signedTransaction.signatures[signedTransaction.signatures.length - 1])); // NOT: signedTransaction.signatures[0].toString() ``` **Solution:** Solana signatures must be base58-encoded, not converted to strings. ## API Key Configuration Issues ### Custom API Key Not Working with Solana ```json theme={null} { "error": "Chain not supported", "message": "Solana operations not enabled for this API key", "statusCode": 400 } ``` **Root Cause:** Solana support must be explicitly enabled for custom API keys. **Solutions:** 1. **Contact support** to enable Solana: * Email: [support@onebalance.io](mailto:support@onebalance.io) * Include your API key prefix (first 6 characters) * Request "Solana enablement" 2. **Use public API key for testing**: ```bash theme={null} # Public test key (development only) x-api-key: 42bb629272001ee1163ca0dbbbc07bcbb0ef57a57baf16c4b1d4672db4562c11 ``` 3. **Verify enablement** with a simple balance check: ```bash theme={null} curl -X GET 'https://be.onebalance.io/api/v3/balances/aggregated-balance?account=solana:J5CCzBULFax899tcirb6wMbenQUd8whbaetG7EfSick5&aggregatedAssetId=ob:sol' \ -H 'x-api-key: YOUR_API_KEY' ``` **Public Key Limitations**: The public API key has rate limits and should only be used for development and testing, never in production. ## Common Operation Issues ### Insufficient Balance ```typescript theme={null} // Always check balances before creating quotes const balances = await fetch('/api/v3/balances/aggregated-balance?account=solana:J5CCzBULFax899tcirb6wMbenQUd8whbaetG7EfSick5', { headers: { 'x-api-key': 'YOUR_API_KEY' } }); const balanceData = await balances.json(); const availableSOL = balanceData.balanceByAggregatedAsset .find(asset => asset.aggregatedAssetId === 'ob:sol')?.balance; if (!availableSOL || parseInt(availableSOL) < requiredAmount) { throw new Error('Insufficient SOL balance for operation'); } ``` **Solution:** Always verify balance before operations to avoid quote failures. ### Invalid Account Address ```typescript theme={null} // Ensure Solana addresses are valid base58 import { PublicKey } from '@solana/web3.js'; try { new PublicKey(accountAddress); console.log('Valid Solana address'); } catch (error) { throw new Error('Invalid Solana address format'); } ``` **Solution:** Validate Solana addresses using `PublicKey` constructor before making API calls. ### Signing Failures ```typescript theme={null} // Handle wallet connection and signing errors try { const signature = await signSolanaOperation(dataToSign, wallet); } catch (error) { if (error.code === 4001) { console.error('User rejected signing'); } else if (error.message?.includes('wallet')) { console.error('Wallet connection issue:', error.message); } throw error; } ``` **Solution:** Handle common wallet errors including user rejection and connection issues. ## Contract Call Operation Errors ### Missing Delegation Signature (EIP-7702) ```json theme={null} { "error": "Validation failed", "message": "Delegation signature required for EIP-7702 account. Sign delegation object from prepare-call-quote response.", "statusCode": 400 } ``` **Solution:** Always sign the delegation object from `prepare-call-quote` before calling `call-quote` when using EIP-7702 accounts. ### Insufficient Balance for Contract Call ```typescript theme={null} // Verify you have enough of the fromAssetId before preparing const balanceResponse = await fetch( 'https://be.onebalance.io/api/v3/balances/aggregated-balance?aggregatedAssetId=ob:usdc,ob:sol&account=solana:YOUR_ACCOUNT', { headers: { 'x-api-key': 'YOUR_API_KEY' } } ); const balance = await balanceResponse.json(); const requiredAsset = balance.balanceByAggregatedAsset.find(b => b.aggregatedAssetId === 'ob:usdc'); if (parseInt(requiredAsset.balance) < requiredAmount) { throw new Error('Insufficient balance for contract call'); } ``` **Solution:** Check aggregated balances before attempting contract calls to ensure sufficient funds. ### Invalid Target Chain Configuration ```typescript theme={null} // Ensure target chain supports the contract address const supportedChains = ['eip155:1', 'eip155:42161', 'eip155:137']; // etc. if (!supportedChains.includes(targetChain)) { throw new Error(`Unsupported target chain: ${targetChain}`); } ``` **Solution:** Verify the target chain is supported and the contract address exists on that chain. ### Address Lookup Table Issues (SOL Operations) ```typescript theme={null} // SOL operations may fail if lookup tables are unavailable // This is handled automatically by OneBalance, but can cause delays console.log('Address lookup tables:', operation.addressLookupTableAddresses); ``` **Solution:** SOL operations with complex swaps may require address lookup tables. OneBalance handles this automatically, but it can cause additional processing time. ## Multi-Account Operation Debugging ### Account Type Mismatch ```typescript theme={null} // Verify account types match your setup const expectedTypes = ['solana', 'kernel-v3.3-ecdsa']; const actualTypes = accounts.map(acc => acc.type); if (!expectedTypes.every(type => actualTypes.includes(type))) { throw new Error('Missing required account types'); } ``` **Solution:** Ensure all required account types are included in multi-account operations. ### Signature Order Issues ```typescript theme={null} // Sign operations in the correct order const signedOps = await Promise.all( quote.originChainsOperations.map(async (op, index) => { console.log(`Signing operation ${index + 1} of type: ${op.type}`); return await signOperation(op); }) ); ``` **Solution:** Sign operations in the order they appear in the quote response and handle each operation type appropriately. ## Common Debugging Steps ### Check Transaction Status ```typescript theme={null} // Check quote execution status const statusResponse = await fetch( `https://be.onebalance.io/api/v3/status/get-execution-status?quoteId=${quoteId}`, { headers: { 'x-api-key': 'YOUR_API_KEY' } } ); const status = await statusResponse.json(); console.log('Status:', status.status.status); ``` ### Validate Account Address ```typescript theme={null} import { PublicKey } from '@solana/web3.js'; // Ensure Solana address is valid try { new PublicKey(accountAddress); console.log('Valid Solana address'); } catch (error) { throw new Error('Invalid Solana address format'); } ``` ## Getting Help Token discovery, API keys, limitations, and common questions Complete code examples for all Solana operations Full API documentation with endpoint details For additional support, use the **Intercom chat widget** in the bottom right corner, join our [Discord](https://discord.com/invite/vHkw7rpdT8), or reach out via [support](mailto:support@onebalance.io). # Turnkey Integration Source: https://docs.onebalance.io/guides/turnkey-integration Learn how to integrate Turnkey with OneBalance API We're building detailed documentation for Turnkey integration with OneBalance. In the meantime, you can explore our open-source implementation example. Complete Turnkey integration example with OneBalance API We're actively working on detailed integration guides and tutorials. These will be added to the documentation soon. ## Need Help? * **[Discord Community](https://discord.com/invite/vHkw7rpdT8)**: Join our Discord for integration questions and support * **[API Reference](/api-reference)**: Full API documentation for implementation details * **[Support](mailto:support@onebalance.io)**: Direct support for enterprise integrations # OneBalance Documentation Source: https://docs.onebalance.io/index A documentation for OneBalance - guides, API reference, tutorials, and resources to build chain-abstracted applications with ease

OneBalance Documentation

Explore our guides and examples to integrate the OneBalance Toolkit

Get started in minutes

Everything you need to integrate OneBalance into your application

**5 minutes** - Make your first chain-abstracted transaction **Step-by-step** - Integration examples and tutorials **Complete docs** - Explore all endpoints with examples

Popular guides

Step-by-step tutorials to get you building quickly

Connect OneBalance with Privy for seamless wallet management Choose the right account configuration for your application Deep dive into how RL Service enable fast cross-chain execution Set up dynamic fees that work towards your goals

Developer resources

Tools and resources to accelerate your development

**GitHub** - Deployable apps built with OneBalance **10+ chains** - Complete list of supported networks **Get help** - Common questions and solutions **Join us** - Connect with other developers **LLM-powered** - Build with AI agents and automation **What's new** - Latest features and updates
# How OneBalance Works Source: https://docs.onebalance.io/overview/how-onebalance-works Learn how the OneBalance toolkit integrates with your application to enable simple one-click interactions on any blockchain, action, or token. ## High-Level Overview of Toolkit Integration 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 account Note that some optimizations from the list are work in progress and will be delivered soon. The application does not need to worry about any of these - they are optimized on the Toolkit side. Read more on how transactions work [in this section](/concepts/transaction-lifecycle). ## Account Overview The OneBalance Toolkit is compatible with embedded signer providers such as Turnkey and Privy, as well as with applications that have direct access to signer keys (such as Web3 wallets and centralized exchanges). The Toolkit utilizes accounts that are compatible with gas abstraction on all chains, so users never have to worry about paying gas. Read more on particular account options in [this section](/concepts/accounts). ## Resource Lock & Fast Path The [Resource Lock](/concepts/resource-locks) concept, introduced by OneBalance in early 2024, has significantly enhanced multichain transaction efficiency. More specifically, it allows asynchronous execution of a multichain intent, separating intent fulfillment from settlement, which makes multichain transactions feel like same-chain transactions for the user. Applications enable Resource Locks via the Toolkit. Once enabled, all user transactions are co-signed (sequenced) by OneBalance, preventing double-spending during asynchronous multichain executions. For a detailed explanation of how Resource Locks work and their impact on cross-chain bridging speed, see our [Resource Locks concept page](/concepts/resource-locks). You can also find definitions of key terms in our [Glossary](/resources/glossary). ## Monetization From inception, applications can define flexible user fees per transaction, configurable by chain and transaction type. Fees collected from users are intended to: * Offset application-subsidized gas fees * Generate robust and flexible revenue streams Fees are settled with every user transaction as a transfer to the application wallet. Read more on how to configure fees [in this section](/concepts/fees). # Onboarding Checklist Source: https://docs.onebalance.io/overview/onboarding-checklist Step-by-step onboarding checklist for building with OneBalance, from your first API call to deploying an app in production. **Minimum 2 weeks notice required** for production launches. Contact [support@onebalance.io](mailto:support@onebalance.io) early in your development process. ## Custom API Key * **Request custom API key**: Contact [support@onebalance.io](mailto:support@onebalance.io) with the following information (minimum 2 weeks notice): 1. Company details (name, website link, and social media links) 2. Use case description (what you're building, target users, and key features) 3. Expected launch ETA (approximate date when you expect to go live) 4. Volume estimates (expected transaction volume per day/month) 5. Chain/asset preferences (select chains from [supported networks](/resources/supported-networks) and specify assets like USDC, USDT, ETH, SOL, or request custom assets) 6. Fee requirements (choose your [fee structure](/concepts/fees): percentage-based, flat per-chain, or combination) * **Set up team communication**: OneBalance team will create a Telegram or Slack channel between your team and ours for faster support and coordination * **Implement authentication**: Set up proper [API authentication](/api-reference/authentication) and secure key storage Rate limits are configured by OneBalance based on your expected transaction volume. Contact us if you need adjustments. ## Development Phase * **Complete getting started tutorials**: [First API Call](/getting-started/first-api-call) and [Chain-Abstracted Swap](/getting-started/chain-abstracted-swap) * **Understand core concepts**: [What is OneBalance](/overview/what-is-onebalance) and [How it Works](/overview/how-onebalance-works) ## Technical Setup * **Choose networks and assets**: Select from [supported networks](/resources/supported-networks) and [available assets](/api-reference/assets/list) * **Select account type**: Choose [EIP-7702 delegated accounts](/guides/eip-7702/overview) (recommended) or [ERC-4337 smart accounts](/concepts/accounts) * **Pick wallet provider**: Privy, Turnkey, or custom solution * **Determine advanced needs**: Contract calls, multi-input transactions ([see guides](/guides/overview)) ## Testing & Launch Prep * **Test with custom API key**: Verify transactions, fee collection (if applicable), and all supported chains * **Validate user flows**: Test wallet integration and complete user journeys end-to-end * **Notify OneBalance for production readiness**: Contact [support@onebalance.io](mailto:support@onebalance.io) when testing is complete to make your custom API key production-ready ## Monetization Setup * **Plan fee structure**: Choose fixed per chain, percentage, or combination approach ([see fee options](/concepts/fees)) * **Set up Pimlico**: Get API key and Policy ID for gas sponsorship (required for fee collection) * **Prepare beneficiary addresses**: Where collected fees will be sent Fee configuration is handled by OneBalance during API key setup. Without Pimlico, you'll use our sponsored gas (no fee collection). ## Launch * **Confirm launch date** with OneBalance team and prepare rollback plan * **Verify first transactions** work successfully and monitor for any issues * **Monitor ongoing performance** and collect user feedback ## Support & Resources ### Getting Help Use the Intercom chat widget for instant help or email [support@onebalance.io](mailto:support@onebalance.io) Connect with other developers building on OneBalance ### Documentation Resources Complete API documentation and examples Step-by-step integration tutorials Complete Privy wallet integration guide DeFi, NFT, and gaming integration examples ### Enterprise Solutions Need custom rate limits, dedicated support, or enterprise features? Contact our sales team for tailored solutions. ## Important Reminders * **Minimum 2 weeks notice** required for production launches * Early communication ensures smooth launch support * Complex integrations may require additional time * **Pimlico setup required** to collect user fees * **You're responsible** for paymaster account funding * **Test fee collection** before production launch * **Never expose production API keys** in client-side code * **Implement proper authentication** server-side * **Set up proper error handling** and logging This checklist covers the essential steps for a successful OneBalance launch. Click the links for detailed documentation on each topic. Contact our support team early and often - we're here to help. # Welcome to OneBalance Toolkit Source: https://docs.onebalance.io/overview/what-is-onebalance The OneBalance Toolkit is the simplest and most reliable way to bring your users onchain. We offer an all-in-one API platform that lets you build one-click crypto products. ## Who is the Toolkit for? The Toolkit is ideal for apps that need fast, highly reliable onchain execution: swaps, yield, transfers, and arbitrary calldata are all supported out of the box. Apps, Wallets, and launchpads offering a one-click user experience Stablecoin-powered apps accessing 14M+ tokens and monetizing tx fees Services that need to execute reliably on any chain with a single API call ## What does the Toolkit enable? Developers recognize that our Toolkit brings the following benefits. 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 ## Why choose OneBalance? Our unique architecture-built from the node up and powered by resource locks-delivers capabilities that competitors cannot replicate. Unified account balance for execution on all chains 14M+ tokens across EVMs, Solana, and Bitcoin 40% lower latency and greater reliability ## Sample Use Cases * Token swapping across multiple chains, including memecoins * Peer-to-peer crypto payments * Lending, borrowing, and yield-optimization products enabling one-click interactions * Perpetual trading across multiple chains from the same UI or user balance * NFT purchases across multiple chains * Interacting with any Web3 protocol on any network with a chain-spread balance * Stablecoin abstraction into a single USD balance ## Toolkit Integrations Connect your preferred key management service like Privy, Turnkey, and Fireblocks Use your preferred onramps to deposit into your user accounts ## Feature List | Category | Feature | Description | OneBalance Toolkit | Reference | | :------------------------- | :-------------------------------------------- | :--------------------------------------------------------------------- | :----------------------------------- | :------------------------------------------------------------------------------------------------------- | | **Account & Identity** | Smart-contract accounts (ERC-4337 & EIP-7702) | Gas abstraction lets users ignore gas tokens | ✓ | [Account Prediction](/api-reference/account/predict-address) | | | Account creation | Lazy deployment - instant onboarding with no upfront costs | ✓ | [Account Management](/concepts/accounts) | | | Key management / custody | Secure private key management via enterprise-grade providers | ✓ (via Privy, Turnkey, Fireblocks) | Partner Integration | | | Authentication | Social login & biometric auth for seamless onboarding | ✓ (via Auth0, Stytch, Passkeys) | Partner Integration | | **On / Off-Ramps** | Fiat ↔ crypto / stablecoin ramps | Direct fiat conversion - users fund accounts with traditional payments | ✓ (via Ramp Network, Stripe, Bridge) | Partner Integration | | **Reads & Analytics** | Aggregated balances & portfolio | View unified balances across all chains instantly | ✓ | [Aggregated Balance](/api-reference/balances/aggregated-balance) | | | Token pricing & metadata | Real-time prices for accurate portfolio values | ✓ | [Assets API](/api-reference/assets/list) | | | Execution status | Real-time tracking across multiple chains and solvers | ✓ | [Quote Status](/api-reference/status/get-quote-status) | | | Account & transaction history | Access a complete, auditable history | ✓ | [Transaction History](/api-reference/status/get-transaction-history) | | **Writes / Execution** | Swaps & transfers | Swap or transfer tokens across chains with one call | ✓ | [Quote API](/api-reference/quotes/get-quote) | | | Optimal multi-/same-chain routing | Intelligent routing for lowest cost & fastest execution | ✓ | [Quote API](/api-reference/quotes/get-quote)
[Call Quote](/api-reference/quotes/prepare-call-quote) | | | Same-chain optimization | Direct DEX routing for faster & cheaper same-chain swaps | ✓ | [Quote API](/api-reference/quotes/get-quote)
[Call Quote](/api-reference/quotes/prepare-call-quote) | | | Contract calls | Interact with smart contracts on any supported chain | ✓ | [Call Quote](/api-reference/quotes/prepare-call-quote) | | | Multi-input spending | Spend from multiple chains automatically | ✓ | [Aggregated Balance](/api-reference/balances/aggregated-balance) | | | Solana integration | Swaps and transfers are available | ✓ | [Quote API](/api-reference/quotes/get-quote) | | | Fast cross-chain (resource locks) | 40% faster transactions for major tokens (USDC, USDT, WETH, WBTC) | ✓ | [Execute Quote](/api-reference/quotes/execute-quote) | | **Revenue & Monetization** | Flexible fee structure | Configurable fees by chain to optimize revenue | ✓ | [Fee Configuration](/concepts/fees) | | **Coming Soon** | Token / LP / DeFi positions | Track complex DeFi positions for complete portfolio visibility | ✗ | Coming Soon | | | Exact input/output | Specify send or receive amounts - perfect for payments & trading | ✗ | Coming Soon | | | Bitcoin support | Native Bitcoin integration for true multi-chain strategies | ✗ | Coming Soon | | | Admin dashboard | Self-service API management without contacting support | ✗ | Coming Soon | | | Client SDKs | TypeScript/JavaScript SDKs to reduce integration time | ✗ | Coming Soon | **Need a specific feature?** Use the Intercom chat widget in the bottom right corner or contact our team at [support@onebalance.io](mailto:support@onebalance.io) to discuss custom implementations or priority development. # Explore All Products Source: https://docs.onebalance.io/products Explore all the OneBalance products from our Toolkit API, consumer app and our innovative cross-chain technology Resource Locks. Browse our products and solutions for building seamless chain-abstracted experiences. ## Browse by Product # Benchmarks & Stress Tests Source: https://docs.onebalance.io/resources/benchmarks Explore our regularly updated OneBalance Toolkit performance benchmarks and stress test results, for reliable cross-chain transactions. ## Performance Data **Coming Soon**: Performance benchmarks and stress test results will be published here. We're preparing detailed performance data including: * **Transaction throughput** across different networks * **API response times** under various load conditions * **Cross-chain execution latency** for different transaction types * **System reliability** metrics and uptime statistics ## Community Updates Join our community for real-time updates and performance discussions For performance discussions specific to your use case, use the **Intercom chat widget** in the bottom right corner or contact us at [support@onebalance.io](mailto:support@onebalance.io). # Glossary Source: https://docs.onebalance.io/resources/glossary A complete glossary of OneBalance-specific and chain abstraction topics, with clear definitions to support developers using our documentation. ## Core Platform Concepts 1. **Chain Abstraction** - A vision for Web3 that significantly improves user experience by hiding blockchain complexities such as chains, bridging, and gas payments from end users. 2. **Multichain Intent** - a declarative intent formed from the user's desired action, where the execution may touch any number of chains. We consider N->1 intent executions only, meaning there might be N input chains and a single output chain. 3. **Solver** - We combine both of the following definitions under the same name: * Liquidity-based bridging entity that accepts assets on the source chain and delivers assets or executes on the destination chain. The solver may use their own inventory or deliver assets through an on-chain swap. * The entity that finds the optimal route and provides quotes for execution on the same chain. Think of individual CoW Swap solvers or routing aggregators. 4. **OneBackend** - Service under the OneBalance API that receives requests, determines the optimal route, and, if needed, forwards the request to the solver. It also determines which solver or solver network to route to. 5. **RL Service** - service that issues Resource Lock guarantees to the solver for Fast Path cross-chain execution. 6. **Resource Lock** - An off-chain partial lock of user funds preventing double-spending. Similar to a bank account, spent but not yet settled funds are locked on the account even though they are still physically present in the same balance. Learn more in our [Resource Locks](/concepts/resource-locks) guide. 7. **Fast Path** - Multichain intent execution type where the intent executes at the speed of the destination chain. Not all multichain intents are eligible for Fast Path execution. See [Transaction Lifecycle](/concepts/transaction-lifecycle) for execution details. 8. **Standard Path** - Multichain intent execution type where the intent executes at the speed of escrow transaction confirmation on the source chain plus destination chain confirmation time. See [Transaction Lifecycle](/concepts/transaction-lifecycle) for execution details. ## Account Architecture **Account/Gas Abstraction** - A blockchain technology that allows smart contracts to act as user accounts, enabling features like gas sponsorship, batch transactions, and custom authentication methods. See also: EIP-4337, EIP-7702. Learn more about OneBalance's implementation in [Account Models](/concepts/accounts). **SCA (Smart Contract Account)** - An alternative to EOA, a blockchain account controlled by smart contract logic rather than a private key, enabling advanced features like multi-signature, spending limits, and account recovery. See [Account Models](/concepts/accounts) for supported configurations. **Credible Accounts** - A new account model that extends external accounts, smart contracts, and stateful accounts with the ability to issue Resource Locks. Details in [Account Models](/concepts/accounts). **Kernel** - The smart contract account implementation used by OneBalance, based on ZeroDev's open-source framework. Two versions are currently supported: * **Kernel 3.1** - Compatible with both ECDSAValidator and RoleBasedValidator * **Kernel 3.3** - Optimized for EIP-7702 deployments with ECDSAValidator **ECDSAValidator** - A validator module that uses the standard Elliptic Curve Digital Signature Algorithm for authentication, compatible with most existing wallet infrastructures. **RoleBasedValidator** - A validator module that enables sophisticated access control with different permission levels for various operations, currently supporting Fast Path execution only. ## Standards & Protocols [**EIP-4337**](https://eips.ethereum.org/EIPS/eip-4337) - The Ethereum Improvement Proposal that defines the Account Abstraction standard, enabling smart contract accounts across EVM chains without consensus-layer changes. [**EIP-7702**](https://eips.ethereum.org/EIPS/eip-7702) - A newer Ethereum Improvement Proposal that provides an optimized approach for account abstraction with potential gas savings through delegated smart contract authentication. ## Infrastructure & Services **Bundler** - A service that aggregates multiple user operations into a single transaction for submission to the blockchain, optimizing gas costs and execution efficiency. **Paymaster** - A service that sponsors on-chain gas fees on behalf of users, enabling gasless transactions and improving user experience. **WaaS (Wallet-as-a-Service)** - Cloud-based wallet infrastructure that enables applications to offer embedded wallets without requiring users to manage private keys directly. Examples include Privy and Turnkey. ## Research & Community **Frontier Research** - An independent research and advisory group that publishes research, incubates products, and organizes community events, including the Cake Working Group. **Fellowship of the OneBalance** - An industry group united under the common mission to implement the Credible Stack and bring Credible Accounts to Web3. # Community & Support Source: https://docs.onebalance.io/resources/support How to get support and connect with other builders using the OneBalance Toolkit building chain abstracted apps. ## Get Help **Quick Help**: Use the Intercom chat widget in the bottom right corner for instant support from our team. Click the chat widget in the bottom right for instant help Technical questions and integration help via email ## Community Join our developer community for discussions and updates Follow us for announcements and ecosystem news ## Resources Open source tools and examples Latest updates and technical insights ## Research & Learning Technical research and chain abstraction discussions Latest features and updates For urgent production issues, use the **Intercom chat widget** in the bottom right corner for fastest response, or email us directly at [support@onebalance.io](mailto:support@onebalance.io). # Supported Networks Source: https://docs.onebalance.io/resources/supported-networks A full list of blockchains supported by the OneBalance Toolkit, including all major EVM networks, Solana, and soon Bitcoin. ## Chain Support OneBalance is constantly expanding network support. If you need support for a network not listed here, please use the **Intercom chat widget** in the bottom right corner or [contact our team](mailto:support@onebalance.io). | Chain | ID | Status | | :------------------------------------------------ | --------------------------------------- | :---------------------- | |
EthereumEthereum Mainnet
| eip155:1 | ✅ | |
OptimismOptimism
| eip155:10 | ✅ | |
ArbitrumArbitrum
| eip155:42161 | ✅ | |
PolygonPolygon
| eip155:137 | ✅ | |
BaseBase
| eip155:8453 | ✅ | |
LineaLinea
| eip155:59144 | ✅ | |
AvalancheAvalanche
| eip155:43114 | ✅ | |
BSCBNB Smart Chain (BSC)
| eip155:56 | ✅ | |
BlastBlast
| eip155:81457 | ✅ | |
BerachainBerachain
| eip155:80094 | ✅ | |
UnichainUnichain
| eip155:130 | ✅ | |
HyperEVMHyperEVM
| eip155:999 | ✅ | |
SolanaSolana
| solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp | ✅ Beta | |
BitcoinBitcoin
| - | TBD | | Other EVMs | - | On demand | | Other Chains | - | On demand, case by case | ## Network References Each network in OneBalance is identified using the [CAIP-2](https://chainagnostic.org/CAIPs/caip-2) chain ID format: * Format: `{namespace}:{chainId}` * Example: `eip155:1` for Ethereum Mainnet or `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp` for Solana Mainnet When using the OneBalance API, you'll reference these networks using their CAIP-2 identifiers. ```javascript CAIP-2 identifier theme={null} // Example of referencing Ethereum in the API const ethereumChain = 'eip155:1'; ```