Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Morpho Stock Vault contracts

Foundry repository for Morpho Stock Vault on Robinhood Chain (EIP-155 4663).

The vault is an official Morpho Vault V2 instance. Depositors put in USDG. The curator allocates that USDG into isolated Morpho Blue markets collateralized by Robinhood Stock Tokens. This repo does not fork Vault V2 or Morpho Blue. It deploys the curator-owned pieces around Morpho’s factories:

Contract Role
Morpho Vault V2 (msvUSDG) ERC-4626 USDG vault, created through Morpho’s factory
Morpho market adapter Supplies and withdraws USDG on Blue
MsvFeeSplitter performanceFeeRecipient. Splits minted vault shares 50/50
MsvFeeDistributor Stake $MSV, stream USDG to stakers
MsvHolderRewards Holder USDG claims. 50% × creator tax after last $MSV tx × holding / 1B

$MSV (0xc6e0A237844AFCa52D9C1B94b612134668a98c2F) is the staking token. It is not the vault asset.

Independent of Morpho, Robinhood, and Paxos. Not an offer of securities. Stock Tokens are not DTCC shares.

Status (15 September 2026)

Piece Status
Vault V2, adapter, splitter, distributor Deployed. Do not re-run DeployMsv.s.sol against this instance
$MSV staking Live. Min 1 $MSV. 7-day reward stream, 7-day unbond
$MSV holder rewards Live. Owner must recordTax and setLastMsvTx before first claims pay. $100 is reserve
Pilot caps Submitted. Morpho 3-day timelock. Execute after 17 September 2026, 21:57 UTC+3 with ExecuteMarkets.s.sol
Allocation / depositor APY 0 until caps execute, then allocate and harvest
Keeper / surplus router Not in this repo

Until caps execute, USDG sent to the vault sits idle. The production app keeps deposit/withdraw closed and staking open.

Do not use ConfigureMarkets.s.sol on this vault. That script submits and executes caps in one run. Caps on this instance are already submitted; a second execute before the timelock reverts.

Deployed addresses (chain 4663)

Verify on Blockscout. Never take an address from a DM.

Morpho Stock Vault

Name Address
Vault V2 (msvUSDG) 0xe2b27f8A96778f3D6C243687A73FC9836621E5eC
Market adapter 0xB37FE8Bb4169D2De64e5ADE7AfB1b342fCF79393
$MSV staking / fee distributor 0x1412a0C9844537561838B4e642AC920EB0a10e14
$MSV holder rewards 0xe698c140588C3C7Cf0DC9e4254096F1D81f206e4
Fee splitter 0x8b6e8F7B7cc46FEe428AA3BE5437fB4C5D2e0967
$MSV 0xc6e0A237844AFCa52D9C1B94b612134668a98c2F
Owner / curator / allocator 0x6A39c2229ba13732068ED93Bc5FA02528c2e6B42
Team fee wallet 0xfA34e3c534CF5C1d958f369372609e3A03C854b1

Share token name/symbol: MSV USDG Vault / msvUSDG (Morpho forbids “morpho” in the ERC-20 name). Product copy still uses Morpho Stock Vault.

Morpho / chain (third party)

Name Address
Morpho Blue 0x9D53d5E3bd5E8d4Cbfa6DB1ca238AEA02E651010
Adaptive Curve IRM 0x2BD3d5965B26B51814AC95127B2b80dD6CcC0fa1
USDG (6 decimals) 0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168
VaultV2Factory 0x0FBad98595b0186dA120E41f77C102beb49f803c
MorphoMarketV1AdapterV2Factory 0x79370Ed003CE325C088E530d5e8655c99c2993e1
Adapter registry 0xe785a2eFD384BA7B95BaEd3851BC76aeD67C676f

RPC: https://rpc.mainnet.chain.robinhood.com

Constants also live in src/MsvAddresses.sol. Confirm factories on Blockscout before any new broadcast.

Architecture

USDG depositor ──deposit──► Vault V2 (msvUSDG)
                                │
                                │ Morpho market adapter
                                ▼
                      Morpho Blue (isolated books)
                      NVDA / AAPL / GOOGL / SPY / TSLA
                      all 62.5% LLTV in this pilot
                                │
              Morpho interest ──┤
                                ├ 80% stays in share price (depositors)
                                ├ 20% performance ──► MsvFeeSplitter.release()
                                │                       ├ 10% of interest → team (vault shares)
                                │                       └ 10% of interest → distributor
                                │                            harvest() → USDG, 7-day stream
                                └ 2% APY management ──► team (vault shares)

Borrowers never enter this vault. They post Stock Tokens on Morpho Blue and borrow USDG there.

Idle USDG in the vault is depositor principal (withdrawal buffer), not the $MSV fee pot.

Fees (as deployed)

Morpho does not pay a fixed APY. Supply rates follow the Adaptive Curve IRM and can be near zero until utilization appears.

Fee Parameter Recipient
Performance 20% of Morpho interest (Morpho cap 50%) Splitter: 50/50 team / stakers → 10% of interest each
Management 2% APY of AUM (Morpho cap 5%/year) Team wallet, as vault shares
Adapter skim Morpho adapter leftover Team wallet

Depositors keep 80% of Morpho interest. Management still mints shares on AUM.

After the vault mints performance-fee shares to the splitter, anyone may call MsvFeeSplitter.release(). The distributor then needs harvest() (anyone) to redeem those shares to USDG and start the 7-day stream.

Pilot markets

Five liquid 62.5% LLTV USDG books. Absolute caps are USDG (6 decimals). Relative caps are WAD (1e18 = 100%). Config: script/MsvPilotMarkets.sol.

Idle-liquidity adapter is SPY 62.5% (lowest utilization of the sized books).

Market Abs cap Rel cap Morpho market id
NVDA 62.5% $35,000 35% 0x8b16891f032a93b771347c9cb470a780e6699dd701553d3402aa3cdba6189c3e
AAPL 62.5% $25,000 25% 0xdeb4782d012d5fd3b24962538c2f6559049d70bda4dabd2e4212dacb96c28d45
GOOGL 62.5% $20,000 20% 0x7fa81b10e5d21b2e4c571f862442bc11aff2ed14f02335868d0ff933fd40d0ba
SPY 62.5% $10,000 15% 0x50bc39b5722fb5634c436d74c6787f3c125b879e7b73cf9e9ecc01bbb57b8e55
TSLA 62.5% $8,000 10% 0xb41b34c5989420ad080e79363a9cfe3e23bec7459fcd2d88029250da370288df

Timelock on cap increases is 3 days (259200 seconds). That is intentional.

$MSV staking (MsvFeeDistributor)

Idle $MSV earns nothing. Stakers earn only USDG that streams while they are staked.

Parameter Value
Stake asset $MSV (18 decimals)
Reward asset USDG (6 decimals)
Accounting Reward-per-token. New stakers do not inherit already-streamed fees
Min stake 1 $MSV (1e18). Remaining stake after unbond must be 0 or ≥ 1
Stake Instant
Reward stream Each harvested USDG amount is paid linearly over 7 days
Empty stake pool Incoming USDG is queued, then streamed over 7 days once someone stakes (no lump to the first wallet)
Claim Anytime; only USDG already accrued to msg.sender
Unbond 7 days. One pending unbond per wallet. No new accrual during unbond. Accrued USDG still claimable
Exit After unlock, withdraw $MSV
harvest() Anyone. Redeems distributor vault shares up to maxRedeem, then streams USDG
notifyRewardAmount Optional keeper/surplus path. Pulls USDG from caller
recover Owner only. Cannot recover $MSV, USDG, or vault shares
pending = stake × (rewardPerToken − userCheckpoint) / 1e18 + storedRewards

Repository layout

src/
  MsvFeeDistributor.sol   # staking + USDG stream
  MsvFeeSplitter.sol      # 50/50 performance-fee share split (immutable)
  MsvAddresses.sol        # 4663 constants
script/
  DeployMsv.s.sol         # factory vault + adapter + fees + timelocks (already broadcast)
  SubmitMarkets.s.sol     # set liquidity adapter + submit caps (already broadcast)
  ExecuteMarkets.s.sol    # execute those caps after the 3-day timelock
  MsvPilotMarkets.sol     # shared five-market config
  ConfigureMarkets.s.sol  # one-market submit+execute — do not run on the live vault
  interfaces/             # local Morpho Vault V2 / Blue interfaces
test/                     # Foundry tests for distributor and splitter

Vault V2 and Morpho Blue source are not vendored. Scripts talk to on-chain factories through script/interfaces/IMorphoVaultV2.sol.

Requirements

  • Foundry (forge, cast)
  • solc 0.8.28 (see foundry.toml; EVM cancun, optimizer 200 runs)
  • OpenZeppelin Contracts v5.4.0
  • forge-std

lib/ is gitignored. Clone dependencies once:

mkdir -p lib
git clone --depth 1 https://github.com/foundry-rs/forge-std.git lib/forge-std
git clone --depth 1 --branch v5.4.0 https://github.com/OpenZeppelin/openzeppelin-contracts.git lib/openzeppelin-contracts

vault-v2/ and morpho-blue/ remappings exist for optional local research. They are not required to compile or test this repo.

Windows: if forge test fails while fetching solc, install solc 0.8.28 to %USERPROFILE%\.svm\0.8.28\solc-0.8.28.exe and pass --use.

Test

forge test -vvv

Coverage is the distributor (stake, unbond, stream, empty-pool queue, harvest) and the splitter (release, bps). Vault V2 behavior is Morpho’s; it is not re-tested here.

Environment

Copy .env.example to .env. Never commit .env. Do not put a private key in git, issues, or chat.

set -a && source .env && set +a

PowerShell:

Get-Content .env | ForEach-Object {
  if ($_ -match '^\s*#' -or $_ -notmatch '=') { return }
  $k,$v = $_.Split('=',2)
  Set-Item -Path "Env:$k" -Value $v.Trim().Trim('"')
}

PRIVATE_KEY is only required for --broadcast. Simulation does not need it.

Scripts

Simulate first (-vvv, no --broadcast). Add --broadcast --private-key $PRIVATE_KEY only when the calldata is correct.

1. DeployMsv.s.sol — already broadcast for this product

Creates Vault V2 via VaultV2Factory.createVaultV2, deploys the adapter, distributor, and 50/50 splitter, sets 20% performance fee, 2% management fee, adapter skim, maxRate, dead deposit, and 3-day timelocks.

Do not re-deploy if the goal is the live msvUSDG instance above. A new salt produces a different vault.

forge script script/DeployMsv.s.sol --rpc-url "$RPC_URL" -vvv

2. SubmitMarkets.s.sol — already broadcast

Sets the liquidity adapter to SPY 62.5% (instant) and submits absolute/relative caps for the five markets. Does not execute.

3. ExecuteMarkets.s.sol — after 17 September 2026, 21:57 UTC+3

Executes the submitted caps. Calldata must match MsvPilotMarkets.sol exactly (same ids and cap values as the submit).

forge script script/ExecuteMarkets.s.sol --rpc-url "$RPC_URL" -vvv
# then, only after the timelock:
# forge script script/ExecuteMarkets.s.sol --rpc-url "$RPC_URL" --broadcast --private-key "$PRIVATE_KEY" -vvv

After execute, an allocator must allocate USDG into the enabled markets. Staking APY remains ~0 until there is interest, release(), and harvest().

4. ConfigureMarkets.s.sol — not for the live vault

Single-market submit and execute in one transaction. Useful only for a new vault with no pending cap timelock. Running it on the live instance will fight the already-submitted caps.

5. MsvHolderRewards — holder USDG claims (live)

Live: 0xe698c140588C3C7Cf0DC9e4254096F1D81f206e4. Owner is the same curator EOA. The contract does not need ETH; the owner wallet does (gas + USDG reserve).

Creator tax is recorded with recordTax / recordTaxBatch (full creator amount, not the 50% cut, non-decreasing taxTime, >= taxOrigin 12 Sep 2026 00:00 UTC). USDG deposit is reserve only. It is not the reward numerator.

First claim needs setLastMsvTx(wallet, lastTransferUnix) once (indexer: last $MSV Transfer, ignoring stake/unstake). Window start is max(taxOrigin, lastMsvTx) until the wallet claims, then checkpoint is now. Min hold 100,000 $MSV.

USDG = 50% × creatorTaxAfter(windowStart) × (wallet $MSV + staked $MSV) / 1,000,000,000

# USDG reserve (FUND_AMOUNT = USDG 6 decimals, e.g. 100000000 = $100)
export FUND_AMOUNT=100000000
forge script script/FundHolderRewards.s.sol --rpc-url "$RPC_URL" --broadcast --private-key "$PRIVATE_KEY" -vvv

# backfill creator tax from script/taxes.json
python3 script/scanTaxes.py
forge script script/RecordTax.s.sol --rpc-url "$RPC_URL" --broadcast --private-key "$PRIVATE_KEY" -vvv

# first-time last $MSV tx
python3 script/scanLastMsvTx.py
forge script script/BackfillLastMsvTx.s.sol --rpc-url "$RPC_URL" --broadcast --private-key "$PRIVATE_KEY" -vvv

# withdraw leftover USDG (or any token) to OWNER
export RECOVER_TOKEN=0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168
export RECOVER_AMOUNT=1000000
forge script script/RecoverHolderRewards.s.sol --rpc-url "$RPC_URL" --broadcast --private-key "$PRIVATE_KEY" -vvv

recover can sweep USDG at any time. If you pull below pending claims, the next claim reverts InsufficientReserve.

Security notes

  • Vault roles (owner, curator, allocator) are Morpho Vault V2 roles. Cap increases are timelocked; that is the listing-friendly path, not a bug.
  • MsvFeeSplitter is not upgradeable. teamBps is immutable (5000 at deploy).
  • MsvFeeDistributor owner can recover stray tokens only. Stake, reward, and vault share tokens are protected.
  • MsvHolderRewards recordTax and setLastMsvTx are owner-only. setLastMsvTx cannot overwrite. Owner recover can sweep leftover USDG.
  • Dead deposit to 0xdead is Morpho’s inflation guard (dust). Seed USDG, if any, is working capital held as vault shares — not a protocol fee.
  • This code has unit tests. It is not a substitute for Morpho’s own Vault V2 audits.

Product sites

Site URL
Markets https://msv.gold
App (stake live) https://lend.msv.gold
Docs https://docs.msv.gold
$MSV https://token.msv.gold

License

GPL-2.0-or-later. See SPDX headers in src/ and script/. Morpho Blue, Vault V2, USDG, and Stock Tokens are third-party deployments with their own licenses and terms.

About

Morpho Blue USDG Loans Against Robinhood Stock Tokens

Topics

Resources

Stars

162 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages