TypeScript SDK for interacting with Levr protocol - a decentralized governance, staking, and liquidity management system built on Uniswap v4.
- 🎯 Type-Safe - Full TypeScript support with comprehensive types
- 🔄 Zero Duplication - Single multicalls per data group (37-53% fewer RPC calls)
- ⚡ Centralized Provider - All queries in one place with smart refetch management
- 🪝 React Hooks - Easy integration with React applications
- 🔌 Server & Client - Works in both server and client environments
- 📦 Tree-Shakeable - Import only what you need
- 💰 USD Pricing - Integrated USD price calculations for tokens, balances, and APR
- 📊 Price Impact - Real-time price impact calculation for swaps
- ⚙️ Manual Accrual - Explicit reward accrual system for security and predictability
npm install levr-sdk
# or
bun add levr-sdk
# or
yarn add levr-sdkFor React applications, use the centralized provider pattern:
// 1. Wrap your app with LevrProvider
import { LevrProvider } from 'levr-sdk/client'
export function App() {
return (
<QueryClientProvider client={queryClient}>
<WagmiConfig config={wagmiConfig}>
<LevrProvider>
<YourApp />
</LevrProvider>
</WagmiConfig>
</QueryClientProvider>
)
}
// 2. Set the active token in your pages
import { useSetClankerToken, useProject } from 'levr-sdk/client'
export function ProjectPage({ clankerToken }: { clankerToken: `0x${string}` }) {
const { data: project, isLoading } = useProject()
// Automatically sets and updates when clankerToken prop changes
useSetClankerToken(clankerToken)
if (isLoading) return <div>Loading...</div>
if (!project) return <div>Project not found</div>
return (
<div>
<h1>{project.token.name}</h1>
<p>Treasury: {project.treasuryStats?.balance.formatted}</p>
<StakeComponent />
<SwapComponent />
<GovernanceComponent />
</div>
)
}
// 3. Use hooks in child components - they automatically share queries!
import { useStake, useUser } from 'levr-sdk/client'
function StakeComponent() {
const { data: user } = useUser()
const { stake, needsApproval } = useStake({
onStakeSuccess: () => {
toast.success('Staked successfully!')
// All related data automatically refetches
},
})
return (
<div>
<p>Balance: {user?.balances.token.formatted}</p>
<p>Staked: {user?.staking.stakedBalance.formatted}</p>
<button onClick={() => stake.mutate(amount)}>
Stake
</button>
</div>
)
}For server-side operations or scripts:
import { getProject, getUser, Stake, Governance } from 'levr-sdk'
import { createPublicClient, createWalletClient, http } from 'viem'
import { base } from 'viem/chains'
import { privateKeyToAccount } from 'viem/accounts'
// Initialize clients
const publicClient = createPublicClient({
chain: base,
transport: http(),
})
const walletClient = createWalletClient({
chain: base,
transport: http(),
account: privateKeyToAccount('0x...'),
})
// Get project data
const projectData = await getProject({
publicClient,
clankerToken: '0x...',
})
// Get user data (includes balances, staking, voting power)
const userData = await getUser({
publicClient,
userAddress: '0x...',
project: projectData,
})
console.log('Balance:', userData.balances.token.formatted)
console.log('Staked:', userData.staking.stakedBalance.formatted)
// Stake tokens
const stake = new Stake({
wallet: walletClient,
publicClient,
project: projectData,
})
// Approve and stake
await stake.approve(1000) // Or '1000' or parseUnits('1000', projectData.token.decimals)
await stake.stake(1000)
// Governance operations
const governance = new Governance({
wallet: walletClient,
publicClient,
project: projectData,
})
// Propose a transfer
const { receipt, proposalId } = await governance.proposeTransfer(
'0x...', // recipient
parseUnits('1000', 18), // amount
'Fund development' // description
)Direct context accessors for read-only data:
import {
useProject, // Project data (token, contracts, treasury, staking stats, governance stats)
useUser, // User data (balances, staking, voting power)
usePool, // Pool state (liquidity, price, fees)
useProposals, // Proposals with vote receipts
useClankerToken, // Token metadata (admin, image, etc.)
} from 'levr-sdk/client'
const { data: project } = useProject()
const { data: user } = useUser()
const { data: pool } = usePool()
const { data: proposals } = useProposals()
const { data: tokenData } = useClankerToken()Hooks with both queries and mutations:
import {
useStake, // Staking operations
useSwap, // Swap operations
useGovernance, // Governance operations
useFeeReceivers, // Fee receiver management
} from 'levr-sdk/client'const {
// Mutations
approve,
stake,
unstake,
claim,
accrueRewards,
accrueAllRewards,
// Helper
needsApproval,
// Loading states
isApproving,
isStaking,
isUnstaking,
isClaiming,
} = useStake({
onStakeSuccess: (receipt) => console.log('Staked!', receipt),
onUnstakeSuccess: (receipt) => console.log('Unstaked!', receipt),
onClaimSuccess: (receipt) => console.log('Claimed!', receipt),
})
// Get data from context
const { data: user } = useUser()
const { data: project } = useProject()
// Access staking data
user?.balances.token // Token balance
user?.staking.stakedBalance // User's staked amount
user?.staking.claimableRewards // User's claimable rewards
project?.stakingStats?.totalStaked // Total staked by all users
project?.stakingStats?.apr // APR percentages
// Use mutations - automatically refetches related data
stake.mutate(1000) // Or '1000' or parseUnits('1000', project.token.decimals)const {
// Mutation
swap,
// Quote query
quote,
// Helper
buildSwapConfig,
// Loading
isSwapping,
} = useSwap({
quoteParams: {
zeroForOne: true, // true = token -> WETH, false = WETH -> token
amountIn: '100',
amountInDecimals: 18,
amountOutDecimals: 18,
},
onSwapSuccess: (receipt) => console.log('Swapped!', receipt),
})
// Get data from context
const { data: user } = useUser()
const { data: project } = useProject()
// Access balances and pool
user?.balances.token // Token balance
user?.balances.weth // WETH balance
project?.pool?.poolKey // Pool key for swaps
// Access quote with price impact and hook fees
console.log(quote.data?.priceImpactBps) // 0.5 (0.5% impact)
console.log(quote.data?.hookFees) // { type: 'static', clankerFee: 500, ... }
// Build and execute swap
const config = buildSwapConfig({
zeroForOne: true,
amountIn: 100, // Accepts number, string, or bigint
amountInDecimals: 18,
minAmountOut: ((quote.data.amountOut * 99n) / 100n).toString(), // 1% slippage
})
swap.mutate(config)const {
// Mutations
proposeTransfer,
proposeBoost,
vote,
executeProposal,
claimAirdrop,
// Helpers
buildProposeTransferConfig,
buildProposeBoostConfig,
// Loading states
isProposing,
isVoting,
isExecuting,
isClaiming,
} = useGovernance({
onVoteSuccess: (receipt) => console.log('Voted!', receipt),
onExecuteProposalSuccess: (receipt) => console.log('Executed!', receipt),
})
// Get data from context
const { data: user } = useUser()
const { data: project } = useProject()
const { data: proposals } = useProposals()
// Access governance data
project?.governanceStats?.currentCycleId // Current cycle
project?.treasury // Treasury address
project?.governor // Governor address
project?.airdrop // Airdrop status
user?.votingPower // User's voting power in Token Days (string)
proposals?.proposals // List of proposals with vote receipts
// Use mutations
const config = buildProposeTransferConfig({
recipient: '0x...',
amount: '1000',
description: 'Fund development',
})
proposeTransfer.mutate(config)const { mutate } = useFeeReceivers({
onSuccess: (hash) => console.log('Updated fee receiver', hash),
})
// Get fee receiver data from project context
const { data: project } = useProject()
const feeReceivers = project?.feeReceivers
// Update a fee receiver
mutate.mutate({
clankerToken: project.token.address,
rewardIndex: 0,
newRecipient: '0x...',
})import {
useSetClankerToken, // Update global token context
useLevrRefetch, // Manual refetch control
useClanker, // Clanker SDK instance
} from 'levr-sdk/client'
// Automatically update active token (in page/route components)
useSetClankerToken('0x...')
// Manual refetch control
const refetch = useLevrRefetch()
await refetch.all() // Refetch everything
await refetch.user() // Refetch user data
await refetch.project() // Refetch project data
await refetch.afterStake() // Smart refetch after stake
await refetch.afterTrade() // Smart refetch after swap
// Get Clanker SDK instance
const clanker = useClanker()The SDK provides 100% refetch coverage with smart cross-domain awareness:
| Action | Auto-Refetches |
|---|---|
| Trade (Swap) | User (balances), Pool (state) |
| Stake/Unstake | User (balances, staking, voting), Project (stats) |
| Claim | User only (balances, claimable rewards) |
| Accrue | Project only (outstanding rewards) |
| Vote | User, Proposals (vote receipts) |
| Propose | Proposals, Project (active count) |
| Execute | Project, Proposals, User (all may change) |
| Airdrop | Project (treasury, airdrop status) |
| Wallet/Chain Change | All Queries |
All mutations automatically trigger appropriate refetches - no manual coordination needed!
Get complete project data including token, contracts, pool, treasury, staking stats, governance stats, and optional USD pricing:
import { getProject } from 'levr-sdk'
const projectData = await getProject({
publicClient,
clankerToken: '0x...',
// Optional: Provide oracle client for USD pricing
oraclePublicClient: mainnetClient, // For WETH/USD and token/USD prices
userAddress: '0x...', // Optional: for areYouAnAdmin in fee receivers
})
// Access project data
console.log(projectData.token.name) // "MyToken"
console.log(projectData.treasuryStats?.balance.formatted) // "50000.00"
console.log(projectData.stakingStats?.apr.token.percentage) // 15.5
console.log(projectData.governanceStats?.currentCycleId) // 5n
// Access USD pricing (if oracle provided)
console.log(projectData.pricing?.tokenUsd) // "0.05"
console.log(projectData.treasuryStats?.balance.usd) // "$2500.00"Get user-specific data including balances, staking, and voting power:
import { getUser, getProject } from 'levr-sdk'
// First get project data (user query needs it)
const projectData = await getProject({
publicClient,
clankerToken: '0x...',
})
// Then get user data
const userData = await getUser({
publicClient,
userAddress: '0x...',
project: projectData,
})
// Access user data
console.log(userData.balances.token.formatted) // "1000.0"
console.log(userData.balances.weth.formatted) // "5.5"
console.log(userData.staking.stakedBalance.formatted) // "500.0"
console.log(userData.staking.claimableRewards.staking.formatted) // "10.5"
console.log(userData.votingPower) // "15000" (Token Days as string)Manage staking operations:
import { Stake, getProject } from 'levr-sdk'
// Get project data first
const projectData = await getProject({
publicClient,
clankerToken: '0x...',
})
// Create stake instance with project data
const stake = new Stake({
wallet: walletClient,
publicClient,
project: projectData, // All fields from project
})
// Perform operations
await stake.approve(1000) // Accepts number, string, or bigint
await stake.stake(1000)
// Unstake (returns new voting power)
const { receipt, newVotingPower } = await stake.unstake({
amount: 500, // Accepts number, string, or bigint
to: '0x...', // Optional
})
// Accrue rewards before claiming
await stake.accrueAllRewards([
projectData.token.address,
wethAddress, // Optional: if WETH rewards exist
])
// Claim rewards
await stake.claimRewards()Manage governance operations:
import { Governance, getProject } from 'levr-sdk'
// Get project data first
const projectData = await getProject({
publicClient,
clankerToken: '0x...',
})
// Create governance instance with project data
const governance = new Governance({
wallet: walletClient,
publicClient,
project: projectData, // All fields from project
})
// Propose actions
const { receipt, proposalId } = await governance.proposeTransfer(
'0x...', // recipient
1000, // amount (accepts number, string, or bigint)
'Fund development' // description
)
// Or with parseUnits for precise amounts
await governance.proposeTransfer('0x...', parseUnits('1000', 18), 'Fund development')
// Propose boost
await governance.proposeBoost(500) // Accepts number, string, or bigint
// Vote on proposals
await governance.vote(proposalId, true) // true = yes, false = no
// Execute proposals
await governance.executeProposal(proposalId)
// Claim airdrop (if available)
await governance.claimAirdrop()
// Get vote receipt
const receipt = await governance.getVoteReceipt(proposalId, '0x...')
console.log(receipt.hasVoted) // true/falseUnified quote API for V3 and V4 swaps:
import { quote, UNISWAP_V3_QUOTER_V2, WETH } from 'levr-sdk'
const chainId = publicClient.chain.id
const quoterAddress = UNISWAP_V3_QUOTER_V2(chainId)
const wethData = WETH(chainId)
// V3 quote
const v3Quote = await quote.v3.read({
publicClient,
quoterAddress,
tokenIn: wethData.address,
tokenOut: '0x...', // USDC
amountIn: parseUnits('1', 18),
fee: 3000, // 0.3%
})
// V4 quote with price impact and hook fees
const v4Quote = await quote.v4.read({
publicClient,
poolKey: {
currency0: '0x...',
currency1: '0x...',
fee: 500,
tickSpacing: 10,
hooks: '0x...',
},
zeroForOne: true,
amountIn: parseUnits('100', 18), // Or use bigint directly
// Optional: Provide pricing for price impact calculation
pricing: { wethUsd: '2543.21', tokenUsd: '0.05' },
tokenAddress: '0x...',
currency0Decimals: 18,
currency1Decimals: 18,
})
console.log(v4Quote.priceImpactBps) // 0.5 (0.5% impact)
console.log(v4Quote.hookFees) // { type: 'static', clankerFee: 500 }Execute swaps on Uniswap V4:
import { swapV4 } from 'levr-sdk'
const receipt = await swapV4({
publicClient,
wallet: walletClient,
poolKey,
zeroForOne: true,
amountIn: parseUnits('100', 18), // Or use number/string
amountOutMinimum: parseUnits('95', 18),
})Get USD price for any token paired with WETH:
import { getUsdPrice } from 'levr-sdk'
// Get token price in USD
// Uses mainnet for accurate WETH/USDC oracle, testnet for token quote
const { priceUsd, tokenPerWeth, wethPerUsdc } = await getUsdPrice({
oraclePublicClient: mainnetClient, // For WETH/USDC price (auto-discovers best V3 pool)
quotePublicClient: testnetClient, // For token/WETH price (your chain)
tokenAddress: '0x...', // Token to price
tokenDecimals: 18, // Token decimals (required)
quoteFee: 3000, // Optional: Pool fee tier (default: 3000 = 0.3%)
})
console.log(`Token price: $${priceUsd}`) // "0.05"The SDK uses a centralized provider pattern that eliminates query duplication and provides 100% refetch coverage:
┌─────────────────────────────────────────────────┐
│ LevrProvider (Global) │
│ │
│ ┌───────────────────────────────────────────┐ │
│ │ Centralized Queries (created once) │ │
│ │ • Project (token, contracts, treasury, │ │
│ │ staking stats, governance stats) │ │
│ │ • User (balances, staking, voting power) │ │
│ │ • Pool (real-time state) │ │
│ │ • Proposals (with vote receipts) │ │
│ │ • Token Data (metadata) │ │
│ └───────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────┐ │
│ │ Smart Refetch Methods │ │
│ │ • afterTrade() → User + Pool │ │
│ │ • afterStake() → User + Project │ │
│ │ • afterClaim() → User only │ │
│ │ • afterAccrue() → Project only │ │
│ │ • afterVote() → User + Proposals │ │
│ │ • afterProposal() → Proposals + Project │ │
│ │ • afterExecute() → Project + Proposals + │ │
│ │ User │ │
│ └───────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
↓ Context shared via hooks
┌─────────────────────────────────────────────────┐
│ Components consume without duplication │
│ • useProject() → Shared query │
│ • useUser() → Shared query │
│ • usePool() → Shared query │
│ • useProposals() → Shared query │
│ • useStake() → Mutations only │
└─────────────────────────────────────────────────┘
✅ Zero Duplication - Each query created once, shared across all components
✅ 37-53% Fewer RPC Calls - Single multicalls per data group
✅ 100% Refetch Coverage - Smart action-based refetches after mutations
✅ Better Performance - Fewer network requests, better caching
✅ Type Safety - Full TypeScript throughout
✅ Easy to Use - Simple hook API, automatic coordination
import { useSetClankerToken } from 'levr-sdk/client'
function TokenSwitcher() {
const setClankerToken = useSetClankerToken()
const switchToToken = (tokenAddress: `0x${string}`) => {
setClankerToken(tokenAddress)
// All queries automatically update!
}
return (
<select onChange={(e) => switchToToken(e.target.value as `0x${string}`)}>
<option value="0x...">Token A</option>
<option value="0x...">Token B</option>
</select>
)
}import { useLevrRefetch } from 'levr-sdk/client'
function RefreshButton() {
const refetch = useLevrRefetch()
return (
<div>
{/* Core refetches */}
<button onClick={() => refetch.all()}>Refresh All</button>
<button onClick={() => refetch.user()}>Refresh User</button>
<button onClick={() => refetch.project()}>Refresh Project</button>
<button onClick={() => refetch.pool()}>Refresh Pool</button>
<button onClick={() => refetch.proposals()}>Refresh Proposals</button>
{/* Action-based smart refetches */}
<button onClick={() => refetch.afterStake()}>After Stake</button>
<button onClick={() => refetch.afterTrade()}>After Trade</button>
<button onClick={() => refetch.afterClaim()}>After Claim</button>
<button onClick={() => refetch.afterVote()}>After Vote</button>
</div>
)
}import { useDeploy } from 'levr-sdk/client'
function DeployToken() {
const deploy = useDeploy({
onSuccess: ({ receipt, address }) => {
console.log('Deployed to:', address)
},
})
const handleDeploy = () => {
deploy.mutate({
name: 'My Token',
ticker: 'TKN',
imageUrl: 'ipfs://...',
description: 'Token description',
// ... other params
})
}
return <button onClick={handleDeploy}>Deploy</button>
}All query keys are centralized and exported:
import { queryKeys } from 'levr-sdk/client'
// Use in custom queries or invalidations
queryClient.invalidateQueries({
queryKey: queryKeys.project(clankerToken, chainId),
})Full TypeScript support with comprehensive types:
import type {
Project,
PoolKey,
ProposalsResult,
BalanceResult,
ClaimParams,
ProposeTransferConfig,
// ... many more
} from 'levr-sdk'# Install dependencies
bun install
# Type check
bun run type-check
# Run tests
bun test
# Build
bun run buildFor comprehensive documentation:
- Getting Started - Installation and setup
- Quick Reference - Fast lookup for common patterns
- Architecture - Understanding the design
- Client Hooks - Complete React hooks reference
- Server API - Server-side API reference
- Migration Guide - Upgrade guide
Apache v2 - see LICENSE.md for details.