SPY DAO is an on-chain governance system built on Rayls that gives SPY-style index investors a structured way to coordinate how their underlying voting rights should be used. It uses an ERC-4626 yield-bearing vault and a Governor contract designed from day one with compliance, risk controls, and institutional workflows in mind.
| Badge | Detail |
|---|---|
| 🏆 Built for the Rayls Hackathon | Rayls-native index governance prototype |
| ✅ Category: Use of ERC-4626 yield-bearing vaults | Yield-bearing, governance-enabled shares |
| ✅ Theme: Bridging TradFi ↔ DeFi for index governance | Compliant on-chain coordination for SPY holders |
- 🧭 Hackathon Framing
- 🌉 Alignment with the Rayls Vision
- 🎯 Problem
- 🚀 Solution
- 🏗️ Architecture (MVP)
- 🧱 Compliance Considerations in the Design
- 🚀 Getting Started (MVP)
- 🛠️ App Development (Frontend / Backend)
- 🧪 Testing
- 🔐 Security & Risk Posture (MVP)
- 🏆 Hackathon Judging Checklist
- 📄 License
- 🏆 Built at Rayls Hackathon
Index funds manage trillions of dollars, but end investors effectively surrender their governance rights to large asset managers. Ordinary SPY holders don’t vote on S&P 500 proposals; intermediaries do. This creates a massive principal–agent problem: the people who own the capital don’t control how it’s used, and there’s no clear, compliant path to route those governance signals on-chain.
SPY DAO is a Rayls-native ERC-4626 vault plus Governor that:
- Wraps a USD-like token in an ERC-4626 vault and mints governance-enabled shares (
spDAO). - Aggregates depositors’ voting weight into a single Governor that decides how to vote on S&P 500 shareholder proposals.
- Rewards voting participation with stablecoin incentives.
- Bakes in KYC gating, sanctions checks, oracle risk controls, and supervised broker withdrawals—so the design is compatible with institutional compliance teams from day one.
Over time, SPY DAO becomes the canonical “index governance layer” for tokenised SPY-style exposure on Rayls:
- Upstream on Rayls Privacy Nodes / Private Networks, real brokerage/custody flows and tokenised SPY exposures are managed by regulated institutions.
- Downstream on the Rayls Public Chain, SPY DAO exposes a liquid ERC-4626 governance vault, giving both institutions and DeFi users a compliant way to express governance preferences over S&P 500 proposals.
- Governance signals can be selectively disclosed to brokers/custodians, aligning with Rayls’ vision of privacy-preserving, regulator-friendly infrastructure.
In production, SPY DAO can generate revenue by:
- Charging basis points on AUM in the ERC-4626 vault (management or platform fee).
- Taking a per-proposal or per-vote fee from issuers, brokers, or activist campaigns that want to poll SPY holders at scale.
- Offering white-label SPY-style governance vaults to asset managers on Rayls Privacy Nodes who want on-chain governance aggregation without building the plumbing themselves.
SPY DAO is designed to sit natively on Rayls and align with the architecture in the litepaper:
- Upstream: Real SPY positions and brokerage flows live on Rayls Privacy Nodes / Private Networks, where institutions tokenise and manage positions privately.
- Downstream: SPY DAO exposes a public ERC-4626 vault on the Rayls Public Chain, aggregating governance power while respecting institutional constraints.
- The MVP models synthetic SPY exposure on-chain, but the design anticipates plugging into Rayls’ privacy stack (e.g. Enygma-style private balances and ZK/TLS attestations) so that raw holdings remain private while governance aggregates are public and auditable.
- KYC and sanctions flags in the vault are a concrete implementation of Rayls’ layered identity approach: only approved addresses can enter/exit the vault or transfer
spDAO, mirroring how Rayls expects regulated flows to work.
- Time-delayed, capped broker withdrawals, emergency accounting modes, and oracle sanity checks reflect Rayls’ focus on institutional-grade risk management and operational controls.
- As an ERC-4626 vault + Governor, SPY DAO is a composable primitive that other Rayls DeFi protocols can integrate (e.g. structured products, meta-vaults, or governance-based indices).
Index funds manage trillions in assets, but investors typically surrender their governance rights.
When the S&P 500 votes on climate policies, compensation, or governance reforms, ordinary SPY holders don’t vote directly – large asset managers vote on their behalf. This creates a massive principal–agent problem:
- The people who own the capital often don’t control how it’s used.
- Governance decisions are opaque and highly intermediated.
- There is no standard, compliant, on-chain way to coordinate the voice of SPY-style investors, even as assets and workflows move onto Rayls.
SPY DAO is a delegated governance vault built on Rayls that:
- Issues liquid shares (
spDAO) representing proportional SPY-style exposure via an ERC-4626 vault over a USD-like token. - Aggregates voting power from all depositors into a single on-chain Governor contract.
- Democratically decides how to vote on S&P 500 shareholder proposals.
- Rewards participation by distributing tokens (e.g. stablecoins) to addresses that vote.
- Builds in compliance & risk controls at the contract level:
- KYC gating for deposits/withdrawals.
- Sanctions blocking on transfers.
- Oracle sanity checks and emergency modes.
- Time-delayed broker withdrawals with configurable limits.
🔮 Future roadmap
ZK-TLS proofs and fully trustless broker integrations are part of the future roadmap, aligned with Rayls’ privacy and auditability goals, but are intentionally out of scope for this MVP.
┌─────────────────────────────────────────────────────────────┐
│ USERS (Rayls) │
│ │
│ 1. Faucet MockUSD │
│ 2. Deposit into SPYVault → Receive spDAO │
│ 3. Delegate / vote in SPYDAOGovernor │
│ 4. Claim governance rewards in MockUSD │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ ON-CHAIN (Rayls) │
│ │
│ MockUSD – 6-decimals test USD token │
│ MockSPYOracle – mock SPY price feed (8 decimals) │
│ │
│ SPYVault (ERC4626 + ERC20Votes + AccessControl) │
│ • totalAssets = on-chain USD + synthetic SPY exposure │
│ • KYC + sanctions gating │
│ • Global pause │
│ • Broker withdrawal pipeline (delay + caps) │
│ │
│ SPYDAOGovernor (Governor + GovernorVotes) │
│ • Proposals for S&P 500 shareholder votes │
│ • Voting power = spDAO voting weight │
│ • Voter reward mechanism using REWARD_TOKEN (MockUSD) │
└─────────────────────────────────────────────────────────────┘
Future extension (not in MVP code yet):
### Future extension (not in MVP code yet):
```text
text
Copy code
┌─────────────────────────────────────────────────────────────┐
│ ZK-TLS Verifier (VoteExecutionProof.sol) │
│ • Verifies broker API calls with zero-knowledge proofs │
│ • Anchors off-chain execution back on-chain │
└─────────────────────────────────────────────────────────────┘
The main vault that wraps a USD-like token and mints governance-enabled shares (spDAO).
-
ERC4626 – vault over MockUSD (underlying asset).
-
ERC20 – share token: SPY DAO Share (spDAO).
-
ERC20Permit – gasless approvals.
-
ERC20Votes – snapshot-based voting power for governance.
-
AccessControl – roles for admin, executor, and compliance.
-
Manages other roles.
-
Can update maxSingleWithdrawal.
-
Can toggle synthetic accounting mode (setIgnoreSynthetic).
-
Updates synthetic SPY exposure (setSyntheticHoldings).
-
Schedules and executes broker withdrawals.
-
Manages KYC and sanctions flags.
-
Controls global pause (setGlobalPause).
- Interface with
latestAnswer()->uint256(SPY price, 8 decimals).
- Mock implementation for devnet with:
setPrice(uint256 newPrice)to adjust SPY price (for testing).syntheticShareBalance– tracks how much synthetic SPY exposure the vault models (trusted).
- Always counts on-chain USD:
IERC20(asset()).balanceOf(address(this)).
- Tries to add synthetic value:
- Calls
spyOracle.latestAnswer()in a try/catch. - Rejects 0 or absurd prices (>
MAX_ORACLE_PRICE). - Computes
offChainValue = price * syntheticShareBalance / 1e8.
- Calls
- If
ignoreSynthetic == trueor oracle fails,totalAssets()falls back to on-chain USD only.
This reflects the reality that underlying SPY execution happens off-chain (via brokers/custodians), while the vault maintains a robust, failure-tolerant on-chain NAV model.
To mirror sending funds to a broker/custodian that actually buys SPY, the vault defines a controlled withdrawal flow:
- BROKER_WALLET – broker hot wallet receiving underlying asset.
- WITHDRAWAL_DELAY = 2 days – minimum delay from scheduling to execution.
- maxSingleWithdrawal – per-withdrawal cap in underlying token units.
- EXECUTOR_ROLE only, vault not paused.
- Requires:
- assets <= maxSingleWithdrawal.
- No existing pending withdrawal.
- assets <= current on-chain balance.
- Sets:
- pendingWithdrawalAmount = assets.
- pendingWithdrawalTime = block.timestamp + WITHDRAWAL_DELAY.
- Emits ScheduledBrokerWithdrawal(amount, executeAfter).
- EXECUTOR_ROLE only, vault not paused.
- Requires:
- block.timestamp >= pendingWithdrawalTime.
- pendingWithdrawalAmount > 0.
- Vault still has enough balance at execution.
- Transfers underlying to BROKER_WALLET.
- Clears pending state.
- Emits ExecutedBrokerWithdrawal(amount, brokerWallet).
- DEFAULT_ADMIN_ROLE only.
- Clears pending state.
- Emits CancelledBrokerWithdrawal().
This makes withdrawals to the broker visible, delayed, capped, and cancellable, in line with how institutional flows are supervised.
The Governor coordinates how SPY DAO votes on S&P 500 shareholder proposals and rewards participation.
- Governor
- GovernorCountingSimple
- GovernorVotes (uses spDAO as voting token)
- VOTING_DELAY = 1 block.
- VOTING_PERIOD = 45_000 blocks (~1 week on Rayls devnet, depending on block time).
- QUORUM = 10 * 1e18 (10 spDAO, assuming 18-decimals share token).
solidity Copy code struct SP500Proposal { string companyTicker; // e.g. "AAPL" uint256 shareholderProposalId; // from Broadridge / Say / etc. bool voteFor; // what the DAO recommends (for/against) uint256 totalVotes; // total voting weight participating bool executed; // set on Governor execution } mapping(uint256 => SP500Proposal) public sp500Proposals;
mapping(uint256 => mapping(address => bool)) public hasClaimedReward;
solidity Copy code function proposeSP500Vote( string memory companyTicker, uint256 shareholderProposalId, bool voteFor, string memory description ) external returns (uint256) Creates an advisory proposal (no on-chain side effects).
Stores metadata in sp500Proposals[proposalId].
Underlying Governor mechanics handle voting windows and states.
Overrides _castVote to track participation:
Calls parent _castVote to record the vote.
Increments proposal.totalVotes by the voter’s weight.
Emits:
solidity Copy code SP500VoteCast( proposalId, proposal.companyTicker, support == 1, account, votes );
IERC20 public immutable REWARD_TOKEN;
In the MVP, this is the MockUSD token.
REWARD_PER_VOTE = 1e16 (0.01 units per 1e18 voting weight).
Rewards are linear in voting weight:
solidity Copy code reward = (votingWeight * REWARD_PER_VOTE) / 1e18;
solidity Copy code function claimRewards(uint256 proposalId) external Checks:
Voting period has ended.
Caller hasn’t already claimed for this proposal.
There were votes (proposal.totalVotes > 0).
Caller had voting power at the proposal snapshot (via getVotes).
If all checks pass:
Transfers reward from Governor to caller.
Increments totalRewardsDistributed.
Marks hasClaimedReward[proposalId][msg.sender] = true.
Emits RewardsClaimed(proposalId, voter, reward).
solidity Copy code function distributeRewards(uint256 amount) external { REWARD_TOKEN.safeTransferFrom(msg.sender, address(this), amount); } Anyone can fund future rewards by transferring MockUSD into the Governor.
A minimal mock stablecoin used for both:
- Vault underlying.
- Governance rewards.
- Name: Mock USD, symbol: mUSD.
- decimals() = 6.
- Mints 1,000,000 mUSD to deployer on construction.
mint(address to, uint256 amount)– unrestricted mint (testing only).faucet()– mints 1,000 mUSD to caller for easy devnet testing.
- Interface with:
solidity Copy code function latestAnswer() external view returns (uint256);
- Simple mock implementation with:
- _price stored internally (8 decimals).
- latestAnswer() returning _price.
setPrice(uint256 newPrice)to adjust price (open to anyone in this MVP).PriceUpdated(oldPrice, newPrice)event for observability.
The contracts are intentionally structured to be compatible with a more regulated environment and with Rayls’ compliance-first vision:
- All deposit, mint, withdraw, redeem operations require:
isKYCCompleted[account] == truefor caller, receiver, and owner (where applicable).
- KYC flags managed by
COMPLIANCE_ROLEviasetComplianceStatus.
isSanctionsBlocked[account]prevents:- Vault entry/exit via deposit/withdraw/redeem.
- Transfers of
spDAOtokens in_update(both from and to).
- This mirrors Rayls’ expectation that sanctioned or high-risk addresses can be blocked at the protocol level.
globalPausecan be toggled byCOMPLIANCE_ROLE.- When true, all deposit/mint/withdraw/redeem operations revert.
- Provides a circuit breaker for regulatory or technical incidents.
totalAssets()uses a try/catch aroundspyOracle.latestAnswer().- Discards zero or out-of-bound prices (>
MAX_ORACLE_PRICE). - If the oracle fails or misbehaves, the vault falls back to on-chain USD only, instead of breaking NAV.
- Admin can call
setIgnoreSynthetic(true)to:- Treat synthetic SPY exposure as zero.
- Only use on-chain USD for NAV until the off-chain side is stable again.
- Withdrawals to the broker are:
- Delayed (2-day minimum).
- Capped per withdrawal (maxSingleWithdrawal).
- Visible via events.
- Cancellable by admin.
- This matches how traditional SPY flows would be supervised by risk and compliance.
- Key actions emit structured events:
- ComplianceStatusUpdated
- GlobalPauseSet
- SyntheticHoldingsUpdated
- SyntheticAccountingModeSet
- ScheduledBrokerWithdrawal / ExecutedBrokerWithdrawal / CancelledBrokerWithdrawal
- SP500VoteCast, ProposalExecuted, RewardsClaimed
- These can feed Rayls-native monitoring, reporting, and regulatory dashboards.
- Node.js 18+
- npm
- Access to Rayls Devnet RPC
- A funded Rayls Devnet account (private key) for deployment
Use bash and Copy code to run:
git clone <repository-url>
cd SpyDao
npm installCreate a .env file in the project root. Use bash and Copy code to populate it:
PRIVATE_KEY=0xYOUR_PRIVATE_KEY # Deployer key (Rayls devnet)
RAYLS_RPC=https://devnet-rpc.rayls.com
PUBLIC_KEY=0xYourPublicAddress # (optional, informational)Ensure .env is in .gitignore (it already is in this repo).
Use bash and Copy code to compile:
npx hardhat compileThis compiles:
- SPYVault
- SPYDAOGovernor
- MockUSD
- MockSPYOracle
- ISPYPublicOracle interface
Use the provided Hardhat deployment script. Use bash and Copy code:
npx hardhat run scripts/deploy.js --network raylsThe script will:
- Deploy MockUSD.
- Deploy MockSPYOracle with an initial SPY price.
- Deploy SPYVault, wired to:
- MockUSD as underlying asset.
- MockSPYOracle as oracle.
- Deployer as EXECUTOR_ROLE, BROKER_WALLET, and initial COMPLIANCE_ROLE.
- Deploy SPYDAOGovernor, wired to:
- SPYVault as voting token.
- MockUSD as REWARD_TOKEN.
| Entity | Address | Explorer Link |
|---|---|---|
| Deployer | 0xaFEf2f5626961ba219Cd9Eb1D7A1f1B08896DD08 | |
| MockUSD | 0xB6c7B1ef0947596FC2d8eBE577b97f34cBBD2Fb1 | https://devnet-explorer.rayls.com/address/0xB6c7B1ef0947596FC2d8eBE577b97f34cBBD2Fb1 |
| MockSPYOracle | 0x19a881AF139D665bD857Aedf6EDcBc0dBee52765 | https://devnet-explorer.rayls.com/address/0x19a881AF139D665bD857Aedf6EDcBc0dBee52765 |
| SPYVault | 0x2181BaAAb16e8a4E08c7fFAB8DA1780B53eD05D2 | https://devnet-explorer.rayls.com/address/0x2181BaAAb16e8a4E08c7fFAB8DA1780B53eD05D2 |
| SPYDAOGovernor | 0x4DD81784392EC93a7e88e190Baca21EfF486895D | https://devnet-explorer.rayls.com/address/0x4DD81784392EC93a7e88e190Baca21EfF486895D |
Use Copy code for:
{
"network": "rayls",
"deployer": "0xaFEf2f5626961ba219Cd9Eb1D7A1f1B08896DD08",
"MockUSD": "0xB6c7B1ef0947596FC2d8eBE577b97f34cBBD2Fb1",
"MockSPYOracle": "0x19a881AF139D665bD857Aedf6EDcBc0dBee52765",
"SPYVault": "0x2181BaAAb16e8a4E08c7fFAB8DA1780B53eD05D2",
"SPYDAOGovernor": "0x4DD81784392EC93a7e88e190Baca21EfF486895D"
}The repo is a full-stack Rayls app.
npm run dev– Start dev server (frontend + backend).npm run build– Build for production.npm run start– Run production build.npm run check– TypeScript type checking.npm run db:push– Apply Drizzle schema changes.
- React 18 + TypeScript
- Vite
- Tailwind CSS
- Radix UI
- Wouter
- TanStack Query
- Framer Motion
- Node.js + Express
- TypeScript
- Drizzle ORM
- Neon/Postgres
- WebSocket for real-time updates
Smart Contracts
Solidity ^0.8.20
Hardhat
OpenZeppelin contracts
Deployed on Rayls Devnet
Integration
Ethers.js for contract calls from the app.
(Future) Broker APIs + ZK-TLS verification.
text
Copy code
SpyDao/
├── server/ # Express backend
│ ├── db/ # Drizzle schema and migrations
│ ├── routes/ # REST endpoints (if used in UI)
│ └── services/ # Business logic
├── src/ # React frontend
│ ├── components/
│ ├── pages/
│ ├── hooks/
│ └── lib/
├── contracts/ # Solidity smart contracts
│ ├── spydao.sol # SPYVault
│ ├── SpyDaoGovernor.sol # Governor + rewards
│ ├── mockusd.sol # MockUSD
│ ├── ISPYPublicOracle.sol
│ └── MockSPYOracle.sol
└── scripts/ # Hardhat deploy scripts
Use bash and Copy code:
npx hardhat test(Add or extend tests in test/ as you build out the protocol.)
If/when app tests are configured:
Use bash and Copy code:
npm test # unit/integration tests
npm run test:e2e # end-to-end tests (if added)This MVP attempts to be honest about the trust model while still being hackathon-stage code:
- Role-based permissions for admin, executor, and compliance operations.
- Executor and admin are powerful; users must currently trust them not to mismanage synthetic exposure or broker withdrawals.
- KYC gating and sanctions blocking integrated into vault entry/exit and token transfers.
- Global pause switch for operational or regulatory incidents.
- Oracle responses are sanity-checked and wrapped in try/catch.
- Hard cap on acceptable prices.
- Emergency mode to ignore synthetic exposure and fall back to on-chain balances.
- Withdrawals to broker are delayed, capped, cancellable, and observable via events.
- No arbitrary “drain” function; the pipeline is explicit.
- Reward logic is transparent and linear in voting weight.
- Rewards must be explicitly funded via
distributeRewards.
For production, this would need additional work (multi-sig governance, richer oracle infrastructure, off-chain monitoring, formal review, etc.), but the MVP contracts are structured so those layers can be added without rewriting from scratch.
- Working prototype built during the hackathon
- ERC-4626 vault (SPYVault) deployed on Rayls devnet.
- Governance contract (SPYDAOGovernor) wired to spDAO and MockUSD.
- End-to-end flow: faucet → deposit → receive spDAO → vote on SP500 proposals → claim rewards.
- Clear articulation of the principal–agent problem in index fund governance and the lack of a compliant, on-chain coordination mechanism for SPY-style investors.
- SPY DAO as the standard governance layer for tokenised index exposure on Rayls, integrating Privacy Nodes, Private Networks, and the Public Chain in one coherent flow.
- Revenue options via AUM fees, proposal/vote sponsorship, and white-label governance vaults for asset managers building on Rayls.
- Built natively for Rayls devnet.
- Designed around privacy, identity, compliance, and institutional workflows.
- ERC-4626-based, composable primitive that bridges TradFi index exposure ↔ DeFi governance.
MIT – see LICENSE.
SPY DAO was built for the Rayls Hackathon as a prototype for bringing real-world index fund governance on-chain, with a focus on practical compliance constraints, institution-friendly risk controls, and user-aligned voting mechanisms rather than purely theoretical token voting.