面向开发者 / SENDIT

现货、pUSD 与跟单合约

这些合约每一个都是银行的场所或银行的账户:资金来自 accountOf(msg.sender),并回到银行。

SenditSpotRouter

用银行中的 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

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,价格取自预言机的 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.