Skip to content

실패 유형 레퍼런스

Aset의 각 엔티티에 걸친 모든 실패 상태, 오류 유형, 복구 조치를 모았다. 각 실패 유형에는 apps/infra/db/seed.sql에 대응하는 목 데이터가 있다.

예시일 뿐, 코드가 실제로 쓰는 값이 아니다

이 페이지는 시드·목 데이터 레퍼런스로 작성됐다. 그래서 아래 표의 failure_type 값과 오류 문자열 대부분은 예시이지 백엔드가 실제로 기록하는 값이 아니다. 프론트엔드에서 이 값들로 failure_type 분기를 만들지 말 것.

코드가 실제로 쓰는 값(V1):

  • 입금: failure_type을 쓰는 코드가 없다. 실패한 입금은 status = FAILED만 설정된다(유형화된 사유가 없다).
  • 상환: INVESTOR_CANCELLED(투자자 자가 취소. status = REJECTED로 저장된다)와 거부 시 어드민이 입력한 사유(없으면 REJECTED). 지급 실패 전용 failure_type은 없다.
  • 수익: ON_CHAIN_TX_FAILED(온체인 distribute 트랜잭션 실패. yield-distributions.post.create.ts, lib/shared/yield/run-distribution.ts).
  • 알림: deliver 워커(notifications.worker.deliver.ts)의 INVALID_RECIPIENTSERVER_ERROR, 그리고 실제 SES 이벤트 경로(notifications.webhook.ses.ts)의 BOUNCEDSPAM_FILTERED. 쓰이지 않는 것은 TIMEOUTRATE_LIMITED뿐이다.

아래 표는 런타임 분류 체계가 아니라 시드·목 행에 대한 표시 문구 가이드로 볼 것.

개요

분류개수
입금 상태5
상환 실패5
수익 실패4
KYC/SBT 상태3
알림 실패6
입금 실패 deposits.status + failure_type

입금 처리, 에스크로 관리, LP 토큰 민팅과 그 복구 절차를 다룬다.

상태 흐름: PENDINGPROCESSINGCOMPLETED 또는 FAILED

상태실패 유형오류 예시어드민 표시투자자 표시복구
FAILEDLP_MINT_FAILEDERC20: transfer amount exceeds balance빨간색 "LP Mint Failed" + 오류"입금을 처리하지 못했습니다"어드민이 재시도
FAILEDFM_NOTIFICATION_FAILEDSMTP delivery failed: mailbox unavailable노란색 "FM Not Notified" + 재시도표시 안 함(내부용)어드민이 FM 통지 재시도
PROCESSING파란색 "Processing" + 스피너"입금을 처리하고 있습니다"1시간 넘게 멈춰 있으면 자동 에스컬레이션
상환 실패 redemption_requests.status + failure_type

락업 검증과 reserve·펀딩 확인을 포함한 워크플로다. FM 사전 확인 단계는 없다(FM_ACCEPTEDv3-34에서 제거됐다).

상태 흐름(즉시): REQUESTEDPROCESSINGCOMPLETED(파트너 자금을 기다리는 동안에는 PENDING_RESERVE) 상태 흐름(회차, v3-26): REQUESTEDQUEUEDPARTIALLY_FILLED* → COMPLETED (*부분 체결분은 청구될 때까지 이월된다)

상태실패 유형오류 예시어드민 표시투자자 표시복구
FAILEDPAYOUT_TX_FAILEDInsufficient reserve balance in escrow빨간색 "Payout Failed" + 에스크로 정보"상환을 완료하지 못했습니다"어드민이 에스크로를 보충하고 재시도
FAILEDFM_NOTIFICATION_FAILEDSMTP connection refused on port 587노란색 "FM Not Notified"투자자에게 표시 안 함FM 통지 재시도
FAILEDCONTRACT_EXECUTION_FAILEDexecution reverted: InsufficientReserve빨간색 "Contract Error" + 부족액"처리 오류입니다. 지원팀에 문의해 주세요"어드민이 reserve 부족을 해결
FAILEDFUND_TRANSFER_FAILEDERC20: transfer exceeds allowance — approval expired빨간색 "Transfer Failed" + 기한"펀드 이체 대기 중"FM이 지갑 승인을 갱신
REJECTEDLOCKUP_NOT_METLockup period (180 days) has not elapsed회색 "Rejected" + 사유"상환 불가: 락업 진행 중"락업이 끝날 때까지 대기

목 데이터: seed.sql의 상환 ID 7~11.

수익 분배 실패 yield_distributions.status + failure_type

수익 자동 발생, 일괄 청구 처리, 트랜잭션 실패 처리를 다룬다.

상태 흐름: PROCESSINGDISTRIBUTED 또는 FAILED

PENDING은 없어졌다. 이 고아 상태는 생산자를 지워서 해결했다(v3-104, 마이그레이션 0113)

PENDING은 원래 레거시 서버키 생성 경로의 insert와 settleYield 사이의 1초 미만 간극에만 존재했다(FM 경로는 PROCESSING으로 insert하고 POST /{id}/distribute를 기다리며 멈추고, 인덱서는 DISTRIBUTED만 쓴다). 거기 머무른 행은 크래시가 남긴 고아였고 아무것도 그것을 고칠 수 없었다. 인덱서는 tx_hash로 정합을 맞추는데 그런 행에는 아직 tx가 없었고, yield.scheduler.reconcileclaimable_yield만 미러링한다. 그래서 그 기간은 영원히 미분배로 읽혔고, 홀더가 지급받았는지를 DB로 알 방법이 없었다.

원래 계획한 해법은 sweeper였는데 대체됐다. 그 레거시 경로는 제품에서 어차피 도달할 수 없었기 때문이다(어드민 UI가 fund_wallet이 있는 풀에 대해 서버키를 거부하고, v3-26에 따라 생성 시 fund_wallet이 필수이며, 표시 전용 풀은 분배할 pool_address가 없다). 그래서 sweeper 대신 그 경로를 삭제했다. POST /yield-distributionsdeposit_tx_hash 없이는 400을 반환한다. 그리고 마이그레이션 0113이 이 상태를 표현 불가능하게 만들었다(기본값 PROCESSING + CHECK (status <> 'PENDING')). tx가 없는 기존 PENDING 행은 FAILED / **ORPHANED_LEGACY_PATH**로 백필했다. tx가 있는 행이 있었다면 마이그레이션을 중단시켜 온체인 수동 정합으로 넘겼을 것이다. 이미 정산됐을 수도 있는 기간을 FAILED로 표시하면 지급된 기간을 미지급으로 기록하게 되기 때문이다.

⚠️ yield_status enum에는 PENDING이 남아 있다. yield_distribution_investors.status가 정당하게 쓰기 때문이다(아직 청구되지 않은 배분분). 금지는 yield_distributions에 대한 CHECK뿐이다.

24시간이 넘은 PROCESSING 행은 사람이 봐야 하는 상태다

실패 유형이 아니라 정체다. FM의 depositYield는 온체인에 올라갔고(행에 deposit_tx_hash가 있다) 수익은 풀에 있는데, POST /{id}/distribute가 실행되지 않아 홀더가 지급받지 못했다. 복구 경로는 있다. 그 엔드포인트, 그리고 어드민 Yield 표의 Distribute 액션이다(검증할 입금 tx가 있을 때 정확히 그때만 제공되고, 없으면 사유와 함께 비활성화된다. deposit_tx_hash가 없는 행은 제거된 서버 경로에서 온 것이고 인덱서가 정합을 맞추므로, 재시도하면 두 번 지급할 위험이 있다).

dashboard_alert_counts.stalled_yield로 노출돼 대시보드의 "Stalled Distributions" 알림이 되고, Yield 화면의 Failed 탭에서 **Stalled**로 보인다. 대응하는 ADMIN 알림은 문구가 없어 만들지 못했다(v3-104).

상태실패 유형오류 예시어드민 표시투자자 표시복구
FAILEDORPHANED_LEGACY_PATHRecorded by the removed legacy server-key create path and never settled (no deposit or settle tx)빨간색 "Orphaned Legacy Path"표시 안 함기간을 다시 기록(체인에 도달한 것이 없다)
FAILEDTX_FAILEDGas price exceeded maximum threshold빨간색 "TX Failed" + 가스 정보"수익 분배가 지연되고 있습니다"어드민이 가스를 올려 재시도
FAILEDFM_NOTIFICATION_FAILEDSendGrid API error 429: rate limit exceeded노란색 "FM Not Notified"표시 안 함레이트 리밋이 풀린 뒤 재시도
FAILEDDISTRIBUTION_TX_FAILEDERC20 transfer failed — pool escrow insufficient빨간색 "Distribution Failed" + tx 해시"수익 분배가 지연되고 있습니다"어드민이 에스크로를 보충
FAILEDCLAIM_PROCESSING_FAILEDBatch claim tx gas estimation failed: out of gas for 4 recipients빨간색 "Claim Failed" + 수령자 수"수익 청구 대기 중"어드민이 배치를 쪼개 재시도

참고

가스 한도를 위한 배치 최적화가 있다. 지수 백오프로 최대 3회 자동 재시도한다. SendGrid 레이트 리밋은 30분에서 2시간 뒤 재시도한다. 목 데이터는 seed.sql의 수익 ID 8~11.

KYC / SBT 실패 users.kyc_status + sbt_status

신원 검증과 Soul Bound Token 민팅(비동기 민팅 포함)을 다룬다.

KYC 흐름: PENDINGAPPROVED / REJECTED

SBT 흐름: NOT_MINTEDMINTED / FAILED

엔티티상태내용어드민 표시투자자 표시복구
KYCREJECTED신원 검증 실패빨간색 "KYC Rejected" + 사유"인증에 실패했습니다. 다시 제출해 주세요"유저가 KYC 재제출
KYCPENDING심사 대기노란색 "Pending Review""심사 중"어드민이 심사
SBTFAILED트랜잭션 타임아웃 또는 체인 오류빨간색 "SBT Mint Failed" + tx 해시"자격증명 발급에 실패했습니다" + 재시도어드민이 재민팅 실행
알림 실패 notification_deliveries.status + .failure_type

⚠️ notification_logs는 더 이상 존재하지 않는다. 마이그레이션 0110이 이를 세 테이블로 나눴다(v3-103). 발송 결과는 이제 **notification_deliveries**에 있고, status(delivery_status enum)와 선택값 failure_type으로 표현된다. 11-db-schema → 알림13-operations → 데이터 모델 참조.

상태 흐름: PENDINGSENT → (SES 이벤트) BOUNCED / COMPLAINED, 또는 발송 오류 시 FAILED, 또는 의도적으로 보내지 않았을 때 SKIPPED.

SKIPPED는 실패가 아니고 실패처럼 스타일링해서도 안 된다. "이 사람에게 이메일을 보내지 않기로 했고 그 이유가 무엇인지"를 기록한 것이다(OPTED_OUT / NO_ADDRESS / SUPPRESSED_ADDRESS). 실패 알림 KPI에 들어가는 것은 FAILED뿐이고, INVALID_RECIPIENT도 거기서 제외된다(0107).

상태실사용기록 주체자동 재시도복구
SENTnotifications.worker.deliver.tsSES가 접수함
FAILEDnotifications.worker.deliver.ts발송이 예외를 던짐(SERVER_ERROR, INVALID_RECIPIENT)SQS 1/5/15/60/240분 → DLQ설정이나 수신자 주소 수정
BOUNCEDnotifications.webhook.ses.tsSES 반송. 하드 바운스면 주소도 차단된다(HARD_BOUNCE)아니오주소를 갱신한 뒤 차단 해제
COMPLAINEDnotifications.webhook.ses.ts수신자가 스팸으로 신고함. 주소를 차단한다(COMPLAINT)아니오동의 없이 다시 추가하지 말 것
SKIPPEDpreferences/decide.ts의도적 미발송이며 사유가 기록됨조치 불필요
PENDING팬아웃 시큐에 들어갔고 아직 시도 전

아직 쓰이지 않는 failure_type 값. TIMEOUTRATE_LIMITEDnotification_failure_type에 있지만 아무것도 기록하지 않는다. SPAM_FILTERED이제 쓰인다. SES complaint 경로가 여기에 매핑된다. 원래 여섯 개 중 넷이 실사용이고 둘이 예약 상태다.

목 데이터: seed.sql의 SBT 민팅 실패, LP 민팅 실패, FM 반송.