haru
v1.0.0
Published
Minimal Haru SDK - Copy-paste package for balances and swaps
Maintainers
Readme
haru
Minimal Haru SDK package - Copy-paste ready for balances and swaps.
Installation
npm install haruQuick Start
import { Haru } from 'haru'
// Initialize with your signed config
const haru = new Haru({
config: {
sig: '0x...',
aid: 'your-app-id',
ts: Date.now(),
chains: ['ethereum', 'arbitrum'],
primaryUsd: ['USDC']
},
apiBaseUrl: 'https://api.haru.so' // optional, has default
})
// Option 1: Pre-fetch hydrated tokens (recommended for multiple calls)
const tokens = await haru.getHydratedTokens()
// Get balances (uses cached tokens automatically)
const balances = await haru.balances('0xUserAddress...')
console.log('Total USD:', balances.totalUsd)
console.log('Spendable:', balances.spendable.usdTotal)
console.log('Invest:', balances.invest.usdTotal)
// Get swap quote (uses cached tokens automatically)
const quote = await haru.swapQuote({
userAddress: '0xUserAddress...',
fromSymbol: 'USDC',
toSymbol: 'WETH',
usdValue: '1000', // $1000 worth
slippageBps: 50 // 0.5% slippage (optional)
})
console.log('Swap Rate:', quote.rate)
console.log('Input:', quote.input.usdValue, quote.input.currency)
console.log('Output:', quote.output.usdValue, quote.output.currency)
// Get Privy call list - one line!
const { calls, metadata } = haru.callList(quote) || {}
if (calls) {
// Optional: Display metadata in UI
if (metadata) {
console.log(`Total steps: ${metadata.totalSteps}`)
metadata.steps.forEach(step => {
console.log(`${step.step}: ${step.name} - ${step.description}`)
})
}
// Use with Privy - copy-paste → run
// const { sendTransaction } = usePrivy()
// const hash = await sendTransaction(calls, { sponsor: true })
}API Reference
Haru Class
Constructor
new Haru(options: HaruOptions)Options:
config(required): Signed config objectapiBaseUrl(optional): API base URL (defaults tohttps://api.haru.so)
Methods
getHydratedTokens()
Verify config and get hydrated tokens. Caches tokens internally for use in other calls.
const tokens = await haru.getHydratedTokens()Returns: Promise<(HydratedToken | AbstractedToken)[]>
balances(userAddress: string)
Get user balances across all chains. Automatically uses cached hydrated tokens if available.
const balances = await haru.balances('0xUserAddress...')Returns: Promise<CategorizedBalanceResult>
Response Structure:
{
userAddress: string
totalUsd: string
spendable: {
usdTotal: string
balances: TokenBalanceDetail[]
}
invest: {
usdTotal: string
balances: TokenBalanceDetail[]
}
}swapQuote(params)
Get swap quote. Automatically uses cached hydrated tokens if available.
const quote = await haru.swapQuote({
userAddress: '0xUserAddress...',
fromSymbol: 'USDC',
toSymbol: 'WETH',
usdValue: '1000',
slippageBps: 50 // optional, defaults to 50 (0.5%)
})Parameters:
userAddress(required): User's Ethereum addressfromSymbol(required): Symbol of token to swap from (e.g., "USDC")toSymbol(required): Symbol of token to swap to (e.g., "WETH")usdValue(required): USD value to swap (e.g., "1000" for $1000)slippageBps(optional): Slippage tolerance in basis points (e.g., 50 = 0.5%)
Returns: Promise<CleanSwapQuote>
Response Structure:
{
quoteId?: string
input: {
usdValue: number
currency: string
rawAmount: string
amountFormatted: string
}
output: {
usdValue: number
currency: string
rawAmount: string
amountFormatted: string
}
rate: number
slippageBps: number
haruFeeBps: number
tokens: {
in: QuoteTokenInfo
out: QuoteTokenInfo
}
txData?: SwapQuoteTxData // Transaction steps including approve and swap
}txData Structure:
{
steps: SwapQuoteStep[]
}
// Each step:
{
step: string // e.g., "1/2", "2/2" - format is "current/total"
stage: 'approve' | 'swap'
kind: 'transaction'
data: QuoteTransaction // Transaction data ready to send to wallet
}callList(quote)
Get Privy call list from swap quote. Returns bare-minimum shape that Privy's smart-wallet accepts.
const quote = await haru.swapQuote({ ... })
// One line - copy-paste → run
const { calls, metadata } = haru.callList(quote) || {}
if (calls) {
// Optional: Display metadata in UI
if (metadata) {
console.log(`Total steps: ${metadata.totalSteps}`)
// Total steps: 2
console.log('Steps:', metadata.steps)
// [
// { name: 'approve', step: '1/2', description: 'Approve USDC' },
// { name: 'swap', step: '2/2', description: 'Swap USDC for WETH' }
// ]
}
// Use with Privy - bare minimum, Privy handles gas/chainId
const { sendTransaction } = usePrivy()
const hash = await sendTransaction(calls, { sponsor: true })
}Parameters:
quote(required): Swap quote fromswapQuote()
Returns: SwapCallList | null
Response Structure:
{
calls: PrivyCall[] // Bare-minimum shape for Privy
metadata?: { // Optional, for UI only
totalSteps: number
steps: Array<{
name: 'approve' | 'swap'
step: string // "1/2", "2/2"
description: string // "Approve USDC", "Swap USDC for WETH"
}>
}
}PrivyCall Structure (bare-minimum):
{
to: string
data: string
value: bigint // BigInt (0n format) - Privy handles gas/chainId
}Note: No gas, maxFeePerGas, chainId, etc. - Privy estimates and attaches them automatically.
steps(quote) (Legacy)
Get legacy steps format with full transaction data (gas fields, chainId). Use callList() instead for Privy integration.
const legacyData = haru.steps(quote)
// Returns: { totalSteps, steps, callList: PrivyTransaction[] }clearCache()
Clear cached hydrated tokens. Forces refresh on next call that requires tokens.
haru.clearCache()getConfig()
Get the current config.
const config = haru.getConfig()setConfig(config: Config)
Update the config (automatically clears cached tokens).
haru.setConfig(newConfig)Usage Patterns
Pattern 1: Pre-fetch tokens (Recommended)
const haru = new Haru({ config: myConfig })
// Pre-fetch tokens once
await haru.getHydratedTokens()
// All subsequent calls use cached tokens
const balances = await haru.balances(address)
const quote1 = await haru.swapQuote({ ... })
const quote2 = await haru.swapQuote({ ... })Pattern 2: Auto-fetch tokens
const haru = new Haru({ config: myConfig })
// Tokens are fetched automatically on first call
const balances = await haru.balances(address) // Fetches tokens if needed
const quote = await haru.swapQuote({ ... }) // Uses cached tokensPattern 3: Custom API URL
const haru = new Haru({
config: myConfig,
apiBaseUrl: 'http://localhost:4000' // Use local API
})Pattern 4: Executing Swap Transactions with Privy
import { Haru } from 'haru'
import { usePrivy } from '@privy-io/react-auth'
const haru = new Haru({ config: myConfig })
const quote = await haru.swapQuote({
userAddress: '0x...',
fromSymbol: 'USDC',
toSymbol: 'WETH',
usdValue: '1000'
})
// Get call list - one line!
const { calls, metadata } = haru.callList(quote) || {}
if (calls) {
const { sendTransaction } = usePrivy()
// Send batch transaction via Privy - copy-paste → run
const hash = await sendTransaction(calls, { sponsor: true })
console.log(`Transaction hash: ${hash}`)
// Optional: Display progress in UI
if (metadata) {
console.log(`Executing ${metadata.totalSteps} steps:`)
metadata.steps.forEach(step => {
console.log(` ${step.step}: ${step.description}`)
})
}
}Features
- ✅ Minimal: Only config + 3 main methods
- ✅ No Service Code: Calls hosted APIs only
- ✅ Automatic Caching: Hydrated tokens cached after first call
- ✅ Type-Safe: Full TypeScript support
- ✅ Copy-Paste Ready: Self-contained package
Requirements
- Node.js 18+ or Bun
- TypeScript 5.0+ (for TypeScript projects)
License
MIT
