Для разработчиков / SENDIT
Контракты спота, pUSD и копирования
Каждый из них - площадка банка или аккаунт в банке: деньги приходят из accountOf(msg.sender) и возвращаются в банк.
Покупает, продаёт и запускает запущенные токены за ETH из банка, стоя перед существующим PepperLauncher, так что токены лежат в банке, где их может продать ключ. Это единственная площадка банка с лимитом. launch солит адрес токена аккаунтом, поэтому у каждого аккаунта своё пространство соли; predictToken(account, ...) заранее сообщает адрес. Лаунчер видит создателем роутер; роутер записывает аккаунт в creatorOf, а collect(token) пересылает комиссии создателя (WETH и сам токен) на кошелёк этого аккаунта. Перед отправкой используйте представления whyNot.
Launched tokens bought, sold and launched with bank money, in front of the existing PepperLauncher. A capped venue: a key's ETH spent here counts against the account's caps. It holds nothing between calls.
Trading
| Function | What it does |
|---|
| buy(address token, uint256 ethIn, uint256 minTokensOut) returns (uint256 tokensOut) | Spends ethIn of the account's bank ETH; tokens and any unused ETH land in its bank balance. |
|---|
| sell(address token, uint256 amountIn, uint256 minEthOut) returns (uint256 ethOut) | Sells bank-held tokens; the ETH lands in the bank balance. |
|---|
| launch((string name, string symbol, bytes32 imageHash, string description, bytes32 salt, uint256 minTokensOut) a, uint256 ethIn) returns (address token, uint256 tokensOut) | Launches with bank ETH (the launch fee, then the creator's first buy). The account is recorded as the creator. |
|---|
| collect(address token) returns (uint256 wethOut, uint256 tokensOut) | Permissionless: a router-launched token's creator fees, forwarded to the creator's wallet. |
|---|
| forwardStrayWeth(address token, uint256 amount) | The bank's operator: WETH a direct launcher collect left here, to that token's creator and nowhere else. |
|---|
Reading
| Function | What it does |
|---|
| whyNotBuy(address account, address token, uint256 ethIn) view returns (string) | Why buy would fail, or "": "paused", the launcher's reason, "balance" or "cap". |
|---|
| whyNotSell(address account, address token, uint256 amountIn) view returns (string) | Why sell would fail, or "". |
|---|
| whyNotLaunch(address account, LaunchArgs a, uint256 ethIn) view returns (string) | Why launch would fail, or "". |
|---|
| predictToken(address creator, string name, string symbol, bytes32 imageHash, bytes32 salt) view returns (address) | Where launch by this account deploys the token. |
|---|
| creatorOf(address token) view returns (address creator) | The account that launched a token through the router. |
|---|
| launcher() view returns (address) | PepperLauncher. |
|---|
| bank() view returns (address) | SenditBank. |
|---|
| weth() view returns (address) | WETH. |
|---|
| VERSION() view returns (uint256) | 1. |
|---|
Events
| Event | What it does |
|---|
| SpotBought(address indexed account, address indexed token, uint256 ethIn, uint256 tokensOut, uint256 ethRefunded) | A buy, naming the account. |
|---|
| SpotSold(address indexed account, address indexed token, uint256 tokensIn, uint256 ethOut) | A sell. |
|---|
| LaunchedFor(address indexed token, address indexed creator, uint256 ethIn, uint256 tokensOut) | A launch; creator is the account. |
|---|
| CreatorFeesForwarded(address indexed token, address indexed creator, uint256 weth, uint256 tokens) | Creator fees sent on to the creator's wallet. |
|---|
pUSD за ETH по одному доллару, по цене из раунда ETH/USD оракула, прочитанного ровно так же, как его читает движок бессрочных контрактов: pusdOut = ethIn x answer / 10^(18 + decimals - 6), с округлением вниз. Без комиссии. Два входа: buyWithEth (ETH из кошелька, открыт с момента развёртывания) и buy (ETH из банка, подпись ключом), который ждёт, пока обменник станет площадкой банка: whyNotBuy отвечает "soon", а venueActiveAt() говорит, когда. Резерв - это собственный баланс обменника в банке, поэтому каждая продажа - это send покупателю внутри банка. maxPerTx равен 10,000 pUSD при развёртывании и задаётся владельцем.
pUSD for ETH at exactly one dollar, priced by the oracle's ETH/USD. One direction, no fee. The pUSD always lands in the buyer's SENDIT balance; the ETH goes to the treasury.
Buying
| Function | What it does |
|---|
| buyWithEth(uint256 minPusdOut) payable returns (uint256 pusdOut) | The wallet's own ETH (msg.value). Refused for a registered key ("key"): the wallet sends it. |
|---|
| buy(uint256 ethIn, uint256 minPusdOut) returns (uint256 pusdOut) | From the SENDIT balance's ETH, key- or wallet-signed. Needs the exchange to be an active bank venue: "soon" until its 24-hour delay has passed. |
|---|
| topUp() returns (uint256 amount) | Permissionless: deposits loose pUSD the exchange holds into its reserve. |
|---|
| sweep() returns (uint256 amount) | Permissionless: ETH a failed treasury payment left here, to the treasury. |
|---|
Reading
| Function | What it does |
|---|
| quote(uint256 ethIn) view returns (uint256 pusdOut, uint256 price, uint256 updatedAt) | What ethIn buys at the latest ETH/USD round, whether or not a buy may use it now. |
|---|
| whyNotBuy(address sender, uint256 ethIn) view returns (string) | "paused", "soon", "venue", "stale", "min", "max", "reserve", "balance", or "". |
|---|
| whyNotBuyWithEth(address sender, uint256 ethIn) view returns (string) | "paused", "key", "stale", "min", "max", "reserve", or "". |
|---|
| reserve() view returns (uint256) | pUSD for sale: the exchange's own bank balance plus loose pUSD. |
|---|
| venueActiveAt() view returns (uint256) | When the balance door opens (unix time), 0 once active. |
|---|
| priceDecimals() view returns (uint8) | The ETH/USD feed's decimals, 8 on Pickle. |
|---|
| maxPerTx() view returns (uint256) | The most pUSD one buy may get. Owner-set; DEFAULT_MAX_PER_TX at deploy. |
|---|
| paused() view returns (bool) | The exchange's own pause. The bank's pause stops it too. |
|---|
| treasury() view returns (address) | Where the ETH goes. |
|---|
| owner() view returns (address) | The owner. |
|---|
| bank() view returns (address) | SenditBank. |
|---|
| pusd() view returns (address) | pUSD. |
|---|
| oracle() view returns (address) | The price feeds. |
|---|
| ethUsdFeedId() view returns (bytes32) | The ETH/USD feed id. |
|---|
| VERSION() view returns (uint256) | 1. |
|---|
| DEFAULT_MAX_PER_TX() view returns (uint256) | 10,000 pUSD. |
|---|
| IMMEDIATE_MAX_AGE() view returns (uint256) | 2 seconds: the oldest ETH/USD round a buy accepts. |
|---|
| STALE_GRACE() view returns (uint256) | 30 seconds added to another feed's heartbeat. |
|---|
| MAX_PRICE_DECIMALS() view returns (uint8) | 36. |
|---|
| TREASURY_GAS() view returns (uint256) | 50,000: the gas the treasury payment gets. |
|---|
Owner
| Function | What it does |
|---|
| setMaxPerTx(uint256 maxPerTx_) | The per-buy ceiling. |
|---|
| setTreasury(address treasury_) | Where the ETH goes. |
|---|
| setPaused(bool paused_) | Pauses both doors. |
|---|
| withdrawReserve(address to, uint256 amount) | pUSD out of the exchange's own reserve, never anybody else's balance. |
|---|
| transferOwnership(address next) | Hands ownership on. |
|---|
Events
| Event | What it does |
|---|
| Bought(address indexed account, address indexed by, uint256 ethIn, uint256 pusdOut, uint256 price) | A buy; by is the signer, price in the feed's decimals. |
|---|
| ReserveToppedUp(address indexed from, uint256 amount) | Loose pUSD deposited into the reserve. |
|---|
| ReserveWithdrawn(address indexed to, uint256 amount) | The owner took pUSD out of the reserve. |
|---|
| TreasuryPaymentHeld(address indexed treasury, uint256 amount) | The treasury refused the ETH; it waits for sweep. |
|---|
| Swept(address indexed treasury, uint256 amount) | Held ETH sent to the treasury. |
|---|
| MaxPerTxSet(uint256 maxPerTx) | The ceiling changed. |
|---|
| TreasurySet(address indexed treasury) | The treasury changed. |
|---|
| PausedSet(bool paused) | The pause switched. |
|---|
| OwnershipTransferred(address indexed previous, address indexed next) | Ownership changed. |
|---|
CopyFactory.open(leader, amount) создаёт CopyAccount копирующего для этого лидера (клон EIP-1167 по адресу CREATE2, один на пару, навсегда) или пополняет его, перемещая amount (не меньше $10) с баланса копирующего в банке через bank.move. Затем кипер копирования вызывает mirrorOpen для каждой позиции лидера, открытой после startedAt, передавая долю капитала, которую задействовал лидер; аккаунт проверяет рынок, сторону и плечо в сети и урезает долю до того, что позволяет баланс лидера. Выводы платят 5% с части сверх principal, половину лидеру и половину FeeRouter. Старые копии на прежней фабрике перечислены в copyFactoryPrevious манифеста.
Copy trading: open(leader, amount) makes or re-funds the copier's CopyAccount for that leader (one clone per pair, for ever) and moves the deposit into it from the copier's bank balance.
Copier
| Function | What it does |
|---|
| open(address leader, uint256 amount) returns (address account) | Starts copying `leader` with amount pUSD (at least MIN_DEPOSIT), or tops up and restarts the pair's account. Key- or wallet-signed. |
|---|
Reading
| Function | What it does |
|---|
| accountOf(address, address) view returns (address) | The account of (copier, leader), or 0. |
|---|
| accountsOf(address copier) view returns (address[]) | Every account a copier opened, stopped ones included. |
|---|
| accountsByLeader(address leader) view returns (address[]) | Every account copying a leader. |
|---|
| predictAccount(address copier, address leader) view returns (address) | The CREATE2 address of a pair's account. |
|---|
| copiersCount(address) view returns (uint256) | A leader's live copiers. |
|---|
| isAccount(address) view returns (bool) | Whether an address is one of this factory's accounts. |
|---|
| keepers(address) view returns (bool) | Whether an address may mirror. |
|---|
| feeRouter() view returns (address) | Where the router's half of the copy fee goes. |
|---|
| implementation() view returns (address) | The CopyAccount clones copy. |
|---|
| perps() view returns (address) | The engine. |
|---|
| orders() view returns (address) | The orders book. |
|---|
| pusd() view returns (address) | pUSD. |
|---|
| bank() view returns (address) | SenditBank. |
|---|
| owner() view returns (address) | The owner. |
|---|
| VERSION() view returns (uint256) | 3. |
|---|
| FEE_BPS() view returns (uint256) | 500: 5% of withdrawn gains. |
|---|
| LEADER_SHARE_BPS() view returns (uint256) | 5000: half the fee to the leader. |
|---|
| MIN_DEPOSIT() view returns (uint256) | 10e6: ten dollars. |
|---|
Accounts and owner
| Function | What it does |
|---|
| onStopped() | Called by an account when it stops: the leader's copier count drops. |
|---|
| onRestarted() | Called by an account when it restarts. |
|---|
| setKeeper(address keeper, bool allowed) | Owner: adds or removes a copy keeper. |
|---|
| setFeeRouter(address next) | Owner: the FeeRouter that receives half the fee. |
|---|
| transferOwnership(address next) | Owner: hands ownership on. |
|---|
Events
| Event | What it does |
|---|
| CopyOpened(address indexed copier, address indexed leader, address indexed account, uint256 amount) | A copy started or topped up. |
|---|
| KeeperSet(address indexed keeper, bool allowed) | A keeper added or removed. |
|---|
| FeeRouterSet(address indexed feeRouter) | The FeeRouter changed. |
|---|
| OwnershipTransferred(address indexed previous, address indexed current) | Ownership changed. |
|---|
One copier copying one leader. Holds the copier's money in its own bank balance and trades it on the engine when, where and how the leader does: same market, side and leverage, the same share of capital, and only leader positions opened after the copy started.
Copier
| Function | What it does |
|---|
| stop() | No new mirrors; every copied position closes (two-step ones by request). The money stays in the account. |
|---|
| stopAndWithdraw() returns (uint256 amount, uint256 fee) | stop, then every free pUSD to the copier's SENDIT balance, and every close that lands later is sent on too (SWEEPING). |
|---|
| withdraw(uint256 amount) returns (uint256 fee) | Free pUSD to the copier's SENDIT balance, less the fee on the part above principal. |
|---|
Keeper, copier or anyone
| Function | What it does |
|---|
| mirrorOpen(uint256 leaderPositionId, uint16 fractionBps) | Keeper only: copies a leader open with equity x fractionBps / 10000, clamped on chain to what the leader's position can be of the leader's capital. Under one dollar of margin, or no free pUSD, it is skipped. |
|---|
| resolveOpen(uint256 leaderPositionId) | Keeper or copier: records a two-step copy that filled or was cancelled. |
|---|
| mirrorClose(uint256 leaderPositionId) | Keeper or copier: closes the copy once the leader's position is gone. |
|---|
| sync() | Anyone: marks copies the engine already closed (a liquidation, say). |
|---|
| sweep() | Anyone, on a SWEEPING account: sends free pUSD to the copier under the fee rule. |
|---|
| fund(uint256 amount) | The factory only, after moving the deposit in. |
|---|
| initialize(address copier_, address leader_) | The factory only, once. |
|---|
Reading
| Function | What it does |
|---|
| equity() view returns (uint256 total) | Free pUSD plus the copied positions' value. |
|---|
| free() view returns (uint256) | The account's bank pUSD: what can be withdrawn now. |
|---|
| previewWithdraw(uint256 amount) view returns (uint256 fee, uint256 toLeader, uint256 toRouter) | The fee a withdrawal of amount would pay. |
|---|
| principal() view returns (uint256) | The copier's money still inside, which is never charged. |
|---|
| deposited() view returns (uint256) | Lifetime deposits. |
|---|
| withdrawn() view returns (uint256) | Lifetime withdrawals, fee included. |
|---|
| feesPaid() view returns (uint256) | Lifetime fees. |
|---|
| mirrors(uint256 leaderPositionId) view returns (uint256 copyPositionId, uint256 requestId, uint8 state) | A copy's state, by the leader position it follows. |
|---|
| mirrorOf(uint256 leaderPositionId) view returns (Mirror) | The full record: state, leverage, copyPositionId, openRequestId, closeRequestId, margin. |
|---|
| liveMirrors() view returns (uint256[]) | Leader position ids with a live copy. |
|---|
| liveCount() view returns (uint256) | How many, at most MAX_LIVE. |
|---|
| copier() view returns (address) | The copier's account. |
|---|
| leader() view returns (address) | The leader's account. |
|---|
| startedAt() view returns (uint64) | Leader positions opened before this are not copied. |
|---|
| stopped() view returns (bool) | Stopped. |
|---|
| sweeping() view returns (bool) | Asked for everything back. |
|---|
| factory() view returns (address) | The factory. |
|---|
| perps() view returns (address) | The engine. |
|---|
| orders() view returns (address) | The orders book. |
|---|
| pusd() view returns (address) | pUSD. |
|---|
| bank() view returns (address) | SenditBank. |
|---|
| VERSION() view returns (uint256) | 3. |
|---|
| MAX_LIVE() view returns (uint256) | 64 copied positions at once. |
|---|
| STATE_NONE() view returns (uint8) | 0: never seen. |
|---|
| STATE_PENDING() view returns (uint8) | 1: a two-step open is waiting. |
|---|
| STATE_OPEN() view returns (uint8) | 2: the copy is live. |
|---|
| STATE_CLOSING() view returns (uint8) | 3: a two-step close was sent. |
|---|
| STATE_CLOSED() view returns (uint8) | 4: over. |
|---|
| STATE_SKIPPED() view returns (uint8) | 5: never opened (too small, no free pUSD, refused, or cancelled). |
|---|
Events
| Event | What it does |
|---|
| Deposited(uint256 amount) | Money in. |
|---|
| Withdrawn(uint256 amount, uint256 fee, uint256 toLeader, uint256 toRouter) | The copier took money out. |
|---|
| Swept(uint256 amount, uint256 fee, uint256 toLeader, uint256 toRouter) | A SWEEPING account paid its copier when a close landed. |
|---|
| MirrorOpened(uint256 indexed leaderPositionId, uint256 copyPositionId, uint256 requestId, uint256 margin, uint16 leverage) | A copy opened, or requested on a two-step market. |
|---|
| MirrorResolved(uint256 indexed leaderPositionId, uint256 copyPositionId) | A two-step copy settled. |
|---|
| MirrorClosed(uint256 indexed leaderPositionId, uint256 copyPositionId, uint256 requestId) | A copy closed, or its close requested. |
|---|
| MirrorSkipped(uint256 indexed leaderPositionId, string reason) | A leader open that was not copied, and why. |
|---|
| Stopped() | Copying stopped. |
|---|
| Restarted(uint256 startedAt) | A top-up restarted a stopped account. |
|---|
| SweepArmed() | stopAndWithdraw marked the account SWEEPING. |
|---|
The test dollar: six decimals, minted by the faucet operator, not backed by anything. Trusted spenders (the bank among them) move it without an approve, so no trade needs a second signature.
ERC-20
| Function | What it does |
|---|
| transfer(address to, uint256 value) returns (bool) | Transfer. |
|---|
| transferFrom(address from, address to, uint256 value) returns (bool) | Transfer by allowance, or by a trusted spender with none. |
|---|
| approve(address spender, uint256 value) returns (bool) | Approve. |
|---|
| balanceOf(address) view returns (uint256) | Wallet balance. pUSD in SENDIT is in the bank, not here. |
|---|
| allowance(address holder, address spender) view returns (uint256) | type(uint256).max for a trusted spender. |
|---|
| totalSupply() view returns (uint256) | Supply. |
|---|
| name() view returns (string) | Name. |
|---|
| symbol() view returns (string) | "pUSD". |
|---|
| decimals() view returns (uint8) | 6. |
|---|
| trustedSpender(address) view returns (bool) | Whether an address moves pUSD with no approve. |
|---|
Minter and owner
| Function | What it does |
|---|
| mint(address to, uint256 value) | The minter (the faucet operator), at most MAX_MINT per call. |
|---|
| minter() view returns (address) | The minter. |
|---|
| owner() view returns (address) | The owner. |
|---|
| setMinter(address next) | Owner: a new minter. |
|---|
| setTrustedSpender(address spender, bool trusted) | Owner: adds or removes a trusted spender. |
|---|
| transferOwnership(address next) | Owner: hands ownership on. |
|---|
| MAX_MINT() view returns (uint256) | 100,000 dollars per mint. |
|---|
| VERSION() view returns (uint256) | The contract version. |
|---|
Events
| Event | What it does |
|---|
| Transfer(address indexed from, address indexed to, uint256 value) | Transfer. |
|---|
| Approval(address indexed owner, address indexed spender, uint256 value) | Approval. |
|---|
| MinterChanged(address indexed previous, address indexed current) | New minter. |
|---|
| TrustedSpenderSet(address indexed spender, bool trusted) | A trusted spender added or removed. |
|---|
| OwnershipTransferred(address indexed previous, address indexed current) | Ownership changed. |
|---|