개발자를 위한 문서 / SENDIT

현물, pUSD, 카피 컨트랙트

이 컨트랙트는 하나하나가 뱅크의 베뉴이거나 뱅크 계정입니다. 돈은 accountOf(msg.sender)에서 나오고 다시 뱅크로 돌아갑니다.

SenditSpotRouter

기존 PepperLauncher 앞에 서서, 뱅크의 ETH로 출시된 토큰을 사고, 팔고, 출시합니다. 그래서 토큰은 키가 팔 수 있는 뱅크 안에 머뭅니다. 뱅크에서 한도가 걸린 유일한 베뉴입니다. 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

FunctionWhat 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

FunctionWhat 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

EventWhat 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.

SenditPusdExchange

ETH를 받고 pUSD를 1달러에 팝니다. 가격은 오라클의 ETH/USD 라운드를 무기한 선물 엔진이 읽는 방식 그대로 읽어 정합니다: pusdOut = ethIn x answer / 10^(18 + decimals - 6), 내림. 수수료는 없습니다. 입구는 두 개입니다. buyWithEth(지갑 ETH, 배포 때부터 열림)와 buy(뱅크 ETH, 키 서명)이며, 후자는 교환 컨트랙트가 뱅크 베뉴가 되기를 기다립니다. 그동안 whyNotBuy는 "soon"을 답하고 venueActiveAt()이 그 시점을 알려 줍니다. 준비금은 교환 컨트랙트 자신의 뱅크 잔액이므로, 모든 판매는 구매자에게 가는 뱅크 내부의 send입니다. maxPerTx는 배포 때 10,000 pUSD이며 소유자가 설정합니다.

SenditPusdExchange

pusdExchange

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

FunctionWhat 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

FunctionWhat 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

FunctionWhat 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

EventWhat 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와 CopyAccount

CopyFactory.open(leader, amount)는 그 리더에 대한 카피하는 사람의 CopyAccount를 만들거나(CREATE2 주소의 EIP-1167 클론, 쌍마다 하나, 영구히) 자금을 추가하며, bank.move로 카피하는 사람의 뱅크 잔액에서 amount(최소 10달러)를 옮깁니다. 그다음 카피 키퍼가 startedAt 이후에 열린 리더의 포지션마다 mirrorOpen을 호출하며, 리더가 투입한 자본 비율을 넘깁니다. 계정은 시장, 방향, 레버리지를 체인 위에서 검사하고, 그 비율을 리더의 잔액이 허용하는 만큼으로 제한합니다. 출금 때는 principal을 넘는 부분의 5%를 내며, 절반은 리더에게, 절반은 FeeRouter에게 갑니다. 이전 팩토리의 오래된 카피는 매니페스트의 copyFactoryPrevious에 나열되어 있습니다.

CopyFactory

copyFactory

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

FunctionWhat 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

FunctionWhat 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

FunctionWhat 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

EventWhat 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.

CopyAccount

copyAccountImpl

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

FunctionWhat 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

FunctionWhat 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

FunctionWhat 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

EventWhat 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.

pUSD

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

FunctionWhat 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

FunctionWhat 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

EventWhat 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.