메인 콘텐츠로 건너뛰기
VedaVault는 Veda의 BoringVault를 기반으로 하는 SuperEarn 고정 APR 볼트 스택입니다. 이 페이지의 VAULT는 BoringVault 주소이자 ERC-20 share 토큰입니다. 사용자는 VedaRouter에 입금하고 AtomicQueue에 직접 출금 요청을 보냅니다. BoringVault 자체에 ERC-4626 deposit/redeem/previewRedeem을 호출하는 방식이 아닙니다. 입금·출금 요청·환율 조회 흐름은 네트워크에 관계없이 동일합니다. 아래에서 연동할 네트워크와 해당 컨트랙트 주소를 선택하세요. ASSET은 해당 배포의 기초 자산 토큰이며, Kairos에서는 mockUSDT를 사용합니다. 구조와 APR 반영 원리는 고정이율 볼트 구조와 환율 갱신을 먼저 참고하세요. 기본 정기 환율 갱신의 운영 목표는 USDT(6 decimals) 24시간마다, JPYC(18 decimals) 1시간마다입니다. 이 페이지는 실제 연동 호출을 설명하며, 자동 keeper가 없는 Kairos에서는 갱신을 팀과 별도로 조율합니다.

1. 네트워크와 컨트랙트 주소

메인넷 컨트랙트 주소는 정식 배포 후 추가할 예정이며, 현재 메인넷 주소 칸은 비워 두었습니다. 선택한 네트워크의 주소를 사용하세요. Router·Keeper·Gateway·CustomVault·Controller는 Transparent proxy이며, 표의 proxy 주소로 연동합니다. 입금된 자산은 BoringVault에 보관됩니다. keeper는 Gateway를 통해 CustomVault로 자산을 이동하거나 출금 유동성을 회수할 수 있습니다. Gateway는 CustomVault shares를 보유하고 입금 중 기초 자산을 일시 보유하며, 정상 CustomVault 출금은 BoringVault로 직접 지급됩니다.

2. 연동 경로와 단위

Router가 내부적으로 BoringVault에 승인하고 Teller의 bulkDeposit을 호출합니다. 사용자가 Teller 또는 BoringVault에 underlying을 승인하지 않습니다. Teller 직접 deposit, depositWithPermit, bulkDeposit은 일반 사용자에게 열려 있지 않습니다. approve 후 deposit을 호출하세요.
  • 기초 자산: ASSET.decimals()를 조회하세요. 6 decimals인 경우 1 token = 1_000_000 최소 단위입니다.
  • BoringVault shares: 18 decimals. 1 share = 10^18.
  • R = Accountant.getRate()는 1e18 share 최소 단위(1 share)당 ASSET 최소 단위입니다. 이 연동에서 ASSET은 Accountant의 base 자산입니다. JPYC 기반 rate는 18 decimals, USDT 기반 rate는 6 decimals이며, shares는 두 경우 모두 18 decimals입니다. 1 share = 1 자산이면 JPYC rate는 10^18, USDT rate는 10^6입니다. 현재 값은 Accountant에서 조회하세요.
  • 입금 견적: shares = floor(assets × 10^18 / R).
  • 확정 환율 기준 가치: assets = floor(shares × R / 10^18).
  • 실제 요청 지급액: floor(offerAmount × atomicPrice / 10^18).
모든 계산은 bigint로 하고 금액을 JavaScript number로 변환하지 마세요. 아래 예제는 입금 최소 수령량에 10 bps(0.1%) 허용 범위를 적용합니다. 이는 예제의 선택이며 앱 정책에 맞게 조정하세요. minimumShares = 0은 사용하지 않습니다. 이 입금용 허용 범위를 출금 atomicPrice에 적용하지 마세요. 출금 가격을 낮추면 실제 지급액이 줄어듭니다.

승인 대상과 금액

approve는 토큰 컨트랙트에 호출하고, spender에 지출 권한을 줄 컨트랙트를 넣습니다. 승인만으로 입금이나 출금 요청이 생성되지는 않습니다. 예를 들어 6-decimal 자산 100개를 입금하면 ASSET.approve(Router, 100_000_000), 10 shares 출금은 VAULT.approve(Queue, 10 × 10^18)입니다. 출금하려고 ASSET을 Queue에 승인하지 마세요. 사용자 지갑의 기초 자산을 추가로 가져가거나 부족분을 청구하는 경로가 아닙니다.
무제한 승인이나 이번 입금·요청량을 초과하는 승인을 사용하지 마세요. 전액 입금·출금이라면 그 금액과 같은 유한 승인은 가능합니다. 기존 allowance가 필요량보다 크면 이를 그대로 재사용하지 말고 approve(spender, 0)을 확정한 뒤 필요한 양만 승인하세요. allowance는 만료일이 없으며, 요청의 deadline과 별개입니다. 토큰을 나중에 다시 받아도 잔여 allowance가 적용됩니다. 최소 승인은 잘못된 spender, 계약 취약점, 권한·업그레이드 변경으로 인한 노출을 제한하며 모든 위험을 없애지는 않습니다.
  • 지갑에서 chain ID, 토큰 주소, spender, 수량을 확인하세요. 이름·심볼만으로 확인하지 마세요. 공개된 해당 네트워크의 주소만 사용하고, 메인넷 주소가 공란인 동안 임의의 주소나 QA 주소로 대체하지 마세요.
  • 이 연동을 위해 Teller, Solver, VedaKeeper, Gateway, 운영자 EOA에 토큰을 승인하거나, BoringVault shares를 Router에 승인할 필요는 없습니다. 특히 EOA를 spender로 지정하면 그 키 보유자가 transferFrom을 직접 호출할 수 있습니다. 잘못 승인했다면 그 토큰에서 실제 잘못된 spender의 allowance를 0으로 바꾸세요. 아래 helper는 지정된 Router/Queue 승인만 정리합니다.
  • 토큰을 Router·Vault·Queue·Keeper 주소로 단순 transfer하지 마세요. 자동 입금·출금 처리가 되지 않으며 shares 발행이나 자동 반환도 보장되지 않습니다. 입금에는 deposit, 새 출금 요청에는 safeUpdateAtomicRequest, 취소에는 updateAtomicRequest를 사용하세요.
  • permit 서명도 지출 권한입니다. 앱이 permit을 별도로 지원한다면 동일한 토큰·체인·spender·금액과 서명 deadline을 확인하세요. permit deadline은 서명 제출 기한이며 제출 후 생긴 allowance의 만료일이 아닙니다. 이 예제는 approve를 사용합니다.

비동기 요청과 원자적 체결

입금은 승인 트랜잭션 → Router deposit 트랜잭션입니다. deposit 안에서 자산 이동과 shares 발행이 함께 성공하거나 되돌려집니다. 이미 성공한 approve는 이후 deposit 실패·서명 거절로 되돌려지지 않습니다. 출금은 share 승인 → 요청 등록 → 나중에 keeper 체결입니다. 요청을 등록해도 shares는 지갑에 남습니다. 권한 있는 운영자가 VedaKeeper를 호출하면 **AtomicQueue가 spender로서 요청량의 shares를 transferFrom**합니다. 운영자 EOA가 사용자 지갑에서 직접 인출하는 방식이 아닙니다. 이때 shares 상환과 ASSET 지급은 한 체결 트랜잭션 안에서 원자적으로 처리되며, 지급에 실패하면 shares 이동도 되돌려집니다. 한 요청은 전량 체결하며 부분 체결하지 않습니다. 유효한 요청과 충분한 share 잔액·allowance가 있으면 추가 사용자 서명 없이 deadline 이내에 체결될 수 있습니다. 요청 트랜잭션 성공을 출금 완료로 표시하지 마세요. 요청 확인, 체결 대기, 지급 완료를 구분하고, 지급은 AtomicRequestFulfilled와 ASSET 전송으로 확인하세요. keeper 역할 제한이나 transfer lock을 불필요한 allowance를 남겨도 된다는 근거로 삼지 마세요.

3. 실행 가능한 지갑 연동 예제

npm install viem 후 TypeScript 브라우저 앱에서 아래 파일을 사용합니다. 네트워크와 컨트랙트 주소를 담은 FixedRateDeployment를 connectFixedRate에 전달하세요. 입금·출금 요청·취소·조회 메서드는 두 네트워크에서 공통으로 사용합니다. 포함된 kairosDeployment로 바로 테스트할 수 있으며, 메인넷 설정은 주소 공개 후 구성합니다. 최소 ABI가 포함되어 있고 기초 자산의 decimals는 선택한 토큰에서 조회합니다. 입력은 소수점 문자열이며 토큰 정밀도를 넘는 자릿수는 거절합니다. helper는 기존의 더 큰 승인도 0으로 초기화한 뒤 정확한 필요량만 승인합니다. 각 메서드는 트랜잭션 영수증을 기다리므로 approve 확정 후 다음 호출이 실행됩니다. 지갑 계정 또는 네트워크가 바뀌면 다시 연결하세요. 예제는 사용자 본인에게 shares를 발행하며, Router의 receiver를 바꾸어 다른 주소에 발행할 수도 있습니다. deployment.json을 직접 설정으로 변환할 때는 주소 키를 다음과 같이 매핑하세요. asset, accountant, controller, teller는 같은 키를 쓰고, router ← vedaRouter, vault ← boringVault, queue ← atomicQueue, keeper ← vedaKeeper입니다. chain은 지갑·RPC와 동일한 viem Chain 설정이어야 합니다. 포함된 kairosDeployment에는 이 매핑이 적용되어 있습니다.
연결한 지갑의 EIP-1193 provider를 provider로 전달합니다(예: 지갑 SDK의 provider 또는 주입형 지갑의 window.ethereum). deployment에는 선택한 네트워크 설정을 전달합니다. 입금 전 해당 기초 자산을 준비하세요. 테스트 토큰 준비는 Kairos에서 테스트하기를 참고하세요.

4. 출금 요청의 정확한 동작

atomicPrice: safe 함수로 실행 시점의 Accountant 환율 저장

atomicPrice는 최소 보장 가격이 아니라 실제 지급액을 결정하는 고정 가격입니다. 신규 요청에는 **safeUpdateAtomicRequest(VAULT, ASSET, request, ACCOUNTANT, 0)**을 사용하세요. 마지막 인자인 discount=0은 할인 없이 요청 트랜잭션 실행 시점의 Accountant 환율을 저장한다는 뜻입니다. 이후 환율이 올라가도 기존 요청 가격은 자동으로 바뀌지 않습니다.
  1. BoringVault shares를 AtomicQueue에 요청 수량만큼 승인하고 확정을 기다리세요.
  2. **공식 배포 주소의 Accountant.getRateSafe()**로 미리보기 가격을 조회하세요. paused이거나 조회가 실패하면 중단합니다. floor(offerAmount × rate / 10^18)로 예상 지급액을 계산하고 수량·가격·지급액을 표시하세요. 가격과 반올림된 지급액은 모두 0보다 커야 합니다.
  3. request.atomicPrice는 0n placeholder로 두고 safe 함수를 호출하세요. safe 함수는 이 필드를 무시하고, 전달한 Accountant의 **getRateInQuoteSafe(ASSET)**를 트랜잭션 안에서 조회해 가격을 덮어씁니다. **ACCOUNTANT에는 반드시 해당 네트워크의 공식 주소, discount에는 0n**을 넣으세요. 임의의 Accountant를 사용자에게 선택하게 하지 마세요. vault() 일치 검사는 공식 Accountant임을 보장하지 않습니다.
  4. 확정 후 getUserAtomicRequest 또는 getStatus()로 저장된 가격과 지급액을 확인하세요. 조회와 실행 사이에 poke가 발생하면 미리보기와 등록 가격이 달라질 수 있습니다. 이미 체결되어 요청이 지워졌다면 등록 영수증의 AtomicRequestUpdated.minPrice와 AtomicRequestFulfilled 이벤트를 확인하세요. 이벤트 필드 이름이 minPrice여도 실제 고정 정산 가격입니다.
예제의 requestWithdraw()는 이 safe 경로를 사용합니다. atomicPrice: 0n은 safe 함수에서만 무시되는 입력입니다. 일반 updateAtomicRequest로 수량이 있는 요청에 가격 0을 넣으면 무상 양도를 허용할 수 있으므로 절대 그렇게 사용하지 마세요. safe 함수는 요청량 대비 잔액·allowance, deadline, 양수 수량, Accountant의 vault 일치, discount 상한과 uint88 가격 범위를 검증합니다. Queue 또는 Accountant가 paused이면 실패합니다. 그러나 자동 poke, 향후 지급 보장, 기존 요청 재가격, 0으로 반올림되는 지급액 방지 기능은 아닙니다. discount는 bps가 아니라 1,000,000 기준 비율이며 상한은 10,000(1%)입니다. 이 연동에서는 항상 0을 사용하세요. ASSET이 Accountant의 base인 현재 구성에서는 getRateInQuoteSafe(ASSET)와 getRateSafe()의 단위·값이 같습니다. 다른 quote 자산을 조회해야 할 때에는 전자를 쓰되, 이 출금 연동의 지원 pair를 임의로 변경하지 마세요. 일반 updateAtomicRequest도 취소를 위해 공개되어 있습니다. 이 함수에 수량을 넣어 직접 등록하면 입력 가격을 그대로 저장하므로 safe 경로가 강제되는 것은 아닙니다. 예를 들어 현재 USDT rate가 1_000_000, 요청량이 100 × 10^18일 때: 두 번째 요청은 나중에 rate가 1_010_000이 되어도 50 USDT로 정산됩니다. 잘못된 요청은 아래 취소·승인 철회 절차를 따르고 safe 경로로 다시 등록하세요. 이미 먼저 체결된 지급은 되돌릴 수 없습니다.

요청 상태와 잠금

  • 요청은 (user, VAULT, ASSET)당 1개입니다. 같은 pair에 다시 쓰면 누적되지 않고 덮어씁니다. 예제는 재승인 전에 만료된 요청을 포함한 기존 저장 요청을 취소하게 합니다. 이 연동이 지원하는 pair는 (VAULT, ASSET) 하나뿐이며, 임의의 offer/want 토큰을 사용하지 마세요.
  • updateAtomicRequest는 잔액·승인·가격을 모두 검증해주지 않습니다. 수량은 보유량과 allowance 이하, atomicPrice > 0, 예상 지급액도 0보다 크게 확인해야 합니다. 가격 0 요청은 무상 양도를 허용하므로 절대 생성하지 마세요.
  • 요청 시 shares는 지갑에 남습니다. 이 (VAULT, ASSET) pair에 활성 요청이 있으면 해당 지갑의 모든 outbound share transfer·transferFrom이 차단됩니다. 요청 수량만 부분적으로 잠그는 방식이 아닙니다. Queue의 체결 전송은 예외이며 Teller deny-list 검사는 유지됩니다.
  • 요청 취소·체결 또는 block.timestamp > deadline이면 이 요청에 의한 잠금은 해제됩니다. 만료는 storage 삭제·자동 체결을 뜻하지 않습니다. Teller deny-list 등 다른 제한은 별개입니다.
  • 예제의 만료는 7일입니다. 이는 대기 기간이나 체결 보장이 아닙니다. 만료 후에도 출금을 원하면 요청을 재등록하세요.
  • Queue가 paused이면 취소도 revert합니다. 팀에 unpause를 요청하세요. share allowance를 0으로 만들면 체결을 막을 수 있지만, 활성 요청의 transfer lock은 취소·만료 전까지 유지됩니다. 취소와 체결이 경합하면 먼저 확정된 트랜잭션이 적용됩니다.
  • offerAmount == 0만으로 미생성·취소·체결을 구별할 수 없습니다. AtomicRequestFulfilled 이벤트와 자산 잔액으로 체결을 확인하세요. 출금 수신자는 요청자 본인이며 별도 receiver 인자는 없습니다. 래퍼 컨트랙트가 요청하면 그 컨트랙트가 shares를 보유·승인하고 지급도 받습니다.

요청 취소와 승인 철회

취소에는 updateAtomicRequest로 offerAmount=0을 등록합니다. safe 함수는 수량 0을 거부하므로 취소에 사용할 수 없습니다. 두 작업은 별도 트랜잭션입니다. cancelWithdraw()는 요청만 지우고 취소 영수증을 반환합니다. revokeQueueAllowance()는 BoringVault의 Queue allowance만 0으로 만듭니다.
allowance 철회는 요청을 삭제하거나 share transfer lock을 풀지 않습니다. 취소·만료 전에는 잠금이 유지되고, 만료 전 다시 승인하면 저장된 옛 가격의 요청이 다시 체결 가능해집니다. 재승인 전에 요청을 조회하고, 더 이상 원하지 않는 요청을 취소하세요. 예제는 만료된 요청도 명시적으로 지운 뒤 새 승인을 진행하도록 합니다. Queue가 paused여도 토큰의 approve(Queue, 0)은 별도 호출입니다. 다만 철회·취소는 온체인 확정 후에만 효과가 있고, 이미 먼저 체결된 요청을 되돌릴 수 없습니다. 전송 중·타임아웃 상태에서는 트랜잭션 해시와 요청·allowance·지급 이벤트를 확인한 뒤 재시도하세요. 요청·승인 작업은 계정당 한 번에 하나만 진행하고, 지갑 계정이나 네트워크가 바뀌면 진행을 중단하고 다시 연결하세요.

5. 환율과 운영 상태 조회

getRate()는 마지막 온체인 확정값입니다. 시간이 지나도 호출만으로 이자가 반영되지 않습니다. 권한이 있는 운영자의 Controller poke가 절대 시간에 anchor된 고정 곡선을 Accountant에 반영합니다.
previewNextRate()는 현재 시점의 예상 (newRate, dt)를 반환하는 view 함수입니다. 입출금에 사용되는 확정 환율이나 지급 약속이 아니며, pause·업데이트 한계 조건에 따라 실제 poke는 실패할 수 있습니다. 환율이 절대 경과 시간에만 의존하므로 poke 주기는 실현 APY를 바꾸지 않습니다 — 늦거나 불규칙한 poke도 같은 곡선 위에 놓이며 drift가 없습니다. aprBps()는 실효 APY를 bps로(2%) 반환합니다. 정수 절삭은 rate 바닥에서 단위 미만만 깎습니다. 환율 갱신과 출금 체결은 keeper의 역할입니다. 일반 사용자 권한으로 Controller poke, Keeper poke/redeemSolve, Queue solve를 호출할 수 없습니다. 수동 테스트 일정 조율은 Kairos 테스트 안내를 참고하세요. 예제 getStatus()는 한 블록 기준 잔액·환율·요청·pause를 조회합니다. sharesLocked는 이 (VAULT, ASSET) pair의 출금 요청 잠금만 나타내며 모든 transfer 가능 여부를 보장하지 않습니다. 상태 조회에 성공해도 cap·whitelist·deny-list·유동성 등의 이유로 쓰기는 실패할 수 있으므로, 예제처럼 제출 전 simulateContract와 실제 receipt를 확인하세요.

기본 환율 갱신 주기

정기 keeper 운영 목표는 USDT(6 decimals) 24시간마다, JPYC(18 decimals) 1시간마다 환율을 갱신하는 것입니다. JPYC는 rate의 정밀도가 높아 짧은 간격의 정수 절삭 영향이 매우 작습니다. 두 경우 모두 getRate()는 성공한 checkpoint 사이에서 동일하게 유지됩니다. 입금·출금 요청·조회가 자동으로 poke하지 않으며, 출금 체결도 별도 작업입니다. 이 주기는 출금 대기 기간이나 완료 보장 시간이 아닙니다. 구조 페이지의 기본 keeper 운영 규칙에서 자산별 주기, 실행 전후 확인, 지연·실패 대응, 정수 절삭 비교를 확인하세요. 실제 확정 여부는 lastPokeAt()과 rate를 읽어 판단하고, 예상 rate와 다음 예정 시각을 확정값으로 표시하지 마세요. 주기는 오프체인 운영 설정이며 decimals를 보고 컨트랙트가 자동 실행하지 않습니다. Controller minInterval과 Accountant minimumUpdateDelayInSeconds가 해당 주기를 허용해야 하고, APR 변경 시 별도 checkpoint가 발생할 수 있습니다. Kairos에서는 자동 keeper를 운영하지 않으므로 팀과 수동 갱신·체결 테스트를 조율합니다.

6. 이벤트와 실패 처리

전체 ABI JSON에는 아래 이벤트와 custom error가 포함되어 있습니다. getLogs 또는 watchContractEvent로 해당 주소의 이벤트를 읽고, 선택한 배포에 맞는 인덱싱 시작 블록을 설정하세요. Kairos 연동 테스트에서는 다운로드한 배포 JSON의 verifiedBlock을 사용할 수 있습니다. Router는 Teller bulkDeposit을 사용하므로 Teller Deposit 이벤트는 발생하지 않습니다. Teller의 BulkDeposit과 Router의 Deposited를 구분하세요. EarnUSDT 연동은 별도 상품의 SuperEarnRouter·CooldownVault 흐름입니다. 이 페이지의 VedaRouter·AtomicQueue 흐름과 ABI를 혼용하지 마세요.

7. Kairos에서 테스트하기

**Kairos 테스트넷(chain ID 1001)**에서는 ASSET으로 **mockUSDT(mUSDT, 6 decimals)**를 사용합니다. mockUSDT와 테스트넷 볼트 shares는 경제적 가치가 없습니다. 테스트 배포는 mockUSDT와 단일 자산 CustomVault를 연결합니다.
  • RPC: https://public-en-kairos.node.kaia.io (Kaia 공식 RPC 안내).
  • 가스용 테스트 KAIA는 Kaia Faucet에서 받으세요. mockUSDT는 아래 mint 호출로 별도로 발행합니다.
  • 배포 검증: 2026-10-14, 블록 230316651. 초기 APR 200 bps = 연 2%, supply cap 제한 없음, whitelist 꺼짐, Teller share-lock 기간 0. 설정은 거버넌스가 변경할 수 있으므로 연동 시 조회하세요.
아래에서 Kairos 주소, 전체 ABI, 본문과 동일한 TypeScript 모듈을 내려받을 수 있습니다. ABI 번들은 asset, vedaRouter, boringVault, accountant, atomicQueue 등 배포 JSON과 동일한 키를 사용합니다. 지갑에서 Kairos를 선택한 뒤 mockUSDT.mint(receiver, amount)를 호출하거나 다음 helper를 사용하세요.
mint는 권한·일일 한도·승인 없이 호출할 수 있어 팀 승인 없이 테스트 토큰을 준비할 수 있습니다. mockUSDT는 EIP-2612 permit을 지원하지 않으므로 approve 후 deposit을 호출합니다. helper를 사용하면 승인을 자동 처리합니다. 테스트 mint 기능은 공개된 Kairos mockUSDT에만 사용합니다.
테스트넷에는 자동 keeper를 운영하지 않습니다. mockUSDT 발행·입금·출금 요청·취소·조회는 직접 테스트할 수 있습니다. 이율 반영(poke) 또는 redeem 체결까지 확인하려면 SuperEarn 팀에 연락하여 연동 테스트를 진행하세요. contact@superearn.io에 Kairos 지갑 주소, 요청 트랜잭션 해시(있는 경우), 테스트할 수량, 희망 일정을 보내주세요. 단순히 기다리는 것으로 환율이 갱신되거나 출금이 체결되지는 않습니다.
이자까지 포함해 redeem을 테스트하려면 팀의 poke → 최신 rate 조회 → 새 요청 → 팀 체결 순서로 진행하세요. 기존 요청은 취소하고 새로 등록합니다. 초기 rate 1_000_000, 목표 APY 2%에서는 anchor 이후 약 **1,593초(26분 33초)**가 지나야 rate가 처음으로 1 증가합니다. rate는 anchor 이후 경과 시간의 함수이므로 그 전에 poke해도 이 시점이 늦춰지지 않으며, 환율은 그대로인 채 lastPokeAt만 갱신될 수 있습니다. mockUSDT는 자체 이자를 만들지 않습니다. 팀이 이자분 지급에 필요한 mock 유동성을 준비하고 체결 일정을 안내합니다.