sNOTE
Summary
Section titled “Summary”fact
| Property | Value |
|---|---|
| Standard | ERC-4626 vault over NOTE, 18 decimals, _decimalsOffset() = 6 (virtual shares) |
| Reward source | RevenueRouter only, via notifyReward(amount) after transferring NOTE |
| Streaming | Linear over STREAM_DURATION = 7 days, re-based on each notify |
| Withdrawal | requestWithdraw(shares) then wait cooldown (constructor argument, MAX_COOLDOWN = 30 days) then redeem/withdraw with owner == msg.sender |
| Share price | Monotonically non-decreasing |
Why streaming
Section titled “Why streaming”If rewards were credited instantly, a large NOTE holder could deposit in the block before a buyback fill, capture the reward, and withdraw. Streaming vests each reward linearly over seven days so that a depositor earns in proportion to time staked. Combined with the withdrawal cooldown, the sandwich is uneconomic.
Accounting
Section titled “Accounting”fact The cooldown is set at deployment. PROTOCOL_SPEC.md specifies a 3-day default; script/DeployToken.s.sol currently defaults SNOTE_COOLDOWN to 7 days unless overridden by environment. Governance can change it with setCooldown up to 30 days. The launch value is tbd.
On notifyReward(a) the router has already transferred a NOTE. The call reverts with RewardNotReceived if the vault balance does not cover the new stream. The vault sets streamAmount = unvestedRewards() + a, streamStart = now, streamEnd = now + 7 days. totalAssets is unchanged at the moment of the call, so the share price does not jump.
Worked example
Section titled “Worked example”illustrative Vault holds 1,000,000 NOTE against 1,000,000 shares (price 1.0). The router notifies 7,000 NOTE at t = 0.
| Time | Unvested | totalAssets | Share price |
|---|---|---|---|
| t = 0 | 7,000 | 1,000,000 | 1.000000 |
| t = 1 day | 6,000 | 1,001,000 | 1.001000 |
| t = 3.5 days | 3,500 | 1,003,500 | 1.003500 |
| t = 7 days | 0 | 1,007,000 | 1.007000 |
At t = 3.5 days the router notifies a further 3,500 NOTE. Stream re-bases: streamAmount = 3,500 (unvested) + 3,500 = 7,000, vesting over the next 7 days from t = 3.5 d. totalAssets remains 1,003,500 at that instant and reaches 1,010,500 at t = 10.5 days.
Withdrawals
Section titled “Withdrawals”requestWithdraw(shares): the shares are transferred into the vault and escrowed as a new request with its ownunlockAt = now + cooldown(at mostMAX_OPEN_REQUESTS = 8open requests per account). A later request never delays an earlier one; redemptions consume matured requests oldest-first. Requesting again adds shares and restarts the clock for the whole pending amount.- After
unlockAt,redeem(shares, receiver, owner)orwithdraw(assets, receiver, owner)for up to the pending amount, at the share price at redemption time. Escrowed shares keep accruing. cancelWithdrawRequest()returns escrowed shares to the caller.
Standard ERC-4626 redeem/withdraw without a matured request revert (NoRequest, CooldownActive(unlockAt), ExceedsRequested); calling on behalf of another owner reverts with OwnerMustBeCaller, so ERC-4626 allowances are not honoured for exits. deposit and mint are unrestricted (when not paused).
Inflation attack protection
Section titled “Inflation attack protection”The 10^6 virtual-share offset makes the classic first-depositor donation attack unprofitable: an attacker must donate a million times the victim’s deposit to steal one wei of value.
Governance surface
Section titled “Governance surface”| Function | Who | Event |
|---|---|---|
setRewardNotifier(addr) | owner | RewardNotifierUpdated |
setCooldown(seconds) (≤ 30 days) | owner | CooldownUpdated |
| pause / unpause | guardian / owner | OZ Paused / Unpaused |
What sNOTE does not do
Section titled “What sNOTE does not do”- It does not mint NOTE. All rewards are NOTE already bought on the open auction with realised fees.
- It does not rebase. Balances are constant; share price rises.
- It does not lock beyond the cooldown. There are no lock tiers or boosted multipliers.