24개 DEX를 스캔하는 어댑터 아키텍처
여러 DEX를 동시에 스캔하는 봇을 만들 때 가장 먼저 부딪히는 문제는, DEX마다 스왑 방식이 다르다는 것입니다. UniswapV2 계열, 집중 유동성(V3), Solidly의 ve(3,3), DODO 같은 PMM(Proactive Market Maker)... 견적 함수도, 스왑 시그니처도 전부 제각각이죠. 24개나 되는 DEX를 상대하면서 봇 코어가 각 DEX를 개별적으로 알아야 한다면, 코드는 금방 지옥이 됩니다.
해법은 고전적입니다. 어댑터 패턴.
공통 인터페이스 뒤로 차이를 숨긴다
봇 코어에게는 오직 하나의 인터페이스만 보이게 합니다. "이 풀에서 X를 넣으면 Y가 얼마 나와?"와 "실제로 스왑해 줘." 이 두 가지면 충분합니다.
interface IDEXAdapter {
// 견적: amountIn 을 넣으면 얼마 나오는지
function getAmountOut(
address pool,
address tokenIn,
uint256 amountIn
) external view returns (uint256 amountOut);
// 실행: 실제 스왑
function swap(
address pool,
address tokenIn,
uint256 amountIn,
address to
) external returns (uint256 amountOut);
}
각 프로토콜은 이 인터페이스를 구현하는 자기만의 어댑터를 갖습니다.
UniV2Adapter—x * y = k상수곱 공식, 견적 계산이 온체인에서 값쌈V3Adapter— 집중 유동성. tick·sqrtPrice 때문에 견적을 Quoter 컨트랙트에 물어봐야 함SolidlyAdapter— 안정/변동 풀 구분, ve(3,3) 수수료 구조DODOAdapter— PMM. 오라클 기반 가격 곡선이라 다른 어댑터와 계산법이 전혀 다름
코어는 이 차이를 전혀 몰라도 됩니다. 그냥 adapter.getAmountOut(...)을 호출할 뿐이죠.
어댑터 등록으로 확장한다
새 DEX를 추가하는 일이 코어 수정이 되면 안 됩니다. 그래서 어댑터를 런타임에 등록합니다. 각 어댑터에 ID를 부여하고, 라우터가 ID로 조회하도록 합니다.
mapping(uint8 => address) public adapters;
function registerAdapter(uint8 id, address adapter) external onlyOwner {
adapters[id] = adapter;
}
새 DEX가 등장하면? 어댑터 하나 배포하고, registerAdapter(24, newAdapter) 한 번. 코어 로직은 손대지 않습니다. 봇 오프체인 쪽에서도 adapterNames 맵에 이름만 추가하면 스캔 대상에 편입됩니다. 이 개방-폐쇄 구조 덕분에 24개까지 늘려도 코어는 그대로였습니다.
유동성 체크 — 함정 풀 거르기
여기가 실전에서 가장 중요한 교훈입니다. 견적상 "10% 차익"처럼 보이는 페어가 있어도, 풀에 유동성이 없으면 신기루입니다. 조금만 사도 가격이 밀려서(슬리피지) 이익이 증발하거나, 아예 상환이 불가능해집니다.
그래서 경로를 실행 후보에 올리기 전에 유동성을 먼저 검증합니다. 프로토콜마다 유동성을 재는 방법이 다르므로, 이 체크도 어댑터 계층과 짝을 이룹니다.
// 개념 예시: 스왑 규모 대비 풀 예비금(reserve)이 충분한가
func hasEnoughLiquidity(reserveIn, amountIn *big.Int) bool {
// 진입 규모가 풀의 일정 비율을 넘으면 슬리피지가 과도 → 스킵
// (V2 는 reserve, V3 는 활성 tick 유동성, Stable 은 곡선 구간으로 각각 판단)
threshold := new(big.Int).Div(reserveIn, big.NewInt(50)) // 예: 풀의 2%
return amountIn.Cmp(threshold) <= 0
}
실제로는 V2는 예비금, V3는 활성 tick의 유동성, 스테이블/Wombat류는 곡선 구간으로 각각 다르게 판단합니다. 핵심은 "견적이 좋아 보인다"와 "실제로 그 규모를 소화한다"는 별개라는 것. 이 게이트 하나가 헛발질 트랜잭션의 대부분을 걸러 줍니다.
정리
- DEX마다 다른 스왑/견적을 하나의 어댑터 인터페이스 뒤로 숨긴다
- 프로토콜별 어댑터(UniV2 / V3 / Solidly / PMM)가 각자 구현을 갖는다
- 어댑터 등록 메커니즘으로 코어 수정 없이 새 DEX를 편입한다
- 유동성 체크로 견적만 좋고 실속 없는 함정 풀을 사전에 거른다
어댑터 패턴은 교과서에 나오는 흔한 패턴이지만, 이질적인 온체인 프로토콜을 24개나 다뤄야 하는 상황에서 그 진가가 드러납니다. 확장이 코어를 건드리지 않는다 — 이게 오래 가는 봇의 조건입니다.
이 글은 교육·정보 목적이며 투자 권유가 아닙니다. DeFi 자동매매에는 원금 손실 위험이 있으며, 결과에 대한 책임은 본인에게 있습니다.
댓글
댓글 쓰기