Introduction to SH500
StandardHook500 (SH500) is a Uniswap V4 Hook that creates a pool where users can trade a synthetic token tracking the S&P 500 index. It is the first DeFi protocol to offer dynamic, oracle-driven fee adjustment based on real-world equity market volatility — entirely on-chain, non-custodial, and open source.
The protocol sits at the intersection of DeFi and traditional finance (TradFi/RWA). Users who want long-term exposure to US equities no longer need to leave the crypto ecosystem. $SH500 tokens represent synthetic index exposure, and the hook's mechanics ensure the pool behaves like an intelligent, self-adjusting ETF.
Architecture
SH500 is composed of four smart contracts deployed on Ethereum Mainnet, all centered around the Uniswap V4 hook system:
| Contract | Type | Description |
|---|---|---|
| SH500Hook | V4 Hook | Core hook — dynamic fees, oracle reads, rebalance triggers |
| SH500Token | ERC20Votes | Governance token — 500M supply, voting delegation |
| SH500Vault | Yield Vault | Deploys idle LP liquidity to Aave v3 for extra yield |
| SH500TWAMMModule | DCA Engine | Time-weighted average buys — spread orders over time |
The hook is the brain. Every swap that touches the SH500 pool passes throughbeforeSwap andafterSwap callbacks, where the protocol reads the oracle, adjusts the fee, and checks if rebalancing is needed.
How It Works
Every swap through the SH500 pool follows this sequence:
SH500Hook.sol
The main contract. It extends BaseHook from Uniswap v4-periphery and implements afterInitialize, beforeSwap, and afterSwap.
Constructor parameters
| Parameter | Type | Description |
|---|---|---|
| _poolManager | address | Uniswap V4 PoolManager — 0x000000000004444c5dc75cB358380D2e3dE08A90 |
| _sp500Oracle | address | Chainlink AggregatorV3 feed for S&P 500 (CSPX/USD) |
| _owner | address | Initial owner — can update fee params and keeper |
Key functions
| Function | Access | Description |
|---|---|---|
| getCurrentFee() | view | Returns current computed dynamic fee based on latest oracle data |
| refreshOraclePrice() | public | Anyone can call to force an oracle price refresh |
| executeRebalance(key) | keeper | Triggers a pool rebalance — only callable by the keeper address |
| setFeeParams(...) | owner | Update feeMin, feeMax, volThreshLow, volThreshHigh |
| setKeeper(addr) | owner | Update the keeper address |
| setDriftThreshold(bps) | owner | Set drift % before rebalance is triggered |
AFTER_INITIALIZE | BEFORE_SWAP | AFTER_SWAP. Use the HookMiner utility or the Python script included in the repo.// Hook flags required
Hooks.AFTER_INITIALIZE_FLAG // 0x0200
Hooks.BEFORE_SWAP_FLAG // 0x0080
Hooks.AFTER_SWAP_FLAG // 0x0040
// The deployed address must have these bits set
// in its lower 14 bits — mine the CREATE2 salt firstOracle Integration
SH500Hook reads the S&P 500 price via Chainlink's AggregatorV3Interface. The feed used is CSPX/USD — CSPX is the iShares Core S&P 500 ETF, which closely tracks the underlying index.
| Property | Value |
|---|---|
| Feed | CSPX / USD |
| Address (Mainnet) | 0x4b531A318B0e44B549F3b2f824721b3D0d51930A |
| Decimals | 8 |
| Staleness threshold | 3600 seconds (1 hour) |
| Update frequency | Heartbeat: 24h / Deviation: 0.5% |
If the oracle price is older than 1 hour, beforeSwap will revert with StaleOraclePrice. This protects the pool from operating on stale data during oracle downtime.
beforeSwap call.Dynamic Fee Model
The fee is not fixed. It is computed on every swap using the absolute percentage change of the S&P 500 over the last 24 hours. When markets are calm, fees drop to attract traders. When volatility spikes, fees rise to protect LPs from impermanent loss.
Fee scale
Interpolation formula
Between the low and high volatility thresholds, the fee is linearly interpolated:
uint256 absDeltaBps = |currentPrice - price24hAgo| * 10_000 / price24hAgo;
if (absDeltaBps <= volThreshLow) return feeMin; // calm market
if (absDeltaBps >= volThreshHigh) return feeMax; // crisis mode
// linear interpolation between thresholds
uint256 position = absDeltaBps - volThreshLow;
uint256 range = volThreshHigh - volThreshLow;
return feeMin + (feeMax - feeMin) * position / range;Default parameters
| Parameter | Default | Description |
|---|---|---|
| feeMin | 100 (0.01%) | Fee during calm markets |
| feeMax | 10,000 (1.00%) | Fee during crisis |
| volThreshLow | 100 bps (1%) | Daily move below this = min fee |
| volThreshHigh | 300 bps (3%) | Daily move above this = max fee |
| driftThresholdBps | 200 bps (2%) | Pool drift before rebalance triggers |
Auto-Rebalancing
After each swap, the hook checks whether the pool's composition has drifted more than driftThresholdBps (default 2%) from the target S&P 500 price weight. If it has, a RebalanceQueued event is emitted.
An off-chain keeper bot monitors these events and calls executeRebalance(poolKey)on the hook. In Phase II the keeper will use Uniswap V4's modifyLiquidityto adjust the pool's tick range and proportions, bringing it back in line with the index.
SH500Token.sol
The protocol's governance token. Built on OpenZeppelin's ERC20Votes + ERC20Permit. Total supply is fixed at 500,000,000 SH500 — minted once at deploy time.
| Allocation | Amount | % |
|---|---|---|
| Liquidity Mining | 175,000,000 | 35% |
| Treasury | 100,000,000 | 20% |
| Team & Advisors | 75,000,000 | 15% |
| Community & Grants | 75,000,000 | 15% |
| Public Sale | 50,000,000 | 10% |
| Ecosystem Partners | 25,000,000 | 5% |
SH500Vault.sol
The yield vault collects a portion of swap fees and deploys them to Aave v3 on Ethereum Mainnet. LPs receive proportional shares that represent their claim on the vault's assets + accrued yield.
How shares work
When fees are deposited, the vault mints shares proportional to the deposit relative to total assets. On withdrawal, shares are burned and the underlying tokens + yield are returned. This follows the ERC-4626 vault pattern (simplified implementation).
| Function | Who calls | Description |
|---|---|---|
| deposit(token, amount, lp) | Hook | Deposit fees, mint LP shares |
| withdraw(token, shares, recipient) | LP | Burn shares, receive tokens + yield |
| harvestYield(token) | Hook / Keeper | Realize Aave yield back into vault |
| previewRedeem(token, lp) | view | Check how many tokens a given LP can redeem |
| setAllocationBps(token, bps) | Owner | Set % of deposits to send to Aave (max 80%) |
TWAMM / DCA Module
The TWAMM module lets users accumulate $SH500 over time without hitting large price impact. Instead of buying all at once, you define a total amount and an interval — the contract executes small buys periodically, averaging your entry price.
Creating a DCA order
SH500TWAMMModule.createOrder(
tokenIn, // USDC, ETH, WBTC
tokenOut, // SH500 token address
totalAmount, // e.g. 1000 USDC total
amountPerInterval,// e.g. 100 USDC per buy
intervalSeconds // e.g. 86400 = once a day
);
// → returns orderId| Constraint | Value |
|---|---|
| Min interval | 1 hour (3,600 seconds) |
| Max interval | 30 days (2,592,000 seconds) |
| Execution | Anyone can call executeOrder() when time has passed |
| Cancellation | Owner can cancel anytime — unexecuted amount refunded |
Deploy on Remix
All four contracts can be deployed directly from remix.ethereum.org. Follow this exact order — each contract depends on the one before it.
Step 1 — SH500Token
No dependencies. Deploy first. Constructor takes 7 addresses: liquidityMining, treasury, team, community, publicSale, ecosystem, owner. For initial deploy you can use your own wallet for all of them, then transfer to multisigs later.
Step 2 — SH500Vault
Needs the hook address — but the hook isn't deployed yet. Pass a placeholder address (address(0) or your wallet) and call setHook(hookAddress) after Step 4. Also pass the Aave v3 pool address: 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2
Step 3 — Mine the hook address
This is the most important step. Uniswap V4 requires the hook address to have specific bits set. You need to find a CREATE2 salt that produces an address with the correct flags. Run the HookMiner tool before deploying SH500Hook.
AFTER_INITIALIZE (0x0200) | BEFORE_SWAP (0x0080) | AFTER_SWAP (0x0040) — the lower bits of the deployed address must match these values.Step 4 — SH500Hook
Deploy with the mined salt. Constructor: IPoolManager address (0x000000000004444c5dc75cB358380D2e3dE08A90), Chainlink oracle (0x4b531A318B0e44B549F3b2f824721b3D0d51930A), and owner address.
Step 5 — SH500TWAMMModule
Pass the deployed SH500Hook address as the single constructor argument.
Step 6 — Wire everything up
// On SH500Vault — set the real hook address
vault.setHook(sh500HookAddress);
// On SH500Hook — set the vault and keeper
hook.setKeeper(keeperWalletAddress);
// On SH500Vault — set Aave allocation (e.g. 60%)
vault.setAllocationBps(usdcAddress, 6000);Frontend Setup
The frontend is built with Next.js 16, no Tailwind. To run it locally:
# Install dependencies
npm install
# Copy and fill the env file
cp .env.example .env.local
# → fill in your Alchemy key, WalletConnect ID,
# and deployed contract addresses
# Run dev server
npm run dev
# Build for production
npm run build && npm startEnvironment Variables
| Variable | Required | Description |
|---|---|---|
| NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID | Yes | From cloud.walletconnect.com |
| NEXT_PUBLIC_ALCHEMY_KEY_MAINNET | Yes | Ethereum RPC — dashboard.alchemy.com |
| NEXT_PUBLIC_SH500_HOOK_ADDRESS | Yes | Deployed SH500Hook contract address |
| NEXT_PUBLIC_SH500_TOKEN_ADDRESS | Yes | Deployed SH500Token address |
| NEXT_PUBLIC_SH500_VAULT_ADDRESS | Yes | Deployed SH500Vault address |
| NEXT_PUBLIC_SH500_TWAMM_ADDRESS | No | Deployed TWAMM module (Phase II) |
| NEXT_PUBLIC_POOL_MANAGER_MAINNET | Yes | Uniswap V4 PoolManager on mainnet |
| NEXT_PUBLIC_CHAINLINK_SP500_FEED | Yes | CSPX/USD Chainlink feed address |
| NEXT_PUBLIC_SWAP_ENABLED | No | Feature flag — set false to disable swap UI |
Revenue Model
The protocol generates revenue from three sources, all distributed on-chain:
| Source | Split |
|---|---|
| Swap fees | LPs 70% · Treasury 20% · Burn 10% |
| Vault yield (Aave) | LPs 80% · Treasury 20% |
| Performance fee | Treasury 50% · Buyback & burn 50% |
Governance
SH500 token holders vote on protocol parameters using on-chain governance (Governor Bravo pattern). Proposals require a minimum quorum and are subject to a 48-hour timelock before execution.
Governable parameters include: fee min/max, volatility thresholds, drift threshold, vault allocation percentages, keeper address, and treasury spending.
Development Phases
- Dynamic fee logic
- Chainlink oracle integration
- Ethereum mainnet pool deployment
- Foundry test suite 95%+
- First security audit
- afterSwap rebalancing logic
- Keeper bot infrastructure
- Drift threshold governance
- Ethereum full deployment
- Mainnet beta launch
- Aave v3 vault live
- TWAMM / DCA module
- Frontend dashboard
- Portfolio tracker
- Public mainnet launch
- SH500 governance token launch
- Performance fee model
- Multi-index expansion
- Institutional onboarding
- Full audit + launch
Security
SH500 contracts are written in Solidity 0.8.24 with overflow protection enabled by default. The codebase follows checks-effects-interactions patterns and uses OpenZeppelin's ReentrancyGuard on all vault functions that move funds.
| Risk | Mitigation |
|---|---|
| Oracle manipulation | Staleness check (1h), Chainlink decentralized aggregator |
| Reentrancy | ReentrancyGuard on vault, checks-effects-interactions |
| Hook address spoof | onlyPoolManager modifier on all hook callbacks |
| Admin key compromise | Multisig owner + 48h timelock on parameter changes |
| Stale price attack | StaleOraclePrice revert if feed older than 1 hour |