@prism-ing/onebalance
v1.0.2
Published
OneBalance account abstraction provider for @prism-ing/wallet.
Downloads
452
Maintainers
Readme
@prism-ing/onebalance
OneBalance account abstraction provider for @prism-ing/wallet. Adds ERC-4337 smart account addresses, cross-chain balances, deposits, swaps, and client-side quote verification.
Install
pnpm add @prism-ing/onebalance @prism-ing/walletAPI key: Get your OneBalance API key from the OneBalance dashboard. Set it as ONEBALANCE_API_KEY in your environment.
API terms: This package integrates with the OneBalance API. OneBalance's public developer terms have not been formally published at time of writing. Review their current terms at docs.onebalance.io and confirm acceptable use with OneBalance before production deployment.
Quickstart
import { createProductionWallet } from '@prism-ing/wallet';
import { createOneBalanceProvider } from '@prism-ing/onebalance';
const provider = createOneBalanceProvider({
apiKey: process.env.ONEBALANCE_API_KEY,
});
const result = await createProductionWallet(
{ signer: { walletName: 'my-agent' } },
{ accountAbstraction: provider },
);
if (!result.ok) {
console.error(result.error.code);
process.exit(1);
}
const wallet = result.value;
console.log('EOA:', wallet.evmAddress);
console.log('Smart account:', wallet.smartAccountAddress);
// Balance, deposit, and status — all powered by OneBalance
const balance = await wallet.getBalance();
console.log(`Balance: $${balance.totalUsd}`);Full-Stack Bootstrap (wallet + onebalance + swap-router)
Combine all three packages to get a wallet with best-in-class security, cross-chain balance, and instant swaps in ~20 lines:
import { createProductionWallet } from '@prism-ing/wallet';
import { createOneBalanceProvider } from '@prism-ing/onebalance';
import { createSwapRouter } from '@prism-ing/swap-router';
// 1. Account abstraction provider (adds smart account + cross-chain balance)
const provider = createOneBalanceProvider({
apiKey: process.env.ONEBALANCE_API_KEY,
});
// 2. Wallet — OWS persistent keys on Node.js, Secure Enclave on iOS
const walletResult = await createProductionWallet(
{ signer: { walletName: 'my-agent' } },
{ accountAbstraction: provider },
);
if (!walletResult.ok) throw new Error(walletResult.error.code);
const wallet = walletResult.value;
// 3. Swap router — points at your Prism aggregator instance
const router = createSwapRouter({
baseUrl: 'https://your-aggregator.fly.dev/v1',
signer: wallet.signer, // PrismSigner satisfies SwapSigner directly
evmRpcUrls: { 8453: process.env.BASE_RPC_URL },
});
// 4. Quote a cross-chain swap (USDC on Base → USDT on Arbitrum)
const quote = await router.quote({
fromAsset: 'USDC:base',
toAsset: 'USDT:arbitrum',
amount: '1000000', // 1 USDC (6 decimals)
sender: wallet.evmAddress,
// Pass your smart account for OneBalance cross-chain routing:
accounts: [{ type: 'kernel-v3.1-ecdsa', accountAddress: wallet.smartAccountAddress, signerAddress: wallet.evmAddress }],
});
// 5. Execute — signs, broadcasts, and polls until COMPLETED
if (quote.ok) {
const exec = await router.execute(quote.value);
console.log(exec.ok ? exec.value.status : exec.error.code);
}Security properties of this stack:
- Private keys never leave the OWS vault (
~/.ows/) — hardware-backed on iOS via Secure Enclave - Quote integrity verified before any signing (prevents blind-signing attacks)
- Cross-chain operations signed client-side; OneBalance only sees EIP-712 signatures
How It Works
createOneBalanceProvider returns an AccountAbstractionProvider — the interface defined by @prism-ing/wallet. When injected into createWallet or createProductionWallet, it enables:
Execution Flow
Your Agent
│
▼
createProductionWallet() ← OWS keys, never leave ~/.ows/
│
▼
wallet.deposit() or router.quote()
│
▼
OneBalance API ── getQuote ──► quote + EIP-712 payload
│
▼
verifyQuoteIntegrity() ← 6 checks: expiry, amount, asset, sender
│ (FAILS FAST if tampered)
▼
wallet.signer.signTypedData() ← client-side signing, key never leaves
│
▼
OneBalance API ── executeQuote
│
▼
Cross-chain settlement ← Resource Locks, no custody handoffSigning stays client-side. OneBalance only sees signed EIP-712 payloads. It never has access to your private key.
| Capability | Method | What OneBalance Does |
|------------|--------|---------------------|
| Smart account address | predictAddress() | Computes counterfactual ERC-4337 address from signer's public key |
| Balance | getBalance() | Aggregates balances across all supported chains |
| Deposit | deposit() | Executes via OneBalance Resource Locks (quote -> sign -> execute) |
| Transaction status | getTransactionStatus() | Polls OneBalance execution status |
Signing stays client-side. OneBalance never sees your private keys. It receives only signed payloads (EIP-712 for EVM, transaction signatures for Solana).
Supported Chains
| Chain | Chain ID | CAIP-2 |
|-------|----------|--------|
| Ethereum | 1 | eip155:1 |
| Optimism | 10 | eip155:10 |
| BNB Chain | 56 | eip155:56 |
| Polygon | 137 | eip155:137 |
| Base | 8453 | eip155:8453 |
| Arbitrum One | 42161 | eip155:42161 |
| Avalanche C-Chain | 43114 | eip155:43114 |
| Linea | 59144 | eip155:59144 |
| Blast | 81457 | eip155:81457 |
| Solana | — | solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp |
Use toCaip2(chainId) to convert numeric chain IDs, or import SOLANA_CAIP2 for Solana.
Asset IDs
OneBalance accepts two asset identifier formats:
| Format | Example | When to Use |
|--------|---------|-------------|
| Aggregated (ob:) | 'ob:usdc', 'ob:eth' | Cross-chain operations — OneBalance resolves to the best chain automatically |
| CAIP-19 | 'eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' | When you need a specific token on a specific chain |
Use ob: shorthand for cross-chain swaps. Use CAIP-19 when you need chain-specific precision.
Depositing into Your Smart Account
Before executing cross-chain swaps, your smart account needs funds. Use wallet.deposit() to move tokens from your EOA into the OneBalance smart account:
import { createProductionWallet } from '@prism-ing/wallet';
import { createOneBalanceProvider } from '@prism-ing/onebalance';
const provider = createOneBalanceProvider({ apiKey: process.env.ONEBALANCE_API_KEY });
const result = await createProductionWallet(
{ signer: { walletName: 'my-agent' } },
{ accountAbstraction: provider },
);
if (!result.ok) throw new Error(result.error.code);
const wallet = result.value;
// Deposit 10 USDC from your EOA into the smart account on Base
const deposit = await wallet.deposit({
asset: 'ob:usdc', // works across all chains — OneBalance routes automatically
amount: '10000000', // 10 USDC (6 decimals)
chainId: 8453, // source chain for this deposit
});
if (!deposit.ok) {
console.error('Deposit failed:', deposit.error.code);
} else {
console.log('Deposited:', deposit.value.quoteId);
// Poll for confirmation
const status = await wallet.getTransactionStatus(deposit.value.quoteId);
}Agent integration note: The
ob:usdcasset ID works across all chains — you don't need to specify which chain you're depositing from when using aggregated IDs. OneBalance resolves the best chain automatically based on your available balances.
Cross-Chain Execution
For direct cross-chain swaps outside the wallet abstraction:
import { executeCrossChain } from '@prism-ing/onebalance';
const result = await executeCrossChain(
oneBalanceClient,
wallet.signer,
{
fromAsset: 'ob:usdc',
toAsset: 'ob:eth',
amount: '1000000', // smallest unit (e.g., 1 USDC = 1000000)
slippageTolerance: 50, // basis points (50 = 0.5%)
accountAddress: wallet.smartAccountAddress,
},
accounts,
);
if (result.ok) {
console.log('Quote ID:', result.value.quoteId);
}The flow is: get quote -> verify integrity -> sign operations -> submit to OneBalance.
Quote Verification
executeCrossChain automatically verifies OneBalance quotes before signing. This prevents blind-signing attacks where a compromised API could present malicious EIP-712 payloads.
6 checks performed automatically:
- Quote has not expired
- OneBalance tamper-proof signature is present
- Origin asset matches the requested
fromAsset - Origin amount is within 1% of the requested amount (configurable)
- Destination asset matches the requested
toAsset - EVM operation
userOp.sendermatchesaccountAddress(when provided)
For manual verification:
import { verifyQuoteIntegrity, verifyEvmOperation } from '@prism-ing/onebalance';
const error = verifyQuoteIntegrity(quote, {
fromAsset: 'ob:usdc',
toAsset: 'ob:eth',
amount: '1000000',
accountAddress: '0x1234...',
maxOriginSlippageFraction: 0.005, // 0.5% tolerance (default 1%)
});API Reference
createOneBalanceProvider(config)
Creates an AccountAbstractionProvider backed by OneBalance.
interface OneBalanceProviderConfig {
apiKey: string;
baseUrl?: string; // Default: https://be.onebalance.io/api
timeoutMs?: number; // Default: 30000
maxRetries?: number; // Default: 3
baseDelayMs?: number; // Default: 1000
}createOneBalanceProviderFromClient(client)
Creates a provider from an existing OneBalanceClient. Useful for testing with mock clients.
createOneBalanceHttpClient(config)
Lower-level: creates a raw OneBalanceClient with retry logic and exponential backoff. Use this when you need direct API access beyond what the provider exposes.
executeCrossChain(client, signer, params, accounts, options?)
Orchestrates the full quote -> verify -> sign -> execute flow. Returns Result<CrossChainResult, CrossChainError>.
verifyQuoteIntegrity(quote, params) / verifyEvmOperation(operation, address)
Client-side quote verification functions. Used automatically by executeCrossChain; exported for custom flows.
Error Handling
All async operations return Result<T, E> — check .ok before using the value:
import { executeCrossChain } from '@prism-ing/onebalance';
const result = await executeCrossChain(client, wallet.signer, params, accounts);
if (!result.ok) {
switch (result.error.code) {
case 'SIGNING_REJECTED':
// User cancelled biometric or denied signing
console.error('Signing cancelled:', result.error.reason);
break;
case 'PROVIDER_TIMEOUT':
// OneBalance API didn't respond — safe to retry
console.error(`Timeout after ${result.error.durationMs}ms`);
break;
case 'PROVIDER_API_ERROR':
// OneBalance returned an error — check statusCode
console.error(`API ${result.error.statusCode}: ${result.error.body}`);
break;
case 'QUOTE_VERIFICATION_FAILED':
// Quote integrity check failed — do NOT sign
console.error('Quote tampered or expired');
break;
}
return;
}
// result.value is CrossChainResult
console.log('Submitted:', result.value.quoteId);HTTP-level errors from the client throw OneBalanceHttpError with statusCode, body, and path fields.
Transaction Status Polling
After submitting a swap, poll for execution status:
const swap = result.value;
const poll = async () => {
const status = await wallet.getTransactionStatus(swap.quoteId);
switch (status.status) {
case 'pending':
// Still processing — poll again
break;
case 'completed':
console.log('Swap completed');
break;
case 'failed':
console.error('Swap failed');
break;
}
};Testing
Use createOneBalanceProviderFromClient to inject a mock client in tests:
import { createOneBalanceProviderFromClient } from '@prism-ing/onebalance';
import type { OneBalanceClient } from '@prism-ing/onebalance';
const mockClient: OneBalanceClient = {
predictAddress: async () => ({ accounts: [], smartAccountAddress: '0x...' }),
getQuote: async () => ({ /* mock quote */ }),
executeQuote: async () => {},
getExecutionStatus: async () => ({ quoteId: '...', status: 'completed' }),
getAggregatedBalance: async () => ({ totalUsd: '100.00', breakdown: [] }),
};
const provider = createOneBalanceProviderFromClient(mockClient);Network Resilience
The HTTP client includes:
- Exponential backoff with jitter (3 retries, base delay 1s -> 2s -> 4s + random jitter)
- Retries only on: timeouts (AbortError), network errors (TypeError), and 5xx server errors
- No retry on 4xx client errors (bad request, auth failure, validation)
- Configurable via
OneBalanceProviderConfig
Documentation
- SPEC.md — Design decisions, security model, API types
License
MIT
