투자 라이프사이클
작업 진행 중
이 페이지는 v3.0 투자 플로우를 설명한다. 백엔드 구현이 진행 중이다. v2.x 플로우(AS/FUND_POOL 분리)는 폐기됐다.
입금에서 LP 민팅, 활성 상태, 수익, 상환까지의 전 과정이다. 설정과 무관하게 모든 풀이 같은 플로우를 쓴다(AS_POOL / FUND_POOL 분리가 없다). 풀마다 다른 동작은 풀 차원에서 나온다.
이 페이지는 투자자의 여정, 즉 무슨 일이 어떤 순서로 일어나는지를 다룬다. 각 단계는 무슨 일이 일어나는지 말하고, 그 메커니즘을 소유한 페이지로 연결한다. 자금 이동은 23-money-path, 수수료 계산은 04 → 수익 동작 방식, 락업과 패널티 규칙은 07-redemption이다(v3-115).
역할: 🔵 투자자 · 🟢 시스템(자동) · 🔴 어드민 · 🟠 파트너 · 🟡 오라클
1단계: 입금
통합된 입금 플로우
v3.0에는 모든 풀에 대해 입금 플로우가 하나뿐이다. 기초 펀드를 어느 파트너가 운영하든 Aset이 항상 입금 시점에 LP 토큰을 민팅한다. Receipt NFT도 없고 D+7 환불 창도 없다.
① 투자자가 지갑을 연결하고 KYC를 통과한다 🔵 Investor
SumSub KYC를 거치면 SBT(Soulbound Token, 전송 불가 온체인 자격증명)가 온체인에 민팅되고 kyc_status = APPROVED가 된다.
풀의 kyc_level_required가 KYC_ONLY면 INDIVIDUAL SBT가 있어야 한다. KYB_ONLY면 INSTITUTION SBT가 있어야 한다. EITHER면 둘 다 된다.
② 투자자가 풀을 고르고 금액을 입력한다 🔵 Investor
시스템이 확인하는 것:
is_paused = false이고is_emergency_frozen = falsekyc_status = APPROVED이고 레벨이 맞을 것- 풀의
lifecycle_status가 입금을 허용할 것(ACTIVE만 가능하다. 온체인whenActive모디파이어가 그 외 모든 상태에서PoolNotActive로 revert한다.UPCOMING은 상세는 보여 주되 Invest 버튼이 비활성이고,IMPAIRED/WIND_DOWN/CLOSED/MATURED는 v3-12에 따라 신규 입금을 거부한다) - 투자자 지갑에 풀의
accepted_currencies잔액이 충분할 것
NAV 모델은 상각 중에도 투자를 열어 둔다. 자동 차단은 없다. 풀 모델 → 허용 스테이블코인 참조.
입금이 막히는 이유는 둘이고 서로 다르다. is_showcase = true(v3-40 마케팅 등급. 보이지만 여기서는 투자 불가)와 custody_mode = 'MIRROR'(이 풀의 자본이 우리 수탁 아래 있지 않아서 어느 방향으로도 경로가 없다)다.
③ 스테이블코인이 Pool 컨트랙트로 이동한다 🟢 System
투자자가 PlatformPool.deposit(stablecoin, amount)를 직접 호출하고 직접 서명한다. Aset 키는 관여하지 않는다. 이 트랜잭션이 스테이블코인을 가져오고, 게이트를 확인하고, 소수 자릿수를 정규화하는 일을 한 번에 한다. 게이트 목록, 연산 순서, revert 이름은 23-money-path §3a와 08a에 있다.
어떤 스테이블코인을 썼는지는 deposits.currency에 기록된다.
④ LP 토큰이 투자자에게 민팅된다 🟢 System
입금과 atomic하다(같은 트랜잭션). 투자자는 즉시 LP를 받고, 가격은 전액 입금액 기준으로 매겨진다. reserve 분리가 투자자의 청구권을 희석하지 않는다(v3-56). 수량, 가격, 예고 NAV인 경우는 23-money-path §3b 참조.
v3.0에는 Receipt NFT가 없다. LP 토큰 자체가 입금 증빙이다.
⑤ 자금이 reserve와 파트너 몫으로 나뉜다 🟢 System
풀은 입금액의 reserve_bps만큼을 reserveBalance로 남기고, 나머지를 같은 트랜잭션에서 fund_wallet으로 릴리스한다. fund_wallet은 항상 파트너가 통제하며 Aset 트레저리가 아니다(v3-26). 분리 계산, 반올림, 그 뒤 돈이 어디로 가는지는 23-money-path §3 참조.
⑥ 입금이 플랫폼에 기록된다 🟢 System
Pool이 Deposited(investor, stablecoin, amount, lp_amount)를 발생시킨다. 입금은 두 번 기록된다. 일반 경로는 POST /deposits이고(트랜잭션이 확정되면 프론트엔드가 호출한다), 온체인 인덱서가 약 2분 주기 정합기 역할을 한다. 어느 쪽도 오프체인에서 LP를 크레딧하지 않는다. 상세는 11-db-schema → deposits와 23-money-path §3f.
취득원가를 보관한다(0125). portfolio_positions.entry_price는 홀더가 LP 토큰당 얼마를 냈는지를 기록하고, 포지션별 손익은 이것을 기준으로 계산한다. effective_value는 현재 평가액이라 포지션이 바뀔 때마다 덮어써진다. 이 값은 가중평균이다. 추가 매수나 재투자가 있으면 각자의 금액 / 민팅된 토큰이 섞여 들어가므로, 실제로 체인이 부과한 가격이 쓰인다(하락 NAV가 큐에 있는 동안에는 nav_per_token이 아니라 예고 NAV다. v3-111). 부분 상환은 이 값을 건드리지 않고, 전량 이탈은 행을 삭제한다. NULL이면 취득원가가 기록에 없다는 뜻이고 손익을 표시하지 않는다.
⚠️ 0125 전까지 이 단계가 부르던 이름이자 v3-115가 누락으로 기록한 nav_at_investment는 스키마에 존재한 적이 없다. 추가하지 않는다. 필드는 entry_price다.
파트너에게 밀어 주는 것은 없다
플로우는 Aset 자신의 기록에서 끝난다. 파트너 webhook도 없고 입금 시 호출하는 파트너 엔드포인트도 없다. 펀드매니저가 플랫폼을 읽는다(GET /deposits가 fund 범위이고 deposit_confirmed_ops가 fundManagers를 담는다). 파트너 push 개념은 대기 중이 아니라 폐기됐다. 발표됐지만 만들어지지 않은 것에 대한 기록은 v3-115에 있다.
풀별로 달라지는 동작
1단계 이후 풀 동작은 차원에 따라 달라진다.
reserve_bps(기본 1000 bps = 10%, 설정 가능)fund_wallet(파트너 잔여분이 가는 곳)operating_currency(USD인지 파트너 통화인지)tranche_group_id+tranche_role(v3-14. 연결된 풀들이 트랜치 그룹을 이룬다. 단독 풀은 NULL)- 그 외
전체 차원 목록은 풀 모델 참조.
2단계: 수익
수익은 기초 펀드의 운영(대출 상환 등)에서 나와 Pool 컨트랙트를 거쳐 투자자에게 돌아온다.
수익 흐름 한눈에 보기
파트너가 총 수익을 입금하고 → Aset Lambda가 수수료를 뺀 net을 계산하고 → LP 홀더에게 분배하고 → 투자자가 청구하거나 재투자한다.
① 파트너가 총 수익을 입금한다 🟠 Partner
파트너가 자기 fund_wallet에서 Pool.depositYield(gross_amount)를 호출한다(role 제한: YIELD_DEPOSITOR_ROLE이 풀의 fund_wallet에 부여된다. Aset 직접 풀은 없다, v3-26).
Pool이 gross_amount를 보유하고 YieldDeposited(gross_amount)를 발생시킨다.
② Aset Lambda가 net 수익을 계산한다(오프체인) 🟢 System
Lambda가 YieldDeposited를 받아 풀의 net_yield_fee_config를 읽고 net = gross − Aset 수수료 − FM 수수료를 계산한다. 수수료 설정이 없는 풀은 net = gross다.
계산 내용(어떤 수수료가 무엇에 붙는지, 성과 hurdle, 예시)은 04 → Net Yield 계산과 23-money-path §4에 있다. 오프체인으로 하는 이유는 수수료 조건이 파트너마다 다르고, DB 설정 변경이 컨트랙트 업그레이드보다 낫기 때문이다.
③ 기간을 정산한다. 홀더의 net과 수수료 두 갈래를 한 트랜잭션에 🟢 System
Lambda가 Pool.settleYield(stablecoin, treasury_fee, pool_mgmt_fee)를 호출한다. 이 한 트랜잭션에서 net이 홀더가 받을 몫을 채우고 수수료 두 갈래가 함께 지급된다. 별도 수수료 호출도 없고 반쯤 정산된 기간도 없다(v3-102). 각 갈래의 목적지는 04 → 수수료 목적지 참조.
⚠️ net_amount는 더 이상 인자가 아니다(v3-131 (3), 2026-08-20). 홀더가 받을 몫은 시간이 결정하므로 호출자가 명시할 것이 없다. net = unclaimedYield − 수수료로 온체인에서 유도한다. 옛 인자는 검증할 수 없는 값이기도 했다. 그에 대한 모든 검사가 상한선이었는데, 스케일이 잘못된 값은 상한선을 언제나 통과한다(08 → 인자 축 참조). 발생 채무가 흡수하지 못하는 현금은 다음 기간의 선납분으로 unclaimedYield에 남는다.
트랜치 그룹에 속한 풀(v3-14)은 Lambda가 각 풀을 정산하기 전에 트랜치 그룹 워터폴을 적용하고, 각 풀의 NAV를 따로 갱신한다.
④ 투자자가 수익을 청구하거나 재투자한다 🔵 Investor
Pool.claimYield(stablecoin)은 파트너가 실제로 자금을 넣은 만큼(claimableYield(investor))에서 지급하고, raw 단위 미만의 잔돈은 청구 가능한 채로 남긴다. 발생했지만 자금이 들어오지 않은 수익은 현금이 아니라 청구권이므로, 이 호출은 NoYieldToClaim이 아니라 YieldAccruedButUnfunded로 revert한다. 연체 중인 풀의 투자자에게 "번 게 없다"고 말하면 안 되기 때문이다. unfundedYield(investor)가 나머지 절반이고, pendingYield(investor)가 둘의 합이다.
Pool.reinvest(stablecoin, amount)는 그 수익으로 새 LP를 민팅한다. 아래 Reinvest V1 정책 참조.
LP 전송 시 수익 정산
왜 중요한가
수익은 LP 토큰 단위로 시간에 따라 발생 인덱스 형태로 쌓인다. LP 토큰이 정산 없이 투자자 사이를 이동하면, 판 사람의 미청구 수익이 토큰을 따라 산 사람에게 잘못 넘어가거나, 산 사람이 자기가 보유하기 전 기간에 대해 지급받게 된다. 잔액이 움직이기 전에 추적 중인 잔액으로 정산하는 것이 이를 막는다.
LP 토큰이 투자자 사이를 이동할 때(세컨더리이며 허가가 필요 없다. 화이트리스트는 없고 pause만 게이트다, v3-58), LP 토큰의 전송 훅(_update)이 추적 잔액이 바뀌기 전에 Pool의 **onLpTransfer(from, to, value)**를 호출한다. 그러면 Pool이 다음 순서로 처리한다.
- 시계를 앞으로 돌린다(
accrue). 민팅과 소각이 발생 기준을 움직이고, 풀 보유 LP의 소각은 어떤 홀더도 건드리지 않으므로, 이 경로의 다른 어떤 것도 방금 끝난 기간을 기록하지 않는다. - 각 당사자의 구간을 그 사람의 이전 잔액으로 가격 매기고 커서를 다시 맞춘다. 판 사람은 자기가 번 것을 유지한다. 그 금액은 청구 버킷에 들어가고 잔액이 0이 돼도 남으므로, 떠난다고 해서 미펀딩 청구권을 몰수당하지 않는다.
- 민팅이나 소각이면 공급량 미러(
totalLpSupply)를 움직인다.
🔴 순서가 곧 계약이고, 3번이 여기 있는 것은 의도적이다. 발생 기준과 청구 가능 주소 집합을 같은 훅이 관리하므로 둘이 어긋날 수 없다. 예전에는 크레딧 대상 집합은 이 훅에 있고 분모는 분배 시점에 LP 토큰에서 다시 계산했다. 에스크로된 LP가 후자에는 들어가고 전자에는 없어서, 그 몫이 아무에게도 크레딧되지 않고 사라졌다.
예시. Alice가 LP 100개를 보유하고 $5가 쌓인 상태에서 100개를 전부 Bob에게 전송한다.
- ❌ 정산이 없으면: Bob이 Alice가 번 구간까지 포함해 전 구간에 대해 지급받는다.
- ✅ 정산이 있으면: Alice의 $5는 그에게 적립되고 잔액이 0이어도 청구 가능한 채로 남는다. Bob의 커서는 지금부터 시작하므로 LP를 보유한 시점부터만 지급받는다.
결과적으로 수익은 항상 실제 보유자를 따라간다. 입금과 상환(Pool을 상대로 하는 민팅·소각)도 같은 훅으로 정산된다.
에스크로는 이탈이 아니다
상환을 위해 LP를 잠그는 것은 Pool로의 전송이라 이 훅을 탄다. 그리고 그 LP는 인식되는 기준에서 빠져 poolHeldLp로 간다. 수익이 멈춰서가 아니라(멈추지 않는다) 그 수익을 에스크로가 끝날 때 요청 단위로, 채무와 크레딧을 한 순간에 기록하기 때문이다. totalLpSupply는 그대로다. 소각된 게 없기 때문이다. ⚠️ 따라서 에스크로가 열려 있는 동안 인식되는 채무는 뒤처진다. 모든 홀더가 요청하고 나면 accrualBasisLp()가 0이 되는데, 만기 후 스케줄이 정확히 그 상황을 만든다. 요청별 차이는 previewEscrowAccrual(requestId)가 보여 준다. 면제되는 것은 없다. 07 → 요청 시점에 수익이 멈추지 않는다와 v3-131 (3) 참조.
연체 추적
next_yield_due는 마지막으로 지급한 기간의 종료일 + yield_frequency다(정산 시각이 아니다. 그렇게 하면 지연이 누적된다). now > next_yield_due이면:
yield_overdue = true(DB 플래그)- 어드민과 투자자에게 알림이 간다
- 어드민에게
settleYield호출을 상기시킨다(수익은 수동 트리거뿐이다. AUTO는 v3-20에서 제거됐다)
yield_frequency는 풀 상세 페이지에서 투자자에게 보이는 공개 약속이다.
수익 정합기(claimable_yield) v3-51
앱의 "청구 가능 수익"은 온체인 전체 합계, 즉 정산된 것과 아직 정산되지 않은 것을 합한 pendingYield(investor) 값을 보여 줘야 한다. DB의 accrued_yield는 정산된 절반만 미러링하므로, 파트너가 온체인에서 직접 분배하거나 투자자가 온체인에서 직접 청구하거나 LP 잔액이 바뀔 때마다 어긋난다.
매시간 도는 정합기가 컨트랙트 변경 없이 이 간극을 메운다. 홀더별 pendingYield를 읽어 portfolio_positions.claimable_yield에 넣고, YieldClaimed 인덱서가 온체인 직접 청구가 있으면 그 홀더를 갱신한다. 표시 규칙: claimable_yield_synced_at이 설정돼 있으면 claimable_yield, 아니면 accrued_yield를 쓴다. 컬럼과 스케줄러는 11-db-schema → portfolio_positions와 v3-51 참조.
Reinvest V1 정책 🟡 BD5
🔴 MVP에서는 어느 화면에도 노출하지 않는다. 2026-08-27 결정(v3-151)으로 재투자를 투자자 앱과 어드민(펀드매니저 포함)에서 뺀다. 아직 반영되지 않았다. 아래 설명하는 CTA는 여전히 화면에 있다.
⚠️ 실제로 막는 것은 UI가 아니라 allow_rollover다. 온체인 Pool.reinvest와 POST /yield/reinvest는 둘 다 남고, DB에서 allow_rollover의 기본값이 false이며(pools.allow_rollover BOOLEAN DEFAULT false) 그것이 reinvest()를 RolloverDisabled로 revert시킨다. 버튼을 없애면 경로가 숨겨지고, 경로를 닫는 것은 기본값이다.
재투자 개요
allow_rollover = true인 풀에서는 투자자가 쌓인 수익을 지갑으로 청구하는 대신 풀에 재투자할 수 있다. 수익은 effectiveNav, 즉 입금과 같은 가격으로 추가 LP 토큰으로 전환된다(v3-111).
핵심 규칙:
- 부분 재투자가 허용된다.
reinvest(stablecoin, amount)는 온체인 청구 가능 잔액 안에서 어떤 금액이든 받고, 초과 요청만 revert된다(InsufficientYield) - 원클릭
[Reinvest $680]이 기본값으로 남고(전액이 미리 채워진다) 금액은 수정할 수 있다 min_reinvest_amount는 풀 설정이다(기본값은 $50과min_investment × 50%중 작은 쪽). 요청 금액을 기준으로 검사하므로 최소액 미만의 부분 재투자는 revert된다(BelowMinReinvest). 잔액 전체가 최소액 미만이면 Reinvest가 비활성화되고 툴팁이 뜬다- 설계상 같은 풀 전용이다.
reinvest()는 그 풀에 쌓인 수익을 소비해 그 풀의 LP를 민팅하며 풀 간 재투자는 없다. 수익을 다른 풀에 넣으려면claimYield()로 지갑에 받은 뒤 그 풀에deposit()한다(재투자가 아니라 일반 입금이다). v3-64 참조
온체인: Pool.reinvest(address stablecoin, uint256 yieldAmount)는 **입금과 정확히 같은 방식으로 effectiveNav**에 새 LP를 민팅한다. 평시에는 현재 NAV이고, 하락이 24시간 타임락에 걸려 있는 동안에는 예고된(더 낮은) NAV다(v3-111 · R2·R3). 따라서 같은 블록에서 같은 금액의 재투자와 신규 입금은 같은 수의 LP 토큰을 받는다. stablecoin은 풀이 받는 통화여야 한다. 재투자 금액 중 reserve가 아닌 몫이 fund_wallet으로 릴리스될 때 쓰는 통화이기 때문이다(입금과의 E1 money-path 정합). 옛 풀에 남아 있는 1인자 형태를 포함한 시그니처 주의사항은 08a 참조.
⚠️ 옛 풀은 여전히 낡은 NAV로 재투자를 가격 매긴다
YieldLib.reinvest가 effectiveNav를 읽게 된 것은 2026-08-06 구현체부터다(v3-111). 입금·상환보다 한 라운드 늦다. 풀은 업그레이드할 수 없는 Clones이고 생성 시점 구현체에 고정되므로, 그 라운드 이전에 만들어진 풀은 하락이 큐에 있는 동안 재투자를 낡은(더 높은) NAV로 민팅해 위 규칙보다 적은 LP를 준다. 이를 마이그레이션하는 것은 없고, 새로 만든 풀만 올바르게 가격을 매긴다. 라운드별 매트릭스는 06 → R2·R3에 있다.
락업: 재투자는 입금과 같은 기준점 규칙을 따른다(v3-130). 살아 있는 보유분의 invested_at은 건드리지 않고, 잔액 0에서 다시 들어오는 경우에만 기준점을 새로 잡는다. BD5는 재투자한 LP를 "락업 면제"라고 불렀는데 대상이 없는 말이었다. 락업은 LP별이 아니라 투자자별 boolean 하나라서, 잔액을 보유한 상태에서는 어느 진입 경로도 기준점을 새로 잡지 않고 면제는 아무것도 바꾸지 않았다. 07 → 3-State Lockup Model 참조.
투자자 UI(allow_rollover = true). ⚠️ MVP에서는 노출하지 않는다(2026-08-27, 여전히 화면에 있음, v3-151):
├─ [Reinvest $680] → 기본 CTA
├─ [Claim to Wallet] → 보조
└─ [Redeem →] → 출금 다이얼로그allow_rollover 설정
풀 생성 시 설정한다. ACTIVE 상태에서 어드민이 타임락 없이 토글할 수 있다(영향이 작은 변경이다). min_reinvest_amount도 어느 lifecycle 단계에서든 수정할 수 있다.
3단계: 상환
투자자가 LP 토큰을 소각하고 USDC를 받아 포지션에서 나간다. NAV는 요청 시점에 스냅샷으로 고정된다. 수익은 별개이고(청구 기반이라 상환 지급액에 포함되지 않는다), 총 상환액은 토큰 가치 − 패널티다.
아래 단계는 즉시 경로(epoch_duration_days = 0)다. 전체 메커니즘(락업 상태, 네 가지 패널티 타입, reserve 분기, 개방형 풀의 회차 플로우)은 상환이 소유한다.
통합 상환 플로우
REQUESTED → COMPLETED(reserve가 모자라면 PENDING_RESERVE)다. PROCESSING은 투자자에게 보이는 표시 라벨일 뿐 저장되는 상환 상태가 아니다.
① 투자자가 상환을 요청한다 🔵 Investor
투자자가 Pool.requestRedemption(lp_amount)를 호출한다. 확인 모달에 토큰 수, NAV 가격, 토큰 가치, 발생 수익, (있다면) 패널티, 총 지급액이 표시된다. LP가 잠기고 status = REQUESTED가 된다.
② 락업 확인 + 패널티 + NAV 스냅샷(같은 tx) 🟢 System
같은 트랜잭션 안에서 컨트랙트가 nav_at_request 스냅샷을 찍고, 투자자 본인의 deposit_time을 기준으로 3-state 락업 검사를 수행하고(LOCKED면 revert, EARLY면 풀의 penalty_type 적용, FREE면 없음) 지급액을 계산한다. 패널티는 reserve가 아니라 fund_wallet으로 간다(v3-85).
상태 정의와 네 가지 패널티 산식은 07 → 3-State Lockup Model에 있다. 락업은 penalty_type과 독립이라 NO_EARLY 풀도 LOCKED일 수 있다(v3-83).
③ reserve가 분기를 결정한다. 승인 시점이 아니라 같은 tx에서 🟢 System
requestRedemption 트랜잭션 자체가 총액에 대해 reserve를 확인한다(v3-82). 충분하면 풀이 LP를 소각하고 USDC를 지급해 그 자리에서 완료한다. 모자라면 LP가 에스크로되고 요청은 PENDING_RESERVE로 대기하다가, 파트너의 fundRedemption이 충당하면 자동으로 정산된다.
⚠️ 정상 플로우에 어드민 승인 단계는 없다. approveRedemption은 reserve가 별도로 늘어난 경우를 위한 선택적 수동 정산으로만 남아 있다. 분기 상세와 무권한 폴백은 07 → 즉시 상환 플로우 참조.
투자자에게 보이는 것
- "Processing": 정산 중
- "Pending partner funds": reserve 부족, 파트너 보충 대기
- "Completed": USDC가 지갑에 들어옴
실패 경로: 어느 단계에서든 FAILED로 갈 수 있고, failure_type과 error_message로 추적한다.
폐기된 필드 (v2.x → v3.0)
escrow_model, lp_issuance_model, pool_type, Receipt NFT, D+7 자동 환불, FM_ACCEPTED 상환 상태는 v3.0에서 모두 제거됐다. 폼에 노출하지 말 것. 대체 항목을 포함한 전체 목록은 04 → 마이그레이션 노트 참조.