여러 DEX를 스캔하는 어댑터: 견적·기능·실행 계약

English version

TECH INSIGHT / EXPLAINED

어댑터 패턴은 서로 다른 DEX의 견적·실행 방식을 같다고 가정하지 않으면서 공통 스캐너가 다루게 합니다. 수십 곳 확장은 여기서 설계 목표이며 24개 운영 연동을 입증한 실적 주장은 아닙니다.

동료 세 명이 DEX 어댑터의 견적·실행 계약 주제를 그림으로 설명하는 모습
이해를 돕기 위한 AI 생성 설명 일러스트. 실제 운영 화면이나 실적 자료가 아닙니다.

실제 프로젝트의 로컬 실행 화면

기존 프로젝트의 화면 또는 함수를 로컬에서 실행해 촬영했습니다. 개인정보 없는 샘플 입력을 사용했으며, 실행한 범위는 각 이미지 아래에 설명했습니다.

실제 프로젝트의 DEX 어댑터 목록 창을 로컬 실행한 화면입니다. 목록은 유형을 설명하기 위한 DEMO 응답이며 실제 연결된 DEX나 실행 가능성을 인증하는 결과는 아닙니다.
실제 프로젝트의 DEX 어댑터 목록 창을 로컬 실행한 화면입니다. 목록은 유형을 설명하기 위한 DEMO 응답이며 실제 연결된 DEX나 실행 가능성을 인증하는 결과는 아닙니다.

지원 능력도 계약에 넣는다

견적에는 체인 ID, 풀 주소, 입력/출력 토큰, 정수 원시 수량, 수수료 모형, 블록 번호/해시, 예상 수령액과 만료가 필요합니다. 실행에는 최소 수령액·수령인·기한·허용 spender/콜백 정책이 추가됩니다. 오프체인 견적은 가능하지만 온체인 view 견적은 안 되는 프로토콜도 있으므로 모든 구현을 동일한 Solidity view 함수에 억지로 맞추지 않습니다.

QuoteRequest { chain_id, pool, token_in, token_out,
               amount_in_raw, block_hash }
QuoteResult  { amount_out_raw, protocol_version,
               fee_model, block_hash, expires_at }
Execution    { route_id, amount_in_raw, min_out_raw,
               recipient, deadline }

V3가 보여 주는 차이

원본 Uniswap V3 Quoter는 스왑을 호출하고 revert를 이용해 값을 구합니다. 오프체인 eth_call로 호출해도 일반적인 view 함수인 것은 아니며 Solidity STATICCALL과는 다릅니다. 필요한 상태와 정수 계산을 갖추면 오프체인 tick 시뮬레이션도 가능합니다. V2 준비금 공식을 관련 없는 V3 풀의 순위를 보장하는 근사식으로 쓰면 안 됩니다.

어댑터 등록은 신뢰 경계의 변경이다

맵에 이름을 추가하는 것으로 끝나지 않습니다. 팩토리·풀 신원, 배포 코드, 토큰 순서, 체인 ID, 수수료 지원, 콜백 권한을 확인하세요. 어댑터는 승인을 받거나 자산을 옮길 수 있어 등록 변경에는 인증된 관리·이력·시험 전 비활성 상태가 필요합니다. 관리자가 등록했다는 이유만으로 임의 주소를 호출하게 만들지 않습니다.

주문 크기별 견적과 실제 수령액을 비교한다

입력 준비금 1,000에서 2% 크기 제한은 20을 허용하지만 모든 곡선에서 같은 가격 충격 한도를 보장하지 않습니다. V3는 tick 경계에서 활성 유동성이, stable형은 곡선 매개변수가 중요합니다. 고정 블록 포크에서 예측 순수령액과 실제 잔고 차이를 비교하고 토큰 순서·decimals·유동성 고갈·기한·최소 수령액 거절 사례를 시험합니다.

숫자 하나로는 어댑터 실패를 표현할 수 없다

견적 함수가 모든 오류를 amountOut=0으로 바꾸면 스캐너는 지원하지 않는 기능과 실제로 수령량이 0인 계산을 구별하지 못합니다. 결과를 quoted, unsupported, no_route, stale_state, rpc_error처럼 나누고 quoted일 때만 수량을 읽게 설계하세요. 여기서 이름은 애플리케이션 예시이며 프로토콜 표준이 아닙니다.

exact-output을 지원하지 않는 어댑터에 목표 수령량을 넣었다면 unsupported가 맞습니다. RPC가 시간 초과됐을 때 빈 경로를 반환하면 유동성이 없다고 오해하게 됩니다. 재시도 정책도 달라야 합니다. 일시적인 전송 오류는 제한적으로 재조회할 수 있지만, 지원하지 않는 기능은 호출을 반복해도 해결되지 않습니다.

캐시 키가 실제 질문을 모두 담고 있는가

같은 풀이라도 입력량, 방향, exact-input/output 모드, 수수료 구간, 가격 제한이 달라지면 다른 질문입니다. 캐시 키에는 어댑터 버전과 상태의 블록 해시도 포함하고 반환 결과가 요청한 해시와 일치하는지 검사합니다. UI 문자열 '1'만 키에 넣으면 소수점 6자리 토큰의 1,000,000과 18자리 토큰의 10^18 원시 수량을 혼동할 수 있습니다.

EIP-1898은 지원되는 RPC에서 블록 해시와 requireCanonical로 상태를 지정하는 방식을 정의합니다. 제공자가 해당 방식을 지원하지 않는다면 기능 제한을 드러내야 합니다. 실패한 요청을 말없이 latest로 바꾸면 비교 중인 견적들이 서로 다른 상태를 읽게 됩니다. 만료 시간만 같다고 그 상태가 같아지지는 않습니다.

콜백에서는 등록된 풀과 현재 실행을 함께 검증한다

콜백 호출자가 알려진 풀이라는 조건만으로 어떤 지급 요청이든 받아들이면 안 됩니다. 실행 시작 시 예상 풀, 입력 토큰, 최대 지급량, 경로 식별자를 저장하고 해당 실행의 콜백인지 확인합니다. 유휴→실행 중→콜백 처리→정산 완료의 상태를 두면 실행이 없거나 이미 끝난 뒤 들어온 콜백을 거절할 위치가 분명해집니다.

V3 CallbackValidation은 팩토리와 토큰·수수료 정보로 계산한 풀 주소를 검증합니다. 그 검증에 애플리케이션의 지급 한도와 실행 상태를 더하는 것은 별도의 설계 책임입니다. 콜백 calldata의 수량을 그대로 신뢰하거나 전체 잔고를 지급 가능액으로 취급하지 마세요.

Quoter의 잠금 실패와 일반 조회 실패를 구분한다

원본 V3 풀은 swap 도중 unlocked를 false로 바꿉니다. 따라서 그 풀의 진행 중인 스왑 콜백 안에서 같은 풀을 Quoter로 다시 견적내려 하면 LOK 거절에 걸릴 수 있습니다. 독립적인 오프체인 호출에서 성공한 견적이 콜백 내부에서도 성공한다는 뜻은 아닙니다. 풀 구현의 실행 순서가 이유입니다.

이는 다른 모든 풀까지 잠긴다는 뜻도 아니며, eth_call과 STATICCALL 차이와도 별개입니다. 테스트 명세에 호출 위치를 입력으로 넣고 같은 풀 재진입과 다른 풀 호출을 분리하세요. 독립 Python 예제에서는 수량·블록 해시가 바뀌면 캐시 키가 달라지고 정산 완료 뒤 콜백 상태로 돌아가지 못하는지만 확인했습니다. 실제 EVM 콜백·잠금 동작 검증은 별도 통합 실험이 필요합니다.

공식 문서와 이어 읽기

내용·예제 검토: 2026-10-01

댓글