SDK Examples
This guide provides full, copy-paste ready code examples for integrating Vault into your frontend application.
Coinchange Vault React SDK
Technology & Liquidity Infrastructure for non-custodial liquidity routing and technology-powered DeFi automation. No guarantee of returns. Rewards are variable and derived from blockchain protocol incentives and CeFi/DeFi strategies across CEX/DEX venues.
- Contracts (per vault): BoringVault, TellerWithMultiAssetSupport, AccountantWithRateProviders, DelayedWithdraw, Lens
- Frontend: React Provider + Hook with typed helpers for reads/writes
- Integrators: wallets, fintechs, exchanges, treasury/enterprise apps
Install
npm i boring-vault-ui ethers
# or
yarn add boring-vault-ui ethersPeer requirements
- React 18+
- Ethers v6
- A stable RPC for your target network
Quick Start
Wrap your app with the Provider, then call the Hook from your components.
import React from "react"
import { BoringVaultV1Provider } from "boring-vault-ui"
import { JsonRpcProvider } from "ethers"
export default function AppRoot() {
const ethersProvider = new JsonRpcProvider(process.env.NEXT_PUBLIC_RPC)
return (
<BoringVaultV1Provider
vaultContract="0x..." // BoringVault
tellerContract="0x..." // TellerWithMultiAssetSupport
accountantContract="0x..." // AccountantWithRateProviders
lensContract="0x..." // Lens (batched reads)
ethersProvider={ethersProvider}
depositTokens={[
{ address: "0xTokenA...", decimals: 18 },
{ address: "0xTokenB...", decimals: 6 }
]}
baseToken={{ address: "0xBase...", decimals: 18 }}
vaultDecimals={18}
>
{/* your app */}
</BoringVaultV1Provider>
)
}Provider
BoringVaultV1Provider (required)
BoringVaultV1Provider (required)Props (exact names):
vaultContract: string
tellerContract: string
accountantContract: string
lensContract: string
ethersProvider: Ethers BrowserProvider or EIP-1193 provider
depositTokens: Array<{ address: string; decimals: number }>
baseToken: { address: string; decimals: number }
vaultDecimals: number
children: ReactNodeNotes
ethersProvidermay be an Ethers v6 BrowserProvider or an EIP-1193 provider (the SDK normalizes to BrowserProvider internally).depositTokensand any token refs in writes use only{ address, decimals }.
Hook API
useBoringVaultV1()
useBoringVaultV1()Readiness
isBoringV1ContextReady: booleanVault reads (no wallet required)
fetchTotalAssets(): Promise<number> // TVL in baseToken units (decimal-adjusted)
fetchShareValue(): Promise<number> // value of 1 share in baseToken (decimal-adjusted)User reads (wallet address)
fetchUserShares(userAddress: string): Promise<number> // decimal-adjusted shares
fetchUserUnlockTime(userAddress: string): Promise<number> // unix secondsWrites (positional args; all return status objects)
// Supply (a.k.a. deposit)
// depositAmount: human-readable string, e.g., "1.25"
// selectedToken: { address, decimals }
deposit(
signer, // Ethers v6 JsonRpcSigner
depositAmount, // string (human-readable)
selectedToken // { address: string; decimals: number }
): Promise<DepositStatus>
// Delayed Withdrawals (T+1 to T+5 business days)
// shareAmount: human-readable shares, e.g., "10.0"
// tokenOut: { address, decimals }
// maxLoss: percent as string ("1" => 1%); "0" => use contract default
// thirdPartyClaimer: allow a third party to complete on behalf of user
delayWithdraw(
signer,
shareAmount,
tokenOut,
maxLoss,
thirdPartyClaimer
): Promise<WithdrawStatus>
// List active (non-zero) intents for the connected user
delayWithdrawStatuses(
signer
): Promise<DelayWithdrawEntry[]>
// Cancel the user’s request for that tokenOut
delayWithdrawCancel(
signer,
tokenOut
): Promise<WithdrawStatus>
// Complete a matured request (UI must verify maturity before calling)
delayWithdrawComplete(
signer,
tokenOut
): Promise<WithdrawStatus>Live status objects (reflect in your UI)
depositStatus: DepositStatus
withdrawStatus: WithdrawStatusTypes
type DepositStatus = {
initiated: boolean
loading: boolean
success?: boolean
error?: string
tx_hash?: string
}
type WithdrawStatus = {
initiated: boolean
loading: boolean
success?: boolean
error?: string
tx_hash?: string
}
type DelayWithdrawEntry = {
allowThirdPartyToComplete: boolean
maxLoss: number // percent; 1 => 1%
maturity: number // unix seconds
shares: number // human-readable number
exchangeRateAtTimeOfRequest: number // human-readable number
token: { address: string; decimals: number }
}Usage Examples
Gate on readiness + show TVL / 1-share value
import React from "react"
import { useBoringVaultV1 } from "boring-vault-ui"
export function VaultOverview() {
const { isBoringV1ContextReady, fetchTotalAssets, fetchShareValue } = useBoringVaultV1()
const [tvl, setTvl] = React.useState(0)
const [shareValue, setShareValue] = React.useState(0)
React.useEffect(() => {
if (!isBoringV1ContextReady) return
fetchTotalAssets().then(setTvl)
fetchShareValue().then(setShareValue)
}, [isBoringV1ContextReady])
if (!isBoringV1ContextReady) return <div>Loading vault…</div>
return (
<div>
<div>TVL (base token): {tvl}</div>
<div>1 Share Value (base token): {shareValue}</div>
</div>
)
}User shares + unlock time
import React from "react"
import { useBoringVaultV1 } from "boring-vault-ui"
export function UserPosition({ user }) {
const { isBoringV1ContextReady, fetchUserShares, fetchUserUnlockTime } = useBoringVaultV1()
const [shares, setShares] = React.useState(0)
const [unlockTs, setUnlockTs] = React.useState(0)
React.useEffect(() => {
if (!isBoringV1ContextReady || !user) return
fetchUserShares(user).then(setShares)
fetchUserUnlockTime(user).then(setUnlockTs)
}, [isBoringV1ContextReady, user])
if (!user) return <div>Connect wallet</div>
return (
<div>
<div>Your shares: {shares}</div>
<div>Unlock time (unix): {unlockTs}</div>
</div>
)
}Supply (deposit)
import React from "react"
import { useBoringVaultV1 } from "boring-vault-ui"
export function DepositForm({ signer }) {
const { deposit, depositStatus } = useBoringVaultV1()
const [amount, setAmount] = React.useState("")
const token = { address: "0xTokenA...", decimals: 18 }
async function onSubmit(e) {
e.preventDefault()
await deposit(signer, amount, token)
}
return (
<form onSubmit={onSubmit}>
<input placeholder="Amount (e.g., 1.25)" value={amount} onChange={e => setAmount(e.target.value)} />
<button type="submit" disabled={depositStatus.loading}>Supply</button>
{depositStatus.initiated && <div>Submitting…</div>}
{depositStatus.tx_hash && <div>Tx: {depositStatus.tx_hash}</div>}
{depositStatus.error && <div>Error: {depositStatus.error}</div>}
{depositStatus.success && <div>Success</div>}
</form>
)
}Delayed withdrawals — request, list, cancel, complete
import React from "react"
import { useBoringVaultV1 } from "boring-vault-ui"
export function WithdrawFlow({ signer }) {
const {
delayWithdraw,
delayWithdrawStatuses,
delayWithdrawCancel,
delayWithdrawComplete,
withdrawStatus,
} = useBoringVaultV1()
const [shareAmount, setShareAmount] = React.useState("10.0")
const [maxLoss, setMaxLoss] = React.useState("1")
const [thirdParty, setThirdParty] = React.useState(false)
const [pending, setPending] = React.useState([])
const tokenOut = { address: "0xTokenA...", decimals: 18 }
async function request(e) {
e.preventDefault()
await delayWithdraw(signer, shareAmount, tokenOut, maxLoss, thirdParty)
await refresh()
}
async function refresh() {
const list = await delayWithdrawStatuses(signer)
setPending(list || [])
}
async function cancel() {
await delayWithdrawCancel(signer, tokenOut)
await refresh()
}
async function complete(maturity) {
const now = Math.floor(Date.now() / 1000)
if (now < maturity) {
alert("Not yet matured")
return
}
await delayWithdrawComplete(signer, tokenOut)
await refresh()
}
React.useEffect(() => { refresh() }, [])
return (
<div>
<form onSubmit={request}>
<input value={shareAmount} onChange={e => setShareAmount(e.target.value)} placeholder="Shares (e.g., 10.0)" />
<input value={maxLoss} onChange={e => setMaxLoss(e.target.value)} placeholder="Max loss % (e.g., 1)" />
<label>
<input type="checkbox" checked={thirdParty} onChange={e => setThirdParty(e.target.checked)} />
Allow third party to complete
</label>
<button type="submit" disabled={withdrawStatus.loading}>Request</button>
</form>
<button onClick={refresh}>Refresh</button>
{pending.length === 0 && <div>No active withdrawal requests</div>}
{pending.map((p, i) => (
<div key={i} style={{ border: "1px solid #444", padding: 8, marginTop: 8 }}>
<div>TokenOut: {p.token.address} (decimals {p.token.decimals})</div>
<div>Shares: {p.shares}</div>
<div>Maturity (unix): {p.maturity}</div>
<div>Max loss %: {p.maxLoss}</div>
<div>3rd-party completion: {String(p.allowThirdPartyToComplete)}</div>
<div>Rate at request: {p.exchangeRateAtTimeOfRequest}</div>
<div style={{ marginTop: 8 }}>
<button disabled={withdrawStatus.loading} onClick={cancel}>Cancel</button>
<button disabled={withdrawStatus.loading} onClick={() => complete(p.maturity)}>Complete</button>
</div>
{withdrawStatus.initiated && <div>Submitting…</div>}
{withdrawStatus.tx_hash && <div>Tx: {withdrawStatus.tx_hash}</div>}
{withdrawStatus.error && <div>Error: {withdrawStatus.error}</div>}
{withdrawStatus.success && <div>Success</div>}
</div>
))}
</div>
)
}Signer (viem → Ethers v6)
import { BrowserProvider } from "ethers"
// viemWalletClient is created by your app (e.g., wagmi/viem)
async function getEthersSigner(viemWalletClient) {
const ethersProvider = new BrowserProvider(viemWalletClient.transport)
return await ethersProvider.getSigner()
}Decimal & Rate Handling
- All read helpers return numbers already decimal-adjusted for display/UX.
- All amount inputs to writes are human-readable strings (e.g.,
"1.25","10.0"). - The SDK’s deposit helper computes the
minimumMintusing current vault/accountant rates and a small slippage buffer; you do not need to hand-roll decimals/rate math.
UX & Product Rules
- Gate all reads/writes on
isBoringV1ContextReady. - Share-lock: surface
fetchUserUnlockTimebefore enabling transfers/redeems. - Delayed withdrawals: treat as T+1 to T+5 business days; only enable Complete after
maturity. - Stacking: calling
delayWithdrawrepeatedly for the sametokenOutstacks requested amounts—disable additional requests while one is ongoing. - Approvals: request the exact approval where possible (avoid unlimited by default).
- Status UX: always reflect
{ initiated, loading, success?, error?, tx_hash? }for deposit/withdraw flows. - Positioning language: “technology-powered DeFi automation,” “non-custodial liquidity routing,” “rewards on digital asset balances.” Include: “No guarantee of returns. Rewards are variable and derived from blockchain protocol incentives and CeFi/DeFi strategies across CEX/DEX venues.”
Updated 2 months ago
What’s Next
