VAULT on this page is BoringVault and its ERC-20 share token. Users deposit through VedaRouter and submit withdrawal requests directly to AtomicQueue. BoringVault does not expose ERC-4626 deposit/redeem/previewRedeem entrypoints.
The deposit, withdrawal, and rate-query flow is the same on each network. Select the matching network and contract addresses below. ASSET is the underlying token for that deployment; on Kairos, it is mockUSDT.
For the architecture and APR model, start with fixed-rate vault architecture and rate updates. The default operating target is a regular update every 24 hours for USDT (6 decimals) and every hour for JPYC (18 decimals). This page focuses on integration calls; coordinate updates separately with the team on Kairos, which has no automated keeper.
1. Networks and contract addresses
Mainnet contract addresses will be added after the production deployment. The mainnet column is intentionally blank.
Use the addresses for your selected network. Router, Keeper, Gateway, CustomVault, and the rate Controller use Transparent proxies; use their proxy addresses in the table.
Deposits enter BoringVault. The keeper can move liquidity through Gateway to CustomVault and return it for withdrawals. Gateway holds CustomVault shares and temporarily holds underlying during deposits; normal CustomVault withdrawals pay BoringVault directly.
2. Integration paths and units
Router approves BoringVault internally and calls Teller
bulkDeposit. Users do not approve underlying to Teller or BoringVault. Direct Teller deposit, depositWithPermit, and bulkDeposit are not public. Use approve followed by deposit.
- Underlying token: read
ASSET.decimals(). With 6 decimals,1 token = 1_000_000base units. - BoringVault shares: 18 decimals.
1 share = 10^18. R = Accountant.getRate()is ASSET base units per 1e18 share base units (1 share). ASSET is Accountant’s base asset in this integration. A JPYC-based rate has 18 decimals; a USDT-based rate has 6 decimals, while shares have 18 decimals in both cases. At 1 share = 1 asset, the JPYC rate is10^18and the USDT rate is10^6. Read the current value from Accountant.- Deposit quote:
shares = floor(assets × 10^18 / R). - Value at the checkpointed rate:
assets = floor(shares × R / 10^18). - Actual request payout:
floor(offerAmount × atomicPrice / 10^18).
bigint for all amounts, never JavaScript number. The example applies 10 bps (0.1%) tolerance to minimum deposit shares. This is an example policy; adjust it for your application. Do not set minimumShares to zero. Do not apply this deposit margin to a withdrawal’s atomicPrice. Lowering the withdrawal price reduces the actual payout.
Approval targets and amounts
Callapprove on the token contract and pass the spending contract as spender. Approval alone creates neither a deposit nor a withdrawal request.
For example, depositing 100 tokens with 6 decimals uses
ASSET.approve(Router, 100_000_000); withdrawing 10 shares uses VAULT.approve(Queue, 10 × 10^18). Do not approve ASSET to Queue to withdraw. Settlement does not charge additional underlying or a shortfall to the user’s wallet.
- Check the chain ID, token address, spender, and amount in the wallet. Names and symbols are insufficient. Use the published addresses for that network. While mainnet addresses are blank, do not substitute arbitrary or QA addresses.
- This integration does not require approving tokens to Teller, Solver, VedaKeeper, Gateway, or an operator EOA, or approving BoringVault shares to Router. An EOA approved as spender can call
transferFromdirectly. If you approve the wrong spender, set that actual spender’s allowance to zero on the affected token. The helpers below only clean up the configured Router/Queue allowances. - Do not send tokens with a plain
transferto Router, Vault, Queue, or Keeper. That does not perform a deposit or withdrawal, mint shares, or guarantee an automatic refund. Usedepositfor deposits,safeUpdateAtomicRequestfor new withdrawal requests, andupdateAtomicRequestfor cancellation. - A permit signature also grants spending rights. If your app separately supports permit, verify its token, chain, spender, amount, and signature deadline. The permit deadline limits signature submission, not the lifetime of the resulting allowance. This example uses
approve.
Asynchronous requests and atomic settlement
A deposit uses an approval transaction → a Router deposit transaction. Withindeposit, the asset transfer and share mint succeed or revert together. A confirmed approval does not revert when the later deposit fails or its signature is rejected.
A withdrawal uses share approval → request submission → later keeper settlement. Shares remain in the wallet after submission. An authorized operator calls VedaKeeper, and AtomicQueue, as spender, calls transferFrom for the requested shares. The operator EOA does not directly withdraw from the user’s wallet. Share redemption and ASSET payment happen atomically within one settlement transaction; payment failure reverts the share transfer too. Each request fills in full, without partial fills.
A valid request with sufficient share balance and allowance can settle before its deadline without another user signature. Do not label a successful request transaction as a completed withdrawal. Distinguish request confirmation, pending settlement, and payment; verify payment with AtomicRequestFulfilled and ASSET transfers. Keeper permissions and transfer locks do not justify leaving unnecessary allowances.
3. Runnable wallet integration
Runnpm install viem and use the following file in a TypeScript browser app. Pass a FixedRateDeployment containing the chain and its contract addresses to connectFixedRate. The same methods handle deposits, withdrawal requests, cancellation, and reads on either network. The included kairosDeployment is ready for testing. Configure mainnet once its addresses are published.
Minimal ABIs are included. Asset decimals are read from the selected token. Amount inputs are decimal strings; excess precision is rejected. The helper resets an existing larger allowance to zero before approving only the exact required amount. Each operation waits for its transaction receipt, so approvals confirm before dependent calls. Reconnect when the wallet account or network changes. The example mints shares to the connected user; Router also accepts a different receiver.
When constructing a configuration from deployment.json, map its address keys explicitly: asset, accountant, controller, and teller keep their names; use router ← vedaRouter, vault ← boringVault, queue ← atomicQueue, and keeper ← vedaKeeper. Set chain to the viem Chain configuration matching the wallet and RPC. The included kairosDeployment already applies this mapping.
provider (for example, your wallet SDK provider or an injected wallet’s window.ethereum). deployment is the selected network configuration. Obtain its underlying token before depositing; for test tokens, follow Test on Kairos.
4. Exact withdrawal semantics
atomicPrice: use the safe entrypoint to store the execution-time rate
- Approve AtomicQueue for exactly the requested BoringVault shares and wait for confirmation.
- Read
Accountant.getRateSafe()at the official deployment address for a preview. Stop if paused or the read fails. Display shares, price, and estimated payout usingfloor(offerAmount × rate / 10^18). Both the price and rounded payout must be positive. - Set
request.atomicPriceto a0nplaceholder and call the safe entrypoint. It ignores this field and overwrites the price using the supplied Accountant’sgetRateInQuoteSafe(ASSET)within the transaction. Use the network’s officialACCOUNTANTaddress anddiscount=0n. Do not allow users to select an arbitrary Accountant: checking itsvault()does not authenticate it as the official Accountant. - After confirmation, check the stored price and payout with
getUserAtomicRequestorgetStatus(). A poke between the preview and execution can change the registered price. If settlement has already cleared the request, inspectAtomicRequestUpdated.minPricein the registration receipt and theAtomicRequestFulfilledevent. Despite the nameminPrice, this event field is the actual fixed settlement price.
requestWithdraw() uses this safe path. The atomicPrice: 0n input is ignored only by the safe entrypoint. Never pass a zero price with a nonzero amount to ordinary updateAtomicRequest: it can permit shares to be given away for nothing.
The safe entrypoint checks share balance and allowance, deadline, a positive amount, the Accountant’s vault, the discount limit, and the uint88 price range. Queue or Accountant pause makes it revert. It does not poke automatically, guarantee future payment, reprice existing requests, or reject a payout that rounds to zero. discount uses a 1,000,000 denominator, not basis points; its limit is 10,000 (1%). Always use zero in this integration. With ASSET as the Accountant base asset, getRateInQuoteSafe(ASSET) and getRateSafe() return the same value and units. For another quote asset, use the former rate getter, but do not change this withdrawal integration’s supported pair arbitrarily.
Ordinary updateAtomicRequest remains public for cancellation. It also permits nonzero requests and stores the supplied price unchanged, so use of the safe path is not enforced. For example, with a current USDT rate of 1_000_000 and offerAmount = 100 × 10^18:
The second request still settles for 50 USDT even if the rate later becomes
1_010_000. Follow the cancellation and revocation steps below for an incorrect request, then resubmit through the safe path. A settlement that confirms first cannot be undone.
Request state and share locks
- There is one request per
(user, VAULT, ASSET). Updating the same pair replaces it rather than adding to it. The example requires clearing any stored request, including an expired one, before reapproving. This integration supports only the(VAULT, ASSET)pair; do not use arbitrary offer/want tokens. updateAtomicRequestdoes not validate every balance, allowance, and price condition. Check sufficient shares and Queue allowance, positiveatomicPrice, and a nonzero rounded payout. A zero-price request can give away shares for nothing; never submit one.- Shares remain in the wallet until settlement. An active request for this
(VAULT, ASSET)pair blocks all outbound sharetransferandtransferFromcalls from that wallet, not just the requested amount. Queue settlement transfers are exempt from this additional lock; Teller deny-list checks still apply. - Cancellation, fulfillment, or
block.timestamp > deadlineremoves this request lock. Expiry does not delete storage or settle the request. Separate Teller restrictions still apply. - The example uses 7 days until expiry. This is neither a mandatory wait nor a fulfillment guarantee. Resubmit expired requests if you still want to withdraw.
- Queue pause also blocks cancellation. Ask the team to unpause. Revoking share allowance blocks settlement but leaves the active request lock until cancellation or expiry. If cancellation races settlement, whichever confirms first takes effect.
offerAmount == 0alone does not distinguish never-created, canceled, and fulfilled requests. UseAtomicRequestFulfilledand token balances to confirm payment. The recipient is always the requester; there is no withdrawal receiver argument. A wrapper contract submitting a request must hold/approve the shares and receives the payout itself.
Canceling requests and revoking approvals
Cancel withupdateAtomicRequest and offerAmount=0. The safe entrypoint rejects zero amounts and cannot be used to cancel.
These are separate transactions. cancelWithdraw() only clears the request and returns its cancellation receipt. revokeQueueAllowance() only sets BoringVault’s allowance to Queue to zero.
Revocation does not delete the request or release its share transfer lock. The lock remains until cancellation or expiry. Reapproving before expiry can make the stored request executable again at its old price. Read and cancel unwanted requests before reapproving. The example also requires explicitly clearing expired stored requests before granting a new approval.
Token
approve(Queue, 0) is a separate call even when Queue is paused. Revocation and cancellation take effect only after on-chain confirmation and cannot undo settlement confirmed first. For pending or timed-out transactions, inspect the transaction hash, request, allowance, and payment events before retrying. Run one request/approval workflow per account at a time. Stop and reconnect if the wallet account or network changes.
5. Rates and operational state
getRate() returns the last on-chain checkpoint. Time passing or calling this view does not accrue interest in storage. An authorized Controller poke applies a fixed curve anchored to absolute time:
previewNextRate() returns the projected (newRate, dt) as a view. It is not the rate currently used for deposits/redemptions or a guaranteed payout; pause and update bounds can make the actual poke fail. The rate depends only on absolute elapsed time, so poke cadence does not change the realized APY — a late or irregular poke lands on the same curve, with no drift. aprBps() returns the effective APY in bps (2%). Integer rounding only shaves sub-unit amounts at the floor of the rate.
Rate updates and withdrawal settlement are keeper operations. Users cannot call Controller poke, Keeper poke/redeemSolve, or Queue solve with ordinary permissions. See the Kairos testing section for manual test coordination.
The example’s
getStatus() reads balances, rates, requests, and pauses at one block. sharesLocked indicates only the withdrawal request lock for this (VAULT, ASSET) pair, not whether every transfer restriction passes. Successful reads do not guarantee a write will pass cap, whitelist, deny-list, liquidity, or other checks. Simulate writes and verify receipts as shown.
Default rate-update cadence
The regular keeper operating target is an update every 24 hours for USDT (6 decimals) and every hour for JPYC (18 decimals). JPYC’s higher rate precision makes integer rounding at short intervals much smaller. In both cases,getRate() stays unchanged between successful checkpoints. Deposits, withdrawal requests, and reads do not automatically poke; withdrawal settlement is a separate operation. These intervals are not withdrawal waiting periods or completion guarantees.
See the architecture page’s default keeper operating rules for asset-specific cadences, execution checks, delay/failure handling, and rounding comparisons. Read lastPokeAt() and the rate to confirm updates; do not show projected rates or scheduled times as confirmed values. The cadence is off-chain operational configuration, not automatic execution selected by contract decimals. Both Controller’s minInterval and Accountant’s minimumUpdateDelayInSeconds must permit it, and APR changes can create an additional checkpoint. Coordinate manual rate-update and settlement tests with the team on Kairos, which has no automated keeper.
6. Events and failure handling
The full ABI JSON includes events and custom errors. Index events at the specified contract address withgetLogs or watchContractEvent. Choose an indexing start block for the selected deployment. For Kairos integration tests, use verifiedBlock from the downloadable deployment JSON.
Router uses Teller
bulkDeposit, so the Teller Deposit event is not emitted. Distinguish Teller BulkDeposit from Router Deposited.
EarnUSDT integration uses the separate SuperEarnRouter/CooldownVault product. Do not mix those interfaces with this VedaRouter/AtomicQueue flow.
7. Test on Kairos
On Kairos testnet (chain ID 1001), use mockUSDT (mUSDT, 6 decimals) as ASSET. mockUSDT and testnet vault shares have no economic value. The test deployment connects mockUSDT to a single-asset CustomVault.
- RPC:
https://public-en-kairos.node.kaia.io(official Kaia RPC reference). - Get test KAIA for gas from the Kaia Faucet. Obtain mockUSDT separately by calling
mintbelow. - Verified 2026-10-14, block 230316651. Initial APR 200 bps = 2%, unlimited supply cap, whitelist off, Teller share-lock period zero. Governance may change settings; read them when integrating.
asset, vedaRouter, boringVault, accountant, atomicQueue, and the other deployment JSON keys.
Select Kairos in your wallet, then call mockUSDT.mint(receiver, amount) or use the helper:
mint has no role restriction, daily limit, or approval requirement. You can obtain test tokens without team approval. mockUSDT does not support EIP-2612 permit; use approve followed by deposit. The helper performs the approval for you. Test minting is available only for the published Kairos mockUSDT.
There is no automated keeper on testnet. You can mint, deposit, submit/cancel withdrawal requests, and read state yourself. To test interest updates (
poke) or completed redemptions, contact the SuperEarn team to coordinate an integration test. Email contact@superearn.io with your Kairos wallet address, request transaction hash if available, amount, and preferred time. Waiting alone does not update the rate or fulfill a withdrawal.1_000_000 and a 2% target APY, the rate first increases by one unit about 1,593 seconds (26 minutes 33 seconds) after the anchor. The rate is a function of elapsed time since the anchor, so earlier pokes do not delay this; they can advance lastPokeAt without changing the rate.
mockUSDT generates no yield. The team prepares mock liquidity for interest payouts and confirms the settlement schedule.