개발자를 위한 문서 / SENDIT
무기한 선물 엔진
하나의 엔진, 오라클 피드마다 하나의 시장. 즉시 체결 시장은 여러분의 트랜잭션 안에서 체결되고, 나머지 시장은 한 가격 라운드 뒤에 주문서를 통해 체결됩니다. 마진은 뱅크에서 오고, 모든 지급은 뱅크로 돌아갑니다.
| 수량 | 단위 |
|---|
| 달러 | pUSD 기본 단위, 1e6마진, 수수료, 지급금 |
|---|
| 가격 | 오라클 단위, 1e8tp, sl, limitPrice, acceptablePrice |
|---|
| 크기 | 자산 단위, 1e18usd = units x price / 1e20 |
|---|
| 시장 id | bytes32 오라클 피드 id/api/perp/markets의 feedId |
|---|
| 레버리지 | uint16, 정수1부터 그 시장의 maxLeverage까지 |
|---|
requestOpen(OpenRequest)는 마진을 에스크로하고 roundAtRequest를 기록합니다. 더 새로운 라운드가 생기면 키퍼가 execute(id)를 호출해 그 라운드에서 스프레드 없이 체결하거나, 사유와 함께 취소합니다. 사유는 acceptablePrice를 넘은 경우(롱은 그 가격 이하에서, 숏은 그 이상에서 체결되며, 0은 제한 없음), 시장이 닫힌 경우, 엔진이 거부한 경우입니다. requestClose(positionId, acceptablePrice)도 같은 방식으로 작동합니다. 트레이더는 TRADER_CANCEL_AFTER(30초) 뒤에, 또는 시장이 열려 있지 않은 동안에는 즉시 cancel할 수 있습니다. EXPIRE_AFTER(120초)가 지나면 요청은 더 이상 체결될 수 없고 누구든 취소할 수 있습니다. 모든 환불은 트레이더의 뱅크 잔액으로 갑니다. 요청 id는 엔진의 idBase를 공유합니다.
setTriggers(positionId, tp, sl, limitPrice)는 어느 시장에서든 여러분의 포지션에 익절, 손절, 지정가 종료를 설정하며, 0은 그 필드를 지웁니다. 롱에서는 TP와 지정가가 그 가격 이상에서, SL이 그 가격 이하에서 작동하고, 숏은 그 반대입니다. 키퍼의 executeTrigger는 트리거가 설정된 라운드보다 새로운 라운드에서만 체결하며, 청산 가능한 포지션은 거부합니다. 닫힌 시장에서는 트리거가 시장이 다시 열릴 때까지 기다립니다.
liquidate(id)는 누구나 호출할 수 있습니다. 새 가격과 유지 마진 미만의 순자산이 필요하며, 순자산은 margin + PnL - funding - 전체 종료 수수료이고, 유지 마진은 포지션 현재 가치의 ceil(value / (2 x maxLeverage))입니다. 스프레드 없이 오라클 가격으로 종료되고, 페널티는 가치의 LIQ_PENALTY_BPS(1%)이며 남은 금액을 상한으로 하여 절반은 호출자에게 pUSD로, 절반은 프로토콜에게 갑니다. 나머지는 트레이더의 뱅크 잔액으로 갑니다. 0 아래는 부실 채무이며 볼트가 떠안습니다. isLiquidatable(id)나 positionState(id)를 읽으십시오. /api/perp/open은 열린 모든 포지션을 체인 밖에서 검사하는 데 필요한 시장 상태와 함께 페이지 단위로 넘겨 줍니다.
오라클 피드의 상태가 closed(2)이면, 게시자는 세션의 마지막 가격을 그대로 유지합니다. 진입은 거부됩니다("market closed"). close, 또는 requestClose(키퍼가 즉시 체결함)는 그 마지막 가격에서 closedSpreadBps(feedId)를 빼고, 즉시 체결 시장이라면 시장 자신의 스프레드를 더해 처리됩니다. 닫힌 시장 스프레드는 소유자가 시장별로 정하며, 상장 때 DEFAULT_CLOSED_SPREAD_BPS(10)이고 최대 MAX_CLOSED_SPREAD_BPS(100)입니다. 마지막 가격은 CLOSED_HEARTBEAT + 30초(피드 자신의 하트비트가 더 길면 그것) 이내의 것이어야 하며, 그 가격에서 유지 마진 아래인 포지션은 종료되지 않습니다("liquidatable"). 펀딩은 멈추고, TP, SL, 지정가, 청산은 다시 열린 뒤의 첫 라운드를 기다립니다. 상태 0(unknown)과 3(halted)은 시장을 양방향으로 모두 동결합니다.
idBase()는 생성 시점에 고정됩니다. 현재 엔진에서는 10,000,000이므로 첫 포지션은 10,000,001입니다. 매니페스트의 perpsPrevious에 version: 1로 나열된 이전 엔진은 1부터 번호를 매기고, 지갑 자신의 pUSD에서 마진을 가져가며 지갑에 지급합니다. 뱅크보다 먼저 만들어졌으므로 키는 그 엔진에서 아무것도 할 수 없습니다. 그 포지션은 포지션을 연 지갑이 서명한, 그 엔진 자신의 close(id)로 종료합니다.
The perps engine, VERSION 3: markets (one per oracle feed), positions, fees, funding and liquidation. Margin comes from the bank and every payout goes back to the trader's bank balance.
Trading
| Function | What it does |
|---|
| open((bytes32 feedId, bool isLong, uint256 margin, uint16 leverage, uint256 tp, uint256 sl) a) returns (uint256 id) | Opens on an immediate market in this transaction, at the latest round plus the spread. Margin is pulled from the signer's account. A non-zero tp or sl is handed to the orders book. |
|---|
| close(uint256 id) | Closes an immediate-market position in full, by the trader or a live key of theirs, also while the market is closed (at the last price). |
|---|
| liquidate(uint256 id) | Permissionless. Closes at the oracle price, no spread, when equity is below maintenance; the caller gets half the penalty. |
|---|
| accrue(bytes32 feedId) | Permissionless: brings a market's funding up to now. |
|---|
| flush() returns (uint256 pusdIn, uint256 wethOut) | Permissionless: sells the protocol's fee share for WETH and sends it to the FeeRouter, inside a price band. |
|---|
Called by the orders book
| Function | What it does |
|---|
| openFromOrders(address trader, (bytes32,bool,uint256,uint16,uint256,uint256) a) returns (uint256) | A two-step open, once the orders book has seen a round newer than the request. |
|---|
| closeFromOrders(uint256 id, uint8 reason) | A two-step close or a TP, SL or limit fill. While closed, only a close request fills. |
|---|
Reading
| Function | What it does |
|---|
| getPosition(uint256 id) view returns (Position) | trader, feedId, isLong, open, leverage, openedAt, margin, units, cost, fundingIndex. |
|---|
| positionState(uint256 id) view returns (int256 equity, uint256 maintenance, int256 pnl, int256 funding, uint256 value) | A position's equity against its maintenance, now. |
|---|
| getMarket(bytes32 feedId) view returns (Market) | A market's config, open interest per side and funding indexes. |
|---|
| quote(bytes32 feedId) view returns (uint256 price, uint256 roundId, uint8 status_, bool fresh) | The oracle round the engine would use. |
|---|
| previewFill(bytes32 feedId, bool isLong, bool opening) view returns (uint256) | The fill price with the spread, as a trade would get it now. |
|---|
| isImmediate(bytes32 feedId) view returns (bool) | True when the market fills in your transaction; false means two-step, through the orders book. |
|---|
| isClosable(bytes32 feedId) view returns (bool) | Whether a close can fill now (a fresh price, or a closed market's standing last price). |
|---|
| isLiquidatable(uint256 id) view returns (bool) | Below maintenance at a fresh price. |
|---|
| belowMaintenance(uint256 id) view returns (bool) | Below maintenance at the latest price, fresh or not. |
|---|
| fundingRate(bytes32 feedId) view returns (uint256 rate) | The hourly rate the heavier side pays now, 1e18 = 100%. |
|---|
| closedSpreadBps(bytes32) view returns (uint16) | The extra spread a close pays while the market is closed. |
|---|
| whyNotOpen(address trader, OpenArgs a) view returns (string) | Why open would fail for this account, or "". Call it before sending: revert reasons do not reach a client on this chain. |
|---|
| whyNotFill(OpenArgs a) view returns (string) | openFromOrders' checks, without the balance, the two-step gate or the age limit. |
|---|
| whyNotClose(address caller, uint256 id) view returns (string) | Why close would fail for this signer, or "". |
|---|
| marketCount() view returns (uint256) | How many markets are listed. |
|---|
| marketIds(uint256) view returns (bytes32) | The listed feed ids, by index. |
|---|
| nextPositionId() view returns (uint256) | The id the next position gets. |
|---|
| idBase() view returns (uint256) | Positions are numbered from idBase + 1. Fixed at construction. |
|---|
| params() view returns (uint16 netExposureCapBps, uint16 flushBandBps, uint16 flushMaxInputBps, uint256 fundingK, uint256 fundingCap) | The engine-wide parameters. |
|---|
| totalMargin() view returns (uint256) | The margin of every open position. |
|---|
| protocolOwed() view returns (uint256) | The protocol's fee share waiting for flush. |
|---|
| unrealisedPnl() view returns (int256 total) | Traders' net open PnL, which the vault's NAV subtracts. |
|---|
| grossWinnings() view returns (uint256 total) | What the winning sides are owed now. |
|---|
| netExposure() view returns (uint256 total) | The open imbalance the vault backs. |
|---|
| pusd() view returns (address) | pUSD. |
|---|
| vault() view returns (address) | The vault. |
|---|
| oracle() view returns (address) | The price feeds. |
|---|
| bank() view returns (address) | SenditBank. |
|---|
| orders() view returns (address) | The orders book. |
|---|
| owner() view returns (address) | The owner. |
|---|
| router() view returns (address) | The flush route's router. |
|---|
| flushPool() view returns (address) | The pUSD/WETH pool flush sells through. |
|---|
| weth() view returns (address) | WETH. |
|---|
| feeRecipient() view returns (address) | Where flushed WETH goes (FeeRouter). |
|---|
| pusdIsToken0() view returns (bool) | The flush pool's token order. |
|---|
Owner
| Function | What it does |
|---|
| setMarket(bytes32 feedId, (uint16 maxLeverage, uint16 spreadBps, uint16 maxOiLongBps, uint16 maxOiShortBps, bool enabled, bool inverse) c) | Lists or reconfigures a market: max leverage 1 to 100, spread at most 100 bps. Lowering maxLeverage raises maintenance for open positions at once. |
|---|
| setClosedSpread(bytes32 feedId, uint16 bps) | The closed-market spread, at most MAX_CLOSED_SPREAD_BPS. Binds the next close. |
|---|
| setParams((uint16,uint16,uint16,uint256,uint256) p) | Exposure cap, flush band and funding parameters (fundingK and fundingCap at most 1% an hour). |
|---|
| setOrders(address orders_) | Sets the orders book, once. |
|---|
| setFlushRoute(address router_, address pool_, address weth_, address feeRecipient_) | Where flush sells and where the WETH goes. |
|---|
| transferOwnership(address next) | Hands ownership on. |
|---|
Constants
| Function | What it does |
|---|
| VERSION() view returns (uint256) | 3. |
|---|
| FEE_BPS() view returns (uint256) | 5: 0.05% of the position's size, on open and again on close. |
|---|
| VAULT_FEE_SHARE_BPS() view returns (uint256) | 7000: 70% of every fee goes to the vault, the rest to the protocol. |
|---|
| LIQ_PENALTY_BPS() view returns (uint256) | 100: 1% of the size, half to the liquidator. |
|---|
| MIN_MARGIN() view returns (uint256) | 1e6: one dollar. |
|---|
| MAX_MARKETS() view returns (uint256) | 64. |
|---|
| IMMEDIATE_MAX_AGE() view returns (uint256) | 2 seconds: the oldest round an immediate fill accepts. |
|---|
| STALE_GRACE() view returns (uint256) | 30 seconds added to a feed's heartbeat. |
|---|
| CLOSED_HEARTBEAT() view returns (uint256) | 3600 seconds: how often a closed market's last price is republished. |
|---|
| DEFAULT_CLOSED_SPREAD_BPS() view returns (uint16) | 10: 0.10%, set on every new market. |
|---|
| MAX_CLOSED_SPREAD_BPS() view returns (uint16) | 100: the owner's ceiling, 1%. |
|---|
| MIN_FLUSH() view returns (uint256) | 1e6: the smallest flush. |
|---|
| ETH_USD() view returns (bytes32) | keccak256("ETH/USD"), the feed flush prices against. |
|---|
| CLOSE_TRADER() view returns (uint8) | 0: closed by the trader. |
|---|
| CLOSE_REQUEST() view returns (uint8) | 1: a two-step close request. |
|---|
| CLOSE_TP() view returns (uint8) | 2: take profit. |
|---|
| CLOSE_SL() view returns (uint8) | 3: stop loss. |
|---|
| CLOSE_LIMIT() view returns (uint8) | 4: limit close. |
|---|
| CLOSE_LIQUIDATION() view returns (uint8) | 5: liquidation. |
|---|
Events
| Event | What it does |
|---|
| PositionOpened(uint256 indexed positionId, address indexed trader, bytes32 indexed feedId, (bool isLong, uint16 leverage, uint256 margin, uint256 units, uint256 cost, uint256 price, uint256 fee, int256 fundingIndex) info) | A position opened. trader is the account, never the key. |
|---|
| PositionClosed(uint256 indexed positionId, address indexed trader, bytes32 indexed feedId, uint8 reason, (uint256 price, int256 pnl, int256 funding, uint256 fee, uint256 penalty, uint256 payout, uint256 badDebt, uint256 value) s) | A position closed; reason is one of the CLOSE_ constants. |
|---|
| PositionLiquidated(uint256 indexed positionId, address indexed liquidator, uint256 reward) | Emitted after PositionClosed on a liquidation. |
|---|
| FundingAccrued(bytes32 indexed feedId, uint256 ratePerHour, int256 longIndex, int256 shortIndex) | Funding brought up to date. |
|---|
| MarketListed(bytes32 indexed feedId) | A new market. |
|---|
| MarketConfigured(bytes32 indexed feedId, (uint16,uint16,uint16,uint16,bool,bool) config) | A market's config set. |
|---|
| ClosedSpreadSet(bytes32 indexed feedId, uint16 bps) | A closed-market spread set. |
|---|
| ParamsSet((uint16,uint16,uint16,uint256,uint256) params) | Engine parameters set. |
|---|
| OrdersSet(address indexed orders) | The orders book set. |
|---|
| FlushRouteSet(address router, address pool, address weth, address feeRecipient) | The flush route set. |
|---|
| Flushed(uint256 pusdIn, uint256 wethOut, uint256 ethUsdPrice, address feeRecipient) | The protocol's share sold and sent. |
|---|
| FlushSkipped(uint256 amountIn, uint256 minOut, uint8 reason) | A flush that did not swap: 1 no usable ETH/USD, 2 the swap was refused. |
|---|
The two-step book and the triggers. On a two-step market an open or close is a request, filled by the keeper at the first price round newer than the request. Take profit, stop loss and limit closes live here for every market.
Trading
| Function | What it does |
|---|
| requestOpen((bytes32 feedId, bool isLong, uint256 margin, uint16 leverage, uint256 acceptablePrice, uint256 tp, uint256 sl) r) returns (uint256 id) | Escrows the margin from the signer's account and records the request. acceptablePrice 0 means no bound; long fills at or below it, short at or above. |
|---|
| requestClose(uint256 positionId, uint256 acceptablePrice) returns (uint256 id) | A close request on a two-step market. On a closed market it fills at once at the last price. |
|---|
| cancel(uint256 id) | The trader after TRADER_CANCEL_AFTER, or at once while the market is not open; anyone after EXPIRE_AFTER. The margin goes back to the trader's bank balance. |
|---|
| setTriggers(uint256 positionId, uint256 tp, uint256 sl, uint256 limitPrice) | Sets TP, SL and limit close on your position; zero clears a field. Long: TP and limit fire at or above, SL at or below; short mirrored. |
|---|
Keeper and engine
| Function | What it does |
|---|
| execute(uint256 id) | Keeper only: fills a request at the round in force, which must be newer than the request, or cancels it with a reason. |
|---|
| executeTrigger(uint256 positionId, uint8 kind) | Keeper only: fills a TP, SL or limit at a round newer than the one it was set at. A liquidatable position is refused: liquidations go first. |
|---|
| initTriggers(uint256 positionId, uint256 tp, uint256 sl) | The engine only: the TP and SL an immediate open carried. |
|---|
Reading
| Function | What it does |
|---|
| getRequest(uint256 id) view returns (Request) | trader, feedId, kind, state, isLong, leverage, createdAt, margin, acceptablePrice, roundAtRequest, positionId, tp, sl. |
|---|
| getTriggers(uint256 positionId) view returns (uint256 tp, uint256 sl, uint256 limitPrice, uint256 roundAtSet) | A position's triggers. |
|---|
| pendingCount() view returns (uint256) | Requests waiting. |
|---|
| pendingPage(uint256 offset, uint256 limit) view returns (uint256[] ids, Request[] reqs) | Waiting requests, at most MAX_PAGE per call. |
|---|
| pendingClose(uint256) view returns (uint256) | The pending close request of a position, or 0. |
|---|
| demandedFeeds() view returns (bytes32[] out) | Feeds with pending requests: what the publisher should price next. |
|---|
| demand(bytes32) view returns (uint256) | Pending requests per feed. |
|---|
| escrowed() view returns (uint256) | The margin of pending open requests. |
|---|
| nextRequestId() view returns (uint256) | The next request id, numbered from the engine's idBase + 1. |
|---|
| whyNotRequestOpen(address trader, OpenRequest r) view returns (string) | Why requestOpen would fail for this account, or "". "immediate" means use the engine's open. |
|---|
| whyNotRequestClose(address caller, uint256 positionId) view returns (string) | Why requestClose would fail for this signer, or "". |
|---|
| whyNotCancel(address caller, uint256 id) view returns (string) | Why cancel would fail, or ""; "early" before the wait is over. |
|---|
| keepers(address) view returns (bool) | Whether an address may execute. |
|---|
| perps() view returns (address) | The engine. |
|---|
| pusd() view returns (address) | pUSD. |
|---|
| oracle() view returns (address) | The price feeds. |
|---|
| bank() view returns (address) | SenditBank. |
|---|
| owner() view returns (address) | The owner. |
|---|
Owner
| Function | What it does |
|---|
| setKeeper(address keeper, bool allowed) | Adds or removes a keeper. |
|---|
| transferOwnership(address next) | Hands ownership on. |
|---|
Constants
| Function | What it does |
|---|
| VERSION() view returns (uint256) | 3. |
|---|
| TRADER_CANCEL_AFTER() view returns (uint256) | 30 seconds. |
|---|
| EXPIRE_AFTER() view returns (uint256) | 120 seconds: after this a request cannot fill and anyone may cancel it. |
|---|
| MAX_PAGE() view returns (uint256) | 100. |
|---|
| KIND_OPEN() view returns (uint8) | 1. |
|---|
| KIND_CLOSE() view returns (uint8) | 2. |
|---|
| STATE_PENDING() view returns (uint8) | 1. |
|---|
| STATE_EXECUTED() view returns (uint8) | 2. |
|---|
| STATE_CANCELLED() view returns (uint8) | 3. |
|---|
| CANCEL_TRADER() view returns (uint8) | 1: cancelled by the trader. |
|---|
| CANCEL_EXPIRED() view returns (uint8) | 2: expired. |
|---|
| CANCEL_PRICE() view returns (uint8) | 3: the price moved past acceptablePrice. |
|---|
| CANCEL_CLOSED() view returns (uint8) | 4: the market closed. |
|---|
| CANCEL_POSITION() view returns (uint8) | 5: the position is gone. |
|---|
| CANCEL_REJECTED() view returns (uint8) | 6: the engine refused the fill. |
|---|
| CANCEL_LIQUIDATABLE() view returns (uint8) | 7: the position is liquidatable. |
|---|
| TRIGGER_TP() view returns (uint8) | 1. |
|---|
| TRIGGER_SL() view returns (uint8) | 2. |
|---|
| TRIGGER_LIMIT() view returns (uint8) | 3. |
|---|
Events
| Event | What it does |
|---|
| RequestCreated(uint256 indexed requestId, address indexed trader, bytes32 indexed feedId, Request request) | A request recorded. |
|---|
| RequestExecuted(uint256 indexed requestId, uint256 indexed positionId, uint256 price) | A request filled. |
|---|
| RequestCancelled(uint256 indexed requestId, address indexed trader, uint8 reason) | A request cancelled; reason is a CANCEL_ constant. |
|---|
| TriggersSet(uint256 indexed positionId, uint256 tp, uint256 sl, uint256 limitPrice) | Triggers set or cleared. |
|---|
| TriggerExecuted(uint256 indexed positionId, uint8 kind, uint256 price) | A TP, SL or limit filled. |
|---|
| KeeperSet(address indexed keeper, bool allowed) | A keeper added or removed. |
|---|
The counterparty to every perp trade. LPs deposit pUSD from their bank balance and hold pLP (18 decimals, one dollar each at the start). NAV is pUSD held minus traders' net open PnL.
LP
| Function | What it does |
|---|
| deposit(uint256 amount) returns (uint256 shares) | Pulls pUSD from the signer's account and mints pLP to the account. Locks all the account's pLP for SHARE_LOCK. |
|---|
| withdraw(uint256 shares) returns (uint256 amount) | Burns the account's pLP after the lock and pays its bank balance, at most withdrawable(). |
|---|
| transfer(address to, uint256 value) returns (bool) | Moves msg.sender's own pLP. A key holds none, so giving pLP away is a wallet action. Locked pLP cannot move. |
|---|
| transferFrom(address from, address to, uint256 value) returns (bool) | ERC-20 transferFrom. |
|---|
| approve(address spender, uint256 value) returns (bool) | ERC-20 approve. |
|---|
Reading
| Function | What it does |
|---|
| nav() view returns (uint256) | pUSD held minus unrealised trader PnL, floored at zero. |
|---|
| sharePrice() view returns (uint256) | pUSD per whole pLP, six decimals. |
|---|
| withdrawable() view returns (uint256) | The most a withdrawal can take now: NAV minus what backs open positions, and never the cash winners are owed. |
|---|
| reserved() view returns (uint256) | Net exposure plus gross winnings: what withdrawals cannot touch. |
|---|
| assets() view returns (uint256) | pUSD held. |
|---|
| previewDeposit(uint256 amount) view returns (uint256) | pLP a deposit would mint now. |
|---|
| previewRedeem(uint256 shares) view returns (uint256) | pUSD a withdrawal would pay now. |
|---|
| lockedUntil(address) view returns (uint256) | When an account's pLP unlocks. |
|---|
| balanceOf(address) view returns (uint256) | pLP held. |
|---|
| totalSupply() view returns (uint256) | pLP in existence. |
|---|
| allowance(address, address) view returns (uint256) | ERC-20 allowance. |
|---|
| name() view returns (string) | "Pepper Perp LP". |
|---|
| symbol() view returns (string) | "pLP". |
|---|
| decimals() view returns (uint8) | 18. |
|---|
| engine() view returns (address) | The engine, the only address that settles. |
|---|
| pusd() view returns (address) | pUSD. |
|---|
| bank() view returns (address) | SenditBank. |
|---|
| owner() view returns (address) | The deployer, who set the engine once. |
|---|
| VERSION() view returns (uint256) | 2. |
|---|
| SHARE_LOCK() view returns (uint256) | 1 hour. |
|---|
Engine and owner
| Function | What it does |
|---|
| settle(int256 amount) | The engine only: positive pulls pUSD in, negative pays it out. |
|---|
| setEngine(address engine_) | Once, by the owner. |
|---|
Events
| Event | What it does |
|---|
| Deposited(address indexed account, uint256 amount, uint256 shares) | An LP deposit. |
|---|
| Withdrawn(address indexed account, uint256 shares, uint256 amount) | An LP withdrawal. |
|---|
| Settled(int256 amount) | Money in or out of the engine. |
|---|
| Transfer(address indexed from, address indexed to, uint256 value) | pLP moved, minted or burned. |
|---|
| Approval(address indexed owner, address indexed spender, uint256 value) | ERC-20 approval. |
|---|
| EngineSet(address indexed engine) | The engine set. |
|---|