페그, 유동성, 슬리피지
도입
스테이블코인이 1달러를 목표로 한다고 해서 모든 순간 1달러로 정산되는 것은 아니다. 사용자는 결제 화면에서 100 USDC를 보냈다고 생각하지만, merchant는 결국 어떤 자산으로 얼마를 받을지 확인해야 한다. 이 간격이 peg, liquidity, slippage의 실무 문제다.
이 강의는 "스테이블코인은 1달러다"라는 문장을 네 개의 가격으로 쪼갠다. Face value, redeem value, market price, executable price가 서로 다르면 checkout, refund, treasury, merchant settlement 정책도 달라져야 한다.
학습 목표
- 페그 이탈과 유동성 부족을 사용자 경험 관점으로 설명한다.
- 스왑 경로와 주문 크기에 따른 슬리피지 리스크를 점검한다.
- oracle price, market price, executable price를 정산 정책으로 연결한다.
개념 설명
페그, 유동성, 슬리피지를 제품 설계에 넣어도 되는가?
발행/상환과 결제 상태가 분리되는가
이벤트와 내부 원장이 대조되는가
페그·유동성 위험이 표시되는가
1. 네 가지 가격을 분리한다
결제 제품은 하나의 priceUsd 필드로 모든 판단을 하면 안 된다. 가격은 사용 목적마다 다르다.
| 가격 | 의미 | 실무에서 쓰는 위치 |
|---|---|---|
| Face value | 1 token = 1 USD라는 명목 가치 | 사용자가 이해하는 표시 단위, 약관 설명 |
| Redeem value | issuer/protocol이 상환해주는 가치 | 최종 안정성, treasury policy, issuer risk review |
| Market price | CEX/DEX에서 관측되는 거래 가격 | 신규 결제 허용, depeg warning |
| Executable price | 지금 route로 실제 체결 가능한 가격 | swap, refund, merchant settlement, treasury rebalance |
예를 들어 oracle price가 1.0000달러이고 market mid price가 0.9990달러여도, 100만 달러를 특정 L2 pool에서 USDC로 바꾸는 executable price가 0.9920달러라면 merchant settlement는 손실을 본다. Checkout은 명목 가격보다 실행 가격을 기준으로 고액 결제 한도를 정해야 한다.
2. Peg가 흔들리는 이유를 원인별로 읽는다
| 원인 | 예시 | 영향 |
|---|---|---|
| 상환 지연 | 은행 휴일, KYC review, redemption queue | 시장 discount |
| 유동성 부족 | 특정 L2 pool liquidity 부족 | 큰 결제에서 slippage |
| 담보 가격 하락 | crypto-backed stablecoin collateral 하락 | liquidation pressure |
| synthetic hedge 문제 | funding rate, exchange/custody 문제 | risk premium |
| bridge/CCTP 지연 | source burn 후 destination mint 대기 | route premium |
원인이 다르면 대응도 다르다. Issuer redemption 지연은 disclosure와 withdrawal policy 문제이고, pool liquidity 부족은 route limit과 quote expiry 문제다. Bridge/CCTP 지연은 사용자가 기다리는 상태를 어떻게 보여줄지의 문제다.
3. AMM에서는 주문 크기가 가격을 만든다
Uniswap 문서는 swap에서 price impact와 slippage를 사용자에게 보여줘야 하는 요소로 다룬다. 결제 시스템도 같은 원리를 쓴다. AMM pool liquidity가 얕으면 작은 주문도 가격을 움직이고, 큰 주문은 route를 쪼개거나 제한해야 한다.
| 확인 항목 | 질문 |
|---|---|
| pool depth | 이 pool이 1,000달러, 10만 달러, 100만 달러 결제를 각각 감당하는가? |
| route output | 같은 input을 넣었을 때 어떤 route가 가장 안정적인 output을 주는가? |
| fee tier | 수수료가 낮은 pool이 항상 최선인가, 아니면 liquidity가 더 중요한가? |
| price impact | 내 주문 자체가 가격을 얼마나 움직이는가? |
| max slippage | quote 대비 얼마까지 체결을 허용할 것인가? |
| MEV/sandwich | public mempool에서 settlement swap이 공격받을 수 있는가? |
| liquidity fragmentation | 같은 token pair라도 chain별 liquidity가 쪼개져 있는가? |
결제 제품에서는 receivedAmount와 settledValue를 분리한다. 사용자가 100,000 PYUSD를 보냈더라도 merchant에게 USDC로 정산할 때 route output이 99,650 USDC라면 누가 350달러 차이를 부담하는지 정책이 필요하다.
4. Slippage 보호는 UX가 아니라 회계 장치다
Slippage tolerance는 단순히 swap 실패를 줄이는 옵션이 아니다. 결제에서는 손익 귀속을 정하는 회계 장치다.
| 상황 | 제품 정책 |
|---|---|
| output이 quote보다 0.2% 낮음 | 자동 체결 허용, merchant fee로 흡수 가능 |
| output이 quote보다 0.7% 낮음 | 고액 결제 manual review 또는 route split |
| output이 quote보다 1.0% 이상 낮음 | 신규 settlement 중지, 사용자에게 재시도 안내 |
| quote가 빠르게 변동 | quote expiry 단축, payment intent 만료 |
| pool imbalance 심화 | 해당 route disable, treasury rebalance 검토 |
정책은 자산별로 달라야 한다. Fiat-backed stablecoin, synthetic dollar, crypto-backed stablecoin은 depeg 원인이 다르기 때문이다. 한 자산의 0.5% discount는 일시적 pool imbalance일 수도 있고, issuer/redemption 신뢰 훼손 신호일 수도 있다.
5. Uniswap v4 Hooks route는 별도 검토한다
Uniswap v4 hooks는 pool lifecycle의 특정 시점에 custom logic을 붙일 수 있다. 동적 수수료, limit order, custom accounting 같은 설계가 가능하지만, 결제 시스템이 hook pool을 route로 쓸 때는 hook contract가 어떤 동작을 하는지 반드시 확인해야 한다.
Uniswap v4 공식 문서는 hooks가 pool, swap, fee, liquidity position 동작을 커스터마이즈할 수 있고 beforeSwap, afterSwap 같은 지점에서 실행될 수 있다고 설명한다. 또 Uniswap support 문서는 hooks가 제3자 개발자에 의해 만들어질 수 있으므로 주의가 필요하다고 안내한다. 결제 route가 hook pool을 통과하면 "토큰 A를 B로 바꾼다" 외의 로직이 실행될 수 있다는 뜻이다.
| 체크 질문 | 결제 route에 미치는 영향 |
|---|---|
| 이 pool에 hook이 붙어 있는가? | plain pool과 다른 fee/revert/custom accounting이 있을 수 있다. |
beforeSwap/afterSwap에서 어떤 조건을 검사하는가? | 특정 sender, amount, time, token 조건에서 settlement가 실패할 수 있다. |
| hook contract가 검증됐는가? | malicious 또는 buggy hook이면 swap output이 예상과 다를 수 있다. |
| router가 hook pool을 자동 선택하는가? | aggregator quote와 실제 execution risk를 분리해야 한다. |
| hook-induced revert를 어떻게 처리하는가? | 결제 상태가 SettlementFailed 또는 RouteRetryRequired로 전환돼야 한다. |
6. 결제 수락 정책은 threshold로 남긴다
| 조건 | 정책 예시 |
|---|---|
| market price < 0.995 USD | 신규 결제 일시 중지 |
| slippage > 0.5% | merchant settlement route 제한 |
| pool imbalance 심화 | treasury rebalance 또는 route disable |
| issuer redemption 지연 | discount warning과 manual review |
| chain liquidity 부족 | 해당 chain 입금 한도 축소 |
Threshold는 절대값이 아니라 product policy다. Retail checkout은 낮은 한도와 짧은 quote expiry를 둘 수 있고, B2B settlement는 더 엄격한 route simulation과 manual approval이 필요하다.
7. 결제 금액별 route simulation을 만든다
| 금액 | route 후보 | 확인할 값 | 정책 예시 |
|---|---|---|---|
| 1,000달러 | direct stable pool | output amount, gas, fee | 자동 승인 |
| 10만 달러 | direct pool + aggregator route | price impact, route split, quote expiry | slippage 0.3% 초과 시 경고 |
| 100만 달러 | OTC, CEX, multiple pools | market depth, settlement delay, custody movement | manual review, batch settlement |
이 표는 실습에서 실제 숫자로 채워야 한다. 숫자를 넣지 않은 "유동성이 충분하다"는 말은 강의 산출물로 인정하지 않는다.
코드로 확인하기
위 설계 결정을 코드에서 확인한다. 상태, 서명, 원장 이동, 실패 처리가 어디에 놓이는지 따라 읽으면 앞의 모델이 실제 구현 경계로 내려온다.
백엔드Quote 단계에서 slippage 보호 — 결제 금액별 분기
결제 직전에 chain DEX quoter를 호출해 expected output을 확인하고, max slippage를 넘으면 결제를 거절한다. Uniswap V3 QuoterV2 예시.
import { createPublicClient, http, parseAbi } from "viem";import { base } from "viem/chains";const quoterAbi = parseAbi([ "function quoteExactInputSingle((address tokenIn, address tokenOut, uint256 amountIn, uint24 fee, uint160 sqrtPriceLimitX96)) view returns (uint256 amountOut, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed, uint256 gasEstimate)"]);const QUOTER_V2 = "0x3d4e44Eb1374240CE5F1B871ab261CD16335B76a";export async function getQuoteWithSlippage(args: { tokenIn: `0x${string}`; tokenOut: `0x${string}`; amountIn: bigint; fee: 100 | 500 | 3000;}) { const client = createPublicClient({ chain: base, transport: http() }); const [amountOut] = await client.readContract({ address: QUOTER_V2, abi: quoterAbi, functionName: "quoteExactInputSingle", args: [{ tokenIn: args.tokenIn, tokenOut: args.tokenOut, amountIn: args.amountIn, fee: args.fee, sqrtPriceLimitX96: 0n }] }); const expectedAt1to1 = args.amountIn; // stablecoin <-> stablecoin 가정 const deltaBps = Number(((expectedAt1to1 - amountOut) * 10_000n) / expectedAt1to1); return { amountOut, slippageBps: deltaBps };}백엔드결제 금액별 정책 — 자동/경고/수동
retail/mid/B2B 세 구간에 다른 정책. quote가 만료되기 전에 결제 트랜잭션을 보내야 하므로 quote_expires_at 도 저장한다.
type PaymentDecision = | { kind: "auto-execute"; route: "direct-pool" } | { kind: "warn-user"; reason: string } | { kind: "block"; reason: string } | { kind: "manual-review"; reason: string };export function decidePayment(args: { amountUsd: number; slippageBps: number; pegDeviationBps: number; poolImbalance: number; // [-1, 1]}): PaymentDecision { if (args.pegDeviationBps > 100) { return { kind: "block", reason: `peg deviated by ${args.pegDeviationBps}bps` }; } if (args.amountUsd >= 100_000) { if (args.slippageBps > 30) { return { kind: "manual-review", reason: `B2B size + slippage ${args.slippageBps}bps` }; } return { kind: "manual-review", reason: "B2B size — operator approval required" }; } if (args.amountUsd >= 10_000) { if (args.slippageBps > 30) return { kind: "warn-user", reason: `slippage ${args.slippageBps}bps` }; return { kind: "auto-execute", route: "direct-pool" }; } if (args.slippageBps > 100) return { kind: "block", reason: `excessive slippage ${args.slippageBps}bps` }; return { kind: "auto-execute", route: "direct-pool" };}인덱서페그 이탈 5분 평균 감지 — SQL
chain별·token별 1분 ticker에서 1분/5분/30분 이동 평균을 비교해 깊은 depeg을 감지한다.
WITH minutely AS ( SELECT token_symbol, chain_id, DATE_TRUNC('minute', observed_at) AS minute, AVG(price_usd) AS avg_price FROM stablecoin_ticks WHERE observed_at >= NOW() - INTERVAL '30 minutes' GROUP BY token_symbol, chain_id, DATE_TRUNC('minute', observed_at)),windows AS ( SELECT token_symbol, chain_id, AVG(avg_price) FILTER (WHERE minute >= NOW() - INTERVAL '5 minutes') AS p5m, AVG(avg_price) FILTER (WHERE minute >= NOW() - INTERVAL '30 minutes') AS p30m FROM minutely GROUP BY token_symbol, chain_id)SELECT token_symbol, chain_id, p5m, p30m, (p5m - 1.0) * 10000 AS deviation_5m_bps, CASE WHEN ABS(p5m - 1.0) > 0.01 THEN 'critical' WHEN ABS(p5m - 1.0) > 0.005 THEN 'warning' ELSE 'ok' END AS severityFROM windows;강의 포인트
| 관점 | 강의 중 확인할 질문 | 학습 후 남길 증거 |
|---|---|---|
| 가격 구분 | face/redeem/market/executable price가 어디서 갈라지는가? | 네 가지 가격 분리표 |
| Route | 같은 금액을 chain과 route별로 실행하면 output이 어떻게 달라지는가? | route simulation table |
| 보호 조건 | slippage, price impact, pool imbalance threshold를 정했는가? | max slippage와 route disable 조건 |
| 운영 대응 | depeg 또는 liquidity stress 때 checkout/refund/settlement를 어떻게 바꾸는가? | depeg runbook |
실무 예시
백엔드[INDEXER] 상황: 사용자가 100만 달러 상당의 stablecoin으로 결제했고, merchant는 USDC로 정산받기를 원한다. Oracle price는 1달러지만, 해당 chain의 DEX route는 output이 0.992달러 수준이다.
| 질문 | 잘못된 처리 | 좋은 처리 |
|---|---|---|
| 결제를 받을 것인가? | oracle price만 보고 자동 승인 | executable price와 order size 기준으로 manual review |
| merchant 정산은 어떻게 할 것인가? | merchant에게 100만 USDC를 보장 | settlement route output과 fee 부담 주체를 사전 정책으로 적용 |
| 환불은 어떻게 할 것인가? | 원 금액을 무조건 USD 기준으로 환불 | original token, settlement asset, slippage loss 귀속을 분리 |
| route 실패는 어떻게 처리할 것인가? | payment failed로 끝냄 | SettlementRouteFailed, RetryWithAlternateRoute, ManualReview 상태로 전환 |
이 예시의 핵심은 결제 성공과 정산 성공이 다르다는 점이다. 사용자의 transfer가 확인되어도 merchant settlement는 route liquidity와 executable price에 의존한다. 그래서 checkout 원장에는 paidAmount, quotedSettlementAmount, actualSettlementAmount, slippageLossOwner가 필요하다.
흔한 오해와 실패 시나리오
| 오해 | 실제로 확인할 것 |
|---|---|
| Stablecoin은 항상 1달러로 정산된다고 본다. | face value와 executable price는 다르다. |
| Oracle price가 정상이면 결제를 받아도 된다고 본다. | 큰 주문은 slippage와 liquidity depth가 별도 리스크다. |
| Slippage tolerance는 사용자가 정하는 UI 옵션이라고만 본다. | merchant settlement와 refund 손익 귀속을 결정하는 policy다. |
| Router가 찾아준 route면 안전하다고 본다. | hook pool, MEV, route split, fee tier, revert 조건을 따로 검토한다. |
실습 과제
- 백엔드네 가지 가격 분리표 만들기: face value, redeem value, market price, executable price를 각각 어떤 source에서 얻고 checkout/refund/settlement에 어떻게 쓰는지 표로 정리한다.
- 백엔드결제 금액별 route simulation 만들기: 1,000달러, 10만달러, 100만달러 결제를 3개 chain과 3개 swap route로 비교하고 output amount, price impact, max slippage, route disable 조건을 적는다.
- 운영Depeg 대응 runbook 작성하기: market price, executable price, pool imbalance, issuer redemption delay가 각각 threshold를 넘을 때 신규 결제, 고액 결제, 환불, merchant settlement를 어떻게 바꿀지 작성한다.
완료 기준
- 페그와 유동성의 차이를 설명한다.
- 슬리피지 보호 조건을 정했다.
- depeg 대응 정책을 작성했다.
- 결제 금액별 quote, max slippage, route disable 조건을 정의했다.
근거 자료
- 페그 유동성 슬리피지: 01-스테이블코인/12-페그-유동성-슬리피지.md
- Uniswap Docs: Swaps: https://developers.uniswap.org/docs/get-started/concepts/traders/swaps
- Uniswap v4 Hooks Concepts: https://developers.uniswap.org/contracts/v4/concepts/hooks
- Uniswap v4 Swap Hooks: https://developers.uniswap.org/contracts/v4/quickstart/hooks/swap
- Uniswap Support: What is a hook on Uniswap v4?: https://support.uniswap.org/hc/en-us/articles/30998263256717-What-is-a-hook-on-Uniswap-v4
- Chainlink Proof of Reserve: https://chain.link/proof-of-reserve