V3 Locker
V3 Locker는 허용 목록에 등록된 INonfungiblePositionManager가 발행한 ERC721 유동성 포지션을 보관합니다. 락 중에는 NFT 가격 범위를 변경할 수 없습니다. 승인된 계정은 수수료를 수집할 수 있고 모든 사용자가 유동성을 추가할 수 있습니다.
ETERNAL_LOCK은 type(uint256).max입니다. 영구 락은 출금하거나 유동성을 줄일 수 없지만 이전, 수수료 수집, 유동성 추가는 가능합니다.
쓰기 함수
lock
function lock(LockParams calldata params)
external
payable
returns (uint256 lockId)
포지션 NFT를 보관하고 락 전에 누적된 수수료를 수집한 뒤 선택한 락 수수료를 적용해 락을 생성합니다.
| 필드 | 타입 | 설명 |
|---|---|---|
params.nftPositionManager | INonfungiblePositionManager | 허용 목록 Position Manager. |
params.nft_id | uint256 | 호출자가 소유하고 승인한 NFT ID. |
params.dustRecipient | address | 락 전 누적 수수료 수령인. |
params.owner | address | 초기 락 Owner. 0 주소 불가. |
params.additionalCollector | address | 선택적 Additional Collector. |
params.collectAddress | address | 자동 수집 순수익 수령인. 0 주소 불가. |
params.unlockDate | uint256 | 초 단위 미래 Unix 시각 또는 ETERNAL_LOCK. |
params.countryCode | uint16 | COUNTRY_LIST가 허용하는 코드. |
params.feeName | string | 일반적으로 DEFAULT. r이 비어 있지 않으면 무시됩니다. |
params.r | bytes[] | 이름 기반 수수료에는 빈 배열, 동적 수수료에는 FeeResolver payload. |
새 락 ID lockId를 반환합니다.
접근 / payable: NFT Owner/호출자; payable; nonReentrant.
Position Manager, Owner, 수집 주소, 날짜와 국가/지역이 유효해야 하며 NFT가 승인되어야 합니다. 기존 누적 수수료는 먼저 dustRecipient로 수집되어 새 락 유동성 수수료 계산에서 제외됩니다. 네이티브 고정 수수료는 msg.value가 정확히 일치해야 하며 ERC20 고정 수수료에는 allowance가 필요합니다. 저장되는 ucf는 선택한 수집 수수료율입니다. NONCE를 증가시키고 onLock을 발생시킵니다.
params.r이 비어 있지 않으면 FEE_RESOLVER.useFee(params.r, msg.sender)가 동적 수수료를 제공합니다. 서명 payload는 nonce, 사용자, 체인과 Resolver를 바인딩합니다.
collect
function collect(
uint256 _lockId,
address _recipient,
uint128 _amount0Max,
uint128 _amount1Max
) external returns (
uint256 amount0,
uint256 amount1,
uint256 fee0,
uint256 fee1
)
락된 포지션에서 token0/token1 수수료를 수집합니다.
| 이름 | 타입 | 설명 |
|---|---|---|
_lockId | uint256 | 수수료를 수집할 락. |
_recipient | address | 일반 호출자의 순수익 수령인. Auto Collector에서는 프로토콜 수수료 수령인. |
_amount0Max | uint128 | token0 최대 수집량. |
_amount1Max | uint128 | token1 최대 수집량. |
| 반환값 | 타입 | 설명 |
|---|---|---|
amount0 | uint256 | 사용자 측 수령인에게 전달되는 token0 순수량. |
amount1 | uint256 | 사용자 측 수령인에게 전달되는 token1 순수량. |
fee0 | uint256 | token0 프로토콜 수수료. |
fee1 | uint256 | token1 프로토콜 수수료. |
접근 / payable: 락 Owner, additionalCollector 또는 AUTO_COLLECT_ACCOUNT; nonpayable; nonReentrant.
일반 호출에서는 프로토콜 수수료를 FEE_ADDR_COLLECT로, 순수익을 _recipient로 보냅니다. AUTO_COLLECT_ACCOUNT에서는 수수료를 _recipient로, 순수익을 락의 collectAddress로 보냅니다. ucf == 0이면 Auto Collector 호출을 포함해 Position Manager가 전액을 _recipient로 직접 보냅니다. AUTO_COLLECT_ACCOUNT를 신뢰 계정으로 취급하고 0 수령인은 피해야 합니다. 순수량 amount0/amount1과 수수료 fee0/fee1을 반환합니다.
collectBatchPreview
function collectBatchPreview(
uint256[] calldata lockIds,
address[] calldata recipients,
uint128[] calldata amount0Maxs,
uint128[] calldata amount1Maxs
) external
여러 collect 호출을 시뮬레이션하고 의도적인 revert로 정확한 결과를 반환합니다.
| 이름 | 타입 | 설명 |
|---|---|---|
lockIds | uint256[] | 미리 볼 락. |
recipients | address[] | 각 collect 호출의 수령인 매개변수. |
amount0Maxs | uint128[] | 각 락의 token0 최대값. |
amount1Maxs | uint128[] | 각 락의 token1 최대값. |
접근 / payable: 각 항목은 collect와 같은 권한을 사용; nonpayable; nonReentrant.
배열 길이가 다르면 LengthMismatch()로 revert합니다. eth_call로만 호출하세요. 성공한 미리보기는 다음 오류로 revert합니다.
error CollectBatchPreviewResult(CollectPreviewItem[] previews);
각 항목에는 락 ID, 수령인, 토큰 주소, 순수량과 수수료가 포함됩니다. revert가 Position Manager, 토큰과 Locker 상태를 원자적으로 되돌립니다. 다른 revert는 실제 실패입니다. 트랜잭션으로 전송하지 마세요.
increaseLiquidity
function increaseLiquidity(
uint256 _lockId,
INonfungiblePositionManager.IncreaseLiquidityParams calldata params
) external payable returns (
uint128 liquidity,
uint256 amount0,
uint256 amount1
)
token0/token1 유동성을 추가합니다.
| 이름 | 타입 | 설명 |
|---|---|---|
_lockId | uint256 | 유동성을 받을 락. |
params.tokenId | uint256 | 락의 NFT ID와 같아야 합니다. |
params.amount0Desired | uint256 | 호출자에게서 전송할 token0 최대 수량. |
params.amount1Desired | uint256 | 호출자에게서 전송할 token1 최대 수량. |
params.amount0Min | uint256 | token0 최소 사용량. |
params.amount1Min | uint256 | token1 최소 사용량. |
params.deadline | uint256 | Position Manager 기한. |
| 반환값 | 타입 | 설명 |
|---|---|---|
liquidity | uint128 | 추가된 유동성. |
amount0 | uint256 | 사용된 token0 수량. |
amount1 | uint256 | 사용된 token1 수량. |
접근 / payable: 모든 호출자; payable; nonReentrant.
두 토큰을 모두 승인해야 합니다. 미사용 토큰은 호출자에게 반환되고 onIncreaseLiquidity가 발생합니다. 구현은 msg.value를 전달하지 않으므로 네이티브 토큰은 0을 보내야 합니다. 호환된다면 Position Manager 직접 호출이 Gas를 절약할 수 있습니다.
decreaseLiquidity
function decreaseLiquidity(
uint256 _lockId,
INonfungiblePositionManager.DecreaseLiquidityParams calldata params
) external payable returns (uint256 amount0, uint256 amount1)
기간제 락 만료 후 유동성을 제거하고 토큰을 Owner에게 보냅니다.
| 이름 | 타입 | 설명 |
|---|---|---|
_lockId | uint256 | 유동성을 줄일 락. |
params.tokenId | uint256 | 락의 NFT ID와 같아야 합니다. |
params.liquidity | uint128 | 제거할 유동성. |
params.amount0Min | uint256 | 반영될 token0 최소 수량. |
params.amount1Min | uint256 | 반영될 token1 최소 수량. |
params.deadline | uint256 | Position Manager 기한. |
| 반환값 | 타입 | 설명 |
|---|---|---|
amount0 | uint256 | decreaseLiquidity가 반영한 token0 수량. |
amount1 | uint256 | decreaseLiquidity가 반영한 token1 수량. |
접근 / payable: 락 Owner만; payable; nonReentrant.
만료된 비영구 락이어야 합니다. 현재 누적 수수료를 Locker 수수료 경로로 먼저 수집한 뒤 NFT가 지급할 모든 토큰을 호출자에게 수집합니다. msg.value는 전달되지 않으므로 0을 보내세요. onDecreaseLiquidity를 발생시킵니다.
relock
function relock(uint256 _lockId, uint256 _unlockDate) external
기간을 연장하거나 영구 락으로 만듭니다.
| 이름 | 타입 | 설명 |
|---|---|---|
_lockId | uint256 | 연장할 락. |
_unlockDate | uint256 | 초 단위 새 타임스탬프 또는 ETERNAL_LOCK. |
접근 / payable: 락 Owner만; nonpayable; nonReentrant.
새 날짜는 현재 unlockDate와 block.timestamp보다 모두 커야 하고 영구 락 이외에는 10_000_000_000보다 작아야 합니다. 기간을 줄일 수 없습니다. onRelock을 발생시킵니다.
withdraw
function withdraw(uint256 _lockId, address _receiver) external
만료된 기간제 락에서 전체 NFT를 이전합니다.
| 이름 | 타입 | 설명 |
|---|---|---|
_lockId | uint256 | 출금할 락. |
_receiver | address | NFT 수령인. |
접근 / payable: 락 Owner만; nonpayable; nonReentrant.
만료된 비영구 락이어야 합니다. ucf > 0이면 _receiver를 일반 수령인으로 사용해 모든 수수료를 먼저 수집합니다. 이후 NFT를 이전하고 사용자 인덱스와 LOCKS를 삭제하며 onWithdraw을 발생시킵니다. safe ERC721 전송을 받을 수 있는 주소를 사용하세요.
setAdditionalCollector
function setAdditionalCollector(uint256 _lockId, address _additionalCollector) external
collect를 호출할 수 있는 선택 계정을 설정합니다.
| 이름 | 타입 | 설명 |
|---|---|---|
_lockId | uint256 | 설정할 락. |
_additionalCollector | address | Collector 주소. 0 주소로 해제할 수 있습니다. |
접근 / payable: 락 Owner만; nonpayable; nonReentrant. 락을 업데이트하고 onSetAdditionalCollector를 발생시킵니다.
setCollectAddress
function setCollectAddress(uint256 _lockId, address _collectAddress) external
자동 수집 순수익 수령인을 설정합니다.
| 이름 | 타입 | 설명 |
|---|---|---|
_lockId | uint256 | 설정할 락. |
_collectAddress | address | 0이 아닌 자동 수집 수령인. |
접근 / payable: 락 Owner만; nonpayable; nonReentrant. onSetCollectAddress를 발생시킵니다.
transferLockOwnership
function transferLockOwnership(uint256 _lockId, address _newOwner) external
2단계 소유권 이전을 시작합니다.
| 이름 | 타입 | 설명 |
|---|---|---|
_lockId | uint256 | 이전할 락. |
_newOwner | address | 제안된 Owner. |
접근 / payable: 락 Owner만; nonpayable; nonReentrant.
새 Owner는 호출자와 달라야 합니다. 0 주소를 허용하지만 0 pending Owner는 수락할 수 없습니다. 새 제안은 기존 pending Owner를 덮어씁니다. onLockOwnershipTransferStarted를 발생시킵니다.
acceptLockOwnership
function acceptLockOwnership(uint256 _lockId, address _collectAddress) external
대기 중 이전을 수락하고 새 자동 수집 주소를 선택합니다.
| 이름 | 타입 | 설명 |
|---|---|---|
_lockId | uint256 | 수락할 락. |
_collectAddress | address | 새로 저장할 수집 주소. |
접근 / payable: pending Owner만; nonpayable; nonReentrant.
사용자 인덱스를 이동하고 pendingOwner와 additionalCollector를 지운 뒤 _collectAddress를 저장합니다. 이 함수는 0 수집 주소를 허용하므로 클라이언트에서 거부해야 합니다. onTransferLockOwnership을 발생시킵니다.
읽기 함수
getLock / LOCKS
function getLock(uint256 _lockId)
external view returns (Lock memory _lock);
function LOCKS(uint256 lockId) external view returns (
uint256 lock_id,
INonfungiblePositionManager nftPositionManager,
address pool,
uint256 nft_id,
address owner,
address pendingOwner,
address additionalCollector,
address collectAddress,
uint256 unlockDate,
uint16 countryCode,
uint256 ucf
);
두 함수는 같은 필드를 반환합니다. getLock은 struct, mapping Getter는 ABI tuple입니다. 출금했거나 알 수 없는 ID는 모두 0인 락을 반환합니다.
| 매개변수 | 설명 |
|---|---|
_lockId / lockId | 조회할 락 ID. |
getLocksLength / NONCE
function getLocksLength() external view returns (uint256);
function NONCE() external view returns (uint256);
두 함수 모두 과거 기록을 포함한 다음 락 ID를 반환하며 활성 락 수가 아닙니다.
사용자 열거
function getNumUserLocks(address _user) external view returns (uint256);
function getUserLockAtIndex(address _user, uint256 _index)
external view returns (Lock memory);
| 매개변수 | 설명 |
|---|---|
_user | 활성 락 집합을 조회할 Owner. |
_index | 0부터 시작하는 활성 집합 인덱스. 범위를 벗어나면 revert합니다. |
첫 함수는 활성 락 수를, 두 번째는 인덱스에 해당하는 ID의 락을 반환합니다.
수수료 조회
function getFee(string memory _name) public view returns (FeeStruct memory);
function getFeeOptionAtIndex(uint256 _index)
external view returns (FeeStruct memory);
function getFeeOptionLength() external view returns (uint256);
| 매개변수 | 설명 |
|---|---|
_name | 정확한 수수료 옵션 이름. 알 수 없는 이름은 revert합니다. |
_index | 0부터 시작하는 수수료 집합 인덱스. 잘못된 값은 revert합니다. |
FeeStruct는 name, lpFee, collectFee, flatFee, flatFeeToken을 포함합니다. 옵션은 즉시 변경될 수 있으므로 락 직전에 읽어야 합니다.
nftPositionManagerIsAllowed
function nftPositionManagerIsAllowed(address _nftPositionManager)
external view returns (bool);
해당 Position Manager를 lock에서 사용할 수 있는지 반환합니다.
공개 설정 Getter
function ETERNAL_LOCK() external view returns (uint256);
function FEE_DENOMINATOR() external view returns (uint256);
function COUNTRY_LIST() external view returns (address);
function FEE_RESOLVER() external view returns (address);
function AUTO_COLLECT_ACCOUNT() external view returns (address);
function FEE_ADDR_LP() external view returns (address);
function FEE_ADDR_COLLECT() external view returns (address);
function owner() external view returns (address);
상수, 외부 의존성, 권한 주소와 2단계 컨트랙트 소유권을 공개합니다.
이벤트
onLock
event onLock(uint256 lock_id, address nftPositionManager, uint256 nft_id, address owner, address additionalCollector, address collectAddress, uint256 unlockDate, uint16 countryCode, uint256 collectFee, address poolAddress, INonfungiblePositionManager.Position position);
lock이 락을 생성할 때 발생합니다. ID, 계정, 해제 조건, ucf 비율, Pool 주소와 전체 Position snapshot을 포함합니다.
| 매개변수 | 설명 |
|---|---|
lock_id | 새 락 ID. |
nftPositionManager | NFT를 정의하는 Position Manager. |
nft_id | 포지션 NFT ID. |
owner | 초기 락 Owner. |
additionalCollector | 선택적 Collector. |
collectAddress | 자동 수집 순수익 수령인. |
unlockDate | 해제 타임스탬프 또는 ETERNAL_LOCK. |
countryCode | 검증된 국가/지역 코드. |
collectFee | 저장된 ucf 비율. |
poolAddress | 포지션에 해당하는 Factory Pool. |
position | Position Manager의 전체 Position snapshot. |
onWithdraw
event onWithdraw(uint256 lock_id, address owner, address receiver);
출금한 락 상태를 삭제하기 전에 발생합니다.
| 매개변수 | 설명 |
|---|---|
lock_id | 출금된 락. |
owner | 락 Owner. |
receiver | NFT 수령인. |
onLockOwnershipTransferStarted
event onLockOwnershipTransferStarted(uint256 lockId, address currentOwner, address pendingOwner);
현재 Owner가 새 Owner를 제안할 때 발생합니다.
| 매개변수 | 설명 |
|---|---|
lockId | 이전 중인 락. |
currentOwner | 현재 Owner. |
pendingOwner | 제안된 Owner. |
onTransferLockOwnership
event onTransferLockOwnership(uint256 lockId, address oldOwner, address newOwner, address newCollectAddress);
pending Owner가 이전을 수락할 때 발생합니다.
| 매개변수 | 설명 |
|---|---|
lockId | 이전된 락. |
oldOwner | 이전 Owner. |
newOwner | 이전을 수락한 새 Owner. |
newCollectAddress | 수락 시 제공한 수집 주소. |
onSetAdditionalCollector
event onSetAdditionalCollector(uint256 lockId, address additionalCollector);
Owner가 Additional Collector를 변경할 때 발생합니다. 0 주소도 가능합니다.
| 매개변수 | 설명 |
|---|---|
lockId | 업데이트된 락. |
additionalCollector | 새 Collector. 0 주소도 가능합니다. |
onSetCollectAddress
event onSetCollectAddress(uint256 lockId, address collectAddress);
Owner가 0이 아닌 자동 수집 주소를 변경할 때 발생합니다.
| 매개변수 | 설명 |
|---|---|
lockId | 업데이트된 락. |
collectAddress | 새 0이 아닌 수령인. |
onRelock
event onRelock(uint256 lockId, uint256 unlockDate);
락 기간을 연장한 후 발생합니다.
| 매개변수 | 설명 |
|---|---|
lockId | 연장된 락. |
unlockDate | 새 해제 타임스탬프. |
onIncreaseLiquidity
event onIncreaseLiquidity(uint256 lockId);
Locker wrapper로 유동성을 추가한 후 발생합니다.
| 매개변수 | 설명 |
|---|---|
lockId | 유동성이 증가한 락. |
onDecreaseLiquidity
event onDecreaseLiquidity(uint256 lockId);
만료된 락에서 Locker를 통해 유동성을 줄인 후 발생합니다.
| 매개변수 | 설명 |
|---|---|
lockId | 유동성이 감소한 락. |
오류
Locker 비즈니스 오류
| Error / Revert | 관련 함수 | 조건 |
|---|---|---|
OWNER CANNOT = address(0) | lock | params.owner가 0입니다. |
COLLECT_ADDR | lock, setCollectAddress | collectAddress가 0입니다. acceptLockOwnership은 이 검사를 수행하지 않습니다. |
MILLISECONDS | lock, relock | 일반 시각이 10_000_000_000 이상입니다. |
DATE PASSED | lock, relock | 시각이 현재보다 늦지 않습니다. |
COUNTRY | lock | countryCode 검증 실패. |
INVALID NFT POSITION MANAGER | lock | Position Manager가 허용되지 않았습니다. |
NOT FOUND | lock, getFee | 이름 기반 수수료가 없습니다. |
FLAT FEE | lock | msg.value가 정확히 일치하지 않습니다. |
Gas token transfer failed | lock, adminRefundEth | 네이티브 전송 실패. |
OWNER | collect, collectBatchPreview, 락 관리 함수 | 호출자가 허용된 Owner, Additional Collector, Auto Collector 또는 pending Owner가 아닙니다. |
NFT ID | increaseLiquidity, decreaseLiquidity | params.tokenId가 nft_id와 다릅니다. |
ETERNAL_LOCK | decreaseLiquidity, withdraw | 영구 락에 대한 작업입니다. |
NOT YET | decreaseLiquidity, withdraw | 락이 아직 만료되지 않았습니다. |
DATE | relock | 새 시각이 unlockDate보다 크지 않습니다. |
SAME OWNER | transferLockOwnership | _newOwner가 현재 Owner입니다. |
L | setUCF | 새 _ucf가 현재 비율보다 작지 않습니다. |
DEFAULT | removeFee | DEFAULT 삭제 시도. |
Fee not exists | removeFee | 옵션이 없습니다. |
배치 미리보기 사용자 정의 오류
error CollectBatchPreviewResult(CollectPreviewItem[] previews);
error LengthMismatch();
| Custom Error | 의미 |
|---|---|
CollectBatchPreviewResult(...) | collectBatchPreview의 예상 성공 payload. previews를 결과로 디코딩합니다. |
LengthMismatch() | lockIds, recipients, amount0Maxs, amount1Maxs 길이가 일치하지 않습니다. |
외부 및 상속 오류
| Error / Revert | 조건 |
|---|---|
STF | safeTransferFrom 실패. 일반적으로 잔액 또는 allowance 부족. |
ST | safeTransfer 실패. |
SA | safeApprove 실패. |
Ownable: caller is not the owner | Owner가 아닌 계정의 관리 함수 호출. |
ReentrancyGuard: reentrant call | nonReentrant 함수 재진입. |
Panic(0x11) | 산술 underflow/overflow. |