@varla/sdk
v4.0.0
Published
TypeScript SDK for **Varla Protocol** — lending and risk infrastructure for prediction markets.
Readme
@varla/sdk
TypeScript SDK for Varla Protocol — lending and risk infrastructure for prediction markets.
Provides contract ABIs, deployed addresses, typed viem bindings, read-only view helpers, simulate-first write actions, leverage planning, error decoding, and formatting utilities.
Supported chains: Polygon (Polymarket) · BSC (Opinion)
Install
bun add @varla/sdk
# or
npm i @varla/sdkPeer dependency: viem ^2.0.0
Subpath imports
| Import | Description |
| --- | --- |
| @varla/sdk | Core: contracts, views, actions, events, batch, format — everything re-exported |
| @varla/sdk/views | Read-only on-chain queries (account, pool, oracle, system) |
| @varla/sdk/actions | Simulate-first write helpers (deposit, borrow, repay, liquidate, oracle config…) |
| @varla/sdk/events | Event log fetching, decoding, and oracle registry sync |
| @varla/sdk/contracts | Typed viem contract instances (getVarlaContracts) |
| @varla/sdk/abi | Raw ABI constants for all deployed contracts |
| @varla/sdk/addresses | Per-chain deployed address books (polygon, bsc) |
| @varla/sdk/errors | Custom error decoding across all Varla contracts |
| @varla/sdk/leverage | Leverage loop planning, execution, and deleverage |
| @varla/sdk/batch | Multicall chunking utilities for RPC-efficient reads |
| @varla/sdk/safe | Gnosis Safe MultiSend encoding for proxy wallet users |
| @varla/sdk/format | Number formatting (formatWad, formatBps, parseUnits) |
| @varla/sdk/types | Shared types (ChainKey, AddressBook) |
import { getVarlaContracts } from "@varla/sdk/contracts";
import * as views from "@varla/sdk/views";
import * as actions from "@varla/sdk/actions";Quick start
import { createPublicClient, http } from "viem";
import { polygon } from "viem/chains";
import { getVarlaContracts } from "@varla/sdk/contracts";
import * as views from "@varla/sdk/views";
const client = createPublicClient({
chain: polygon,
transport: http(process.env.POLYGON_RPC_URL!),
});
const c = getVarlaContracts({ chain: "polygon", client });Reading on-chain data
All view helpers are async functions that accept a typed viem contract instance and return plain objects.
Lender / pool views
// Pool rates, utilization, and APY
const rates = await views.readPoolRates({ pool: c.pool });
// Pool health score (utilization-based safety signal)
const health = await views.readPoolHealthScore({ pool: c.pool });
// → { kind: "ok", utilizationWad, scoreBps } or { kind: "no-liquidity" }
// Share price (assets per share, WAD precision)
const price = await views.readPoolSharePrice({ pool: c.pool });
// Full pool snapshot (caps, accounting, rates, share price)
const poolSnap = await views.readPoolSnapshot({ pool: c.pool });
// User's lender position: shares, current value, max withdrawable
const lender = await views.readLenderSnapshot({ pool: c.pool, user: "0x..." });
// → { shares, assets, maxWithdrawAssets, maxRedeemShares }Borrower / core views
// Full account overview: positions, debt, health factor, borrow limits
const account = await views.readAccountSnapshot({ core: c.core, user: "0x..." });
// Just borrow limits + health factor
const limits = await views.readBorrowLimits({ core: c.core, user: "0x..." });
// Health factor (liquidation threshold check)
const hf = await views.readHealthFactor({ core: c.core, user: "0x..." });
// → { healthFactor, canBeLiquidated }
// LTV tier configuration
const tiers = await views.readTieredLtvConfig({ core: c.core });
// → { tier0, tier1, tier2 } (CONSERVATIVE / MODERATE / RISK)
// Effective LTV for a specific position
const ltv = await views.readLtvForPosition({ core: c.core, positionId: 123n });Wallet position discovery
ERC-1155 has no tokenIdsOf(owner) — readWalletPositionIds solves this by reading all oracle-configured position IDs and batch-checking balances via multicall.
// Discover which Varla positions a wallet holds (without an indexer)
const wallet = await views.readWalletPositionIds({
oracle: c.oracle, // reads getConfiguredPositions()
positionsToken: c.ctf, // ERC-1155 contract
client, // multicall provider
user: "0x...",
});
// → { user, candidateCount: 150, positionIds: [42n, 99n], balances: [5n, 12n] }Hypothetical borrow capacity
Computes how much a user could borrow if they deposited their wallet-held positions.
// With explicit position IDs (e.g., from your own indexer)
const capacity = await views.readHypotheticalBorrowCapacity({
core: c.core,
oracle: { address: c.oracle.address },
positionsToken: c.ctf,
client,
user: "0x...",
walletPositionIds: [42n, 99n], // known IDs
});
// Or auto-discover from oracle registry (no walletPositionIds needed)
const capacityAuto = await views.readHypotheticalBorrowCapacity({
core: c.core,
oracle: c.oracle, // must have .read.getConfiguredPositions
positionsToken: c.ctf,
client,
user: "0x...",
// walletPositionIds omitted → auto-discovers via readWalletPositionIds
});
console.log(capacityAuto.current.maxBorrow); // from deposited positions
console.log(capacityAuto.wallet.portfolioValue); // wallet positions value
console.log(capacityAuto.potential.maxBorrow); // if all wallet positions depositedNote: Auto-discovery scans all oracle-configured positions (one
balanceOfmulticall per position). For registries with 500+ positions this is efficient via chunked multicall, but prefer passing explicitwalletPositionIdswhen you have them from an indexer.
Oracle views
// Oracle registry (all configured positions)
const registry = await views.readOracleRegistry({ oracle: c.oracle });
// Price data for a position
const priceData = await views.readOraclePriceData({ oracle: c.oracle, positionId: 123n });
// Position snapshot (price + metadata)
const snap = await views.readPositionSnapshot({ oracle: c.oracle, positionId: 123n });
// Batch: many position snapshots in one multicall
const snaps = await views.readManyPositionSnapshots({
oracle: c.oracle,
positionIds: [1n, 2n, 3n],
});System snapshot
// Everything at once: pool + core + oracle state
const system = await views.readSystemSnapshot({
core: c.core,
pool: c.pool,
oracle: c.oracle,
});Writing transactions
All write helpers use the simulate-first pattern — they return a SimulatedTx with a pre-validated request you can send via any viem wallet client.
import * as actions from "@varla/sdk/actions";
// Deposit collateral
const sim = await actions.prepareCoreDeposit({
publicClient: client,
coreAddress: c.core.address,
account: "0x...",
positionId: 123n,
amount: 1_000_000n,
});
// Send the transaction
const hash = await actions.sendTx({ walletClient, request: sim.request });Common actions
// Borrow from pool
await actions.prepareCoreBorrow({ publicClient, coreAddress, account, amount });
// Repay debt
await actions.prepareCoreRepay({ publicClient, coreAddress, account, amount });
// Withdraw collateral
await actions.prepareCoreWithdraw({ publicClient, coreAddress, account, positionId, amount });
// ERC-20 approval (for pool deposits)
await actions.prepareErc20Approve({ publicClient, tokenAddress, spender, amount });
// Pool deposit (lender)
await actions.preparePoolDeposit({ publicClient, poolAddress, account, assets });
// Pool withdraw (lender)
await actions.preparePoolWithdraw({ publicClient, poolAddress, account, assets });Leverage
Plan and execute leveraged positions through iterative borrow → buy → deposit loops.
import { planLeverage, executeLeverage } from "@varla/sdk/leverage";
// Plan a 3× leveraged long position
const plan = planLeverage({
initialCapital: 100_000_000n, // 100 pUSD (6 decimals)
tokenPriceE8: 50_000_000n, // $0.50
ltvWad: 650_000_000_000_000_000n, // 65% LTV
targetLeverageWad: 3_000_000_000_000_000_000n, // 3.0×
});
console.log(plan.summary.effectiveLeverageWad); // actual achieved leverage
console.log(plan.summary.estimatedLiquidationPriceE8); // liquidation price
console.log(plan.steps); // ordered steps: buy → deposit → borrow → buy → ...Safe / proxy wallet integration
Polymarket users hold ERC-1155 positions inside Gnosis Safe proxy wallets. The @varla/sdk/safe subpath provides helpers to batch multiple Varla operations into a single Safe transaction — matching the UX of competitors like Gondor.
Key insight: Varla contracts already accept calls from any msg.sender (EOA or Safe). No contract changes are needed. The SDK helper simply packs multiple prepare*() results into the Safe MultiSend format.
Full example: approve → deposit → borrow in one Safe tx
import { encodeSafeMultiSend } from "@varla/sdk/safe";
import * as actions from "@varla/sdk/actions";
const safeAddress = "0x..."; // user's Gnosis Safe address
// 1. Prepare each step (simulate against the Safe as msg.sender)
const approve = await actions.prepareErc1155SetApprovalForAll({
publicClient: client,
tokenAddress: ctfAddress,
account: safeAddress,
operator: coreAddress,
approved: true,
});
const deposit = await actions.prepareCoreDeposit({
publicClient: client,
coreAddress: c.core.address,
account: safeAddress,
positionIds: [positionId],
amounts: [amount],
});
const borrow = await actions.prepareCoreBorrow({
publicClient: client,
coreAddress: c.core.address,
account: safeAddress,
amount: borrowAmount,
});
// 2. Encode as a single Safe MultiSend batch
const batch = encodeSafeMultiSend([approve, deposit, borrow]);
// batch.to → Safe MultiSend contract address
// batch.data → encoded multiSend(bytes) calldata
// batch.value → total ETH value (usually 0n)
// batch.count → 3
// 3. Submit via Safe SDK (or Safe Apps SDK in a dApp)
const safeTx = await safeSdk.createTransaction({
transactions: [{
to: batch.to,
data: batch.data,
value: batch.value.toString(),
operation: 1, // DELEGATECALL to MultiSend
}],
});
await safeSdk.executeTransaction(safeTx);API
| Function | Description |
| --- | --- |
| encodeSafeMultiSend(inputs, options?) | Encode transactions into MultiSend format. Accepts raw { to, data, value } or prepare*() results. |
| encodeSafeMultiSendCallOnly(inputs) | Same but uses the "call only" MultiSend (no DELEGATECALL in sub-txs). |
| SAFE_MULTISEND_ADDRESS | Canonical MultiSend address (0x3886…) — same on all EVM chains. |
| SAFE_MULTISEND_CALL_ONLY_ADDRESS | Canonical MultiSend "call only" address (0x9641…). |
Optimization: When the batch contains a single transaction, encodeSafeMultiSend returns it directly without a MultiSend wrapper.
Important: Always pass account: safeAddress (not the EOA signer) to prepare*() helpers. The contracts track positions and debt under msg.sender, which will be the Safe address.
Error decoding
Automatically decode custom Solidity errors from any Varla contract.
import { decodeVarlaError, formatVarlaError } from "@varla/sdk/errors";
try {
await publicClient.simulateContract({ /* ... */ });
} catch (err) {
const decoded = decodeVarlaError(err);
if (decoded) {
console.log(formatVarlaError(decoded));
// → "VarlaCore.InsufficientCollateral(required=1000, actual=500)"
console.log(decoded.contract, decoded.name, decoded.args, decoded.selector);
}
}Formatting utilities
import { formatWad, formatBps, formatPriceE8, parseUnits, parseWad } from "@varla/sdk/format";
formatWad(1_500_000_000_000_000_000n); // "1.5"
formatWad(1_500_000_000_000_000_000n, { fractionDigits: 4 }); // "1.5"
formatBps(1500); // "15%"
formatBps(1234); // "12.34%"
formatPriceE8(50_000_000n); // "0.5"
parseUnits("1.5", 18); // 1_500_000_000_000_000_000n
parseUnits("100", 6); // 100_000_000n
parseWad("2.5"); // 2_500_000_000_000_000_000nMulticall batching
Chunk large read batches to stay within RPC provider limits.
import { multicallChunks } from "@varla/sdk/batch";
const results = await multicallChunks({
client,
contracts: [
{ address: poolAddr, abi: poolAbi, functionName: "totalAssets" },
{ address: poolAddr, abi: poolAbi, functionName: "totalSupply" },
// ... hundreds of calls
],
chunkSize: 64, // calls per multicall (default: 128)
});Events & indexing
import {
getEventLogsChunked,
syncOracleRegistryViaEvents,
getRecentEvents,
ORACLE_EVENTS,
CORE_EVENTS,
POOL_EVENTS,
} from "@varla/sdk/events";
// Fetch oracle events in manageable chunks
const logs = await getEventLogsChunked({
client,
address: c.oracle.address,
events: ORACLE_EVENTS,
fromBlock: 50_000_000n,
toBlock: "latest",
chunkSize: 10_000,
});
// Reconstruct oracle registry from events (no paginated RPC needed)
const registry = await syncOracleRegistryViaEvents({
client,
oracleAddress: c.oracle.address,
fromBlock: 50_000_000n,
});Chain metadata
import { getChainMeta } from "@varla/sdk";
const meta = getChainMeta("polygon");
// → { chain: "polygon", platform: "polymarket", collateral: { kind: "PUSD", symbol: "pUSD", decimals: 6 } }
const bscMeta = getChainMeta("bsc");
// → { chain: "bsc", platform: "opinion", collateral: { kind: "USDT", symbol: "USDT", decimals: 18 } }Testing
cd packages/sdk
bun run test:quick # build + run all tests
bun run test:types # typecheck only (no runtime)
bun run test:rpc # RPC integration tests (needs POLYGON_RPC_URL, BSC_RPC_URL)
bun run test:rpc:slow # slow RPC tests (opt-in via SDK_SLOW_RPC_TESTS=1)Contributing
This package lives in the varla-protocol monorepo under packages/sdk/.
bun install # install from repo root
cd packages/sdk && bun run prepack # regenerate artifacts + build
cd packages/sdk && bun run test:quick # verifySee AGENTS.md for the complete auto-generated API reference.
